clixon/lib/src/clixon_xml_io.c

1604 lines
53 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-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 *****
* Clixon XML object parse and print functions
* @see https://www.w3.org/TR/2008/REC-xml-20081126
* https://www.w3.org/TR/2009/REC-xml-names-20091208
* Canonical XML version (just for info)
* https://www.w3.org/TR/xml-c14n
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_string.h"
#include "clixon_map.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_digest.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_module.h"
#include "clixon_yang_schema_mount.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_vec.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_parse.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_default.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_datastore.h"
#include "clixon_xml_io.h"
/*
* Constants
*/
/* Size of xml read buffer */
#define BUFLEN 1024
/* Forward */
static int xml_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1, int level, int skiptop);
/*------------------------------------------------------------------------
* XML printing functions. Output a parse tree to file, string cligen buf
*------------------------------------------------------------------------*/
/*! For xml2 output: compute with-defaults: if object should be printed or not
*
* @param[in] x Clixon xml tree
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @param[out] tag If set, use XML tag to mark value (WITHDEFAULTS_REPORT_ALL_TAGGED)
* @retval 1 Keep it
* @retval 0 Remove it
* @retval -1 Error
*/
static int
xml2output_wdef(cxobj *x,
withdefaults_type wdef,
int *tag)
{
int retval = -1;
int keep = 1;
cg_var *cv;
char *yv = NULL;
char *body;
cxobj *xc;
yang_stmt *y;
int ret;
int config;
if ((y = xml_spec(x)) == NULL)
goto ok;
switch (wdef){
case WITHDEFAULTS_EXPLICIT:
case WITHDEFAULTS_TRIM:
/* inline of xml_default_nopresence()
and xml_flag_state_default_value()
*/
switch(yang_keyword_get(y)){
case Y_CONTAINER:
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
keep = 0;
/* Loop thru children */
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
if ((ret = xml2output_wdef(xc, wdef, NULL)) < 0)
goto done;
if (ret == 1)
keep = 1;
}
}
break;
case Y_LEAF:
config = yang_config_ancestor(y);
if (xml_flag(x, XML_FLAG_DEFAULT)) {
/* RFC 6243: Any value set by the NETCONF server [eg: state-data] that is not the schema
defined default value is also considered explicitly set data.*/
if (!config && wdef == WITHDEFAULTS_EXPLICIT)
;
else
keep = 0;
}
else if (wdef == WITHDEFAULTS_TRIM &&
(cv = yang_cv_get(y)) != NULL &&
(body = xml_body(x)) != NULL){
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (body && strcmp(body, yv) == 0)
keep = 0;
}
break;
default:
break;
}
break;
case WITHDEFAULTS_REPORT_ALL_TAGGED:
if (tag && yang_keyword_get(y) == Y_LEAF){
if (xml_flag(x, XML_FLAG_DEFAULT))
*tag = 1;
else if ((cv = yang_cv_get(y)) != NULL &&
(body = xml_body(x)) != NULL){
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (body && strcmp(body, yv) == 0)
*tag = 1;
}
}
break;
default:
break;
}
ok:
retval = keep;
done:
if (yv)
free(yv);
return retval;
}
/*! Print an XML tree structure to an output stream and encode chars "<>&"
*
* @param[in] f UNIX output stream
* @param[in] x Clixon xml tree
* @param[in] level How many spaces to insert before each line
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (if pretty)
* @param[in] fn Callback to make print function
* @param[in] autocliext How to handle autocli extensions: 0: ignore 1: follow
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @param[in] multi Multi-file split datastore, see CLICON_XMLDB_MULTI
* @retval 0 OK
* @retval -1 Error
* One can use clixon_xml2cbuf to get common code, but using fprintf is
* much faster than using cbuf and then printing that,...
* wdef changes the output as follows:
* - WITHDEFAULTS_REPORT_ALL - keep as-is
* - WITHDEFAULTS_TRIM - remove defaults + equal value, and no-presence
* - WITHDEFAULTS_EXPLICIT - remove defaults and no-presence
* - WITHDEFAULTS_REPORT_ALL_TAGGED
* @see xml2cbuf_recurse same with cbuf
*/
static int
xml2file_recurse(FILE *f,
cxobj *x,
int level,
int pretty,
char *prefix,
clicon_output_cb *fn,
int autocliext,
withdefaults_type wdef,
int multi)
{
int retval = -1;
char *name;
char *namespace;
cxobj *xc;
int hasbody;
int haselement;
char *val;
char *encstr = NULL; /* xml encoded string */
int exist = 0;
yang_stmt *y;
int level1;
int tag = 0;
int ret;
int subfile = 0; /* File is split into subfile */
char *xpath = NULL;
char *hexstr = NULL;
if (x == NULL)
goto ok;
level1 = level*PRETTYPRINT_INDENT;
if (prefix)
level1 -= strlen(prefix);
if ((y = xml_spec(x)) != NULL){
if (autocliext){
if (yang_extension_value(y, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
goto done;
if (exist)
goto ok;
}
if ((ret = xml2output_wdef(x, wdef, &tag)) < 0)
goto done;
if (ret == 0)
goto ok;
}
name = xml_name(x);
namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_encode(&encstr, 0, "%s", val) < 0)
goto done;
(*fn)(f, "%s", encstr);
break;
case CX_ATTR:
(*fn)(f, " ");
if (namespace)
(*fn)(f, "%s:", namespace);
(*fn)(f, "%s=\"%s\"", name, xml_value(x));
break;
case CX_ELMNT:
if (pretty && prefix)
(*fn)(f, "%s", prefix);
(*fn)(f, "%*s<", pretty?level1:0, "");
if (namespace)
(*fn)(f, "%s:", namespace);
(*fn)(f, "%s", name);
if (tag) /* If default and WITHDEFAULTS_REPORT_ALL_TAGGED */
(*fn)(f, " wd:default=\"true\"");
hasbody = 0;
haselement = 0;
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
switch (xml_type(xc)){
case CX_ATTR:
if (xml2file_recurse(f, xc, level+1, pretty, prefix, fn, autocliext, wdef, multi) < 0)
goto done;
break;
case CX_BODY:
hasbody=1;
break;
case CX_ELMNT:
haselement=1;
break;
default:
break;
}
}
/* Check for special case <a/> instead of <a></a>:
* Ie, no CX_BODY or CX_ELMNT child.
*/
if (hasbody==0 && haselement==0)
(*fn)(f, "/>");
else{
/* Check if this is a multi-file split-point */
if (multi && (y = xml_spec(x)) != NULL){
if (yang_extension_value(y, "xmldb-split", CLIXON_LIB_NS, &exist, NULL) < 0)
goto done;
if (exist){
subfile++;
if (xml2xpath(x, NULL, 1, 0, &xpath) < 0)
goto done;
if (clixon_digest_hex(xpath, &hexstr) < 0)
goto done;
(*fn)(f, " xmlns:%s=\"%s\"", CLIXON_LIB_PREFIX, CLIXON_LIB_NS);
(*fn)(f, " %s:link=\"%s.xml\"", CLIXON_LIB_PREFIX, hexstr);
(*fn)(f, "/>");
}
}
if (!subfile) {
(*fn)(f, ">");
if (pretty && hasbody == 0){
(*fn)(f, "\n");
}
}
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
cxobj *xa = NULL;
char *ns = NULL;
if (wdef == WITHDEFAULTS_REPORT_ALL_TAGGED &&
y == NULL &&
xml_spec(xc) != NULL){
if (xml2ns(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, &ns) < 0)
goto done;
if (ns == NULL){
if (xmlns_set(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE) < 0)
goto done;
xa = xml_find_type(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE, CX_ATTR);
}
}
if (xml_type(xc) != CX_ATTR && !subfile)
if (xml2file_recurse(f, xc, level+1, pretty, prefix,
fn, autocliext, wdef, multi) <0)
goto done;
if (xa){
if (xml_purge(xa) < 0)
goto done;
}
}
if (subfile == 0){
if (pretty && hasbody==0){
if (pretty && prefix)
(*fn)(f, "%s", prefix);
(*fn)(f, "%*s", level1, "");
}
(*fn)(f, "</");
if (namespace)
(*fn)(f, "%s:", namespace);
(*fn)(f, "%s>", name);
}
}
if (pretty){
(*fn)(f, "\n");
}
break;
default:
break;
}/* switch */
ok:
retval = 0;
done:
if (encstr)
free(encstr);
if (xpath)
free(xpath);
if (hexstr)
free(hexstr);
return retval;
}
/*! Print an XML tree structure to an output stream and encode chars "<>&"
*
* Extended version with with-defaults
* Assume xn being in REPORT_ALL state, modify default values according to wdef
* @param[in] f Output file
* @param[in] xn XML tree
* @param[in] level How many spaces to insert before each line
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (if pretty)
* @param[in] fn File print function (if NULL, use fprintf)
* @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
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @param[in] multi Multi-file split datastore, see CLICON_XMLDB_MULTI
* @retval 0 OK
* @retval -1 Error
* @see clixon_xml2cbuf print to a cbuf string
* @note There is a slight "layer violation" with the autocli parameter: it should normally be set
* for CLI calls, but not for others.
*/
int
clixon_xml2file1(FILE *f,
cxobj *xn,
int level,
int pretty,
char *prefix,
clicon_output_cb *fn,
int skiptop,
int autocliext,
withdefaults_type wdef,
int multi)
{
int retval = 1;
cxobj *xc;
if (fn == NULL)
fn = fprintf;
if (skiptop){
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (xml2file_recurse(f, xc, level, pretty, prefix, fn, autocliext, wdef, multi) < 0)
goto done;
}
else {
if (xml2file_recurse(f, xn, level, pretty, prefix, fn, autocliext, wdef, multi) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Print an XML tree structure to an output stream and encode chars "<>&"
*
* Assume xn being in REPORT_ALL state, modify default values according to wdef
* @param[in] f Output file
* @param[in] xn XML tree
* @param[in] level How many spaces to insert before each line
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (if pretty)
* @param[in] fn File print function (if NULL, use fprintf)
* @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
* @see clixon_xml2cbuf print to a cbuf string
* @note There is a slight "layer violation" with the autocli parameter: it should normally be set
* for CLI calls, but not for others.
* @see clixon_xml2file1 for extended version with wdef
*/
int
clixon_xml2file(FILE *f,
cxobj *xn,
int level,
int pretty,
char *prefix,
clicon_output_cb *fn,
int skiptop,
int autocliext)
{
return clixon_xml2file1(f, xn, level, pretty, prefix, fn, skiptop, autocliext, 0, 0);
}
/*! Print an XML tree structure to an output stream
*
* Utility function eg in gdb. For code, use clixon_xml2file
* @param[in] f UNIX output stream
* @param[in] xn clixon xml tree
* @see clixon_xml2cbuf
* @see clixon_xml2cbuf_cb print using a callback
*/
int
xml_print(FILE *f,
cxobj *x)
{
return xml2file_recurse(f, x, 0, 1, NULL, fprintf, 0, WITHDEFAULTS_REPORT_ALL, 0);
}
/*! Dump cxobj structure with pointers and flags for debugging, internal function
*/
static int
xml_dump1(FILE *f,
cxobj *x,
int indent)
{
cxobj *xc;
if (xml_type(x) != CX_ELMNT)
return 0;
fprintf(f, "%*s %s(%s)",
indent*PRETTYPRINT_INDENT, "",
// x,
xml_name(x),
xml_type2str(xml_type(x)));
if (xml_flag(x, XML_FLAG_ADD))
fprintf(f, " add");
if (xml_flag(x, XML_FLAG_DEL))
fprintf(f, " delete");
if (xml_flag(x, XML_FLAG_CHANGE))
fprintf(f, " change");
if (xml_flag(x, XML_FLAG_MARK))
fprintf(f, " mark");
fprintf(f, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
xml_dump1(f, xc, indent+1);
}
return 0;
}
/*! Dump cxobj structure with pointers and flags for debugging
*
* @param[in] f UNIX output stream
* @param[in] x Clixon xml tree
* @see xml_print
*/
int
xml_dump(FILE *f,
cxobj *x)
{
return xml_dump1(f, x, 0);
}
/*! Internal: print XML tree structure to a cligen buffer and encode chars "<>&"
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Clixon xml tree
* @param[in] level Indentation level for prettyprint
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (if pretty)
* @param[in] depth Limit levels of child resources: -1 is all, 0 is none, 1 is node itself
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @retval 0 OK
* @retval -1 Error
* wdef changes the output as follows:
* - WITHDEFAULTS_REPORT_ALL - keep as-is
* - WITHDEFAULTS_TRIM - remove defaults + equal value, and no-presence
* - WITHDEFAULTS_EXPLICIT - remove defaults and no-presence
* - WITHDEFAULTS_REPORT_ALL_TAGGED
* @see xml2file_recurse same with FILE
*/
static int
xml2cbuf_recurse(cbuf *cb,
cxobj *x,
int level,
int pretty,
char *prefix,
int32_t depth,
withdefaults_type wdef)
{
int retval = -1;
cxobj *xc;
char *name;
int hasbody;
int haselement;
char *namespace;
char *val;
int level1;
yang_stmt *y;
int tag = 0;
int ret;
if (depth == 0)
goto ok;
if ((y = xml_spec(x)) != NULL){
/* with-defaults: if object should be printed or not */
if ((ret = xml2output_wdef(x, wdef, &tag)) < 0)
goto done;
if (ret == 0)
goto ok;
}
level1 = level*PRETTYPRINT_INDENT;
if (prefix)
level1 -= strlen(prefix);
name = xml_name(x);
namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_cbuf_append(cb, 0, val) < 0)
goto done;
break;
case CX_ATTR:
cbuf_append_str(cb, " ");
if (namespace){
cbuf_append_str(cb, namespace);
cbuf_append_str(cb, ":");
}
cprintf(cb, "%s=\"%s\"", name, xml_value(x));
break;
case CX_ELMNT:
if (pretty){
if (prefix)
cprintf(cb, "%s", prefix);
cprintf(cb, "%*s<", level1, "");
}
else
cbuf_append_str(cb, "<");
if (namespace){
cbuf_append_str(cb, namespace);
cbuf_append_str(cb, ":");
}
cbuf_append_str(cb, name);
if (tag) /* If default and WITHDEFAULTS_REPORT_ALL_TAGGED */
cbuf_append_str(cb, " wd:default=\"true\"");
hasbody = 0;
haselement = 0;
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL)
switch (xml_type(xc)){
case CX_ATTR:
if (xml2cbuf_recurse(cb, xc, level+1, pretty, prefix, -1, wdef) < 0)
goto done;
break;
case CX_BODY:
hasbody=1;
break;
case CX_ELMNT:
haselement=1;
break;
default:
break;
}
/* Check for special case <a/> instead of <a></a> */
if (hasbody==0 && haselement==0)
cbuf_append_str(cb, "/>");
else{
cbuf_append_str(cb, ">");
if (pretty && hasbody == 0)
cbuf_append_str(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL)
if (xml_type(xc) != CX_ATTR){
cxobj *xa = NULL;
char *ns = NULL;
/* If tagged withdefaults */
if (wdef == WITHDEFAULTS_REPORT_ALL_TAGGED &&
y == NULL &&
xml_spec(xc) != NULL){
if (xml2ns(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, &ns) < 0)
goto done;
if (ns == NULL){
if (xmlns_set(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE) < 0)
goto done;
xa = xml_find_type(xc, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE, CX_ATTR);
}
}
if (xml2cbuf_recurse(cb, xc, level+1, pretty, prefix, depth-1, wdef) < 0)
goto done;
if (xa){
if (xml_purge(xa) < 0)
goto done;
}
}
if (pretty && hasbody == 0){
if (prefix)
cprintf(cb, "%s", prefix);
cprintf(cb, "%*s", level1, "");
}
cbuf_append_str(cb, "</");
if (namespace){
cbuf_append_str(cb, namespace);
cbuf_append_str(cb, ":");
}
cbuf_append_str(cb, name);
cbuf_append_str(cb, ">");
}
if (pretty)
cbuf_append_str(cb, "\n");
break;
default:
break;
}/* switch */
ok:
retval = 0;
done:
return retval;
}
/*! Print an XML tree structure to a cligen buffer and encode chars "<>&"
*
* Extended version with with-defaults
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Top-level xml object
* @param[in] level Indentation level for pretty
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (or NULL) (if pretty)
* @param[in] depth Limit levels of child resources: -1: all, 0: none, 1: node itself
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @retval 0 OK
* @retval -1 Error
* Depth is used in NACM
* @code
* cbuf *cb = cbuf_new();
* if (clixon_xml2cbuf(cb, xn, 0, 1, NULL, -1, 0, 0) < 0)
* goto err;
* fprintf(stderr, "%s", cbuf_get(cb));
* cbuf_free(cb);
* @endcode
* @see clixon_xml2file to file, which is faster
*/
int
clixon_xml2cbuf1(cbuf *cb,
cxobj *xn,
int level,
int pretty,
char *prefix,
int32_t depth,
int skiptop,
withdefaults_type wdef)
{
int retval = -1;
cxobj *xc;
if (skiptop){
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (xml2cbuf_recurse(cb, xc, level, pretty, prefix, depth, wdef) < 0)
goto done;
}
else {
if (xml2cbuf_recurse(cb, xn, level, pretty, prefix, depth, wdef) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Print an XML tree structure to a cligen buffer and encode chars "<>&"
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Top-level xml object
* @param[in] level Indentation level for pretty
* @param[in] pretty Insert \n and spaces to make the xml more readable.
* @param[in] prefix Add string to beginning of each line (or NULL) (if pretty)
* @param[in] depth Limit levels of child resources: -1: all, 0: none, 1: node itself
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK
* @retval -1 Error
* Depth is used in NACM
* @code
* cbuf *cb = cbuf_new();
* if (clixon_xml2cbuf(cb, xn, 0, 1, NULL, -1, 0, 0) < 0)
* goto err;
* fprintf(stderr, "%s", cbuf_get(cb));
* cbuf_free(cb);
* @endcode
* @see clixon_xml2file to file, which is faster
* @see clixon_xml2cbuf1 for extended version with wdef
*/
int
clixon_xml2cbuf(cbuf *cb,
cxobj *xn,
int level,
int pretty,
char *prefix,
int32_t depth,
int skiptop)
{
return clixon_xml2cbuf1(cb, xn, level, pretty, prefix, depth, skiptop, 0);
}
/*! Print actual xml tree datastructures (not xml), mainly for debugging
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Clixon xml tree
* @param[in] level Indentation level
*/
int
xmltree2cbuf(cbuf *cb,
cxobj *x,
int level)
{
cxobj *xc;
int i;
for (i=0; i<level*PRETTYPRINT_INDENT; i++)
cprintf(cb, " ");
if (xml_type(x) != CX_BODY)
cprintf(cb, "%s", xml_type2str(xml_type(x)));
if (xml_prefix(x)==NULL)
cprintf(cb, " %s", xml_name(x));
else
cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x));
if (xml_value(x))
cprintf(cb, " value:\"%s\"", xml_value(x));
if (xml_flag(x, 0xff))
cprintf(cb, " flags:0x%x", xml_flag(x, 0xff));
if (xml_child_nr(x))
cprintf(cb, " {");
cprintf(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL)
xmltree2cbuf(cb, xc, level+1);
if (xml_child_nr(x)){
for (i=0; i<level*PRETTYPRINT_INDENT; i++)
cprintf(cb, " ");
cprintf(cb, "}\n");
}
return 0;
}
/*--------------------------------------------------------------------
* XML parsing functions. Create XML parse tree from string and file.
*--------------------------------------------------------------------*/
/*! Common internal xml parsing function string to parse-tree
*
* Given a string containing XML, parse into existing XML tree and return
* @param[in] str Pointer to string containing XML definition.
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
* @param[in,out] xtop Top of XML parse tree. Assume created. Holds new tree.
* @param[out] xerr Reason for failure (yang assignment not made)
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval -1 Error
* @see clixon_xml_parse_file
* @see clixon_xml_parse_string
* @see _json_parse
* @note special case is empty XML where the parser is not invoked.
* It is questionable empty XML is legal. From https://www.w3.org/TR/2008/REC-xml-20081126 Sec 2.1:
* A well-formed document ... contains one or more elements.
* But in clixon one can invoke a parser on a sub-part of a document where it makes sense to accept
* an empty XML. For example where an empty config: <config></config> is parsed.
* In other cases, such as receiving netconf ]]>]]> it should represent a complete document and
* therefore not well-formed.
* Therefore checking for empty XML must be done by a calling function which knows wether the
* the XML represents a full document or not.
* @note may be called recursively, some yang-bind (eg rpc) semantic checks may trigger error message
* @note yang-binding over schema mount-points do not work, you need to make a separate bind call
*/
static int
_xml_parse(const char *str,
yang_bind yb,
yang_stmt *yspec,
cxobj *xt,
cxobj **xerr)
{
int retval = -1;
clixon_xml_yacc xy = {0,};
cxobj *x;
int ret;
int failed = 0; /* yang assignment */
int i;
clixon_debug(CLIXON_DBG_PARSE, "%s", str);
if (strlen(str) == 0){
return 1; /* OK */
}
if (xt == NULL){
clixon_err(OE_XML, errno, "Unexpected NULL XML");
return -1;
}
if ((xy.xy_parse_string = strdup(str)) == NULL){
clixon_err(OE_XML, errno, "strdup");
return -1;
}
xy.xy_xtop = xt;
xy.xy_xparent = xt;
xy.xy_yspec = yspec;
if (clixon_xml_parsel_init(&xy) < 0)
goto done;
if (clixon_xml_parseparse(&xy) != 0) /* yacc returns 1 on error */
goto done;
/* Purge all top-level body objects */
x = NULL;
while ((x = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
xml_purge(x);
/* Traverse new objects */
for (i = 0; i < xy.xy_xlen; i++) {
x = xy.xy_xvec[i];
/* Verify namespaces after parsing */
if (xml2ns_recurse(x) < 0)
goto done;
/* Populate, ie associate xml nodes with yang specs
*/
switch (yb){
case YB_NONE:
break;
case YB_PARENT:
/* xt:n Has spec
* x: <a> <-- populate from parent
*/
if ((ret = xml_bind_yang0(NULL, x, YB_PARENT, NULL, 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:
/* xt:<top> nospec
* x: <a> <-- populate from modules
*/
if ((ret = xml_bind_yang0(NULL, x, YB_MODULE, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
case YB_RPC:
if ((ret = xml_bind_yang_rpc(NULL, x, yspec, xerr)) < 0)
goto done;
if (ret == 0){ /* Add message-id */
if (*xerr && clixon_xml_attr_copy(x, *xerr, "message-id") < 0)
goto done;
failed++;
}
break;
} /* switch */
}
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_PARSE, "retval:%d", retval);
clixon_xml_parsel_exit(&xy);
if (xy.xy_parse_string != NULL)
free(xy.xy_parse_string);
if (xy.xy_xvec)
free(xy.xy_xvec);
return retval;
fail: /* invalid */
retval = 0;
goto done;
}
/*! Read an XML definition from file and parse it into a parse-tree, advanced API
*
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
* @param[out] xerr Pointer to XML error tree, if retval is 0
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval -1 Error
*
* @code
* cxobj *xt = NULL;
* cxobj *xerr = NULL;
* FILE *f;
* if ((f = fopen(filename, "r")) == NULL)
* err;
* if ((ret = clixon_xml_parse_file(f, YB_MODULE, yspec, &xt, &xerr)) < 0)
* err;
* xml_free(xt);
* @endcode
* @see clixon_xml_parse_string
* @see clixon_json_parse_file
* @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
*/
int
clixon_xml_parse_file(FILE *fp,
yang_bind yb,
yang_stmt *yspec,
cxobj **xt,
cxobj **xerr)
{
int retval = -1;
int ret;
int len = 0;
char ch;
char *xmlbuf = NULL;
char *ptr;
int xmlbuflen = BUFLEN; /* start size */
int oldxmlbuflen;
int failed = 0;
int xtempty; /* empty on entry */
if (xt == NULL || fp == NULL){
clixon_err(OE_XML, EINVAL, "arg is NULL");
return -1;
}
xtempty = (*xt == NULL);
if (yb == YB_MODULE && yspec == NULL){
clixon_err(OE_XML, EINVAL, "yspec is required if yb == YB_MODULE");
return -1;
}
if ((xmlbuf = malloc(xmlbuflen)) == NULL){
clixon_err(OE_XML, errno, "malloc");
goto done;
}
memset(xmlbuf, 0, xmlbuflen);
ptr = xmlbuf;
while (1){
if ((ret = fread(&ch, 1, 1, fp)) < 0){
clixon_err(OE_XML, errno, "read");
break;
}
if (ret != 0){
xmlbuf[len++] = ch;
}
if (ret == 0) {
if (*xt == NULL)
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if ((ret = _xml_parse(ptr, yb, yspec, *xt, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
}
if (len >= xmlbuflen-1){ /* Space: one for the null character */
oldxmlbuflen = xmlbuflen;
xmlbuflen *= 2;
if ((xmlbuf = realloc(xmlbuf, xmlbuflen)) == NULL){
clixon_err(OE_XML, errno, "realloc");
goto done;
}
memset(xmlbuf+oldxmlbuflen, 0, xmlbuflen-oldxmlbuflen);
ptr = xmlbuf;
}
} /* while */
retval = (failed==0) ? 1 : 0;
done:
if (retval < 0 && *xt && xtempty){
free(*xt);
*xt = NULL;
}
if (xmlbuf)
free(xmlbuf);
return retval;
}
/*! Read an XML definition from string and parse it into a parse-tree, advanced API
*
* @param[in] str String containing XML definition.
* @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 will be created.
* @param[out] xerr Reason for failure (yang assignment not made) if retval = 0
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial), xerr is set
* @retval -1 Error
*
* @code
* cxobj *xt = NULL;
* cxobj *xerr = NULL;
* if ((ret = clixon_xml_parse_string(str, YB_MODULE, yspec, &xt, &xerr)) < 0)
* err;
* if (ret == 0)
* // use xerr
* if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
* err;
* @endcode
* @see clixon_xml_parse_file
* @see clixon_xml_parse_va
* @note You need to free the xml parse tree after use, using xml_free()
* @note If empty on entry, a new TOP xml will be created named "top"
*/
int
clixon_xml_parse_string(const char *str,
yang_bind yb,
yang_stmt *yspec,
cxobj **xt,
cxobj **xerr)
{
if (xt==NULL){
clixon_err(OE_XML, EINVAL, "xt is NULL");
return -1;
}
if (yb == YB_MODULE && yspec == NULL){
clixon_err(OE_XML, EINVAL, "yspec is required if yb == YB_MODULE");
return -1;
}
if (*xt == NULL){
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
return -1;
}
return _xml_parse(str, yb, yspec, *xt, xerr);
}
/*! Read XML from var-arg list and parse it into xml tree
*
* Utility function using stdarg instead of static string.
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xtop Top of XML parse tree. If it is NULL, top element
* called 'top' will be created. Call xml_free() after use
* @param[out] xerr Reason for failure (yang assignment not made)
* @param[in] format Format string for stdarg according to printf(3)
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error
*
* @code
* cxobj *xt = NULL;
* if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL, "<xml>%d</xml>", 22) < 0)
* err;
* xml_free(xt);
* @endcode
* @see clixon_xml_parse_string
* @see clixon_xml_parse_file
* @note If vararg list is empty, consider using clixon_xml_parse_string()
*/
int
clixon_xml_parse_va(yang_bind yb,
yang_stmt *yspec,
cxobj **xtop,
cxobj **xerr,
const char *format, ...)
{
int retval = -1;
va_list args;
char *str = NULL;
int len;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((str = malloc(len)) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, len);
va_start(args, format);
len = vsnprintf(str, len, format, args) + 1;
va_end(args);
retval = clixon_xml_parse_string(str, yb, yspec, xtop, xerr);
done:
if (str)
free(str);
return retval;
}
/*! Copy an attribute value(eg message-id) from one xml (eg rpc input) to another xml (eg rpc outgoing)
*
* @param[in] xin Get attr value from this XML
* @param[in] xout Set attr value to this XML
* @param[in] name Attribute name
* @retval 0 OK
* @retval -1 Error
* Alternative is to use: char *val = xml_find_value(x, name);
* @code
* if (clixon_xml_attr_copy(xin, xout, "message-id") < 0)
* err;
* @endcode
*/
int
clixon_xml_attr_copy(cxobj *xin,
cxobj *xout,
char *name)
{
int retval = -1;
char *msgid;
cxobj *xa;
if (xin == NULL || xout == NULL){
clixon_err(OE_XML, EINVAL, "xin or xout NULL");
goto done;
}
if ((msgid = xml_find_value(xin, name)) != NULL){
if ((xa = xml_new(name, xout, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xa, msgid) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Print list keys
*/
static int
xml_diff_keys(cbuf *cb,
cxobj *x,
yang_stmt *y,
int level)
{
cvec *cvk;
cg_var *cvi;
char *keyname;
char *keyval;
if (y && yang_keyword_get(y) == Y_LIST){
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
keyval = xml_find_body(x, keyname);
cprintf(cb, "%*s<%s>%s</%s>\n", level, "", keyname, keyval, keyname);
}
}
return 0;
}
/*! Print one line of context around diff
*/
static int
xml_diff_context(cbuf *cb,
cxobj *xn,
int level)
{
int retval = -1;
char *prefix;
char *namespace = NULL;
prefix = xml_prefix(xn);
if (xml2ns(xn, prefix, &namespace) < 0)
goto done;
cprintf(cb, "%*s<", level, "");
if (namespace){
if (prefix)
cprintf(cb, "%s:", prefix);
cprintf(cb, "%s xmlns", xml_name(xn));
if (prefix)
cprintf(cb, ":%s", prefix);
cprintf(cb, "=\"%s\"", namespace);
cprintf(cb, ">\n");
}
else
cprintf(cb, "%s>\n", xml_name(xn));
retval = 0;
done:
return retval;
}
/*! Handle order-by user(leaf)list for xml_diff2cbuf
*
* @param[out] cb CLIgen buffer
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @param[in] x0c Start of sublist in first XML tree
* @param[in] x1c Start of sublist in second XML tree
* @param[in] yc Yang of x0c/x1c. If NULL special case of anydata
* @param[in] level How many spaces to insert before each line
* @retval 0 Ok
* @retval -1 Error
* @see xml_diff_ordered_by_user
* @see text_diff2cbuf_ordered_by_user
*/
static int
xml_diff2cbuf_ordered_by_user(cbuf *cb,
cxobj *x0,
cxobj *x1,
cxobj *x0c,
cxobj *x1c,
yang_stmt *yc,
int level)
{
int retval = 1;
cxobj *xi;
cxobj *xj;
xj = x1c;
do { /* Mark all x1 as ADD */
xml_flag_set(xj, XML_FLAG_ADD);
} while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
xml_spec(xj) == yc);
/* If in both sets, unmark add/del */
xi = x0c;
do {
xml_flag_set(xi, XML_FLAG_DEL);
xj = x1c;
do {
if (xml_flag(xj, XML_FLAG_ADD) &&
xml_cmp(xi, xj, 0, 0, NULL) == 0){
/* Unmark node in x0 and x1 */
xml_flag_reset(xi, XML_FLAG_DEL);
xml_flag_reset(xj, XML_FLAG_ADD);
if (xml_diff2cbuf(cb, xi, xj, level+1, 0) < 0)
goto done;
break;
}
}
while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
xml_spec(xj) == yc);
}
while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL &&
xml_spec(xi) == yc);
retval = 0;
done:
return retval;
}
/*! xml_diff2cbuf helper function to compute leaf difference
*
* @param[out] cb CLIgen buffer
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @param[in] level How many spaces to insert before each line
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 Ok
* @retval -1 Error
*/
static int
xml_diff2cbuf_leaf(cbuf *cb,
cxobj *x0,
cxobj *x1,
int level,
int skiptop,
yang_stmt *y0,
cxobj *x0c,
cxobj *x1c,
char *b0,
char *b1,
int *nr)
{
int retval = -1;
char *e0 = NULL;
char *e1 = NULL;
if (*nr==0 && skiptop==0){
xml_diff_context(cb, x0, level*PRETTYPRINT_INDENT);
xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT);
(*nr)++;
}
/* Encode data to XML */
if (b0){
if (xml_chardata_encode(&e0, 0, "%s", b0) < 0)
goto done;
}
else
e0 = NULL;
if (b1){
if (xml_chardata_encode(&e1, 0, "%s", b1) < 0)
goto done;
}
else
e1 = NULL;
cprintf(cb, "-%*s%s>%s</%s>\n", ((level+1)*PRETTYPRINT_INDENT-1), "<",
xml_name(x0c), e0, xml_name(x0c));
cprintf(cb, "+%*s%s>%s</%s>\n", ((level+1)*PRETTYPRINT_INDENT-1), "<",
xml_name(x1c), e1, xml_name(x1c));
if (e0){
free(e0);
e0 = NULL;
}
if (e1){
free(e1);
e1 = NULL;
}
retval = 0;
done:
if (e0)
free(e0);
if (e1)
free(e1);
return retval;
}
/*! Print XML diff of two cxobj trees into a cbuf
*
* YANG dependent
* Uses underlying XML diff algorithm with better result than clixon_compare_xmls
* @param[out] cb CLIgen buffer
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @param[in] level How many spaces to insert before each line
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 Ok
* @retval -1 Error
* @code
* cbuf *cb = cbuf_new();
* if (clixon_xml_diff2cbuf(cb, 0, x0, x1) < 0)
* err();
* cligen_output(stdout, "%s", cbuf_get(cb));
* @endcode
* @see xml_diff which returns diff sets
* @see clixon_compare_xmls which uses files and is independent of YANG
* @see text_diff2cbuf for curly
* @see xml_tree_equal Equal or not
* @see xml_diff Diff sets
*/
static int
xml_diff2cbuf(cbuf *cb,
cxobj *x0,
cxobj *x1,
int level,
int skiptop)
{
int retval = -1;
cxobj *x0c = NULL; /* x0 child */
cxobj *x1c = NULL; /* x1 child */
yang_stmt *y0;
yang_stmt *y0c;
yang_stmt *y1c;
char *b0;
char *b1;
int eq;
int nr=0;
int level1;
cxobj *xi;
cxobj *xj;
int extflag;
level1 = level*PRETTYPRINT_INDENT;
y0 = xml_spec(x0);
/* Traverse x0 and x1 in lock-step */
x0c = x1c = NULL;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
x1c = xml_child_each(x1, x1c, CX_ELMNT);
for (;;){
if (x0c == NULL && x1c == NULL)
goto ok;
y0c = NULL;
y1c = NULL;
/* If cl:ignore-compare extension, skip */
if (x0c && (y0c = xml_spec(x0c)) != NULL){
if (yang_extension_value(y0c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0)
goto done;
if (extflag){ /* skip */
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
}
if (x1c && (y1c = xml_spec(x1c)) != NULL){
if (yang_extension_value(y1c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0)
goto done;
if (extflag){ /* skip */
x1c = xml_child_each(x1, x1c, CX_ELMNT);
continue;
}
}
if (x0c == NULL){
/* Check if one or both subtrees are NULL */
if (nr==0 && skiptop==0){
xml_diff_context(cb, x1, level1);
xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0)
goto done;
x1c = xml_child_each(x1, x1c, CX_ELMNT);
continue;
}
else if (x1c == NULL){
if (nr==0 && skiptop==0){
xml_diff_context(cb, x0, level1);
xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0)
goto done;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
/* Both x0c and x1c exists, check if yang equal */
eq = xml_cmp(x0c, x1c, 0, 0, NULL);
b0 = xml_body(x0c);
b1 = xml_body(x1c);
if (eq && y0c && y1c && y0c == y1c && yang_find(y0c, Y_ORDERED_BY, "user")){
if (xml_diff2cbuf_ordered_by_user(cb, x0, x1, x0c, x1c, y0c, level) < 0)
goto done;
/* Show all marked as DELETE as - entries
*/
xi = x0c;
do {
if (xml_flag(xi, XML_FLAG_DEL)){
xml_flag_reset(xi, XML_FLAG_DEL);
if (nr==0 && skiptop==0){
xml_diff_context(cb, x0, level1);
xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, xi, level+1, 1, "-", -1, 0) < 0)
goto done;
}
}
while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL &&
xml_spec(xi) == y0c);
x0c = xi; /* skip entries in this yang class */
/* Show all marked as ADD as + entries
*/
xj = x1c;
do {
if (xml_flag(xj, XML_FLAG_ADD)){
xml_flag_reset(xj, XML_FLAG_ADD);
if (nr==0 && skiptop==0){
xml_diff_context(cb, x1, level1);
xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, xj, level+1, 1, "+", -1, 0) < 0)
goto done;
}
}
while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
xml_spec(xj) == y1c);
x1c = xj;
continue;
} /* ordered-by user */
else if (eq < 0){
if (nr==0 && skiptop==0){
xml_diff_context(cb, x0, level1);
xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0)
goto done;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
else if (eq > 0){
if (nr==0 && skiptop==0){
xml_diff_context(cb, x1, level1);
xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0)
goto done;
x1c = xml_child_each(x1, x1c, CX_ELMNT);
continue;
}
else{ /* equal */
/* xml-spec NULL could happen with anydata children for example,
* if so, continute compare children but without yang
*/
if (y0c && y1c && y0c != y1c){ /* choice */
if (nr==0 && skiptop==0){
xml_diff_context(cb, x0, level1);
xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT);
nr++;
}
if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0)
goto done;
if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0)
goto done;
}
else if (y0c && yang_keyword_get(y0c) == Y_LEAF){
/* if x0c and x1c are leafs w bodies, then they may be changed */
if (b0 == NULL && b1 == NULL)
;
else if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0){
if (xml_diff2cbuf_leaf(cb, x0, x1, level, skiptop,
y0, x0c, x1c, b0, b1, &nr) < 0)
goto done;
}
}
else if (y0c == NULL && y1c == NULL && (b0 || b1)) { /* Anydata terminals */
if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0){
if (xml_diff2cbuf_leaf(cb, x0, x1, level, skiptop,
y0, x0c, x1c, b0, b1, &nr) < 0)
goto done;
}
}
else if (xml_diff2cbuf(cb, x0c, x1c, level+1, 0) < 0)
goto done;
}
/* Get next */
x0c = xml_child_each(x0, x0c, CX_ELMNT);
x1c = xml_child_each(x1, x1c, CX_ELMNT);
} /* for */
ok:
if (nr)
cprintf(cb, "%*s</%s>\n", level*PRETTYPRINT_INDENT, "", xml_name(x0));
retval = 0;
done:
return retval;
}
/*! Print XML diff of two cxobj trees into a cbuf
*
* YANG dependent
* Uses underlying XML diff algorithm with better result than clixon_compare_xmls
* @param[out] cb CLIgen buffer
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @retval 0 Ok
* @retval -1 Error
* @cod
* cbuf *cb = cbuf_new();
* if (clixon_xml_diff2cbuf(cb, 0, x0, x1) < 0)
* err();
* cligen_output(stdout, "%s", cbuf_get(cb));
* @endcode
* @see xml_diff which returns diff sets
* @see clixon_compare_xmls which uses files and is independent of YANG
* @see clixon_text_diff2cbuf
*/
int
clixon_xml_diff2cbuf(cbuf *cb,
cxobj *x0,
cxobj *x1)
{
return xml_diff2cbuf(cb, x0, x1, 0, 1);
}