diff --git a/CHANGELOG.md b/CHANGELOG.md index 6752cf31..93ec183f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ Expected: Early March 2020 * Bugfix of config false statement may cause change of sorting of lists in GET opertions (lists that were sorted should not have been sorted) * New clixon-config@2020-02-22.yang revision * Search index extension `search_index` for declaring which non-key variables are search indexes - * Added `clixon-stats` for clixon XML and memory statistics. + * Added `clixon-stats` state for clixon XML and memory statistics. * JSON parse error messages change from ` on line x: syntax error,..` to `json_parse: line x: syntax error` * Unknown-element error message is more descriptive, eg from `namespace is: urn:example:clixon` to: `Failed to find YANG spec of XML node: x with parent: xp in namespace urn:example:clixon`. * C-API parse and validation API more capable diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 6e6513d0..0f5e8829 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 933777b2..25cb68c2 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -134,9 +134,6 @@ int nscache_set(cxobj *x, char *prefix, char *namespace); int nscache_clear(cxobj *x); int nscache_replace(cxobj *x, cvec *ns); -int xml2ns(cxobj *x, char *localname, char **namespace); -int xml2prefix(cxobj *xn, char *namespace, char **prefixp); - int xmlns_set(cxobj *x, char *prefix, char *namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -193,20 +190,6 @@ cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); int xml_free(cxobj *xn); -int xml_print(FILE *f, cxobj *xn); -int clicon_xml2file(FILE *f, cxobj *xn, int level, int prettyprint); -int clicon_xml2cbuf(cbuf *xf, cxobj *xn, int level, int prettyprint, int32_t depth); -int xml_parse_file(int fd, yang_stmt *yspec, cxobj **xt); -int xml_parse_file2(int fd, enum yang_bind bind, yang_stmt *yspec, char *endtag, cxobj **xt, cxobj **xerr); -int xml_parse_string(const char *str, yang_stmt *yspec, cxobj **xml_top); -int xml_parse_string2(const char *str, enum yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr); - -#if defined(__GNUC__) && __GNUC__ >= 3 -int xml_parse_va(cxobj **xt, yang_stmt *yspec, const char *format, ...) __attribute__ ((format (printf, 3, 4))); -#else -int xml_parse_va(cxobj **xt, yang_stmt *yspec, const char *format, ...); -#endif -int xmltree2cbuf(cbuf *cb, cxobj *x, int level); int xml_copy_one(cxobj *xn0, cxobj *xn1); int xml_copy(cxobj *x0, cxobj *x1); cxobj *xml_dup(cxobj *x0); @@ -219,9 +202,6 @@ int xml_apply0(cxobj *xn, enum cxobj_type type, xml_applyfn_t fn, void *ar int xml_apply_ancestor(cxobj *xn, xml_applyfn_t fn, void *arg); int xml_isancestor(cxobj *x, cxobj *xp); -int xml_body_parse(cxobj *xb, enum cv_type type, cg_var **cvp); -int xml_body_int32(cxobj *xb, int32_t *val); -int xml_body_uint32(cxobj *xb, uint32_t *val); int xml_operation(char *opstr, enum operation_type *op); char *xml_operation2str(enum operation_type op); int xml_attr_insert2val(char *instr, enum insert_type *ins); diff --git a/lib/clixon/clixon_xml_io.h b/lib/clixon/clixon_xml_io.h new file mode 100644 index 00000000..c89fb9f3 --- /dev/null +++ b/lib/clixon/clixon_xml_io.h @@ -0,0 +1,65 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC + + 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 + */ +#ifndef _CLIXON_XML_IO_H_ +#define _CLIXON_XML_IO_H_ + +/* + * Prototypes + */ +int clicon_xml2file(FILE *f, cxobj *x, int level, int prettyprint); +int xml_print(FILE *f, cxobj *xn); +int clicon_xml2cbuf(cbuf *cb, cxobj *x, int level, int prettyprint, int32_t depth); +int xmltree2cbuf(cbuf *cb, cxobj *x, int level); + +int xml_parse_file(int fd, yang_stmt *yspec, cxobj **xt); +int xml_parse_file2(int fd, enum yang_bind yb, yang_stmt *yspec, char *endtag, cxobj **xt, cxobj **xerr); +int xml_parse_string2(const char *str, enum yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr); +int xml_parse_string(const char *str, yang_stmt *yspec, cxobj **xt); +#if defined(__GNUC__) && __GNUC__ >= 3 +int xml_parse_va(cxobj **xt, yang_stmt *yspec, const char *format, ...) __attribute__ ((format (printf, 3, 4))); +#else +int xml_parse_va(cxobj **xt, yang_stmt *yspec, const char *format, ...); +#endif +#ifdef NOTUSED +int xml_body_int32(cxobj *xb, int32_t *val); +int xml_body_uint32(cxobj *xb, uint32_t *val); +#endif + +#endif /* _CLIXON_XML_IO_H_ */ diff --git a/lib/clixon/clixon_xml_nsctx.h b/lib/clixon/clixon_xml_nsctx.h index 5d6987ee..740ba078 100644 --- a/lib/clixon/clixon_xml_nsctx.h +++ b/lib/clixon/clixon_xml_nsctx.h @@ -57,4 +57,8 @@ int xml_nsctx_node(cxobj *x, cvec **ncp); int xml_nsctx_yang(yang_stmt *yn, cvec **ncp); int xml_nsctx_yangspec(yang_stmt *yspec, cvec **ncp); +int xml2ns(cxobj *x, char *localname, char **namespace); +int xml2prefix(cxobj *xn, char *namespace, char **prefixp); +int xml_localname_check(cxobj *xn, void *arg); + #endif /* _CLIXON_XML_NSCTX_H */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 183451c9..07f1341d 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -69,7 +69,8 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \ - clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c clixon_json.c \ + clixon_xml.c clixon_xml_io.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c \ + clixon_json.c \ clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_yang_parse_lib.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ clixon_path.c clixon_validate.c \ diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 754b9cb5..4c21846a 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -75,6 +75,7 @@ #include "clixon_netconf_lib.h" #include "clixon_yang_module.h" #include "clixon_xml_map.h" +#include "clixon_xml_io.h" #include "clixon_xml_nsctx.h" #include "clixon_datastore.h" @@ -646,7 +647,7 @@ xmldb_get_zerocopy(clicon_handle h, /*! Get content of datastore and return a copy of the XML tree * @param[in] h Clicon handle - * @param[in] db Name of database to search in (filename including dir path + * @param[in] db Name of database to search in, eg "running" * @param[in] nsc XML namespace context for XPATH * @param[in] xpath String with XPATH syntax. or NULL for all * @param[out] xret Single return XML tree. Free with xml_free() diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index a759cef9..f02b5f57 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -76,8 +76,8 @@ #include "clixon_yang_type.h" #include "clixon_yang_module.h" #include "clixon_xml_nsctx.h" +#include "clixon_xml_io.h" #include "clixon_xml_map.h" - #include "clixon_datastore.h" #include "clixon_datastore_write.h" #include "clixon_datastore_read.h" diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 2e4c8cfc..2a4e8f85 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -64,6 +64,7 @@ #include "clixon_options.h" #include "clixon_data.h" #include "clixon_xml_map.h" +#include "clixon_xml_io.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_yang_module.h" diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index ccc1550c..3d852fed 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -77,6 +77,7 @@ #include "clixon_yang_parse_lib.h" #include "clixon_netconf_lib.h" #include "clixon_xml_nsctx.h" +#include "clixon_xml_io.h" #include "clixon_validate.h" #include "clixon_xml_map.h" diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 5d48ba17..eaf7e024 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -61,6 +61,7 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_nsctx.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 2baaf325..c312f661 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -70,6 +70,7 @@ #include "clixon_yang.h" #include "clixon_sig.h" #include "clixon_xml.h" +#include "clixon_xml_io.h" #include "clixon_options.h" #include "clixon_proto.h" diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 8e9bc51b..b4371911 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -78,6 +78,7 @@ #include "clixon_xml_nsctx.h" #include "clixon_xml_map.h" #include "clixon_xml_sort.h" +#include "clixon_xml_io.h" #include "clixon_netconf_lib.h" #include "clixon_proto_client.h" diff --git a/lib/src/clixon_stream.c b/lib/src/clixon_stream.c index 3d1d51a7..970da91f 100644 --- a/lib/src/clixon_stream.c +++ b/lib/src/clixon_stream.c @@ -77,6 +77,7 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_io.h" #include "clixon_options.h" #include "clixon_data.h" #include "clixon_xpath_ctx.h" diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 2dc1f773..17ca920f 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -71,18 +71,13 @@ #include "clixon_xml_map.h" /* xml_spec_populate */ #include "clixon_xml_vec.h" #include "clixon_xml_sort.h" +#include "clixon_xml_io.h" #include "clixon_xml_parse.h" #include "clixon_xml_nsctx.h" /* * Constants */ -/* Size of xml read buffer */ -#define BUFLEN 1024 -/* Indentation for xml pretty-print. Consider option? */ -#define XML_INDENT 3 -/* Name of xml top object created by xml parse functions */ -#define XML_TOP_SYMBOL "top" /* How many XML children to start with if any (and then add exponentialy) */ #define XML_CHILDVEC_MAX_DEFAULT 4 /* Initial length of x_value malloced string */ @@ -461,227 +456,6 @@ nscache_clear(cxobj *x) return 0; } -/*! Given an xml tree return URI namespace recursively : default or localname given - * - * Given an XML tree and a prefix (or NULL) return URI namespace. - * @param[in] x XML tree - * @param[in] prefix prefix/ns localname. If NULL then return default. - * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree - * @retval 0 OK - * @retval -1 Error - * @code - * if (xml2ns(xt, NULL, &namespace) < 0) - * err; - * @endcode - * @see xmlns_check - * @see xmlns_set cache is set - * @note, this function uses a cache. - */ -int -xml2ns(cxobj *x, - char *prefix, - char **namespace) -{ - int retval = -1; - char *ns = NULL; - cxobj *xp; - - if ((ns = nscache_get(x, prefix)) != NULL) - goto ok; - if (prefix != NULL) /* xmlns:="" */ - ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); - else{ /* xmlns="" */ - ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); - } - /* namespace not found, try parent */ - if (ns == NULL){ - if ((xp = xml_parent(x)) != NULL){ - if (xml2ns(xp, prefix, &ns) < 0) - goto done; - } - /* If no parent, return default namespace if defined */ -#ifdef USE_NETCONF_NS_AS_DEFAULT - else{ - if (prefix == NULL) - ns = NETCONF_BASE_NAMESPACE; - else - ns = NULL; - } -#endif - } - /* Set default namespace cache (since code is at this point, - * no cache was found */ - if (ns && nscache_set(x, prefix, ns) < 0) - goto done; - ok: - if (namespace) - *namespace = ns; - retval = 0; - done: - return retval; -} - -/*! Add a namespace attribute to an XML node, either default or specific prefix - * @param[in] x XML tree - * @param[in] prefix prefix/ns localname. If NULL then set default xmlns - * @param[in] ns URI namespace (or NULL). Will be copied - * @retval 0 OK - * @retval -1 Error - * @see xml2ns - */ -int -xmlns_set(cxobj *x, - char *prefix, - char *ns) -{ - int retval = -1; - cxobj *xa; - - if (prefix != NULL){ /* xmlns:="" */ - if ((xa = xml_new(prefix, x, NULL)) == NULL) - goto done; - if (xml_prefix_set(xa, "xmlns") < 0) - goto done; - } - else{ /* xmlns="" */ - if ((xa = xml_new("xmlns", x, NULL)) == NULL) - goto done; - } - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, ns) < 0) - goto done; - /* (re)set namespace cache (as used in xml2ns) */ - if (ns && nscache_set(x, prefix, ns) < 0) - goto done; - retval = 0; - done: - return retval; -} - -/*! Get namespace given prefix recursively - * @param[in] xn XML node - * @param[in] namespace Namespace - * @param[out] prefixp Pointer to prefix if found - * @retval -1 Error - * @retval 0 No namespace found - * @retval 1 Namespace found, prefix returned in prefixp - */ -int -xml2prefix(cxobj *xn, - char *namespace, - char **prefixp) -{ - int retval = -1; - cxobj *xa = NULL; - cxobj *xp; - char *prefix = NULL; - char *xaprefix; - int ret; - - if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */ - goto found; - xa = NULL; - while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) { - /* xmlns=namespace */ - if (strcmp("xmlns", xml_name(xa)) == 0){ - if (strcmp(xml_value(xa), namespace) == 0){ - if (nscache_set(xn, NULL, namespace) < 0) - goto done; - prefix = NULL; /* Maybe should set all caches in ns:s children? */ - goto found; - } - } - /* xmlns:prefix=namespace */ - else if ((xaprefix=xml_prefix(xa)) != NULL && - strcmp("xmlns", xaprefix) == 0){ - if (strcmp(xml_value(xa), namespace) == 0){ - prefix = xml_name(xa); - if (nscache_set(xn, prefix, namespace) < 0) - goto done; - goto found; - } - } - } - if ((xp = xml_parent(xn)) != NULL){ - if ((ret = xml2prefix(xp, namespace, &prefix)) < 0) - goto done; - if (ret == 1){ - if (nscache_set(xn, prefix, namespace) < 0) - goto done; - goto found; - } - } - retval = 0; - done: - return retval; - found: - *prefixp = prefix; - retval = 1; - goto done; -} - -/*! See if xmlns:[=] exists, if so return - * - * @param[in] xn XML node - * @param[in] nsn Namespace name - * @retval URI return associated URI if found - * @retval NULL No namespace name binding found for nsn - * @see xml2ns XXX coordinate - */ -static char * -xmlns_check(cxobj *xn, - char *nsn) -{ - cxobj *x = NULL; - char *xns; - - while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) - if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 && - strcmp(xml_name(x), nsn) == 0) - return xml_value(x); - return NULL; -} - -/*! Check namespace of xml node by searching recursively among ancestors - * @param[in] xn xml node - * @param[in] namespace check validity of namespace - * @retval 0 Found / validated or no yang spec - * @retval -1 Not found - * @note This function is grossly inefficient - */ -static int -xml_localname_check(cxobj *xn, - void *arg) -{ - cxobj *xp = NULL; - char *nsn; - char *n; - yang_stmt *ys = xml_spec(xn); - - /* No namespace name - comply */ - if ((nsn = xml_prefix(xn)) == NULL) - return 0; - /* Check if NSN defined in same node */ - if (xmlns_check(xn, nsn) != NULL) - return 0; - /* Check if NSN defined in some ancestor */ - while ((xp = xml_parent(xn)) != NULL) { - if (xmlns_check(xp, nsn) != NULL) - return 0; - xn = xp; - } - /* Check if my namespace */ - if ((n = yang_find_myprefix(ys)) != NULL && strcmp(nsn,n)==0) - return 0; - /* Check if any imported module */ - if (yang_find_module_by_prefix(ys, nsn) != NULL) - return 0; - /* Not found, error */ - clicon_err(OE_XML, ENOENT, "Namespace name %s in %s:%s not found", - nsn, nsn, xml_name(xn)); - return -1; -} - /*! Get parent of xnode * @param[in] xn xml node * @retval parent xml node @@ -1793,674 +1567,6 @@ xml_free(cxobj *x) return 0; } -/*------------------------------------------------------------------------ - * XML printing functions. Output a parse tree to file, string cligen buf - *------------------------------------------------------------------------*/ - -/*! Print an XML tree structure to an output stream and encode chars "<>&" - * - * @param[in] f UNIX output stream - * @param[in] xn clicon xml tree - * @param[in] level how many spaces to insert before each line - * @param[in] prettyprint insert \n and spaces tomake the xml more readable. - * @see clicon_xml2cbuf - * One can use clicon_xml2cbuf to get common code, but using fprintf is - * much faster than using cbuf and then printing that,... - */ -int -clicon_xml2file(FILE *f, - cxobj *x, - int level, - int prettyprint) -{ - int retval = -1; - char *name; - char *namespace; - cxobj *xc; - int hasbody; - int haselement; - char *val; - char *encstr = NULL; /* xml encoded string */ - - if (x == NULL) - 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, "%s", val) < 0) - goto done; - fprintf(f, "%s", encstr); - break; - case CX_ATTR: - fprintf(f, " "); - if (namespace) - fprintf(f, "%s:", namespace); - fprintf(f, "%s=\"%s\"", name, xml_value(x)); - break; - case CX_ELMNT: - fprintf(f, "%*s<", prettyprint?(level*XML_INDENT):0, ""); - if (namespace) - fprintf(f, "%s:", namespace); - fprintf(f, "%s", name); - 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 (clicon_xml2file(f, xc, level+1, prettyprint) <0) - goto done; - break; - case CX_BODY: - hasbody=1; - break; - case CX_ELMNT: - haselement=1; - break; - default: - break; - } - } - /* Check for special case instead of : - * Ie, no CX_BODY or CX_ELMNT child. - */ - if (hasbody==0 && haselement==0) - fprintf(f, "/>"); - else{ - fprintf(f, ">"); - if (prettyprint && hasbody == 0) - fprintf(f, "\n"); - xc = NULL; - while ((xc = xml_child_each(x, xc, -1)) != NULL) { - if (xml_type(xc) != CX_ATTR) - if (clicon_xml2file(f, xc, level+1, prettyprint) <0) - goto done; - } - if (prettyprint && hasbody==0) - fprintf(f, "%*s", level*XML_INDENT, ""); - fprintf(f, "", name); - } - if (prettyprint) - fprintf(f, "\n"); - break; - default: - break; - }/* switch */ - ok: - retval = 0; - done: - if (encstr) - free(encstr); - return retval; -} - -/*! Print an XML tree structure to an output stream - * - * Uses clicon_xml2file internally - * - * @param[in] f UNIX output stream - * @param[in] xn clicon xml tree - * @see clicon_xml2cbuf - * @see clicon_xml2file - */ -int -xml_print(FILE *f, - cxobj *xn) -{ - return clicon_xml2file(f, xn, 0, 1); -} - -/*! Print an XML tree structure to a cligen buffer and encode chars "<>&" - * - * @param[in,out] cb Cligen buffer to write to - * @param[in] xn Clicon xml tree - * @param[in] level Indentation level for prettyprint - * @param[in] prettyprint insert \n and spaces tomake the xml more readable. - * @param[in] depth Limit levels of child resources: -1 is all, 0 is none, 1 is node itself - * - * @code - * cbuf *cb; - * cb = cbuf_new(); - * if (clicon_xml2cbuf(cb, xn, 0, 1, -1) < 0) - * goto err; - * fprintf(stderr, "%s", cbuf_get(cb)); - * cbuf_free(cb); - * @endcode - * @see clicon_xml2file - */ -int -clicon_xml2cbuf(cbuf *cb, - cxobj *x, - int level, - int prettyprint, - int32_t depth) -{ - int retval = -1; - cxobj *xc; - char *name; - int hasbody; - int haselement; - char *namespace; - char *encstr = NULL; /* xml encoded string */ - char *val; - - if (depth == 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, "%s", val) < 0) - goto done; - cprintf(cb, "%s", encstr); - break; - case CX_ATTR: - cprintf(cb, " "); - if (namespace) - cprintf(cb, "%s:", namespace); - cprintf(cb, "%s=\"%s\"", name, xml_value(x)); - break; - case CX_ELMNT: - cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, ""); - if (namespace) - cprintf(cb, "%s:", namespace); - cprintf(cb, "%s", name); - 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 (clicon_xml2cbuf(cb, xc, level+1, prettyprint, -1) < 0) - goto done; - break; - case CX_BODY: - hasbody=1; - break; - case CX_ELMNT: - haselement=1; - break; - default: - break; - } - /* Check for special case instead of */ - if (hasbody==0 && haselement==0) - cprintf(cb, "/>"); - else{ - cprintf(cb, ">"); - if (prettyprint && hasbody == 0) - cprintf(cb, "\n"); - xc = NULL; - while ((xc = xml_child_each(x, xc, -1)) != NULL) - if (xml_type(xc) != CX_ATTR) - if (clicon_xml2cbuf(cb, xc, level+1, prettyprint, depth-1) < 0) - goto done; - if (prettyprint && hasbody == 0) - cprintf(cb, "%*s", level*XML_INDENT, ""); - cprintf(cb, "", name); - } - if (prettyprint) - cprintf(cb, "\n"); - break; - default: - break; - }/* switch */ - ok: - retval = 0; - done: - if (encstr) - free(encstr); - return retval; -} -/*! Print actual xml tree datastructures (not xml), mainly for debugging - * @param[in,out] cb Cligen buffer to write to - * @param[in] xn Clicon xml tree - * @param[in] level Indentation level - */ -int -xmltree2cbuf(cbuf *cb, - cxobj *x, - int level) -{ - cxobj *xc; - int i; - - for (i=0; ix_flags) - cprintf(cb, " flags:0x%x", x->x_flags); - 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 <-- populate from parent - */ - if ((ret = xml_spec_populate0_parent(x, xerr)) < 0) - goto done; - if (ret == 0) - failed++; - break; - case YB_TOP: - /* xt: nospec - * x: <-- populate from modules - */ -#ifdef XMLDB_CONFIG_HACK - if (strcmp(xml_name(x),"config") == 0){ - /* xt: nospec - * x: - * <-- populate from modules - */ - if ((ret = xml_spec_populate(x, yspec, xerr)) < 0) - goto done; - } - else -#endif - if ((ret = xml_spec_populate0(x, yspec, xerr)) < 0) - goto done; - if (ret == 0) - failed++; - break; - } - } - /* Sort the complete tree after parsing. Sorting is less meaningful if Yang not bound */ - if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) - goto done; - retval = (failed==0) ? 1 : 0; - done: - 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; -} - -/*! Read an XML definition from file and parse it into a parse-tree. - * - * @param[in] fd A file descriptor containing the XML file (as ASCII characters) - * @param[in] yspec Yang specification, or NULL - * @param[in,out] xt Pointer to XML parse tree. If empty, create. - * @retval 1 Parse OK and all yang assignment made - * @retval 0 Parse OK but yang assigment not made (or only partial) - * @retval -1 Error with clicon_err called. Includes parse error * - * @code - * cxobj *xt = NULL; - * int fd; - * fd = open(filename, O_RDONLY); - * xml_parse_file(fd, yspec, &xt); - * xml_free(xt); - * @endcode - * @see xml_parse_string - * @see xml_parse_va - * @note, If xt empty, a top-level symbol will be added so that will be: - * @note May block on file I/O - * @see xml_parse_file2 for a more advanced API - */ -int -xml_parse_file(int fd, - yang_stmt *yspec, - cxobj **xt) -{ - enum yang_bind yb = YB_PARENT; - - if (xt==NULL){ - clicon_err(OE_XML, EINVAL, "xt is NULL"); - return -1; - } - if (*xt==NULL) - yb = YB_TOP; - return xml_parse_file2(fd, yb, yspec, NULL, xt, NULL); -} - -/*! FSM to detect substring - */ -static inline int -FSM(char *tag, - char ch, - int state) -{ - if (tag[state] == ch) - return state+1; - else - return 0; -} - -/*! 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] endtag Read until encounter "endtag" in the stream, or NULL - * @param[in,out] xt Pointer to XML parse tree. If empty, create. - * @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 with clicon_err called. Includes parse error - * - * @code - * cxobj *xt = NULL; - * cxobj *xerr = NULL; - * int fd; - * fd = open(filename, O_RDONLY); - * if ((ret = xml_parse_file2(fd, YB_TOP, yspec, "", &xt, &xerr)) < 0) - * err; - * xml_free(xt); - * @endcode - * @see xml_parse_string - * @see xml_parse_file - * @note, If xt empty, a top-level symbol will be added so that will be: - * @note May block on file I/O - */ -int -xml_parse_file2(int fd, - enum yang_bind yb, - yang_stmt *yspec, - char *endtag, - 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 endtaglen = 0; - int state = 0; - int oldxmlbuflen; - int failed = 0; - - if (endtag != NULL) - endtaglen = strlen(endtag); - if ((xmlbuf = malloc(xmlbuflen)) == NULL){ - clicon_err(OE_XML, errno, "malloc"); - goto done; - } - memset(xmlbuf, 0, xmlbuflen); - ptr = xmlbuf; - while (1){ - if ((ret = read(fd, &ch, 1)) < 0){ - clicon_err(OE_XML, errno, "read: [pid:%d]", - (int)getpid()); - break; - } - if (ret != 0){ - if (endtag) - state = FSM(endtag, ch, state); - xmlbuf[len++] = ch; - } - if (ret == 0 || - (endtag && (state == endtaglen))){ - state = 0; - if (*xt == NULL) - if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == 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){ - clicon_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){ - 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) - * @retval 1 Parse OK and all yang assignment made - * @retval 0 Parse OK but yang assigment not made (or only partial) - * @retval -1 Error with clicon_err called. Includes parse error - * - * @code - * cxobj *xt = NULL; - * cxobj *xerr = NULL; - * if (xml_parse_string2(str, YB_TOP, yspec, &xt, &xerr) < 0) - * err; - * if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP - * err; - * @endcode - * @see xml_parse_file - * @see 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 -xml_parse_string2(const char *str, - enum yang_bind yb, - yang_stmt *yspec, - cxobj **xt, - cxobj **xerr) -{ - if (xt==NULL){ - clicon_err(OE_XML, EINVAL, "xt is NULL"); - return -1; - } - if (*xt == NULL){ - if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) - return -1; - } - return _xml_parse(str, yb, yspec, *xt, xerr); -} - -/*! Read an XML definition from string and parse it into a parse-tree - * - * @param[in] str String containing XML definition. - * @param[in] yspec Yang specification, or NULL - * @param[in,out] xt Pointer to XML parse tree. If empty will be created. - * @retval 1 Parse OK and all yang assignment made - * @retval 0 Parse OK but yang assigment not made (or only partial) - * @retval -1 Error with clicon_err called. Includes parse error - * - * @code - * cxobj *xt = NULL; - * if (xml_parse_string(str, yspec, &xt) < 0) - * err; - * if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP - * err; - * @endcode - * @see xml_parse_file - * @see xml_parse_va - * @note You need to free the xml parse tree after use, using xml_free() - * @note If xt is empty on entry, a new TOP xml will be created named "top" and yang binding - * assumed to be TOP - */ -int -xml_parse_string(const char *str, - yang_stmt *yspec, - cxobj **xt) -{ - enum yang_bind yb = YB_PARENT; - - if (xt==NULL){ - clicon_err(OE_XML, EINVAL, "xt is NULL"); - return -1; - } - if (*xt == NULL){ - yb = YB_TOP; /* ad-hoc #1 */ - if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) - return -1; - } - else{ - if (xml_spec(*xt) == NULL) - yb = YB_TOP; /* ad-hoc #2 */ - } - return _xml_parse(str, yb, yspec, *xt, NULL); -} - -/*! Read XML from var-arg list and parse it into xml tree - * - * Utility function using stdarg instead of static string. - * @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[in] yspec Yang specification, or NULL - * @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 with clicon_err called. Includes parse error - * - * @code - * cxobj *xt = NULL; - * if (xml_parse_va(&xt, NULL, "%d", 22) < 0) - * err; - * xml_free(xt); - * @endcode - * @see xml_parse_string - * @see xml_parse_file - * @note If vararg list is empty, consider using xml_parse_string() - */ -int -xml_parse_va(cxobj **xtop, - yang_stmt *yspec, - 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){ - clicon_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 = xml_parse_string(str, yspec, xtop); /* xml_parse_string2 */ - done: - if (str) - free(str); - return retval; -} - /*! Copy single xml node from x0 to x1 without copying children * @param[in] x0 Source XML tree * @param[in] x1 Destination XML tree (must exist) @@ -2803,98 +1909,6 @@ xml_isancestor(cxobj *x, return 0; } -/*! Generic parse function for xml values - * @param[in] xb xml tree body node, ie containing a value to be parsed - * @param[in] type Type of value to be parsed in value - * @param[out] cvp CLIgen variable containing the parsed value - * @note free cv with cv_free after use. - * @see xml_body_int32 etc, for type-specific parse functions - * @note range check failure returns 0 - */ -int -xml_body_parse(cxobj *xb, - enum cv_type type, - cg_var **cvp) -{ - int retval = -1; - cg_var *cv = NULL; - int cvret; - char *bstr; - char *reason = NULL; - - if ((bstr = xml_body(xb)) == NULL){ - clicon_err(OE_XML, 0, "No body found"); - goto done; - } - if ((cv = cv_new(type)) == NULL){ - clicon_err(OE_XML, errno, "cv_new"); - goto done; - } - if ((cvret = cv_parse1(bstr, cv, &reason)) < 0){ - clicon_err(OE_XML, errno, "cv_parse"); - goto done; - } - if (cvret == 0){ /* parsing failed */ - clicon_err(OE_XML, errno, "Parsing CV: %s", reason); - if (reason) - free(reason); - } - *cvp = cv; - retval = 0; - done: - if (retval < 0 && cv != NULL) - cv_free(cv); - return retval; -} - -/*! Parse an xml body as int32 - * The real parsing functions are in the cligen code - * @param[in] xb xml tree body node, ie containing a value to be parsed - * @param[out] val Value after parsing - * @retval 0 OK, parsed value in 'val' - * @retval -1 Error, one of: body not found, parse error, - * alloc error. - * @note extend to all other cligen var types and generalize - * @note use yang type info? - * @note range check failure returns 0 - */ -int -xml_body_int32(cxobj *xb, - int32_t *val) -{ - cg_var *cv = NULL; - - if (xml_body_parse(xb, CGV_INT32, &cv) < 0) - return -1; - *val = cv_int32_get(cv); - cv_free(cv); - return 0; -} - -/*! Parse an xml body as uint32 - * The real parsing functions are in the cligen code - * @param[in] xb xml tree body node, ie containing a value to be parsed - * @param[out] val Value after parsing - * @retval 0 OK, parsed value in 'val' - * @retval -1 Error, one of: body not found, parse error, - * alloc error. - * @note extend to all other cligen var types and generalize - * @note use yang type info? - * @note range check failure returns 0 - */ -int -xml_body_uint32(cxobj *xb, - uint32_t *val) -{ - cg_var *cv = NULL; - - if (xml_body_parse(xb, CGV_UINT32, &cv) < 0) - return -1; - *val = cv_uint32_get(cv); - cv_free(cv); - return 0; -} - /*! Map xml operation from string to enumeration * @param[in] opstr String, eg "merge" * @param[out] op Enumeration, eg OP_MERGE diff --git a/lib/src/clixon_xml_changelog.c b/lib/src/clixon_xml_changelog.c index 2949a1fc..b228c446 100644 --- a/lib/src/clixon_xml_changelog.c +++ b/lib/src/clixon_xml_changelog.c @@ -72,6 +72,7 @@ #include "clixon_netconf_lib.h" #include "clixon_xml_nsctx.h" #include "clixon_xml_map.h" +#include "clixon_xml_io.h" #include "clixon_validate.h" #include "clixon_xml_changelog.h" #include "clixon_xpath_ctx.h" diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c new file mode 100644 index 00000000..f1c5e024 --- /dev/null +++ b/lib/src/clixon_xml_io.c @@ -0,0 +1,851 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC + + 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_err.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_log.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_options.h" /* xml_spec_populate */ +#include "clixon_yang_module.h" +#include "clixon_xml_map.h" /* xml_spec_populate */ +#include "clixon_xml_vec.h" +#include "clixon_xml_sort.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xml_parse.h" +#include "clixon_xml_io.h" + +/* + * Constants + */ +/* Size of xml read buffer */ +#define BUFLEN 1024 +/* Indentation for xml pretty-print. Consider option? */ +#define XML_INDENT 3 +/* Name of xml top object created by xml parse functions */ +#define XML_TOP_SYMBOL "top" + + +/*------------------------------------------------------------------------ + * XML printing functions. Output a parse tree to file, string cligen buf + *------------------------------------------------------------------------*/ + +/*! Print an XML tree structure to an output stream and encode chars "<>&" + * + * @param[in] f UNIX output stream + * @param[in] xn clicon xml tree + * @param[in] level how many spaces to insert before each line + * @param[in] prettyprint insert \n and spaces tomake the xml more readable. + * @see clicon_xml2cbuf + * One can use clicon_xml2cbuf to get common code, but using fprintf is + * much faster than using cbuf and then printing that,... + */ +int +clicon_xml2file(FILE *f, + cxobj *x, + int level, + int prettyprint) +{ + int retval = -1; + char *name; + char *namespace; + cxobj *xc; + int hasbody; + int haselement; + char *val; + char *encstr = NULL; /* xml encoded string */ + + if (x == NULL) + 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, "%s", val) < 0) + goto done; + fprintf(f, "%s", encstr); + break; + case CX_ATTR: + fprintf(f, " "); + if (namespace) + fprintf(f, "%s:", namespace); + fprintf(f, "%s=\"%s\"", name, xml_value(x)); + break; + case CX_ELMNT: + fprintf(f, "%*s<", prettyprint?(level*XML_INDENT):0, ""); + if (namespace) + fprintf(f, "%s:", namespace); + fprintf(f, "%s", name); + 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 (clicon_xml2file(f, xc, level+1, prettyprint) <0) + goto done; + break; + case CX_BODY: + hasbody=1; + break; + case CX_ELMNT: + haselement=1; + break; + default: + break; + } + } + /* Check for special case instead of : + * Ie, no CX_BODY or CX_ELMNT child. + */ + if (hasbody==0 && haselement==0) + fprintf(f, "/>"); + else{ + fprintf(f, ">"); + if (prettyprint && hasbody == 0) + fprintf(f, "\n"); + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) { + if (xml_type(xc) != CX_ATTR) + if (clicon_xml2file(f, xc, level+1, prettyprint) <0) + goto done; + } + if (prettyprint && hasbody==0) + fprintf(f, "%*s", level*XML_INDENT, ""); + fprintf(f, "", name); + } + if (prettyprint) + fprintf(f, "\n"); + break; + default: + break; + }/* switch */ + ok: + retval = 0; + done: + if (encstr) + free(encstr); + return retval; +} + +/*! Print an XML tree structure to an output stream + * + * Uses clicon_xml2file internally + * + * @param[in] f UNIX output stream + * @param[in] xn clicon xml tree + * @see clicon_xml2cbuf + * @see clicon_xml2file + */ +int +xml_print(FILE *f, + cxobj *xn) +{ + return clicon_xml2file(f, xn, 0, 1); +} + +/*! Print an XML tree structure to a cligen buffer and encode chars "<>&" + * + * @param[in,out] cb Cligen buffer to write to + * @param[in] xn Clicon xml tree + * @param[in] level Indentation level for prettyprint + * @param[in] prettyprint insert \n and spaces tomake the xml more readable. + * @param[in] depth Limit levels of child resources: -1 is all, 0 is none, 1 is node itself + * + * @code + * cbuf *cb; + * cb = cbuf_new(); + * if (clicon_xml2cbuf(cb, xn, 0, 1, -1) < 0) + * goto err; + * fprintf(stderr, "%s", cbuf_get(cb)); + * cbuf_free(cb); + * @endcode + * @see clicon_xml2file + */ +int +clicon_xml2cbuf(cbuf *cb, + cxobj *x, + int level, + int prettyprint, + int32_t depth) +{ + int retval = -1; + cxobj *xc; + char *name; + int hasbody; + int haselement; + char *namespace; + char *encstr = NULL; /* xml encoded string */ + char *val; + + if (depth == 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, "%s", val) < 0) + goto done; + cprintf(cb, "%s", encstr); + break; + case CX_ATTR: + cprintf(cb, " "); + if (namespace) + cprintf(cb, "%s:", namespace); + cprintf(cb, "%s=\"%s\"", name, xml_value(x)); + break; + case CX_ELMNT: + cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, ""); + if (namespace) + cprintf(cb, "%s:", namespace); + cprintf(cb, "%s", name); + 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 (clicon_xml2cbuf(cb, xc, level+1, prettyprint, -1) < 0) + goto done; + break; + case CX_BODY: + hasbody=1; + break; + case CX_ELMNT: + haselement=1; + break; + default: + break; + } + /* Check for special case instead of */ + if (hasbody==0 && haselement==0) + cprintf(cb, "/>"); + else{ + cprintf(cb, ">"); + if (prettyprint && hasbody == 0) + cprintf(cb, "\n"); + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + if (clicon_xml2cbuf(cb, xc, level+1, prettyprint, depth-1) < 0) + goto done; + if (prettyprint && hasbody == 0) + cprintf(cb, "%*s", level*XML_INDENT, ""); + cprintf(cb, "", name); + } + if (prettyprint) + cprintf(cb, "\n"); + break; + default: + break; + }/* switch */ + ok: + retval = 0; + done: + if (encstr) + free(encstr); + return retval; +} + +/*! Print actual xml tree datastructures (not xml), mainly for debugging + * @param[in,out] cb Cligen buffer to write to + * @param[in] xn Clicon xml tree + * @param[in] level Indentation level + */ +int +xmltree2cbuf(cbuf *cb, + cxobj *x, + int level) +{ + cxobj *xc; + int i; + + for (i=0; i <-- populate from parent + */ + if ((ret = xml_spec_populate0_parent(x, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + break; + case YB_TOP: + /* xt: nospec + * x: <-- populate from modules + */ +#ifdef XMLDB_CONFIG_HACK + if (strcmp(xml_name(x),"config") == 0){ + /* xt: nospec + * x: + * <-- populate from modules + */ + if ((ret = xml_spec_populate(x, yspec, xerr)) < 0) + goto done; + } + else +#endif + if ((ret = xml_spec_populate0(x, yspec, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + break; + } + } + /* Sort the complete tree after parsing. Sorting is less meaningful if Yang not bound */ + if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) + goto done; + retval = (failed==0) ? 1 : 0; + done: + 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; +} + +/*! Read an XML definition from file and parse it into a parse-tree. + * + * @param[in] fd A file descriptor containing the XML file (as ASCII characters) + * @param[in] yspec Yang specification, or NULL + * @param[in,out] xt Pointer to XML parse tree. If empty, create. + * @retval 1 Parse OK and all yang assignment made + * @retval 0 Parse OK but yang assigment not made (or only partial) + * @retval -1 Error with clicon_err called. Includes parse error * + * @code + * cxobj *xt = NULL; + * int fd; + * fd = open(filename, O_RDONLY); + * xml_parse_file(fd, yspec, &xt); + * xml_free(xt); + * @endcode + * @see xml_parse_string + * @see xml_parse_va + * @note, If xt empty, a top-level symbol will be added so that will be: + * @note May block on file I/O + * @see xml_parse_file2 for a more advanced API + */ +int +xml_parse_file(int fd, + yang_stmt *yspec, + cxobj **xt) +{ + enum yang_bind yb = YB_PARENT; + + if (xt==NULL){ + clicon_err(OE_XML, EINVAL, "xt is NULL"); + return -1; + } + if (*xt==NULL) + yb = YB_TOP; + return xml_parse_file2(fd, yb, yspec, NULL, xt, NULL); +} + +/*! FSM to detect substring + */ +static inline int +FSM(char *tag, + char ch, + int state) +{ + if (tag[state] == ch) + return state+1; + else + return 0; +} + +/*! 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] endtag Read until encounter "endtag" in the stream, or NULL + * @param[in,out] xt Pointer to XML parse tree. If empty, create. + * @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 with clicon_err called. Includes parse error + * + * @code + * cxobj *xt = NULL; + * cxobj *xerr = NULL; + * int fd; + * fd = open(filename, O_RDONLY); + * if ((ret = xml_parse_file2(fd, YB_TOP, yspec, "", &xt, &xerr)) < 0) + * err; + * xml_free(xt); + * @endcode + * @see xml_parse_string + * @see xml_parse_file + * @note, If xt empty, a top-level symbol will be added so that will be: + * @note May block on file I/O + */ +int +xml_parse_file2(int fd, + enum yang_bind yb, + yang_stmt *yspec, + char *endtag, + 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 endtaglen = 0; + int state = 0; + int oldxmlbuflen; + int failed = 0; + + if (endtag != NULL) + endtaglen = strlen(endtag); + if ((xmlbuf = malloc(xmlbuflen)) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xmlbuf, 0, xmlbuflen); + ptr = xmlbuf; + while (1){ + if ((ret = read(fd, &ch, 1)) < 0){ + clicon_err(OE_XML, errno, "read: [pid:%d]", + (int)getpid()); + break; + } + if (ret != 0){ + if (endtag) + state = FSM(endtag, ch, state); + xmlbuf[len++] = ch; + } + if (ret == 0 || + (endtag && (state == endtaglen))){ + state = 0; + if (*xt == NULL) + if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == 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){ + clicon_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){ + 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) + * @retval 1 Parse OK and all yang assignment made + * @retval 0 Parse OK but yang assigment not made (or only partial) + * @retval -1 Error with clicon_err called. Includes parse error + * + * @code + * cxobj *xt = NULL; + * cxobj *xerr = NULL; + * if (xml_parse_string2(str, YB_TOP, yspec, &xt, &xerr) < 0) + * err; + * if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP + * err; + * @endcode + * @see xml_parse_file + * @see 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 +xml_parse_string2(const char *str, + enum yang_bind yb, + yang_stmt *yspec, + cxobj **xt, + cxobj **xerr) +{ + if (xt==NULL){ + clicon_err(OE_XML, EINVAL, "xt is NULL"); + return -1; + } + if (*xt == NULL){ + if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) + return -1; + } + return _xml_parse(str, yb, yspec, *xt, xerr); +} + +/*! Read an XML definition from string and parse it into a parse-tree + * + * @param[in] str String containing XML definition. + * @param[in] yspec Yang specification, or NULL + * @param[in,out] xt Pointer to XML parse tree. If empty will be created. + * @retval 1 Parse OK and all yang assignment made + * @retval 0 Parse OK but yang assigment not made (or only partial) + * @retval -1 Error with clicon_err called. Includes parse error + * + * @code + * cxobj *xt = NULL; + * if (xml_parse_string(str, yspec, &xt) < 0) + * err; + * if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP + * err; + * @endcode + * @see xml_parse_file + * @see xml_parse_va + * @note You need to free the xml parse tree after use, using xml_free() + * @note If xt is empty on entry, a new TOP xml will be created named "top" and yang binding + * assumed to be TOP + */ +int +xml_parse_string(const char *str, + yang_stmt *yspec, + cxobj **xt) +{ + enum yang_bind yb = YB_PARENT; + + if (xt==NULL){ + clicon_err(OE_XML, EINVAL, "xt is NULL"); + return -1; + } + if (*xt == NULL){ + yb = YB_TOP; /* ad-hoc #1 */ + if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) + return -1; + } + else{ + if (xml_spec(*xt) == NULL) + yb = YB_TOP; /* ad-hoc #2 */ + } + return _xml_parse(str, yb, yspec, *xt, NULL); +} + +/*! Read XML from var-arg list and parse it into xml tree + * + * Utility function using stdarg instead of static string. + * @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[in] yspec Yang specification, or NULL + * @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 with clicon_err called. Includes parse error + * + * @code + * cxobj *xt = NULL; + * if (xml_parse_va(&xt, NULL, "%d", 22) < 0) + * err; + * xml_free(xt); + * @endcode + * @see xml_parse_string + * @see xml_parse_file + * @note If vararg list is empty, consider using xml_parse_string() + */ +int +xml_parse_va(cxobj **xtop, + yang_stmt *yspec, + 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){ + clicon_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 = xml_parse_string(str, yspec, xtop); /* xml_parse_string2 */ + done: + if (str) + free(str); + return retval; +} + +#ifdef NOTUSED +/*! Generic parse function for xml values + * @param[in] xb xml tree body node, ie containing a value to be parsed + * @param[in] type Type of value to be parsed in value + * @param[out] cvp CLIgen variable containing the parsed value + * @note free cv with cv_free after use. + * @see xml_body_int32 etc, for type-specific parse functions + * @note range check failure returns 0 + */ +static int +xml_body_parse(cxobj *xb, + enum cv_type type, + cg_var **cvp) +{ + int retval = -1; + cg_var *cv = NULL; + int cvret; + char *bstr; + char *reason = NULL; + + if ((bstr = xml_body(xb)) == NULL){ + clicon_err(OE_XML, 0, "No body found"); + goto done; + } + if ((cv = cv_new(type)) == NULL){ + clicon_err(OE_XML, errno, "cv_new"); + goto done; + } + if ((cvret = cv_parse1(bstr, cv, &reason)) < 0){ + clicon_err(OE_XML, errno, "cv_parse"); + goto done; + } + if (cvret == 0){ /* parsing failed */ + clicon_err(OE_XML, errno, "Parsing CV: %s", reason); + if (reason) + free(reason); + } + *cvp = cv; + retval = 0; + done: + if (retval < 0 && cv != NULL) + cv_free(cv); + return retval; +} + +/*! Parse an xml body as int32 + * The real parsing functions are in the cligen code + * @param[in] xb xml tree body node, ie containing a value to be parsed + * @param[out] val Value after parsing + * @retval 0 OK, parsed value in 'val' + * @retval -1 Error, one of: body not found, parse error, + * alloc error. + * @note extend to all other cligen var types and generalize + * @note use yang type info? + * @note range check failure returns 0 + */ +int +xml_body_int32(cxobj *xb, + int32_t *val) +{ + cg_var *cv = NULL; + + if (xml_body_parse(xb, CGV_INT32, &cv) < 0) + return -1; + *val = cv_int32_get(cv); + cv_free(cv); + return 0; +} + +/*! Parse an xml body as uint32 + * The real parsing functions are in the cligen code + * @param[in] xb xml tree body node, ie containing a value to be parsed + * @param[out] val Value after parsing + * @retval 0 OK, parsed value in 'val' + * @retval -1 Error, one of: body not found, parse error, + * alloc error. + * @note extend to all other cligen var types and generalize + * @note use yang type info? + * @note range check failure returns 0 + */ +int +xml_body_uint32(cxobj *xb, + uint32_t *val) +{ + cg_var *cv = NULL; + + if (xml_body_parse(xb, CGV_UINT32, &cv) < 0) + return -1; + *val = cv_uint32_get(cv); + cv_free(cv); + return 0; +} + +#endif /* NOTUSED */ diff --git a/lib/src/clixon_xml_nsctx.c b/lib/src/clixon_xml_nsctx.c index f79a500e..d84d9430 100644 --- a/lib/src/clixon_xml_nsctx.c +++ b/lib/src/clixon_xml_nsctx.c @@ -66,6 +66,7 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_yang_module.h" #include "clixon_xml_nsctx.h" /*! Create and initialize XML namespace context @@ -408,3 +409,224 @@ xml_nsctx_yangspec(yang_stmt *yspec, done: return retval; } + +/*! Given an xml tree return URI namespace recursively : default or localname given + * + * Given an XML tree and a prefix (or NULL) return URI namespace. + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then return default. + * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree + * @retval 0 OK + * @retval -1 Error + * @code + * if (xml2ns(xt, NULL, &namespace) < 0) + * err; + * @endcode + * @see xmlns_check + * @see xmlns_set cache is set + * @note, this function uses a cache. + */ +int +xml2ns(cxobj *x, + char *prefix, + char **namespace) +{ + int retval = -1; + char *ns = NULL; + cxobj *xp; + + if ((ns = nscache_get(x, prefix)) != NULL) + goto ok; + if (prefix != NULL) /* xmlns:="" */ + ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); + else{ /* xmlns="" */ + ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); + } + /* namespace not found, try parent */ + if (ns == NULL){ + if ((xp = xml_parent(x)) != NULL){ + if (xml2ns(xp, prefix, &ns) < 0) + goto done; + } + /* If no parent, return default namespace if defined */ +#ifdef USE_NETCONF_NS_AS_DEFAULT + else{ + if (prefix == NULL) + ns = NETCONF_BASE_NAMESPACE; + else + ns = NULL; + } +#endif + } + /* Set default namespace cache (since code is at this point, + * no cache was found */ + if (ns && nscache_set(x, prefix, ns) < 0) + goto done; + ok: + if (namespace) + *namespace = ns; + retval = 0; + done: + return retval; +} + +/*! Add a namespace attribute to an XML node, either default or specific prefix + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then set default xmlns + * @param[in] ns URI namespace (or NULL). Will be copied + * @retval 0 OK + * @retval -1 Error + * @see xml2ns + */ +int +xmlns_set(cxobj *x, + char *prefix, + char *ns) +{ + int retval = -1; + cxobj *xa; + + if (prefix != NULL){ /* xmlns:="" */ + if ((xa = xml_new(prefix, x, NULL)) == NULL) + goto done; + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + } + else{ /* xmlns="" */ + if ((xa = xml_new("xmlns", x, NULL)) == NULL) + goto done; + } + xml_type_set(xa, CX_ATTR); + if (xml_value_set(xa, ns) < 0) + goto done; + /* (re)set namespace cache (as used in xml2ns) */ + if (ns && nscache_set(x, prefix, ns) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Get namespace given prefix recursively + * @param[in] xn XML node + * @param[in] namespace Namespace + * @param[out] prefixp Pointer to prefix if found + * @retval -1 Error + * @retval 0 No namespace found + * @retval 1 Namespace found, prefix returned in prefixp + */ +int +xml2prefix(cxobj *xn, + char *namespace, + char **prefixp) +{ + int retval = -1; + cxobj *xa = NULL; + cxobj *xp; + char *prefix = NULL; + char *xaprefix; + int ret; + + if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */ + goto found; + xa = NULL; + while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) { + /* xmlns=namespace */ + if (strcmp("xmlns", xml_name(xa)) == 0){ + if (strcmp(xml_value(xa), namespace) == 0){ + if (nscache_set(xn, NULL, namespace) < 0) + goto done; + prefix = NULL; /* Maybe should set all caches in ns:s children? */ + goto found; + } + } + /* xmlns:prefix=namespace */ + else if ((xaprefix=xml_prefix(xa)) != NULL && + strcmp("xmlns", xaprefix) == 0){ + if (strcmp(xml_value(xa), namespace) == 0){ + prefix = xml_name(xa); + if (nscache_set(xn, prefix, namespace) < 0) + goto done; + goto found; + } + } + } + if ((xp = xml_parent(xn)) != NULL){ + if ((ret = xml2prefix(xp, namespace, &prefix)) < 0) + goto done; + if (ret == 1){ + if (nscache_set(xn, prefix, namespace) < 0) + goto done; + goto found; + } + } + retval = 0; + done: + return retval; + found: + *prefixp = prefix; + retval = 1; + goto done; +} + +/*! See if xmlns:[=] exists, if so return + * + * @param[in] xn XML node + * @param[in] nsn Namespace name + * @retval URI return associated URI if found + * @retval NULL No namespace name binding found for nsn + * @see xml2ns XXX coordinate + */ +static char * +xmlns_check(cxobj *xn, + char *nsn) +{ + cxobj *x = NULL; + char *xns; + + while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) + if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 && + strcmp(xml_name(x), nsn) == 0) + return xml_value(x); + return NULL; +} + +/*! Check namespace of xml node by searching recursively among ancestors + * @param[in] xn xml node + * @param[in] namespace check validity of namespace + * @retval 0 Found / validated or no yang spec + * @retval -1 Not found + * @note This function is grossly inefficient + */ +int +xml_localname_check(cxobj *xn, + void *arg) +{ + cxobj *xp = NULL; + char *nsn; + char *n; + yang_stmt *ys = xml_spec(xn); + + /* No namespace name - comply */ + if ((nsn = xml_prefix(xn)) == NULL) + return 0; + /* Check if NSN defined in same node */ + if (xmlns_check(xn, nsn) != NULL) + return 0; + /* Check if NSN defined in some ancestor */ + while ((xp = xml_parent(xn)) != NULL) { + if (xmlns_check(xp, nsn) != NULL) + return 0; + xn = xp; + } + /* Check if my namespace */ + if ((n = yang_find_myprefix(ys)) != NULL && strcmp(nsn,n)==0) + return 0; + /* Check if any imported module */ + if (yang_find_module_by_prefix(ys, nsn) != NULL) + return 0; + /* Not found, error */ + clicon_err(OE_XML, ENOENT, "Namespace name %s in %s:%s not found", + nsn, nsn, xml_name(xn)); + return -1; +} diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index e6164f3e..5865d482 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -62,6 +62,8 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xml_io.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_options.h" diff --git a/lib/src/clixon_xml_vec.c b/lib/src/clixon_xml_vec.c index de657fd8..5294eb59 100644 --- a/lib/src/clixon_xml_vec.c +++ b/lib/src/clixon_xml_vec.c @@ -62,6 +62,7 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_io.h" #include "clixon_xml_vec.h" //typedef struct clixon_xml_vec clixon_xvec; /* struct defined in clicon_xml_vec.c */ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 98537417..ae289858 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -74,6 +74,7 @@ #include "clixon_yang.h" #include "clixon_hash.h" #include "clixon_xml.h" +#include "clixon_xml_nsctx.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_data.h" diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 85b9099f..1f21e58b 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -70,6 +70,7 @@ #include "clixon_file.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_io.h" #include "clixon_xml_nsctx.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h"