/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2009-2019 Olof Hagsand
  Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)

  This file is part of CLIXON.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Alternatively, the contents of this file may be used under the terms of
  the GNU General Public License Version 3 or later (the "GPL"),
  in which case the provisions of the GPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of the GPL, and not to allow others to
  use your version of this file under the terms of Apache License version 2, 
  indicate your decision by deleting the provisions above and replace them with
  the  notice and other provisions required by the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the Apache License version 2 or the GPL.

  ***** END LICENSE BLOCK *****

 * JSON support functions.
 * JSON syntax is according to:
 * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
 * RFC 7951 JSON Encoding of Data Modeled with YANG
 * XXX: The complexity of xml2json1_cbuf() mapping from internal cxobj structure to JSON output
 * needs a rewrite due to complexity of lists/leaf-lists/null-values, etc.
 */

#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>

/* cligen */
#include <cligen/cligen.h>

/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_string.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_options.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_yang_type.h"
#include "clixon_yang_module.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_map.h"
#include "clixon_xml_nsctx.h" /* namespace context */
#include "clixon_netconf_lib.h"
#include "clixon_json.h"
#include "clixon_json_parse.h"

/* Let xml2json_cbuf_vec() return json array: [a,b].
   ALternative is to create a pseudo-object and return that: {top:{a,b}}
*/
#define VEC_ARRAY 1

/* Size of json read buffer when reading from file*/
#define BUFLEN 1024

/* Name of xml top object created by parse functions */
#define JSON_TOP_SYMBOL "top"

enum array_element_type{
    NO_ARRAY=0,
    FIRST_ARRAY,  /* [a, */
    MIDDLE_ARRAY, /*  a, */
    LAST_ARRAY,   /*  a] */
    SINGLE_ARRAY, /* [a] */
    BODY_ARRAY
};

enum childtype{
    NULL_CHILD=0, /* eg <a/> no children. Translated to null if in 
                   * array or leaf terminal, and to {} if proper object, ie container.
                   * anyxml/anydata?
                   */
    BODY_CHILD,   /* eg one child which is a body, eg <a>1</a> */
    ANY_CHILD,    /* eg <a><b/></a> or <a><b/><c/></a> */
};

/*! x is element and has exactly one child which in turn has none 
 * remove attributes from x
 * @see tleaf in clixon_xml_map.c
 */
static enum childtype
child_type(cxobj *x)
{
    cxobj *xc;   /* the only child of x */
    int    clen; /* nr of children */

    clen = xml_child_nr_notype(x, CX_ATTR);
    if (xml_type(x) != CX_ELMNT)
        return -1; /* n/a */
    if (clen == 0)
        return NULL_CHILD;
    if (clen > 1)
        return ANY_CHILD;
    /* From here exactly one noattr child, get it */
    xc = NULL;
    while ((xc = xml_child_each(x, xc, -1)) != NULL)
        if (xml_type(xc) != CX_ATTR)
            break;
    if (xc == NULL)
        return -2; /* n/a */
    if (xml_child_nr_notype(xc, CX_ATTR) == 0 && xml_type(xc)==CX_BODY)
        return BODY_CHILD;
    else
        return ANY_CHILD;
}

static char*
childtype2str(enum childtype lt)
{
    switch(lt){
    case NULL_CHILD:
        return "null";
        break;
    case BODY_CHILD:
        return "body";
        break;
    case ANY_CHILD:
        return "any";
        break;
    }
    return "";
}

static char*
arraytype2str(enum array_element_type lt)
{
    switch(lt){
    case NO_ARRAY:
        return "no";
        break;
    case FIRST_ARRAY:
        return "first";
        break;
    case MIDDLE_ARRAY:
        return "middle";
        break;
    case LAST_ARRAY:
        return "last";
        break;
    case SINGLE_ARRAY:
        return "single";
        break;
    case BODY_ARRAY:
        return "body";
        break;
    }
    return "";
}

/*! Check typeof x in array
 * 
 * Check if element is in an array, and if so, if it is in the start "[x,", in the middle: "[..,x,..]"
 * in the end: ",x]", or a single element: "[x]"
 * Some complexity when x is in different namespaces
 * @param[in]  xprev     The previous element (if any)
 * @param[in]  x         The element itself
 * @param[in]  xnext     The next element (if any)
 * @retval     arraytype Type of array 
 */
static enum array_element_type
array_eval(cxobj *xprev, 
           cxobj *x, 
           cxobj *xnext)
{
    enum array_element_type arraytype = NO_ARRAY;
    int                     eqprev=0;
    int                     eqnext=0;
    yang_stmt              *ys;
    char                   *nsx; /* namespace of x */
    char                   *ns2;

    nsx = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
    if (xml_type(x) != CX_ELMNT){
        arraytype = BODY_ARRAY;
        goto done;
    }
    if (xnext && 
        xml_type(xnext)==CX_ELMNT &&
        strcmp(xml_name(x), xml_name(xnext))==0){
        ns2 = xml_find_type_value(xnext, NULL, "xmlns", CX_ATTR);
        if ((!nsx && !ns2)
            || (nsx && ns2 && strcmp(nsx,ns2)==0))
            eqnext++;
    }
    if (xprev &&
        xml_type(xprev)==CX_ELMNT &&
        strcmp(xml_name(x),xml_name(xprev))==0){
        ns2 = xml_find_type_value(xprev, NULL, "xmlns", CX_ATTR);
        if ((!nsx && !ns2)
            || (nsx && ns2 && strcmp(nsx,ns2)==0))
            eqprev++;
    }
    if (eqprev && eqnext)
        arraytype = MIDDLE_ARRAY;
    else if (eqprev)
        arraytype = LAST_ARRAY;
    else if (eqnext)
        arraytype = FIRST_ARRAY;
    else if ((ys = xml_spec(x)) != NULL) {
        if (yang_keyword_get(ys) == Y_LIST || yang_keyword_get(ys) == Y_LEAF_LIST)
            arraytype = SINGLE_ARRAY;
        else
            arraytype = NO_ARRAY;
    }
    else
        arraytype = NO_ARRAY;
 done:
    return arraytype;
}

/*! Escape a json string as well as decode xml cdata
 * @param[out] cb   cbuf   (encoded)
 * @param[in]  str  string (unencoded)
 */
