* XPATH canonical form implemented for NETCONF get and get-config.

This commit is contained in:
Olof hagsand 2019-10-09 22:13:04 +02:00
parent 8cdb0bb062
commit 03acf8e19c
14 changed files with 430 additions and 86 deletions

View file

@ -39,6 +39,8 @@
* Replaced JSON `null` with `[null]` as proper empty JSON leaf/leaf-list encoding.
### Minor changes
* XPATH canonical form implemented for NETCONF get and get-config. This means that all callbacks (including state callbacks) will have the prefixes that corresponds to module prefixes. If an xpath have other prefixes (or null as default), they will be translated to canonical form before any callbacks.
* Example of a canonical form: `/a:x/a:y`, then symbols must belong to a yang module with prefix `a`.
* Configure and test modification for better Freebsd port
### Corrected Bugs

View file

@ -369,7 +369,7 @@ from_client_get_config(clicon_handle h,
int retval = -1;
char *db;
cxobj *xfilter;
char *xpath = "/";
char *xpath = NULL;
cxobj *xret = NULL;
cbuf *cbx = NULL; /* Assist cbuf */
cxobj *xnacm = NULL;
@ -378,8 +378,13 @@ from_client_get_config(clicon_handle h,
int ret;
char *username;
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
yang_stmt *yspec;
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
if ((db = netconf_db_find(xe, "source")) == NULL){
clicon_err(OE_XML, 0, "db not found");
goto done;
@ -395,8 +400,11 @@ from_client_get_config(clicon_handle h,
goto ok;
}
if ((xfilter = xml_find(xe, "filter")) != NULL){
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
char *xpath0;
cvec *nsc1 = NULL;
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
xpath0="/";
/* Create namespace context for xpath from <filter>
* The set of namespace declarations are those in scope on the
* <filter> element.
@ -404,6 +412,11 @@ from_client_get_config(clicon_handle h,
else
if (xml_nsctx_node(xfilter, &nsc) < 0)
goto done;
if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
goto done;
if (nsc)
xml_nsctx_free(nsc);
nsc = nsc1;
}
/* Note xret can be pruned by nacm below (and change name),
* so zero-copy cant be used
@ -437,6 +450,8 @@ from_client_get_config(clicon_handle h,
ok:
retval = 0;
done:
if (xpath)
free(xpath);
if (xnacm)
xml_free(xnacm);
if (xvec)
@ -868,7 +883,7 @@ from_client_get(clicon_handle h,
{
int retval = -1;
cxobj *xfilter;
char *xpath = "/";
char *xpath = NULL;
cxobj *xret = NULL;
int ret;
cxobj **xvec = NULL;
@ -879,11 +894,18 @@ from_client_get(clicon_handle h,
char *attr;
netconf_content content = CONTENT_ALL;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
yang_stmt *yspec;
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
if ((xfilter = xml_find(xe, "filter")) != NULL){
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
char *xpath0;
cvec *nsc1 = NULL;
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
xpath0 = "/";
/* Create namespace context for xpath from <filter>
* The set of namespace declarations are those in scope on the
* <filter> element.
@ -891,6 +913,11 @@ from_client_get(clicon_handle h,
else
if (xml_nsctx_node(xfilter, &nsc) < 0)
goto done;
if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
goto done;
if (nsc)
xml_nsctx_free(nsc);
nsc = nsc1;
}
/* Clixon extensions: depth and content */
if ((attr = xml_find_value(xe, "content")) != NULL)
@ -909,7 +936,6 @@ from_client_get(clicon_handle h,
goto ok;
}
}
if (content != CONTENT_NONCONFIG){
/* Get config
* Note xret can be pruned by nacm below and change name and
@ -958,6 +984,8 @@ from_client_get(clicon_handle h,
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xpath)
free(xpath);
if (xnacm)
xml_free(xnacm);
if (xvec)

View file

@ -116,7 +116,6 @@ api_data_post(clicon_handle h,
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
yang_stmt *ymoddata = NULL; /* yang module of data (-d) */
yang_stmt *yspec;
yang_stmt *ydata;
@ -156,8 +155,6 @@ api_data_post(clicon_handle h,
goto done;
goto ok;
}
if (ybot)
ymodapi = ys_module(ybot);
}
#if 1
if (debug){

View file

@ -48,12 +48,12 @@
/*
* Prototypes
*/
cvec *xml_nsctx_init(char *prefix, char *namespace);
int xml_nsctx_free(cvec *nsc);
char *xml_nsctx_get(cvec *nsc, char *prefix);
int xml_nsctx_get_prefix(cvec *cvv, char *namespace, char **prefix);
int xml_nsctx_set(cvec *nsc, char *prefix, char *namespace);
cvec *xml_nsctx_init(char *prefix, char *namespace);
int xml_nsctx_add(cvec *nsc, char *prefix, char *namespace);
int xml_nsctx_node(cxobj *x, cvec **ncp);
int xml_nsctx_yang(yang_stmt *yn, cvec **ncp);
int xml_nsctx_free(cvec *nsc);
#endif /* _CLIXON_XML_NSCTX_H */

View file

@ -115,8 +115,9 @@ typedef struct xpath_tree xpath_tree;
char* xpath_tree_int2str(int nodetype);
int xpath_tree_print_cb(cbuf *cb, xpath_tree *xs);
int xpath_tree_print(FILE *f, xpath_tree *xs);
int xpath_tree2cbuf(xpath_tree *xs, cbuf *xpathcb);
int xpath_tree_free(xpath_tree *xs);
int xpath_parse(cvec *nsc, char *xpath, xpath_tree **xptree);
int xpath_parse(char *xpath, xpath_tree **xptree);
int xpath_vec_ctx(cxobj *xcur, cvec *nsc, char *xpath, xp_ctx **xrp);
#if defined(__GNUC__) && __GNUC__ >= 3
@ -151,5 +152,6 @@ int xpath_vec(cxobj *xcur, char *xpformat, cxobj ***vec, size_t *veclen, ...)
cxobj *xpath_first(cxobj *xcur, char *xpformat, ...);
int xpath_vec(cxobj *xcur, char *xpformat, cxobj ***vec, size_t *veclen, ...);
#endif
int xpath2canonical(char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1);
#endif /* _CLIXON_XPATH_H */

View file

@ -295,7 +295,7 @@ nscache_set(cxobj *x,
goto done;
}
else
return xml_nsctx_set(x->x_ns_cache, prefix, namespace);
return xml_nsctx_add(x->x_ns_cache, prefix, namespace);
retval = 0;
done:
return retval;

View file

@ -2513,7 +2513,7 @@ api_path2xpath_cvv(cvec *api_path,
xprefix = yang_find_myprefix(y);
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
/* not found, add it to nsc */
if (xml_nsctx_set(nsc, xprefix, namespace) < 0)
if (xml_nsctx_add(nsc, xprefix, namespace) < 0)
goto done;
}
/* Check if has value, means '=' */

View file

@ -68,6 +68,53 @@
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
/*! Create and initialize XML namespace context
* @param[in] prefix Namespace prefix, or NULL for default
* @param[in] namespace Set this namespace. If NULL create empty nsctx
* @retval nsc Return namespace context in form of a cvec
* @retval NULL Error
* @code
* cvec *nsc = NULL;
* if ((nsc = xml_nsctx_init(NULL, "urn:example:example")) == NULL)
* err;
* ...
* xml_nsctx_free(nsc);
* @endcode
* @see xml_nsctx_node Use namespace context of an existing XML node
* @see xml_nsctx_free Free the reutned handle
*/
cvec *
xml_nsctx_init(char *prefix,
char *namespace)
{
cvec *cvv = NULL;
if ((cvv = cvec_new(0)) == NULL){
clicon_err(OE_XML, errno, "cvec_new");
goto done;
}
if (namespace && xml_nsctx_add(cvv, prefix, namespace) < 0)
goto done;
done:
return cvv;
}
/*! Free XML namespace context
* @param[in] prefix Namespace prefix, or NULL for default
* @param[in] namespace Cached namespace to set (assume non-null?)
* @retval nsc Return namespace context in form of a cvec
* @retval NULL Error
*/
int
xml_nsctx_free(cvec *nsc)
{
cvec *cvv = (cvec*)nsc;
if (cvv)
cvec_free(cvv);
return 0;
}
/*! Get namespace given prefix (or NULL for default) from namespace context
* @param[in] cvv Namespace context
* @param[in] prefix Namespace prefix, or NULL for default
@ -120,7 +167,7 @@ xml_nsctx_get_prefix(cvec *cvv,
* @retval -1 Error
*/
int
xml_nsctx_set(cvec *cvv,
xml_nsctx_add(cvec *cvv,
char *prefix,
char *namespace)
{
@ -136,36 +183,6 @@ xml_nsctx_set(cvec *cvv,
return retval;
}
/*! Create and initialize XML namespace context
* @param[in] prefix Namespace prefix, or NULL for default
* @param[in] namespace Set this namespace. If NULL create empty nsctx
* @retval nsc Return namespace context in form of a cvec
* @retval NULL Error
* @code
* cvec *nsc = NULL;
* if ((nsc = xml_nsctx_init(NULL, "urn:example:example")) == NULL)
* err;
* ...
* xml_nsctx_free(nsc);
* @endcode
* @see xml_nsctx_node Use namespace context of an existing XML node
* @see xml_nsctx_free Free the reutned handle
*/
cvec *
xml_nsctx_init(char *prefix,
char *namespace)
{
cvec *cvv = NULL;
if ((cvv = cvec_new(0)) == NULL){
clicon_err(OE_XML, errno, "cvec_new");
goto done;
}
if (namespace && xml_nsctx_set(cvv, prefix, namespace) < 0)
goto done;
done:
return cvv;
}
static int
xml_nsctx_node1(cxobj *xn,
@ -188,7 +205,7 @@ xml_nsctx_node1(cxobj *xn,
if (strcmp(nm, "xmlns")==0 && /* set default namespace context */
xml_nsctx_get(nsc, NULL) == NULL){
val = xml_value(xa);
if (xml_nsctx_set(nsc, NULL, val) < 0)
if (xml_nsctx_add(nsc, NULL, val) < 0)
goto done;
}
}
@ -196,7 +213,7 @@ xml_nsctx_node1(cxobj *xn,
if (strcmp(pf, "xmlns")==0 && /* set prefixed namespace context */
xml_nsctx_get(nsc, nm) == NULL){
val = xml_value(xa);
if (xml_nsctx_set(nsc, nm, val) < 0)
if (xml_nsctx_add(nsc, nm, val) < 0)
goto done;
}
}
@ -204,7 +221,7 @@ xml_nsctx_node1(cxobj *xn,
#ifdef USE_NETCONF_NS_AS_DEFAULT
/* If not default namespace defined, use the base netconf ns as default */
if (xml_nsctx_get(nsc, NULL) == NULL)
if (xml_nsctx_set(nsc, NULL, NETCONF_BASE_NAMESPACE) < 0)
if (xml_nsctx_add(nsc, NULL, NETCONF_BASE_NAMESPACE) < 0)
goto done;
#endif
}
@ -300,9 +317,9 @@ xml_nsctx_yang(yang_stmt *yn,
goto done;
}
/* Add my prefix and default namespace (from real module) */
if (xml_nsctx_set(nc, NULL, mynamespace) < 0)
if (xml_nsctx_add(nc, NULL, mynamespace) < 0)
goto done;
if (xml_nsctx_set(nc, myprefix, mynamespace) < 0)
if (xml_nsctx_add(nc, myprefix, mynamespace) < 0)
goto done;
/* Find top-most module or sub-module and get prefixes from that */
if ((ymod = ys_module(yn)) == NULL){
@ -328,7 +345,7 @@ xml_nsctx_yang(yang_stmt *yn,
continue;
if ((namespace = yang_argument_get(yns)) == NULL)
continue;
if (xml_nsctx_set(nc, prefix, namespace) < 0)
if (xml_nsctx_add(nc, prefix, namespace) < 0)
goto done;
}
}
@ -338,18 +355,3 @@ xml_nsctx_yang(yang_stmt *yn,
return retval;
}
/*! Free XML namespace context
* @param[in] prefix Namespace prefix, or NULL for default
* @param[in] namespace Cached namespace to set (assume non-null?)
* @retval nsc Return namespace context in form of a cvec
* @retval NULL Error
*/
int
xml_nsctx_free(cvec *nsc)
{
cvec *cvv = (cvec*)nsc;
if (cvv)
cvec_free(cvv);
return 0;
}

View file

@ -163,6 +163,7 @@ xpath_tree_print_cb(cbuf *cb,
/*! Print a xpath_tree
* @param[in] f UNIX output stream
* @param[in] xs XPATH tree
* @see xpath_tree2str
*/
int
xpath_tree_print(FILE *f,
@ -183,6 +184,86 @@ xpath_tree_print(FILE *f,
return retval;
}
/*! Create an xpath string from an xpath tree, ie "unparsing"
* @param[in] xs XPATH tree
* @param[out] xpath Xpath string as CLIgen buf
* @see xpath_tree_print
* @note NOT COMPLETE, just simple xpaths eg a/b
*/
int
xpath_tree2cbuf(xpath_tree *xs,
cbuf *xcb)
{
int retval = -1;
switch (xs->xs_type){
case XP_NODE: /* s0 is namespace prefix, s1 is name */
if (xs->xs_s0)
cprintf(xcb, "%s:", xs->xs_s0);
cprintf(xcb, "%s", xs->xs_s1);
break;
case XP_ABSPATH:
if (xs->xs_int == A_DESCENDANT_OR_SELF)
cprintf(xcb, "/");
cprintf(xcb, "/");
break;
case XP_PRIME_STR:
cprintf(xcb, "'%s'", xs->xs_s0);
break;
case XP_PRIME_NR:
cprintf(xcb, "%lf", xs->xs_double);
break;
case XP_STEP:
switch (xs->xs_int){
case A_SELF:
cprintf(xcb, ".");
break;
case A_PARENT:
cprintf(xcb, "..");
break;
default:
break;
}
break;
default:
break;
}
if (xs->xs_c0 && xpath_tree2cbuf(xs->xs_c0, xcb) < 0)
goto done;
switch (xs->xs_type){
case XP_RELLOCPATH:
if (xs->xs_c1){
if (xs->xs_int == A_DESCENDANT_OR_SELF)
cprintf(xcb, "/");
cprintf(xcb, "/");
}
break;
case XP_PRED:
if (xs->xs_c1)
cprintf(xcb, "[");
break;
case XP_RELEX:
if (xs->xs_c1)
cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int));
break;
default:
break;
}
if (xs->xs_c1 && xpath_tree2cbuf(xs->xs_c1, xcb) < 0)
goto done;
switch (xs->xs_type){
case XP_PRED:
if (xs->xs_c1)
cprintf(xcb, "]");
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*! Free a xpath_tree
* @param[in] xs XPATH tree
* @see xpath_parse creates a xpath_tree
@ -202,26 +283,22 @@ xpath_tree_free(xpath_tree *xs)
return 0;
}
/*! Given XML tree and xpath, parse xpath, eval it and return xpath context,
* This is a raw form of xpath where you can do type conversion, etc,
* not just a nodeset.
* @param[in] nsc External XML namespace context, or NULL
/*! Given xpath, parse it, and return structured xpath tree
* @param[in] xpath String with XPATH 1.0 syntax
* @param[out] xptree Xpath-tree, parsed, structured XPATH, free:xpath_tree_free
* @retval 0 OK
* @retval -1 Error
* @code
* xpath_tree *xpt = NULL;
* if (xpath_parse(NULL, xpath, &xpt) < 0)
* if (xpath_parse(xpath, &xpt) < 0)
* err;
* if (xpt)
* xptree_free(xpt);
* xpath_tree_free(xpt);
* @endcode
* @see xpath_tree_free
*/
int
xpath_parse(cvec *nsc,
char *xpath,
xpath_parse(char *xpath,
xpath_tree **xptree)
{
int retval = -1;
@ -287,7 +364,7 @@ xpath_vec_ctx(cxobj *xcur,
xpath_tree *xptree = NULL;
xp_ctx xc = {0,};
if (xpath_parse(nsc, xpath, &xptree) < 0)
if (xpath_parse(xpath, &xptree) < 0)
goto done;
xc.xc_type = XT_NODESET;
xc.xc_node = xcur;
@ -688,3 +765,136 @@ xpath_vec_bool(cxobj *xcur,
return retval;
}
static int
traverse_canonical(xpath_tree *xs,
yang_stmt *yspec,
cvec *nsc0,
cvec *nsc1)
{
int retval = -1;
char *prefix0;
char *prefix1;
char *namespace;
yang_stmt *ymod;
switch (xs->xs_type){
case XP_NODE: /* s0 is namespace prefix, s1 is name */
prefix0 = xs->xs_s0;
if ((namespace = xml_nsctx_get(nsc0, prefix0)) == NULL){
clicon_err(OE_XML, ENOENT, "No namespace found for prefix: %s",
prefix0);
goto done;
}
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){
clicon_err(OE_XML, ENOENT, "No modules found for namespace: %s",
namespace);
goto done;
}
if ((prefix1 = yang_find_myprefix(ymod)) == NULL){
clicon_err(OE_XML, ENOENT, "No prefix found in module: %s",
yang_argument_get(ymod));
goto done;
}
if (xml_nsctx_get(nsc1, prefix1) == NULL)
if (xml_nsctx_add(nsc1, prefix1, namespace) < 0)
goto done;
if (prefix0==NULL || strcmp(prefix0, prefix1) != 0){
if (xs->xs_s0)
free(xs->xs_s0);
if ((xs->xs_s0 = strdup(prefix1)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
break;
default:
break;
}
if (xs->xs_c0)
if (traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1) < 0)
goto done;
if (xs->xs_c1)
if (traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes
* @param[in] xpath0 Input xpath
* @param[in] nsc0 Input namespace context
* @param[in] yspec Yang spec containing all modules, associated with namespaces
* @param[out] xpath1 Output xpath. Free after use with free
* @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free
* @retval 0 OK, xpath1 and nsc1 allocated
* @retval -1 Error
* Example:
* Module A has prefix a and namespace urn:example:a and symbols x
* Module B with prefix b and namespace urn:example:b and symbols y
* Then incoming:
* xpath0: /x/c:y
* nsc0: NULL:"urn:example:a"; c:"urn:example:b"
* will be translated to:
* xpath1: /a:x/b:y
* nsc1: a:"urn:example:a"; b:"urn:example:b"
* @code
* char *xpath1 = NULL;
* cvec *nsc1 = NULL;
* if (xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1) < 0)
* err;
* ...
* if (xpath1) free(xpath1);
* if (nsc1) xml_nsctx_free(nsc1);
* @endcode
*/
int
xpath2canonical(char *xpath0,
cvec *nsc0,
yang_stmt *yspec,
char **xpath1,
cvec **nsc1p)
{
int retval = -1;
xpath_tree *xpt = NULL;
cvec *nsc1 = NULL;
cbuf *xcb = NULL;
/* Parse input xpath into an xpath-tree */
if (xpath_parse(xpath0, &xpt) < 0)
goto done;
/* Create new nsc */
if ((nsc1 = xml_nsctx_init(NULL, NULL)) == NULL)
goto done;
/* Traverse tree to find prefixes, transform them to canonical form and
* create a canonical network namespace
*/
if (traverse_canonical(xpt, yspec, nsc0, nsc1) < 0)
goto done;
/* Print tree with new prefixes */
if ((xcb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xpath_tree2cbuf(xpt, xcb) < 0)
goto done;
if (xpath1){
if ((*xpath1 = strdup(cbuf_get(xcb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if (nsc1p){
*nsc1p = nsc1;
nsc1 = NULL;
}
retval = 0;
done:
if (xcb)
cbuf_free(xcb);
if (nsc1)
xml_nsctx_free(nsc1);
if (xpt)
xpath_tree_free(xpt);
return retval;
}

View file

@ -807,8 +807,8 @@ yang_find_mynamespace(yang_stmt *ys)
* @param[out] prefix Local prefix to access module with (direct pointer)
* @retval 0 not found
* @retval -1 found
* @code
* @note prefix NULL is not returned, if own module, then return its prefix
* @code
* char *pfx = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix);
* @endcode
*/

View file

@ -334,7 +334,7 @@ expecteq(){
# Example: expecteq $(fn arg) 0 "my return"
# - evaluated expression
# - expected command return value (0 if OK)
# - expected stdout outcome
# - expected stdout outcome*
expectpart(){
r=$?
ret=$1

View file

@ -11,6 +11,11 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
xml=$dir/xml.xml
xml2=$dir/xml2.xml
xml3=$dir/xml3.xml
ydir=$dir/yang
if [ ! -d $ydir ]; then
mkdir $ydir
fi
cat <<EOF > $xml
<aaa>
@ -203,4 +208,54 @@ expecteof "$clixon_util_xpath -f $xml3 -p bbb[ccc='fie']" 0 "" "^nodeset:$"
new "xpath derived-from-or-self"
expecteof "$clixon_util_xpath -f $xml3 -p 'derived-from-or-self(../../change-operation,modify)'" 0 "" "derived-from-or-self"
# canonical namespace xpath tests
# need yang modules
cat <<EOF > $ydir/a.yang
module a{
namespace "urn:example:a";
prefix a;
container x{
leaf xa{
type string;
}
}
}
EOF
cat <<EOF > $ydir/b.yang
module b{
namespace "urn:example:b";
prefix b;
container y{
leaf ya{
type string;
}
}
}
EOF
new "xpath canonical form (already canonical)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /a:x/b:y -n a:urn:example:a -n b:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"'
new "xpath canonical form (default)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /x/b:y -n null:urn:example:a -n b:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"'
new "xpath canonical form (other)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:a -n j:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"'
new "xpath canonical form predicate 1"
expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y='e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"'
new "xpath canonical form predicate self"
expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[.='42'\]" '0 : a = "urn:example:a"'
new "xpath canonical form descendants"
expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"'
new "xpath canonical form (no default should fail)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b)" 255
new "xpath canonical form (wrong namespace should fail)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b)" 255
rm -rf $dir

View file

@ -185,7 +185,7 @@ main(int argc, char **argv)
clicon_debug(1, "xi:");
xml_print(stderr, xi);
}
if (xml_insert(xb, xi, INS_LAST, NULL) < 0)
if (xml_insert(xb, xi, INS_LAST, NULL, NULL) < 0)
goto done;
if (debug){
clicon_debug(1, "x0:");

View file

@ -69,6 +69,8 @@ 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-n <pfx:id>\tNamespace binding (pfx=NULL for default)\n"
"\t-c \t\tMap xpath to canonical form\n"
"\t-y <filename> \tYang filename or dir (load all files)\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"and the following extra rules:\n"
@ -131,6 +133,8 @@ main(int argc,
cbuf *cb = NULL;
clicon_handle h;
struct stat st;
cvec *nsc = NULL;
int canonical = 0;
clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
@ -139,7 +143,7 @@ main(int argc,
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:f:p:i:y:Y:")) != -1)
while ((c = getopt(argc, argv, "hD:f:p:i:n:cy:Y:")) != -1)
switch (c) {
case 'h':
usage(argv0);
@ -161,6 +165,29 @@ main(int argc,
case 'i': /* Optional initial XPATH string */
xpath0 = optarg;
break;
case 'n':{ /* Namespace binding */
char *prefix;
char *id;
if (nsc == NULL &&
(nsc = xml_nsctx_init(NULL, NULL)) == NULL)
goto done;
if (nodeid_split(optarg, &prefix, &id) < 0)
goto done;
if (prefix && strcmp(prefix, "null")==0){
free(prefix);
prefix = NULL;
}
if (xml_nsctx_add(nsc, prefix, id) < 0)
goto done;
if (prefix)
free(prefix);
if (id)
free(id);
break;
}
case 'c': /* Map namespace to canonical form */
canonical = 1;
break;
case 'y':
yang_file_dir = optarg;
break;
@ -220,6 +247,24 @@ main(int argc,
}
xpath = buf;
}
/* If canonical, translate nsc and xpath to canonical form */
if (canonical){
char *xpath1 = NULL;
cvec *nsc1 = NULL;
if (xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1) < 0)
goto done;
xpath = xpath1;
if (xpath)
fprintf(stdout, "%s\n", xpath);
if (nsc)
xml_nsctx_free(nsc);
nsc = nsc1;
if (nsc)
cvec_print(stdout, nsc);
goto ok; /* need a switch to continue, now just print and quit */
}
/*
* If fd=0, then continue reading from stdin (after CR)
* If fd>0, reading from file opened as argv[1]
@ -270,16 +315,19 @@ main(int argc,
x = x0;
/* Parse XPATH (use nsc == NULL to indicate dont use) */
if (xpath_vec_ctx(x, NULL, xpath, &xc) < 0)
if (xpath_vec_ctx(x, nsc, xpath, &xc) < 0)
return -1;
/* Print results */
cb = cbuf_new();
ctx_print2(cb, xc);
fprintf(stdout, "%s\n", cbuf_get(cb));
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (nsc)
xml_nsctx_free(nsc);
if (xc)
ctx_free(xc);
if (xv)