diff --git a/CHANGELOG.md b/CHANGELOG.md index c944ff9e..de207001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -136,6 +136,8 @@ Users may have to change how they access the system Developers may need to change their code +* Added `nsc` parameter to `xml2xpath()` and ensured the xpath uses prefixes. + * Old code: add `NULL` as second parameter * Added `eof` parameter to `clicon_rpc()` and `clicon_rpc1()` and error handling modified ## 5.6.0 diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index f0da904b..1ebec813 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -68,7 +68,7 @@ int xml_nopresence_default(cxobj *xt); int xml_nopresence_default_mark(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, cxobj **xerr); -int xml2xpath(cxobj *x, char **xpath); +int xml2xpath(cxobj *x, cvec *nsc, char **xpath); int assign_namespace_element(cxobj *x0, cxobj *x1, cxobj *x1p); int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 20fc6cb9..bec6142e 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1375,7 +1375,7 @@ netconf_minmax_elements_xml(cxobj **xret, goto done; } if (xml_parent(xp)){ /* Dont include root, eg */ - if (xml2xpath(xp, &path) < 0) + if (xml2xpath(xp, NULL, &path) < 0) goto done; if (path) cprintf(cb, "%s", path); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 1422526a..2240d237 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1291,12 +1291,16 @@ xml_non_config_data(cxobj *xt, return retval; } -/*! Given an XML node, build an xpath to root, internal function +/*! Given an XML node, build an xpath recursively to root, internal function + * @param[in] x XML object + * @param[in] nsc Namespace context + * @param[out] cb XPath string as cbuf. * @retval 0 OK * @retval -1 Error. eg XML malformed */ static int xml2xpath1(cxobj *x, + cvec *nsc, cbuf *cb) { int retval = -1; @@ -1309,12 +1313,30 @@ xml2xpath1(cxobj *x, cxobj *xb; char *b; enum rfc_6020 keyword; + char *prefix = NULL; + char *namespace; if ((xp = xml_parent(x)) == NULL) goto ok; - xml2xpath1(xp, cb); + if (xml2xpath1(xp, nsc, cb) < 0) + goto done; + if (nsc){ + if (xml2ns(x, xml_prefix(x), &namespace) < 0) + goto done; + if (namespace){ + if (xml_nsctx_get_prefix(nsc, namespace, &prefix) == 0) + ; /* maybe NULL? */ + } + else + prefix = xml_prefix(x); /* maybe NULL? */ + } + else + prefix = xml_prefix(x); /* XXX: sometimes there should be a /, sometimes not */ - cprintf(cb, "/%s", xml_name(x)); + cprintf(cb, "/"); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x)); if ((y = xml_spec(x)) != NULL){ keyword = yang_keyword_get(y); switch (keyword){ @@ -1334,7 +1356,10 @@ xml2xpath1(cxobj *x, if ((xb = xml_find(x, keyname)) == NULL) goto done; b = xml_body(xb); - cprintf(cb, "[%s=\"%s\"]", keyname, b?b:""); + cprintf(cb, "["); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s=\"%s\"]", keyname, b?b:""); } break; default: @@ -1348,8 +1373,14 @@ xml2xpath1(cxobj *x, } /*! Given an XML node, build an xpath to root - * Builds only unqualified xpaths, ie no predicates [] + * + * Creates an XPath from an XML node with some limitations, see notes below. + * The prefixes used are from the given namespace context if any, otherwise the native prefixes are used, if any. + * Note that this means that prefixes may be translated such as if the XML namespace mapping is different than the once used + * in the XML. + * Therefore, if nsc is "canonical", the returned xpath is also "canonical", even though the XML is not. * @param[in] x XML object + * @param[in] nsc Namespace context * @param[out] xpath Malloced xpath string. Need to free() after use * @retval 0 OK * @retval -1 Error. (eg XML malformed) @@ -1357,6 +1388,7 @@ xml2xpath1(cxobj *x, */ int xml2xpath(cxobj *x, + cvec *nsc, char **xpathp) { int retval = -1; @@ -1367,7 +1399,7 @@ xml2xpath(cxobj *x, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - if (xml2xpath1(x, cb) < 0) + if (xml2xpath1(x, nsc, cb) < 0) goto done; /* XXX: see xpath in test statement,.. */ xpath = cbuf_get(cb); diff --git a/test/test_xpath_inverse.sh b/test/test_xpath_inverse.sh new file mode 100755 index 00000000..cb99a6cc --- /dev/null +++ b/test/test_xpath_inverse.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Test xpath inverse function. +# That is, given an xml + xpath -> specific node x in xml -> xml2xpath(x) + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +: ${clixon_util_xpath:=clixon_util_xpath} + +ydir=$dir/yang +xml1=$dir/xml1.xml +xml2=$dir/xml2.xml + +if [ ! -d $ydir ]; then + mkdir $ydir +fi + +# canonical namespace xpath tests +# need yang modules +cat < $ydir/a.yang +module a{ + namespace "urn:example:a"; + prefix a; + container x{ + leaf y{ + type string; + } + list z { + key k; + leaf k{ + type string; + } + } + } +} +EOF + +# Default prefix +cat < $xml1 + + foo + + 1 + + + 2 + + +EOF + +# Explicit indexes +cat < $xml2 + + foo + + 1 + + + 2 + + +EOF + +new "xpath leaf default ns" +expectpart "$($clixon_util_xpath -If $xml1 -y $ydir -p /x/y)" 0 'Inverse: /x/y' + +new "xpath leaf explicit prefix" +expectpart "$($clixon_util_xpath -If $xml2 -y $ydir -p /a:x/a:y)" 0 'Inverse: /a:x/a:y' + +new "xpath leaf explicit prefix" +expectpart "$($clixon_util_xpath -If $xml2 -y $ydir -p /a:x/a:y -n a:urn:example:a)" 0 'Inverse: /a:x/a:y' + +new "xpath leaf no prefix" +expectpart "$($clixon_util_xpath -If $xml1 -y $ydir -p /x/y -n a:urn:example:a)" 0 'Inverse: /a:x/a:y' + +new "xpath leaf other nsc" +expectpart "$($clixon_util_xpath -If $xml1 -y $ydir -p /a:x/a:y -n b:urn:example:a)" 0 'Inverse: /b:x/b:y' --not-- /a:x/a:y + +new "xpath list same nsc" +expectpart "$($clixon_util_xpath -If $xml1 -y $ydir -p /a:x/a:z[a:k='2'] -n a:urn:example:a)" 0 'Inverse: /a:x/a:z\[a:k="2"\]' + +new "xpath list same nsc" +expectpart "$($clixon_util_xpath -If $xml1 -y $ydir -p /a:x/a:z[a:k='2'] -n b:urn:example:a)" 0 'Inverse: /b:x/b:z\[b:k="2"\]' --not-- '/a:x/a:z' + +rm -rf $dir + +# unset conditional parameters +unset clixon_util_xpath + +new "endtest" +endtest diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index 9327085f..04a0fb63 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -60,7 +60,7 @@ See https://www.w3.org/TR/xpath/ #include "clixon/clixon.h" /* Command line options to be passed to getopt(3) */ -#define XPATH_OPTS "hD:f:p:i:n:cl:y:Y:" +#define XPATH_OPTS "hD:f:p:i:In:cl:y:Y:" static int usage(char *argv0) @@ -72,6 +72,7 @@ usage(char *argv0) "\t-f \tXML file\n" "\t-p \tPrimary XPATH string\n" "\t-i \t(optional) Initial XPATH string\n" + "\t-I \t\tCheck inverse, map back xml result to xpath and check if equal\n" "\t-n \tNamespace binding (pfx=NULL for default)\n" "\t-c \t\tMap xpath to canonical form\n" "\t-l > \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n" @@ -120,7 +121,6 @@ main(int argc, int retval = -1; char *argv0 = argv[0]; int i; - cxobj **xv = NULL; cxobj *x0 = NULL; cxobj *x; int c; @@ -144,6 +144,7 @@ main(int argc, cxobj *xerr = NULL; /* malloced must be freed */ int logdst = CLICON_LOG_STDERR; int dbg = 0; + int xpath_inverse = 0; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init("xpath", LOG_DEBUG, logdst); @@ -180,6 +181,9 @@ main(int argc, case 'i': /* Optional initial XPATH string */ xpath0 = optarg; break; + case 'I': /* Check inverse */ + xpath_inverse++; + break; case 'n':{ /* Namespace binding */ char *prefix; char *id; @@ -307,7 +311,7 @@ main(int argc, */ if (clixon_xml_parse_file(fp, YB_NONE, NULL, &x0, NULL) < 0){ fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); - return -1; + goto done; } /* Validate XML as well */ @@ -361,6 +365,23 @@ main(int argc, x = x0; if (xpath_vec_ctx(x, nsc, xpath, 0, &xc) < 0) return -1; + /* Check inverse, eg XML back to xpath and compare with original, only if nodes */ + if (xpath_inverse && xc->xc_type == XT_NODESET){ + cxobj *xi; + char *xpathi = NULL; + for (i=0; ixc_size; i++){ + xi = xc->xc_nodeset[i]; + if (xml2xpath(xi, nsc, &xpathi) < 0) + goto done; + fprintf(stdout, "Inverse: %s\n", xpathi); + if (xpathi){ + free(xpathi); + xpathi = NULL; + } + } + goto ok; + } + /* Print results */ cb = cbuf_new(); ctx_print2(cb, xc); @@ -376,8 +397,6 @@ main(int argc, ctx_free(xc); if (xcfg) xml_free(xcfg); - if (xv) - free(xv); if (buf) free(buf); if (x0)