static int
json_str_escape_cdata(cbuf *cb,
                      char *str)
{
    int   retval = -1;
    int   i;
    size_t len;
    
    len = strlen(str);
    for (i=0; i<len; i++)
        switch (str[i]){
        case '\n':
            cprintf(cb, "\\n");
            break;
        case '\"':
            cprintf(cb, "\\\"");
            break;
        case '\\':
            cprintf(cb, "\\\\");
            break;
        default: /* fall thru */
            cprintf(cb, "%c", str[i]);
            break;
        }
    retval = 0;
    // done:
    return retval;
}

/*! Decode types from JSON to XML identityrefs
 * Assume an xml tree where prefix:name have been split into "module":"name"
 * In other words, from JSON RFC7951 to XML namespace trees
 * @param[in]     x     XML tree. Must be yang populated.
 * @param[in]     yspec Yang spec
 * @param[out]    xerr  Reason for invalid tree returned as netconf err msg or NULL
 * @retval        1     OK 
 * @retval        0     Invalid, wrt namespace.  xerr set
 * @retval       -1     Error
 * @see RFC7951 Sec 4 and 6.8
 */
static int
json2xml_decode_identityref(cxobj     *x,
                            yang_stmt *y,
                            cxobj    **xerr)
{
    int        retval = -1;
    char      *ns;
    char      *body;
    cxobj     *xb;
    char      *prefix = NULL;
    char      *id = NULL;
    yang_stmt *ymod;
    yang_stmt *yspec;
    cvec      *nsc = NULL;
    char      *prefix2 = NULL;
    cbuf      *cbv = NULL;
    
    clicon_debug(1, "%s", __FUNCTION__);
    yspec = ys_spec(y);
    if ((xb = xml_body_get(x)) == NULL)
        goto ok;
    body = xml_value(xb);
    if (nodeid_split(body, &prefix, &id) < 0)
        goto done;
    /* prefix is a module name -> find module */
    if (prefix){
        if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL){
            ns = yang_find_mynamespace(ymod);
            /* Is this namespace in the xml context?
             * (yes) use its prefix (unless it is NULL)
             * (no)  insert a xmlns:<prefix> statement
             * Get the whole namespace context from x
             */
            if (xml_nsctx_node(x, &nsc) < 0)
                goto done;
            clicon_debug(1, "%s prefix:%s body:%s namespace:%s",
                         __FUNCTION__, prefix, body, ns);
            if (!xml_nsctx_get_prefix(nsc, ns, &prefix2)){
                /* (no)  insert a xmlns:<prefix> statement
                 * Get yang prefix from import statement of my mod 
                 * I am not sure this is correct
                 */
                if (yang_find_prefix_by_namespace(y, ns, &prefix2) < 0)
                    goto done;
                /* if prefix2 is NULL here, we get the canonical prefix */
                if (prefix2 == NULL)
                    prefix2 = yang_find_myprefix(ymod);
                /* Add "xmlns:prefix2=namespace" */
                if (xml_add_attr(x, prefix2, ns, "xmlns", NULL) < 0)
                    goto done;

            }
            /* Here prefix2 is valid and can be NULL
               Change body prefix to prefix2:id */
            if ((cbv = cbuf_new()) == NULL){
                clicon_err(OE_JSON, errno, "cbuf_new");
                goto done;
            }
            if (prefix2)
                cprintf(cbv, "%s:%s", prefix2, id);
            else
                cprintf(cbv, "%s", id);

            if (xml_value_set(xb, cbuf_get(cbv)) < 0)
                goto done;
        }
        else{
            if (xerr && netconf_unknown_namespace_xml(xerr, "application",
                                              prefix,
                                              "No module corresponding to prefix") < 0)         
                goto done;
            goto fail;
        }
    } /* prefix */
 ok:
    retval = 1;
 done:
    if (prefix)
        free(prefix);
    if (id)
        free(id);
    if (nsc)
        xml_nsctx_free(nsc);
    if (cbv)
        cbuf_free(cbv);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Decode leaf/leaf_list types from JSON to XML after parsing and yang
 *
 * Assume an xml tree where prefix:name have been split into "module":"name"
 * In other words, from JSON RFC7951 to XML namespace trees
 * 
 * @param[in]     x     XML tree. Must be yang populated. After json parsing
 * @param[in]     yspec Yang spec
 * @param[out]    xerr  Reason for invalid tree returned as netconf err msg or NULL
 * @retval        1     OK 
 * @retval        0     Invalid, wrt namespace.  xerr set
 * @retval       -1     Error
 * @see RFC7951 Sec 4 and 6.8
 */
int
json2xml_decode(cxobj     *x,
                cxobj    **xerr)
{
    int           retval = -1;
    yang_stmt    *y;
    enum rfc_6020 keyword;
    cxobj        *xc;
    int           ret;
    yang_stmt    *ytype = NULL;

    if ((y = xml_spec(x)) != NULL){
        keyword = yang_keyword_get(y);
        if (keyword == Y_LEAF || keyword == Y_LEAF_LIST){
            if (yang_type_get(y, NULL, &ytype, NULL, NULL, NULL, NULL, NULL) < 0)
                goto done;

            if (ytype){
                if (strcmp(yang_argument_get(ytype), "identityref")==0){
                    if ((ret = json2xml_decode_identityref(x, y, xerr)) < 0)
                        goto done;
                    if (ret == 0)
                        goto fail;
                }
                else if (strcmp(yang_argument_get(ytype), "empty")==0)
                    ; /* dont need to do anything */
            }
        }
    }
    xc = NULL;
    while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){
        if ((ret = json2xml_decode(xc, xerr)) < 0)
            goto done;
        if (ret == 0)
            goto fail;
    }
    retval = 1;
 done:
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Encode leaf/leaf_list identityref type from XML to JSON
 * @param[in]     x    XML body node
 * @param[in]     body body string
 * @param[in]     ys   Yang spec of parent
 * @param[out]    cb   Encoded string
 */
