* Added nsc parameter to xml2xpath() and ensured the xpath uses prefixes.

* Old code: add `NULL` as second parameter
This commit is contained in:
Olof hagsand 2022-05-09 13:26:42 +02:00
parent 82a0670031
commit 614c927343
6 changed files with 157 additions and 13 deletions

View file

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

View file

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

View file

@ -1375,7 +1375,7 @@ netconf_minmax_elements_xml(cxobj **xret,
goto done;
}
if (xml_parent(xp)){ /* Dont include root, eg <config> */
if (xml2xpath(xp, &path) < 0)
if (xml2xpath(xp, NULL, &path) < 0)
goto done;
if (path)
cprintf(cb, "%s", path);

View file

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

91
test/test_xpath_inverse.sh Executable file
View file

@ -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 <<EOF > $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 <<EOF > $xml1
<x xmlns="urn:example:a">
<y>foo</y>
<z>
<k>1</k>
</z>
<z>
<k>2</k>
</z>
</x>
EOF
# Explicit indexes
cat <<EOF > $xml2
<a:x xmlns:a="urn:example:a">
<a:y>foo</a:y>
<a:z>
<a:k>1</a:k>
</a:z>
<a:z>
<a:k>2</a:k>
</a:z>
</a:x>
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

View file

@ -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 <file> \tXML file\n"
"\t-p <xpath> \tPrimary XPATH string\n"
"\t-i <xpath0>\t(optional) Initial XPATH string\n"
"\t-I \t\tCheck inverse, map back xml result to xpath and check if equal\n"
"\t-n <pfx:id>\tNamespace binding (pfx=NULL for default)\n"
"\t-c \t\tMap xpath to canonical form\n"
"\t-l <s|e|o|f<file>> \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; i<xc->xc_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)