* Identity/identityref mapped between XML and JSON

This commit is contained in:
Olof hagsand 2019-07-28 18:09:55 +02:00
parent ccc95b2826
commit 70ebfa4d80
22 changed files with 779 additions and 133 deletions

View file

@ -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