From 03acf8e19c46eb19b354463383e6fedffb9baeaf Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 9 Oct 2019 22:13:04 +0200 Subject: [PATCH] * XPATH canonical form implemented for NETCONF get and get-config. --- CHANGELOG.md | 2 + apps/backend/backend_client.c | 44 ++++- apps/restconf/restconf_methods_post.c | 3 - lib/clixon/clixon_xml_nsctx.h | 6 +- lib/clixon/clixon_xpath.h | 4 +- lib/src/clixon_xml.c | 2 +- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_xml_nsctx.c | 106 ++++++------ lib/src/clixon_xpath.c | 232 ++++++++++++++++++++++++-- lib/src/clixon_yang.c | 2 +- test/lib.sh | 2 +- test/test_xpath.sh | 55 ++++++ util/clixon_util_insert.c | 2 +- util/clixon_util_xpath.c | 54 +++++- 14 files changed, 430 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8effa6bc..d7576fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 385ca4de..35771988 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -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 * The set of namespace declarations are those in scope on the * 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 * The set of namespace declarations are those in scope on the * 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) diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index a10e0e6b..b3cd7ec5 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -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){ diff --git a/lib/clixon/clixon_xml_nsctx.h b/lib/clixon/clixon_xml_nsctx.h index e397c056..3c383a85 100644 --- a/lib/clixon/clixon_xml_nsctx.h +++ b/lib/clixon/clixon_xml_nsctx.h @@ -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 */ diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index dea6f832..ff228042 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -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 */ diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index defff2b2..38723f7b 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -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; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 7d09098e..a1dc2730 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -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 '=' */ diff --git a/lib/src/clixon_xml_nsctx.c b/lib/src/clixon_xml_nsctx.c index ae73c916..c7f803a4 100644 --- a/lib/src/clixon_xml_nsctx.c +++ b/lib/src/clixon_xml_nsctx.c @@ -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; -} diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index b9eb88b1..b5f2ee1a 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -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, @@ -182,7 +183,87 @@ xpath_tree_print(FILE *f, done: 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; @@ -264,7 +341,7 @@ xpath_parse(cvec *nsc, * This is a raw form of xpath where you can do type conversion of the return * value, etc, not just a nodeset. * @param[in] xcur XML-tree where to search - * @param[in] nsc External XML namespace context, or NULL + * @param[in] nsc External XML namespace context, or NULL * @param[in] xpath String with XPATH 1.0 syntax * @param[out] xrp Return XPATH context * @retval 0 OK @@ -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; +} diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 9584732d..051627d7 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -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 */ diff --git a/test/lib.sh b/test/lib.sh index d2fa7c2d..fa601e19 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -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 diff --git a/test/test_xpath.sh b/test/test_xpath.sh index 39cf3353..338a1da6 100755 --- a/test/test_xpath.sh +++ b/test/test_xpath.sh @@ -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 < $xml @@ -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 < $ydir/a.yang +module a{ + namespace "urn:example:a"; + prefix a; + container x{ + leaf xa{ + type string; + } + } +} +EOF + +cat < $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 diff --git a/util/clixon_util_insert.c b/util/clixon_util_insert.c index 34775f3d..f4e86e83 100644 --- a/util/clixon_util_insert.c +++ b/util/clixon_util_insert.c @@ -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:"); diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index d799c4bb..57d5cac6 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -69,6 +69,8 @@ usage(char *argv0) "\t-f \tXML file\n" "\t-p \tPrimary XPATH string\n" "\t-i \t(optional) Initial XPATH string\n" + "\t-n \tNamespace binding (pfx=NULL for default)\n" + "\t-c \t\tMap xpath to canonical form\n" "\t-y \tYang filename or dir (load all files)\n" "\t-Y \tYang dirs (can be several)\n" "and the following extra rules:\n" @@ -130,7 +132,9 @@ main(int argc, xp_ctx *xc = NULL; cbuf *cb = NULL; clicon_handle h; - struct stat st; + 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)