1700 lines
52 KiB
C
1700 lines
52 KiB
C
/*
|
|
*
|
|
***** 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.
|
|
* @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
|
* and RFC 7951 JSON Encoding of Data Modeled with YANG
|
|
* and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
|
|
* 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_queue.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_debug.h"
|
|
#include "clixon_options.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)
|
|
* @retval 0 OK
|
|
*/
|
|
static int
|
|
json_str_escape_cdata(cbuf *cb,
|
|
char *str)
|
|
{
|
|
int retval = -1;
|
|
size_t len;
|
|
int i;
|
|
|
|
len = strlen(str);
|
|
for (i=0; i<len; i++)
|
|
switch (str[i]){
|
|
case '\"':
|
|
cprintf(cb, "\\\"");
|
|
break;
|
|
case '\\':
|
|
cprintf(cb, "\\\\");
|
|
break;
|
|
case '\b':
|
|
cprintf(cb, "\\b");
|
|
break;
|
|
case '\f':
|
|
cprintf(cb, "\\f");
|
|
break;
|
|
case '\n':
|
|
cprintf(cb, "\\n");
|
|
break;
|
|
case '\r':
|
|
cprintf(cb, "\\r");
|
|
break;
|
|
case '\t':
|
|
cprintf(cb, "\\t");
|
|
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;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "");
|
|
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;
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "prefix:%s body:%s namespace:%s",
|
|
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) == NULL)
|
|
goto done;
|
|
|
|
}
|
|
/* Here prefix2 is valid and can be NULL
|
|
Change body prefix to prefix2:id */
|
|
if ((cbv = cbuf_new()) == NULL){
|
|
clixon_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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
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;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", 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 */
|
|
// clixon_debug(CLIXON_DBG_DEFAULT, "body:%s prefix:%s namespace:%s", 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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
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){
|
|
clixon_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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*
|
|
* @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){
|
|
clixon_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){
|
|
clixon_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){
|
|
clixon_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
|
|
* @retval 1 OK and valid
|
|
* @retval 0 Invalid (only if yang spec)
|
|
* @retval -1 Error
|
|
*
|
|
* @see _xml_parse for XML variant
|
|
* @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 */
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%d %s", 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 */
|
|
clixon_log(NULL, LOG_NOTICE, "JSON error: line %d", jy.jy_linenum);
|
|
if (clixon_err_category() == 0)
|
|
clixon_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){
|
|
clixon_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:
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "retval:%d", 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
|
|
*
|
|
* @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)
|
|
{
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "");
|
|
if (xt==NULL){
|
|
clixon_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
|
|
* @retval 1 OK and valid
|
|
* @retval 0 Invalid (only if yang spec) w xerr set
|
|
* @retval -1 Error
|
|
*
|
|
* @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
|
|
* @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){
|
|
clixon_err(OE_JSON, EINVAL, "xt is NULL");
|
|
return -1;
|
|
}
|
|
if ((jsonbuf = malloc(jsonbuflen)) == NULL){
|
|
clixon_err(OE_JSON, errno, "malloc");
|
|
goto done;
|
|
}
|
|
memset(jsonbuf, 0, jsonbuflen);
|
|
ptr = jsonbuf;
|
|
while (1){
|
|
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
|
clixon_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){
|
|
clixon_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;
|
|
}
|
|
|
|
|