New `ca_system_only` backend callback for reading system-only data New `CLICON_XMLDB_SYSTEM_ONLY_CONFIG` configuration option API: Added `system_only` parameter to clixon_xml2file1() Cleared running on commit and inited candidate on startup with system-only data Added callback code in main example
1620 lines
53 KiB
C
1620 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
|
|
* @param[in] system_only Enable checks for system-only-config extension
|
|
* @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 system_only)
|
|
{
|
|
int retval = -1;
|
|
char *name;
|
|
char *namespace;
|
|
cxobj *xc;
|
|
int hasbody;
|
|
int haselement;
|
|
char *val;
|
|
char *encstr = NULL; /* xml encoded string */
|
|
int exist;
|
|
yang_stmt *y;
|
|
int level1;
|
|
int tag = 0;
|
|
int subfile = 0; /* File is split into subfile */
|
|
char *xpath = NULL;
|
|
char *hexstr = NULL;
|
|
int ret;
|
|
|
|
if (x == NULL)
|
|
goto ok;
|
|
y = xml_spec(x);
|
|
/* Check if system-only, then do not write to datastore
|
|
*/
|
|
if (y != NULL && system_only){
|
|
exist = 0;
|
|
if (yang_extension_value(y, "system-only-config", CLIXON_LIB_NS, &exist, NULL) < 0)
|
|
goto done;
|
|
if (exist){
|
|
goto ok;
|
|
}
|
|
}
|
|
level1 = level*PRETTYPRINT_INDENT;
|
|
if (prefix)
|
|
level1 -= strlen(prefix);
|
|
if (y != NULL){
|
|
if (autocliext){
|
|
exist = 0;
|
|
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, system_only) < 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, system_only) <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
|
|
* @param[in] system_only Enable checks for system-only-config extension
|
|
* @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 system_only)
|
|
{
|
|
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, system_only) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
if (xml2file_recurse(f, xn, level, pretty, prefix, fn, autocliext, wdef, multi, system_only) < 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, 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, 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);
|
|
}
|