static int
xml2json_encode_identityref(cxobj     *xb,
                            char      *body,
                            yang_stmt *yp,
                            cbuf      *cb)
{
    int        retval = -1;
    char      *prefix = NULL;
    char      *id = NULL;
    char      *namespace = NULL;
    yang_stmt *ymod;
    yang_stmt *yspec;
    yang_stmt *my_ymod;

    clicon_debug(1, "%s %s", __FUNCTION__, body);
    my_ymod = ys_module(yp);
    yspec = ys_spec(yp);
    if (nodeid_split(body, &prefix, &id) < 0)
        goto done;
    /* prefix is xml local -> get namespace */
    if (xml2ns(xb, prefix, &namespace) < 0)
        goto done;
    /* We got the namespace, now get the module */
    //    clicon_debug(1, "%s body:%s prefix:%s namespace:%s", __FUNCTION__, body, prefix, namespace);
#ifdef IDENTITYREF_KLUDGE
    if (namespace == NULL){
    /* If we dont find namespace here, we assume it is because of a missing
     * xmlns that should be there, as a kludge we search for its (own)
     * prefix in mymodule.
    */
        if ((ymod = yang_find_module_by_prefix_yspec(yspec, prefix)) != NULL)
            cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
        else
            cprintf(cb, "%s", id);
    }
    else
#endif
        {
    if ((ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){
        
        if (ymod == my_ymod)
            cprintf(cb, "%s", id);
        else{
            cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
        }
    }
    else
        cprintf(cb, "%s", id);
        }
    retval = 0;
 done:
    if (prefix)
        free(prefix);
    if (id)
        free(id);
    return retval;
}

/*! Encode leaf/leaf_list types from XML to JSON
 * @param[in]     xb   XML body
 * @param[in]     xp   XML parent
 * @param[in]     yp   Yang spec of parent
 * @param[out]    cb0  Encoded string
 */
static int
xml2json_encode_leafs(cxobj     *xb,
                      cxobj     *xp,
                      yang_stmt *yp,
                      cbuf      *cb0)
{
    int           retval = -1;
    enum rfc_6020 keyword;
    yang_stmt    *ytype;
    char         *restype;  /* resolved type */
    char         *origtype=NULL;   /* original type */
    char         *body;
    enum cv_type  cvtype;
    int           quote = 1; /* Quote value w string: "val" */
    cbuf         *cb = NULL; /* the variable itself */

    if ((cb = cbuf_new()) ==NULL){
        clicon_err(OE_XML, errno, "cbuf_new");
        goto done;
    }
    body = xb?xml_value(xb):NULL;
    if (yp == NULL){
        cprintf(cb, "%s", body?body:"null"); 
        goto ok; /* unknown */
    }
    keyword = yang_keyword_get(yp);
    switch (keyword){
    case Y_LEAF:
    case Y_LEAF_LIST:
        if (yang_type_get(yp, &origtype, &ytype, NULL, NULL, NULL, NULL, NULL) < 0)
            goto done;
        restype = ytype?yang_argument_get(ytype):NULL;
        cvtype = yang_type2cv(yp);
        switch (cvtype){ 
        case CGV_STRING:
        case CGV_REST:
            if (body==NULL)
                ; /* empty: "" */
            else if (ytype){
                if (strcmp(restype, "identityref")==0){
                    if (xml2json_encode_identityref(xb, body, yp, cb) < 0)
                        goto done;
                }
                else{
                    cprintf(cb, "%s", body);
                }
            }
            else
                cprintf(cb, "%s", body);
            break;
        case CGV_INT64:
        case CGV_UINT64:
        case CGV_DEC64:
                // [RFC7951] JSON Encoding of YANG Data
                // 6.1 Numeric Types - A value of the "int64", "uint64", or "decimal64" type is represented as a JSON string
                if (yang_keyword_get(yp) == Y_LEAF_LIST && xml_child_nr_type(xml_parent(xp), CX_ELMNT) == 1) {
                        cprintf(cb, "[%s]", body);
                }
                else {
                        cprintf(cb, "%s", body);
                }
                quote = 1;
                break;
        case CGV_INT8:
        case CGV_INT16:
        case CGV_INT32:
        case CGV_UINT8:
        case CGV_UINT16:
        case CGV_UINT32:
        case CGV_BOOL:
            cprintf(cb, "%s", body);
            quote = 0;
            break;
        case CGV_VOID:
            /* special case YANG empty type */
            if (body == NULL && strcmp(restype, "empty")==0){
                quote = 0;
                if (keyword == Y_LEAF)
                    cprintf(cb, "[null]");
                else if (keyword == Y_LEAF_LIST && strcmp(restype, "empty") == 0)
                    cprintf(cb, "[null]");
                else
                    cprintf(cb, "null");
            }
            break;
        default:
            if (body)
                cprintf(cb, "%s", body);
            else
                cprintf(cb, "{}"); /* dont know */
        }
        break;
    default:
        cprintf(cb, "%s", body);
        break;
    }
 ok:
    /* write into original cb0
     * includign quoting and encoding 
     */
    if (quote){
        cprintf(cb0, "\"");
        json_str_escape_cdata(cb0, cbuf_get(cb));
    }
    else
        cprintf(cb0, "%s", cbuf_get(cb));
    if (quote)
        cprintf(cb0, "\"");
    retval = 0;
 done:
    if (cb)
        cbuf_free(cb);
    if (origtype)
        free(origtype);
    return retval;
}

/* X has no XML child - no body.
 * If x is a container, use {} instead of null 
 * if leaf or leaf-list then assume EMPTY type, then [null]
 * else null
 */
static int
nullchild(cbuf      *cb,
          cxobj     *x,
          yang_stmt *y)
{
    int           retval = -1;

    if (y == NULL){
        /* This is very problematic.
         * RFC 7951 explicitly forbids "null" to be used unless for empty types in [null]
         */
        cprintf(cb, "{}");
    }
    else{
        switch (yang_keyword_get(y)){
        case Y_ANYXML:
        case Y_ANYDATA:
        case Y_CONTAINER:
            cprintf(cb, "{}");
            break;
        case Y_LEAF:
        case Y_LEAF_LIST:
            if (xml2json_encode_leafs(NULL, x, y, cb) < 0)
                goto done;
            break;
        default:
            /* This is very problematic.
             * RFC 7951 explicitly forbids "null" to be used unless for empty types in [null]
             */
            cprintf(cb, "{}");
            break;
        }
    }
    retval = 0;
 done:
    return retval;
}

/*! Encode json metadata
 * This function could be more general, code based on two examples:
 * 1) ietf-list-pagination:remaining
 *  {
 *     "example-social:uint8-numbers": [17],
 *     "@example-social:uint8-numbers": [
 *        {
 *           "ietf-list-pagination:remaining": 5
 *        }
 *      ]
 *    }
 * 2) ietf-netconf-with-defaults:default:
 *     "@mtu" : {
 *          "ietf-netconf-with-defaults:default" : true
 *      },
 */
static int
json_metadata_encoding(cbuf  *cb,
                       cxobj *x,
                       int    level,
                       int    pretty,
                       char  *prefix,
                       char  *name,
                       char  *modname2,
                       char  *name2,
                       char  *val,
                       int    list)
{
    cprintf(cb, ",\"@");
    if (prefix)
        cprintf(cb, "%s:", prefix);
    cprintf(cb, "%s\":", name);
    if (list)
        cprintf(cb, "[");
    cprintf(cb, "%*s", pretty?((level+1)*PRETTYPRINT_INDENT):0, "{");
    cprintf(cb, "\"%s:%s\":%s", modname2, name2, val);
    cprintf(cb, "%*s", pretty?((level+1)*PRETTYPRINT_INDENT):0, "}");
    if (list)
        cprintf(cb, "%*s", pretty?(level*PRETTYPRINT_INDENT):0, "]");
    return 0;
}

/*! Encode XML attributes as JSON meta-data
 *
 * There are two methods for this:
 * 1) Registered meta-data according to RFC 7952 using md:annotate. 
 *    This is derived from a YANG module
 *    Examples: ietf-origin:origin, ietf-list-pagination: remaining
 * 2) Assigned, if someother mechanism, eg XSD is used
 *    Example: ietf-restconf: default
 * If neither matches, the attribute is skipped
 * @param[in]     xc       XML attribute
 * @param[in]     xp       XML node, parent of xa
 * @param[in]     yp       Yang spec of xp
 * @param[in]     level    Indentation level
 * @param[in]     pretty   Pretty-print output (2 means debug)
 * @param[in]     modname  Name of yang module
 * @param[in,out] metacb   Encode into cbuf
 * @see RFC7952
 */
static int
xml2json_encode_attr(cxobj     *xa,
                     cxobj     *xp,
                     yang_stmt *yp,
                     int        level,
                     int        pretty,
                     char      *modname,
                     cbuf      *metacb)
{
    int           retval = -1;
    int           ismeta = 0;
    char         *namespace = NULL;
    yang_stmt    *ymod;
    enum rfc_6020 ykeyw;
    
    if (xml2ns(xa, xml_prefix(xa), &namespace) < 0)
        goto done;
    /* Check for (1) registered meta-data */
    if (namespace != NULL && yp){
        ykeyw = yang_keyword_get(yp);
        if ((ymod = yang_find_module_by_namespace(ys_spec(yp), namespace)) != NULL){
            if (yang_metadata_annotation_check(xa, ymod, &ismeta) < 0)
                goto done;
            if (ismeta)
                if (json_metadata_encoding(metacb, xp, level, pretty,
                                           modname, xml_name(xp),
                                           yang_argument_get(ymod),
                                           xml_name(xa),
                                           xml_value(xa),
                                           ykeyw == Y_LEAF_LIST || ykeyw == Y_LIST) < 0)
                    goto done;
        }
        /* Check for (2) assigned - hardcoded for now */
        else if (strcmp(namespace, "urn:ietf:params:xml:ns:netconf:default:1.0") == 0 &&
                 strcmp(xml_name(xa), "default") == 0){
            /* RFC 7952 / RFC 8040 defaults attribute */
            if (json_metadata_encoding(metacb, xp, level, pretty,
                                       modname, xml_name(xp),
                                       "ietf-netconf-with-defaults",
                                       xml_name(xa),
                                       xml_value(xa),
                                       ykeyw == Y_LEAF_LIST || ykeyw == Y_LIST) < 0)
                goto done;
        }
    }
    retval = 0;
 done:
    return retval;
}

/*! Do the actual work of translating XML to JSON 
 * @param[out]   cb        Cligen text buffer containing json on exit
 * @param[in]    x         XML tree structure containing XML to translate
 * @param[in]    arraytype Does x occur in a array (of its parent) and how?
 * @param[in]    level     Indentation level
 * @param[in]    pretty    Pretty-print output (2 means debug)
 * @param[in]    flat      Dont print NO_ARRAY object name (for _vec call)
 * @param[in]    modname0
 * @param[out]   metacbp   Meta encoding of attribute
 *
 * @note Does not work with XML attributes
 * The following matrix explains how the mapping is done.
 * You need to understand what arraytype means (no/first/middle/last)
 * and what childtype is (null,body,any)
  +----------+--------------+--------------+--------------+
  |array,leaf| null         | body         | any          |
  +----------+--------------+--------------+--------------+
  |no        | <a/>         |<a>1</a>      |<a><b/></a>   |
  |          |              |              |              |
  |  json:   |\ta:null      |\ta:          |\ta:{\n       |
  |          |              |              |\n}           |
  +----------+--------------+--------------+--------------+
  |first     |<a/><a..      |<a>1</a><a..  |<a><b/></a><a.|
  |          |              |              |              |
  |  json:   |\ta:[\n\tnull |\ta:[\n\t     |\ta:[\n\t{\n  |
  |          |              |              |\n\t}         |
  +----------+--------------+--------------+--------------+
  |middle    |..a><a/><a..  |.a><a>1</a><a.|              |
  |          |              |              |              |
  |  json:   |\tnull        |\t            |\t{a          |
  |          |              |              |\n\t}         |
  +----------+--------------+--------------+--------------+
  |last      |..a></a>      |..a><a>1</a>  |              |
  |          |              |              |              |
  |  json:   |\tnull        |\t            |\t{a          |
  |          |\n\t]         |\n\t]         |\n\t}\t]      |
  +----------+--------------+--------------+--------------+
 */
static int 
xml2json1_cbuf(cbuf                   *cb,
               cxobj                  *x,
               enum array_element_type arraytype,
               int                     level,
               int                     pretty,
               int                     flat,
               char                   *modname0,
               cbuf                   *metacbp)
{
    int              retval = -1;
    int              i;
    cxobj           *xc;
    cxobj           *xp;
    enum childtype   childt;
    enum array_element_type xc_arraytype;
    yang_stmt       *ys;
    yang_stmt       *ymod = NULL; /* yang module */
    int              commas;
    char            *modname = NULL;
    cbuf            *metacbc = NULL;

    if ((ys = xml_spec(x)) != NULL){
        if (ys_real_module(ys, &ymod) < 0)
            goto done;
        modname = yang_argument_get(ymod);
        /* Special case for ietf-netconf -> ietf-restconf translation 
         * A special case is for return data on the form {"data":...}
         * See also json_xmlns_translate()
         */
        if (strcmp(modname, "ietf-netconf") == 0)
            modname = "ietf-restconf";
        if (modname0 && strcmp(modname, modname0) == 0)
            modname=NULL;
        else
            modname0 = modname; /* modname0 is ancestor ns passed to child */
    }
    childt = child_type(x);
    if (pretty==2)
        cprintf(cb, "#%s_array, %s_child ", 
                arraytype2str(arraytype),
                childtype2str(childt));
    switch(arraytype){
    case BODY_ARRAY: /* Only place in fn where body is printed (except nullchild) */
        xp = xml_parent(x);
        if (xml2json_encode_leafs(x, xp, xml_spec(xp), cb) < 0)
            goto done;
        break;
    case NO_ARRAY:
        if (!flat){
            cprintf(cb, "%*s\"", pretty?(level*PRETTYPRINT_INDENT):0, "");
            if (modname) 
                cprintf(cb, "%s:", modname);
            cprintf(cb, "%s\":%s", xml_name(x), pretty?" ":"");
        }
        switch (childt){
        case NULL_CHILD:
            if (nullchild(cb, x, ys) < 0)
                goto done;
            break;
        case BODY_CHILD:
            break;
        case ANY_CHILD:
            cprintf(cb, "{%s", pretty?"\n":"");
            break;
        default:
            break;
        }
        break;
    case FIRST_ARRAY:
    case SINGLE_ARRAY:
        cprintf(cb, "%*s\"", pretty?(level*PRETTYPRINT_INDENT):0, "");
        if (modname)
            cprintf(cb, "%s:", modname);
        cprintf(cb, "%s\":%s", xml_name(x), pretty?" ":"");
        level++;
        cprintf(cb, "[%s%*s", 
                pretty?"\n":"",
                pretty?(level*PRETTYPRINT_INDENT):0, "");
        switch (childt){
        case NULL_CHILD:
            if (nullchild(cb, x, ys) < 0)
                goto done;
            break;
        case BODY_CHILD:
            break;
        case ANY_CHILD:
            cprintf(cb, "{%s", pretty?"\n":"");
            break;
        default:
            break;
        }
        break;
    case MIDDLE_ARRAY:
    case LAST_ARRAY:
        level++;
        cprintf(cb, "%*s", 
                pretty?(level*PRETTYPRINT_INDENT):0, "");
        switch (childt){
        case NULL_CHILD:
            if (nullchild(cb, x, ys) < 0)
                goto done;
            break;
        case BODY_CHILD:
            break;
        case ANY_CHILD:
            cprintf(cb, "{%s", pretty?"\n":"");
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
    if ((metacbc = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    /* Check for typed sub-body if:
     * arraytype=* but child-type is BODY_CHILD 
     * This is code for writing <a>42</a> as "a":42 and not "a":"42"
     */
    commas = xml_child_nr_notype(x, CX_ATTR) - 1;
    for (i=0; i<xml_child_nr(x); i++){
        xc = xml_child_i(x, i);
        if (xml_type(xc) == CX_ATTR){
            if (metacbp &&
                xml2json_encode_attr(xc, x, ys, level, pretty, modname, metacbp) < 0)
                goto done;
            continue;
        }
        xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL, 
                                xc, 
                                xml_child_i(x, i+1));
        if (xml2json1_cbuf(cb, 
                           xc, 
                           xc_arraytype,
                           level+1, pretty, 0, modname0,
                           metacbc) < 0)
            goto done;
        if (commas > 0) {
            cprintf(cb, ",%s", pretty?"\n":"");
            --commas;
        }
    }
    if (cbuf_len(metacbc)){
        cprintf(cb, "%s", cbuf_get(metacbc));
    }

    switch (arraytype){
    case BODY_ARRAY:
        break;
    case NO_ARRAY:
        switch (childt){
        case NULL_CHILD:
        case BODY_CHILD:
            break;
        case ANY_CHILD:
            cprintf(cb, "%s%*s}", 
                    pretty?"\n":"",
                    pretty?(level*PRETTYPRINT_INDENT):0, "");
            break;
        default:
            break;
        }
        level--;
        break;
    case FIRST_ARRAY:
    case MIDDLE_ARRAY:
        switch (childt){
        case NULL_CHILD:
        case BODY_CHILD:
            break;
        case ANY_CHILD:
            cprintf(cb, "%s%*s}", 
                    pretty?"\n":"",
                    pretty?(level*PRETTYPRINT_INDENT):0, "");
            level--;
            break;
        default:
            break;
        }
        break;
    case SINGLE_ARRAY:
    case LAST_ARRAY:
        switch (childt){
        case NULL_CHILD:
        case BODY_CHILD:
            cprintf(cb, "%s",pretty?"\n":"");
            break;
        case ANY_CHILD:
            cprintf(cb, "%s%*s}", 
                    pretty?"\n":"",
                    pretty?(level*PRETTYPRINT_INDENT):0, "");
            cprintf(cb, "%s",pretty?"\n":"");
            level--;
            break;
        default:
            break;
        }
        cprintf(cb, "%*s]",
                pretty?(level*PRETTYPRINT_INDENT):0,"");
        break;
    default:
        break;
    }
    retval = 0;
 done:
    if (metacbc)
        cbuf_free(metacbc);
    return retval;
}

/*! Translate an XML tree to JSON in a CLIgen buffer
 *
 * XML-style namespace notation in tree, but RFC7951 in output assume yang 
 * populated 
 *
 * @param[in,out] cb     Cligen buffer to write to
 * @param[in]     x      XML tree to translate from
 * @param[in]     pretty Set if output is pretty-printed
 * @param[in]     autocliext How to handle autocli extensions: 0: ignore 1: follow
 * @retval        0      OK
 * @retval       -1      Error
 *
 * @see clixon_xml2cbuf     XML corresponding function
 * @see xml2json_cbuf_vec   Top symbol is list
 */
static int 
xml2json_cbuf1(cbuf   *cb, 
               cxobj  *x, 
               int     pretty,
               int     autocliext)
{
    int                     retval = 1;
    int                     level = 0;
    yang_stmt              *y;
    enum array_element_type arraytype = NO_ARRAY;
    int                     exist = 0;
        
    y = xml_spec(x);
    if (autocliext && y != NULL) {
        if (yang_extension_value(y, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
            goto done;
        if (exist)
            goto ok;
    }
    cprintf(cb, "%*s{%s", 
            pretty?level*PRETTYPRINT_INDENT:0,"", 
            pretty?"\n":"");
    
    if (y != NULL){
        switch (yang_keyword_get(y)){
        case Y_LEAF_LIST:
        case Y_LIST:
            arraytype = SINGLE_ARRAY;
            break;
        default:
            arraytype = NO_ARRAY;
            break;
        }
    }
    if (xml2json1_cbuf(cb, 
                       x, 
                       arraytype,
                       level+1,
                       pretty,
                       0,
                       NULL, /* ancestor modname / namespace */
                       NULL) < 0)
        goto done;
    cprintf(cb, "%s%*s}%s", 
            pretty?"\n":"",
            pretty?level*PRETTYPRINT_INDENT:0,"",
            pretty?"\n":"");
 ok:
    retval = 0;
 done:
    return retval;
}

/*! Translate an XML tree to JSON in a CLIgen buffer skip top-level object
 *
 * XML-style namespace notation in tree, but RFC7951 in output assume yang 
 * populated 
 *
 * @param[in,out] cb      Cligen buffer to write to
 * @param[in]     xt      Top-level xml object
 * @param[in]     pretty  Set if output is pretty-printed
 * @param[in]     skiptop 0: Include top object 1: Skip top-object, only children, 
 * @param[in]     autocliext How to handle autocli extensions: 0: ignore 1: follow
 * @retval        0       OK
 * @retval       -1       Error
 * @code
 *   cbuf *cb = cbuf_new();
 *   if (xml2json_cbuf(cb, xn, 0, 0, 0) < 0)
 *     goto err;
 *   cbuf_free(cb);
 * @endcode
 * @see xml2json_cbuf where the top level object is included
 */
int 
clixon_json2cbuf(cbuf  *cb, 
                 cxobj *xt, 
                 int    pretty,
                 int    skiptop,
                 int    autocliext)
{
    int    retval = -1;
    cxobj *xc;
    int    i=0;

    if (skiptop){
        xc = NULL;
        while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){
            if (i++)
                cprintf(cb, ",");
            if (xml2json_cbuf1(cb, xc, pretty, autocliext) < 0)
                goto done;
        }
    }
    else {
        if (xml2json_cbuf1(cb, xt, pretty, autocliext) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Translate a vector of xml objects to JSON Cligen buffer.
 * This is done by adding a top pseudo-object, and add the vector as subs,
 * and then not printing the top pseudo-object using the 'flat' option.
 * @param[out] cb     Cligen buffer to write to
 * @param[in]  vec    Vector of xml objecst
 * @param[in]  veclen Length of vector
 * @param[in]  pretty Set if output is pretty-printed (2 for debug)
 * @param[in]  skiptop 0: Include top object 1: Skip top-object, only children, 
 * @retval     0      OK
 * @retval    -1      Error
 * @note This only works if the vector is uniform, ie same object name.
 * Example: <b/><c/> --> <a><b/><c/></a> --> {"b" : null,"c" : null}
 * @see clixon_json2cbuf
 */
int 
xml2json_cbuf_vec(cbuf      *cb, 
                  cxobj    **vec,
                  size_t     veclen,
                  int        pretty,
                  int        skiptop)
{
    int    retval = -1;
    int    level = 0;
    cxobj *xp = NULL;
    int    i;
    cxobj *xc0;
    cxobj *xc;
    cvec  *nsc = NULL; 

    if ((xp = xml_new("xml2json", NULL, CX_ELMNT)) == NULL)
        goto done;
    /* Make a copy of old and graft it into new top-object
     * Also copy namespace context */
    for (i=0; i<veclen; i++){
        xc0 = vec[i];
        if (xml_nsctx_node(xc0, &nsc) < 0)
            goto done;
        if (skiptop){
            cxobj *x = NULL;
            while ((x = xml_child_each(xc0, x, CX_ELMNT)) != NULL) {
                if ((xc = xml_dup(x)) == NULL)
                    goto done;
                xml_addsub(xp, xc);
                xmlns_set_all(xc, nsc); // ?
            }
            cvec_free(nsc);
        }
        else {
                if ((xc = xml_dup(xc0)) == NULL)
                    goto done;
                xml_addsub(xp, xc);
                nscache_replace(xc, nsc);
            }
        nsc = NULL; /* nsc consumed */
    }
    if (0){
        cprintf(cb, "[%s", pretty?"\n":" ");
        level++;
    }
    if (xml2json1_cbuf(cb, 
                       xp, 
                       NO_ARRAY,
                       level,
                       pretty,
                       1, NULL, NULL) < 0)
        goto done;

    if (0){
        level--;
        cprintf(cb, "%s]%s", 
            pretty?"\n":"",
            pretty?"\n":""); /* top object */
    }
    retval = 0;
 done:
    if (nsc)
        xml_nsctx_free(nsc);
    if (xp)
        xml_free(xp);
    return retval;
}

/*! Translate from xml tree to JSON and print to file using a callback
 * @param[in]  f       File to print to
 * @param[in]  xn      XML tree to translate from
 * @param[in]  pretty  Set if output is pretty-printed
 * @param[in]  fn       File print function
 * @param[in]  skiptop 0: Include top object 1: Skip top-object, only children, 
 * @param[in]  autocliext How to handle autocli extensions: 0: ignore 1: follow
 * @retval     0       OK
 * @retval    -1       Error
 *
 * @note yang is necessary to translate to one-member lists,
 * eg if a is a yang LIST <a>0</a> -> {"a":["0"]} and not {"a":"0"}
 * @code
 * if (clixon_json2file(stderr, xn, 0, fprintf, 0, 0) < 0)
 *   goto err;
 * @endcode
 */
int 
clixon_json2file(FILE             *f, 
                 cxobj            *xn,
                 int               pretty,
                 clicon_output_cb *fn,
                 int               skiptop,
                 int               autocliext)
{
    int   retval = 1;
    cbuf *cb = NULL;

    if (fn == NULL)
        fn = fprintf;
    if ((cb = cbuf_new()) ==NULL){
        clicon_err(OE_XML, errno, "cbuf_new");
        goto done;
    }
    if (clixon_json2cbuf(cb, xn, pretty, skiptop, autocliext) < 0)
        goto done;
    (*fn)(f, "%s", cbuf_get(cb));
    retval = 0;
 done:
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! Print an XML tree structure to an output stream as JSON
 *
 * @param[in]   f           UNIX output stream
 * @param[in]   xn          clicon xml tree
 */
int
json_print(FILE  *f, 
           cxobj *x)
{
    return clixon_json2file(f, x, 1, fprintf, 0, 0);
}

/*! Translate a vector of xml objects to JSON File.
 * This is done by adding a top pseudo-object, and add the vector as subs,
 * and then not pritning the top pseudo-.object using the 'flat' option.
 * @param[out] cb     Cligen buffer to write to
 * @param[in]  vec    Vector of xml objecst
 * @param[in]  veclen Length of vector
 * @param[in]  pretty Set if output is pretty-printed (2 for debug)
 * @param[in]  fn       File print function 
 * @param[in]  skiptop 0: Include top object 1: Skip top-object, only children, 
 * @retval     0      OK
 * @retval    -1      Error
 * @note This only works if the vector is uniform, ie same object name.
 * Example: <b/><c/> --> <a><b/><c/></a> --> {"b" : null,"c" : null}
 * @see xml2json1_cbuf
 */
int 
xml2json_vec(FILE             *f, 
             cxobj           **vec,
             size_t            veclen,
             int               pretty,
             clicon_output_cb *fn,
             int               skiptop)
{
    int   retval = 1;
    cbuf *cb = NULL;

    if ((cb = cbuf_new()) == NULL){
        clicon_err(OE_XML, errno, "cbuf_new");
        goto done;
    }
    if (xml2json_cbuf_vec(cb, vec, veclen, pretty, skiptop) < 0)
        goto done;
    (*fn)(f, "%s\n", cbuf_get(cb));
    retval = 0;
 done:
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively
 * Assume an xml tree where prefix:name have been split into "module":"name"
 * In other words, from JSON to XML namespace trees
 * 
 * @param[in]     yspec Yang spec
 * @param[in,out] x     XML tree. Translate it in-line
 * @param[out]    xerr  Reason for invalid tree returned as netconf err msg or NULL
 * @retval        1     OK 
 * @retval        0     Invalid, wrt namespace.  xerr set
 * @retval       -1     Error
 * @note the opposite - xml2ns is made inline in xml2json1_cbuf
 * Example: <top><module:input> --> <top><input xmlns="">
 * @see RFC7951 Sec 4
 */
static int
json_xmlns_translate(yang_stmt *yspec,
                     cxobj     *x,
                     cxobj    **xerr)
{
    int        retval = -1;
    yang_stmt *ymod;
    char      *namespace;
    char      *modname = NULL;
    cxobj     *xc;
    int        ret;
    
    if ((modname = xml_prefix(x)) != NULL){ /* prefix is here module name */
        /* Special case for ietf-netconf -> ietf-restconf translation 
         * A special case is for return data on the form {"data":...}
         * See also xml2json1_cbuf
         */
        if (strcmp(modname, "ietf-restconf") == 0)
            modname = "ietf-netconf";
        if ((ymod = yang_find_module_by_name(yspec, modname)) == NULL){
            if (xerr &&
                netconf_unknown_namespace_xml(xerr, "application",
                                              modname,
                                              "No yang module found corresponding to prefix") < 0)
                goto done;
            goto fail;
        }
        namespace = yang_find_mynamespace(ymod);
        /* It would be possible to use canonical prefixes here, but probably not
         * necessary or even right. Therefore, the namespace given by the JSON prefix / module
         * is always the default namespace with prefix NULL.
         * If not, this would be the prefix to pass instead of NULL
         * prefix = yang_find_myprefix(ymod);
         */
        if (xml_namespace_change(x, namespace, NULL) < 0)
            goto done;
    }
    xc = NULL;
    while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){
        if ((ret = json_xmlns_translate(yspec, xc, xerr)) < 0)
            goto done;
        if (ret == 0)
            goto fail;
    }
    retval = 1;
 done:
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Parse a string containing JSON and return an XML tree
 *
 * Parsing using yacc according to JSON syntax. Names with <prefix>:<id>
 * are split and interpreted as in RFC7951
 *
 * @param[in]  str    Input string containing JSON
 * @param[in]  rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG
 * @param[in]  yb     How to bind yang to XML top-level when parsing (if rfc7951)
 * @param[in]  yspec  Yang specification (if rfc 7951)
 * @param[out] xt     XML top of tree typically w/o children on entry (but created)
 * @param[out] xerr   Reason for invalid returned as netconf err msg 
 * 
 * @see _xml_parse for XML variant
 * @retval        1   OK and valid
 * @retval        0   Invalid (only if yang spec)
 * @retval       -1   Error with clicon_err called
 * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
 * @see RFC 7951
 */
static int 
_json_parse(char      *str, 
            int        rfc7951,
            yang_bind  yb,
            yang_stmt *yspec,
            cxobj     *xt,
            cxobj    **xerr)
{
    int              retval = -1;
    clixon_json_yacc jy = {0,};
    int              ret;
    cxobj           *x;
    cbuf            *cberr = NULL;
    int              i;
    int              failed = 0; /* yang assignment */
    
    clicon_debug(1, "%s %d %s", __FUNCTION__, yb, str);
    jy.jy_parse_string = str;
    jy.jy_linenum = 1;
    jy.jy_current = xt;
    jy.jy_xtop = xt;
    if (json_scan_init(&jy) < 0)
        goto done;
    if (json_parse_init(&jy) < 0)
        goto done;
    if (clixon_json_parseparse(&jy) != 0) { /* yacc returns 1 on error */
        clicon_log(LOG_NOTICE, "JSON error: line %d", jy.jy_linenum);
        if (clicon_errno == 0)
            clicon_err(OE_JSON, 0, "JSON parser error with no error code (should not happen)");
        goto done;
    }
    /* Traverse new objects */
    for (i = 0; i < jy.jy_xlen; i++) {
        x = jy.jy_xvec[i];
        /* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all 
         * members of a top-level JSON object 
         */
        if (rfc7951 && xml_prefix(x) == NULL){
            /* XXX: For top-level config file: */
            if (yb != YB_NONE || strcmp(xml_name(x),DATASTORE_TOP_SYMBOL)!=0){
                if ((cberr = cbuf_new()) == NULL){
                    clicon_err(OE_UNIX, errno, "cbuf_new");
                    goto done;
                }
                cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x));
                if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
                    goto done;
                goto fail;
            }
        }
        /* Names are split into name/prefix, but now add namespace info */
        if ((ret = json_xmlns_translate(yspec, x, xerr)) < 0)
            goto done;
        if (ret == 0)
            goto fail;
        /* Now assign yang stmts to each XML node 
         * XXX should be xml_bind_yang0_parent() sometimes.
         */
        switch (yb){
        case YB_PARENT:
            if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
                    goto done;
            if (ret == 0)
                failed++;
            break;
        case YB_MODULE_NEXT:
            if ((ret = xml_bind_yang(NULL, x, YB_MODULE, yspec, xerr)) < 0)
                goto done;
            if (ret == 0)
                failed++;
            break;
        case YB_MODULE:
            if ((ret = xml_bind_yang0(NULL, x, yb, yspec, xerr)) < 0)
                goto done;
            if (ret == 0)
                failed++;
            break;
        case YB_NONE:
            break;
        case YB_RPC: 
            if ((ret = xml_bind_yang_rpc(NULL, x, yspec, xerr)) < 0)
                goto done;
            if (ret == 0)
                failed++;
            break;
        }
        /* Now find leafs with identityrefs (+transitive) and translate 
         * prefixes in values to XML namespaces */
        if ((ret = json2xml_decode(x, xerr)) < 0)
            goto done;
        if (ret == 0) /* XXX necessary? */
            goto fail;
    }
    if (failed)
        goto fail;
    /* Sort the complete tree after parsing. Sorting is not really meaningful if Yang 
       not bound */
    if (yb != YB_NONE)
        if (xml_sort_recurse(xt) < 0)
            goto done;
    retval = 1;
 done:
    clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
    if (cberr)
        cbuf_free(cberr);
    json_parse_exit(&jy);
    json_scan_exit(&jy);
    if (jy.jy_xvec)
        free(jy.jy_xvec);
    return retval; 
 fail: /* invalid */
    retval = 0;
    goto done;
}

/*! Parse string containing JSON and return an XML tree
 *
 * @param[in]     str   String containing JSON
 * @param[in]  rfc7951  Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG
 * @param[in]     yb    How to bind yang to XML top-level when parsing
 * @param[in]     yspec Yang specification, mandatory to make module->xmlns translation
 * @param[in,out] xt    Top object, if not exists, on success it is created with name 'top'
 * @param[out]    xerr  Reason for invalid returned as netconf err msg 
 * @retval        1     OK and valid
 * @retval        0     Invalid (only if yang spec) w xerr set
 * @retval       -1     Error with clicon_err called
 *
 * @code
 *  cxobj *x = NULL;
 *  if (clixon_json_parse_string(str, 1, YB_MODULE, yspec, &x, &xerr) < 0)
 *    err;
 *  xml_free(x);
 * @endcode
 * @note  you need to free the xml parse tree after use, using xml_free()
 * @see clixon_xml_parse_string  XML instead of JSON
 * @see clixon_json_parse_file   From a file
 */
int 
clixon_json_parse_string(char      *str, 
                         int        rfc7951,
                         yang_bind  yb,
                         yang_stmt *yspec,
                         cxobj    **xt,
                         cxobj    **xerr)
{
    clicon_debug(1, "%s", __FUNCTION__);
    if (xt==NULL){
        clicon_err(OE_JSON, EINVAL, "xt is NULL");
        return -1;
    }
    if (*xt == NULL){
        if ((*xt = xml_new("top", NULL, CX_ELMNT)) == NULL)
            return -1;
    }
    return _json_parse(str, rfc7951, yb, yspec, *xt, xerr);
}

/*! Read a JSON definition from file and parse it into a parse-tree. 
 *
 * File will be parsed as follows:
 *   (1) parsed according to JSON; # Only this check if yspec is NULL
 *   (2) sanity checked wrt yang  
 *   (3) namespaces check (using <ns>:<name> notation
 *   (4) an xml parse tree will be returned
 * Note, only (1) and (4) will be done if yspec is NULL.
 * Part of (3) is to split json names if they contain colon, 
 *   eg: name="a:b" -> prefix="a", name="b"
 * But this is not done if yspec=NULL, and is not part of the JSON spec
 * 
 * @param[in]     fp    File descriptor to the JSON file (ASCII string)
 * @param[in]  rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG
 * @param[in]     yb    How to bind yang to XML top-level when parsing
 * @param[in]     yspec Yang specification, or NULL
 * @param[in,out] xt    Pointer to (XML) parse tree. If empty, create.
 * @param[out]    xerr  Reason for invalid returned as netconf err msg 
 *
 * @code
 *  cxobj *xt = NULL;
 *  if (clixon_json_parse_file(stdin, 1, YB_MODULE, yspec, &xt) < 0)
 *    err;
 *  xml_free(xt);
 * @endcode
 * @note  you need to free the xml parse tree after use, using xml_free()
 * @note, If xt empty, a top-level symbol will be added so that <tree../> will be:  <top><tree.../></tree></top>
 * @note May block on file I/O
 *
 * @retval        1     OK and valid
 * @retval        0     Invalid (only if yang spec) w xerr set
 * @retval       -1     Error with clicon_err called
 *
 * @see clixon_json_parse_string
 * @see RFC7951
 */
int
clixon_json_parse_file(FILE      *fp,
                       int        rfc7951,
                       yang_bind  yb,
                       yang_stmt *yspec,
                       cxobj    **xt,
                       cxobj    **xerr)
{
    int       retval = -1;
    int       ret;
    char     *jsonbuf = NULL;
    int       jsonbuflen = BUFLEN; /* start size */
    int       oldjsonbuflen;
    char     *ptr;
    char      ch;
    int       len = 0;

    if (xt==NULL){
        clicon_err(OE_JSON, EINVAL, "xt is NULL");
        return -1;
    }
    if ((jsonbuf = malloc(jsonbuflen)) == NULL){
        clicon_err(OE_JSON, errno, "malloc");
        goto done;
    }
    memset(jsonbuf, 0, jsonbuflen);
    ptr = jsonbuf;
    while (1){
        if ((ret = fread(&ch, 1, 1, fp)) < 0){
            clicon_err(OE_JSON, errno, "read");
            break;
        }
        if (ret != 0)
            jsonbuf[len++] = ch;
        if (ret == 0){
            if (*xt == NULL)
                if ((*xt = xml_new(JSON_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
                    goto done;
            if (len){
                if ((ret = _json_parse(ptr, rfc7951, yb, yspec, *xt, xerr)) < 0)
                    goto done;
                if (ret == 0)
                    goto fail;
            }
            break;
        }
        if (len >= jsonbuflen-1){ /* Space: one for the null character */
            oldjsonbuflen = jsonbuflen;
            jsonbuflen *= 2;
            if ((jsonbuf = realloc(jsonbuf, jsonbuflen)) == NULL){
                clicon_err(OE_JSON, errno, "realloc");
                goto done;
            }
            memset(jsonbuf+oldjsonbuflen, 0, jsonbuflen-oldjsonbuflen);
            ptr = jsonbuf;
        }
    }
    retval = 1;
 done:
    if (retval < 0 && *xt){
        free(*xt);
        *xt = NULL;
    }
    if (jsonbuf)
        free(jsonbuf);
    return retval;    
 fail:
    retval = 0;
    goto done;
}


