* Identity/identityref mapped between XML and JSON
This commit is contained in:
parent
ccc95b2826
commit
70ebfa4d80
22 changed files with 779 additions and 133 deletions
|
|
@ -36,6 +36,10 @@
|
|||
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -64,6 +68,7 @@
|
|||
#include "clixon_xml.h"
|
||||
#include "clixon_xml_sort.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"
|
||||
|
|
@ -224,7 +229,8 @@ array_eval(cxobj *xprev,
|
|||
}
|
||||
|
||||
/*! Escape a json string as well as decode xml cdata
|
||||
* And a
|
||||
* @param[out] cb cbuf (encoded)
|
||||
* @param[in] str string (unencoded)
|
||||
*/
|
||||
static int
|
||||
json_str_escape_cdata(cbuf *cb,
|
||||
|
|
@ -272,25 +278,287 @@ json_str_escape_cdata(cbuf *cb,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! If set, quoute the json value with double quotes
|
||||
* @þaram[in] xb XML body object
|
||||
@ @retval 0 Value should not be quouted, XML value is int, boolean,..
|
||||
@ @retval 1 Value should be quouted, XML value is string,..
|
||||
/*! 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
|
||||
jsonvaluestr(cxobj *xb)
|
||||
json2xml_decode_identityref(cxobj *x,
|
||||
yang_stmt *y,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = 1;
|
||||
cxobj *xp;
|
||||
yang_stmt *yp;
|
||||
enum rfc_6020 keyword;
|
||||
int retval = -1;
|
||||
char *namespace;
|
||||
char *body;
|
||||
cxobj *xb;
|
||||
cxobj *xa;
|
||||
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){
|
||||
namespace = 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, namespace);
|
||||
if (!xml_nsctx_get_prefix(nsc, namespace, &prefix2)){
|
||||
/* (no) insert a xmlns:<prefix> statement
|
||||
* Get yang prefix from import statement of my mod */
|
||||
if (yang_find_prefix_by_namespace(y, namespace, &prefix2) == 0){
|
||||
#ifndef IDENTITYREF_KLUDGE
|
||||
/* Just get the prefix from the module's own namespace */
|
||||
if (netconf_unknown_namespace_xml(xerr, "application",
|
||||
namespace,
|
||||
"No local prefix corresponding to namespace") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
#endif
|
||||
}
|
||||
/* if prefix2 is NULL here, we get the canonical prefix */
|
||||
if (prefix2 == NULL)
|
||||
prefix2 = yang_find_myprefix(ymod);
|
||||
/* Add "xmlns:prefix2=namespace" */
|
||||
if ((xa = xml_new(prefix2, x, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
if (xml_prefix_set(xa, "xmlns") < 0)
|
||||
goto done;
|
||||
if (xml_value_set(xa, namespace) < 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_XML, 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 (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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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] x XML body
|
||||
* @param[in] ys Yang spec of parent
|
||||
* @param[out] cb0 Encoded string
|
||||
*/
|
||||
static int
|
||||
xml2json_encode(cxobj *xb,
|
||||
cbuf *cb0)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xp;
|
||||
yang_stmt *yp;
|
||||
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 = xml_value(xb);
|
||||
if ((xp = xml_parent(xb)) == NULL ||
|
||||
(yp = xml_spec(xp)) == NULL)
|
||||
goto done; /* unknown */
|
||||
(yp = xml_spec(xp)) == NULL){
|
||||
cprintf(cb, "%s", body);
|
||||
goto ok; /* unknown */
|
||||
}
|
||||
keyword = yang_keyword_get(yp);
|
||||
if ((keyword == Y_LEAF || keyword == Y_LEAF_LIST))
|
||||
switch (yang_type2cv(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:
|
||||
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_INT8:
|
||||
case CGV_INT16:
|
||||
case CGV_INT32:
|
||||
|
|
@ -301,18 +569,42 @@ jsonvaluestr(cxobj *xb)
|
|||
case CGV_UINT64:
|
||||
case CGV_DEC64:
|
||||
case CGV_BOOL:
|
||||
retval = 0;
|
||||
cprintf(cb, "%s", body);
|
||||
quote = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
cprintf(cb, "%s", body);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/*! 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] yp Parent yang spec needed for body
|
||||
* @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)
|
||||
|
|
@ -381,17 +673,10 @@ xml2json1_cbuf(cbuf *cb,
|
|||
arraytype2str(arraytype),
|
||||
childtype2str(childt));
|
||||
switch(arraytype){
|
||||
case BODY_ARRAY:{
|
||||
if (jsonvaluestr(x)) { /* Only print quotation if string-type */
|
||||
cprintf(cb, "\"");
|
||||
if (json_str_escape_cdata(cb, xml_value(x)) < 0)
|
||||
goto done;
|
||||
cprintf(cb, "\"");
|
||||
}
|
||||
else /* No quotation marks */
|
||||
cprintf(cb, "%s", xml_value(x));
|
||||
case BODY_ARRAY: /* Only place in fn where body is printed */
|
||||
if (xml2json_encode(x, cb) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
case NO_ARRAY:
|
||||
if (!flat){
|
||||
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
|
||||
|
|
@ -739,7 +1024,7 @@ xml2json_vec(FILE *f,
|
|||
|
||||
/*! 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 RFC7951 to XML namespace trees
|
||||
* In other words, from JSON to XML namespace trees
|
||||
*
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[in,out] x XML tree. Translate it in-line
|
||||
|
|
@ -749,8 +1034,9 @@ xml2json_vec(FILE *f,
|
|||
* @retval -1 Error
|
||||
* @note the opposite - xml2ns is made inline in xml2json1_cbuf
|
||||
* Example: <top><module:input> --> <top><input xmlns="">
|
||||
* @see RFC7951 Sec 4
|
||||
*/
|
||||
int
|
||||
static int
|
||||
json_xmlns_translate(yang_stmt *yspec,
|
||||
cxobj *x,
|
||||
cxobj **xerr)
|
||||
|
|
@ -799,7 +1085,7 @@ json_xmlns_translate(yang_stmt *yspec,
|
|||
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>
|
||||
|
|
@ -809,17 +1095,17 @@ json_xmlns_translate(yang_stmt *yspec,
|
|||
* @param[in] yspec If set, also do yang validation
|
||||
* @param[in] name Log string, typically filename
|
||||
* @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
|
||||
* @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
|
||||
* @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,
|
||||
json_parse(char *str,
|
||||
yang_stmt *yspec,
|
||||
const char *name,
|
||||
cxobj *xt,
|
||||
|
|
@ -855,6 +1141,12 @@ json_parse(char *str,
|
|||
goto done;
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
|
||||
goto done;
|
||||
/* Now find leafs with identityrefs (+transitive) and translate
|
||||
* prefixes in values to XML namespaces */
|
||||
if ((ret = json2xml_decode(xt, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0) /* XXX necessary? */
|
||||
goto fail;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
|
|
@ -871,7 +1163,7 @@ json_parse(char *str,
|
|||
*
|
||||
* @param[in] str String containing JSON
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt On success a top of XML parse tree is created with name 'top'
|
||||
* @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
|
||||
*
|
||||
* @code
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue