The Clixon API has been extended with namespaces, or namespace contexts in the following cases:

* CLIspec functions have added namespace parameter:
    * `cli_show_config <db> <format> <xpath>` --> `cli_show_config <db> <format> <xpath> <namespace>`
    * `cli_copy_config <db> <xpath> ...` --> `cli_copy_config <db> <xpath> <namespace> ...`
  * Xpath API
    * `xpath_first(x, format, ...)` --> `xpath_first(x, nsc, format, ...)`
    * `xpath_vec(x, format, vec, veclen, ...)` --> `xpath_vec(x, nsc, format, vec, veclen, ...)`
    * `xpath_vec_flag(x, format, flags, vec, veclen, ...)` --> `xpath_vec_flag(x, format, flags, vec, veclen, ...)`
    * `xpath_vec_bool(x, format, ...)` --> `xpath_vec_bool(x, nsc, format, ...)`
    * `xpath_vec_ctx(x, xpath, xp)` --> `xpath_vec_ctx(x, nsc, xpath, xp)`
  * xmldb_get0 has an added `nsc` parameter:
    * `xmldb_get0(h, db, xpath, copy, xret, msd)` --> `xmldb_get0(h, db, nsc, xpath, copy, xret, msd)`
  * The plugin statedata callback (ca_statedata) has been extended with an nsc parameter:
    * `int example_statedata(clicon_handle h, cvec *nsc, char *xpath, cxobj *xstate);`
  * rpc get and get-config api function has an added namespace argument:
    * `clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, char *namespace, cxobj **xt);`
    * `int clicon_rpc_get(clicon_handle h, char *xpath, char *namespace, cxobj **xt);`
This commit is contained in:
Olof hagsand 2019-07-08 10:36:37 +02:00
parent 73d8e97a01
commit 67b8685bab
78 changed files with 1507 additions and 538 deletions

View file

@ -32,6 +32,56 @@
***** END LICENSE BLOCK *****
* Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10
*
* Some notes on namespace extensions in Netconf/Yang
* RFC6241 8.9.1
* The set of namespace declarations are those in scope on the <filter> element.
* <rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
* <get-config>
* <filter xmlns:t="http://example.com/schema/1.2/config"
* type="xpath"
* select="/t:top/t:users/t:user[t:name='fred']"/>
* </get-config>
* We need to add namespace context to the cpath tree, typically in eval. How do
* we do that?
* One observation is that the namespace context is static, so it can not be a part
* of the xpath-tree, which is context-dependent.
* Best is to send it as a (read-only) parameter to the xp_eval family of functions
* as an exlicit namespace context.
* For that you need an API to get/set namespaces: clixon_xml_nscache.c?
* Then you need to fix API functions and this is the real work:
* - Replace all existing functions or create new?
* - Expose explicit namespace parameter, or xml object, or default namespace?
*
* On namespaces and xpath
* =======================
* XPATHs may contain prefixes such as /if:a/if:b
* XPATHs excecutes in a "namespace context" (nsc)
* The namespace context is either:
* (1) the same as the xml that is evaluated, typical for basic XML, or
* (2) separate from the XML that is evaluated. typical netconf and yang.
* 1. Same nsc as XML
* This happens in base XML (not yang), where the nsc is given implicitly by
* the XML being evaluated. In node comparisons (eg of ip:a) only name and
* prefixes are compared.
* XML: <if:a xmlns:if="urn:example:if" xmlns:ip="urn:example:ip"><ip:b/></if>
* XPATH: /if:a/ip:b
* When you call an xpath function, then call it with nsc=NULL.
* 2. Separate nsc.
* This happens if the namespace context is independent from the XML. It can
* happen for example in NETCONF GET using :xpath when the XML is not known
* so that xpath and xml may use different prefixes for the same namespace.
* In that case you cannot rely on the prefix but must compare namespaces.
* The namespace context of the XML is given (by the XML), but the xpath
* nsc must then be explicitly given in the xpath call.
* Example:
* XML: <if:a xmlns:if="urn:example:if" xmlns:ip="urn:example:ip"><ip:b/></if>
* NETCONF:<get-config><filter select="/x:a/y:b"
* xmlns:x="urn:example:if" xmlns:y="urn:example:ip/>
* Here, x,y are prefixes used for two namespaces that are given by if,ip in
* the xml. In this case, the namespaces (eg urn:example:if) must be compared
* instead.
* Another case is Yang path expressions.
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
@ -62,6 +112,7 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_parse.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
@ -69,6 +120,9 @@
/*
* Variables
*/
/* XXX assert break if xpath dont match. Set to 0 if ypu want it to pass */
int xpatherrordiff=1;
/* Mapping between XPATH operator string <--> int */
const map_str2int xpopmap[] = {
{"and", XO_AND},
@ -141,8 +195,7 @@ xpath_tree_print(cbuf *cb,
}
static int
xpath_tree_free(
xpath_tree *xs)
xpath_tree_free(xpath_tree *xs)
{
if (xs->xs_s0)
free(xs->xs_s0);
@ -156,45 +209,118 @@ xpath_tree_free(
return 0;
}
/*!
* @retval -1 Error XXX: retval -1 not properly handled
* @retval 0 No match
* @retval 1 Match
*/
static int
nodetest_eval_node(cxobj *x,
xpath_tree *xs,
cvec *nsc)
{
int retval = -1;
char *name1 = xml_name(x);
char *prefix1 = xml_prefix(x);
char *nsxml = NULL; /* xml body namespace */
char *nsxpath = NULL; /* xpath context namespace */
char *prefix2 = NULL;
char *name2 = NULL;
/* Namespaces is s0, name is s1 */
if (strcmp(xs->xs_s1, "*")==0)
return 1;
/* get namespace of xml tree */
if (xml2ns(x, prefix1, &nsxml) < 0)
goto done;
prefix2 = xs->xs_s0;
name2 = xs->xs_s1;
/* Before going into namespaces, check name equality and filter out noteq */
if (strcmp(name1, name2) != 0){
retval = 0; /* no match */
goto done;
}
/* here names are equal
* Now look for namespaces
* 1) prefix1 and prefix2 point to same namespace <<-- try this first
* 2) prefix1 is equal to prefix2 <<-- then try this
* (1) is strict yang xml
* (2) without yang
*/
if (nsc != NULL) { /* solution (1) */
nsxpath = xml_nsctx_get(nsc, prefix2);
if (nsxml != NULL && nsxpath != NULL)
retval = (strcmp(nsxml, nsxpath) == 0);
else
retval = (nsxml == nsxpath); /* True only if both are NULL */
}
else{ /* solution (2) */
if (prefix1 == NULL && prefix2 == NULL)
retval = 1;
else if (prefix1 == NULL || prefix2 == NULL)
retval = 0;
else
retval = strcmp(prefix1, prefix2) == 0;
}
/* If retval == 0 here, then there is name match, but not ns match */
if (retval == 0){
fprintf(stderr, "%s NOMATCH xml: (%s)%s\n\t\t xpath: (%s)%s\n", __FUNCTION__,
name1, nsxml,
name2, nsxpath);
if (xpatherrordiff)
assert(retval == 1);
}
done:
return retval;
}
/*! Make a nodetest
* @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN
* @retval 0 Match
* @retval 1 No match
* @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN
* @param[in] nsc XML Namespace context
* @retval -1 Error
* @retval 0 No match
* @retval 1 Match
* - node() is true for any node of any type whatsoever.
* - text() is true for any text node.
*/
static int
nodetest_eval(cxobj *x,
xpath_tree *xs)
xpath_tree *xs,
cvec *nsc)
{
int retval = 0; /* NB: no match is default (not error) */
char *fn;
if (xs->xs_type == XP_NODE){
/* Namespaces is s0, name is s1 */
if (strcmp(xs->xs_s1, "*")==0)
return 1;
else if (strcmp(xml_name(x), xs->xs_s1)==0)
return 1;
else
return 0;
}
if (xs->xs_type == XP_NODE)
retval = nodetest_eval_node(x, xs, nsc);
else if (xs->xs_type == XP_NODE_FN){
fn = xs->xs_s0;
if (strcmp(fn, "node")==0)
return 1;
retval = 1;
else if (strcmp(fn, "text")==0)
return 1;
retval = 1;
}
return 0;
/* note, retval set by previous statement */
return retval;
}
/*!
* @param[in] xn
* @param[in] nodetest XPATH stack
* @param[in] node_type
* @param[in] flags
* @param[in] nsc XML Namespace context
* @param[out] vec0
* @param[out] vec0len
*/
int
nodetest_recursive(cxobj *xn,
nodetest_recursive(cxobj *xn,
xpath_tree *nodetest,
int node_type,
uint16_t flags,
cxobj ***vec0,
size_t *vec0len)
int node_type,
uint16_t flags,
cvec *nsc,
cxobj ***vec0,
size_t *vec0len)
{
int retval = -1;
cxobj *xsub;
@ -203,14 +329,14 @@ nodetest_recursive(cxobj *xn,
xsub = NULL;
while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) {
if (nodetest_eval(xsub, nodetest) == 1){
if (nodetest_eval(xsub, nodetest, nsc) == 1){
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags));
if (flags==0x0 || xml_flag(xsub, flags))
if (cxvec_append(xsub, &vec, &veclen) < 0)
goto done;
// continue; /* Dont go deeper */
}
if (nodetest_recursive(xsub, nodetest, node_type, flags, &vec, &veclen) < 0)
if (nodetest_recursive(xsub, nodetest, node_type, flags, nsc, &vec, &veclen) < 0)
goto done;
}
retval = 0;
@ -220,12 +346,13 @@ nodetest_recursive(cxobj *xn,
return retval;
}
static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp);
static int xp_eval(xp_ctx *xc, xpath_tree *xs, cvec *nsc, xp_ctx **xrp);
/*! Evaluate xpath step rule of an XML tree
*
* @param[in] xc0 Incoming context
* @param[in] xs XPATH node tree
* @param[in] nsc XML Namespace context
* @param[out] xrp Resulting context
*
* - A node test that is a QName is true if and only if the type of the node (see [5 Data Model])
@ -237,6 +364,7 @@ static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp);
static int
xp_eval_step(xp_ctx *xc0,
xpath_tree *xs,
cvec *nsc,
xp_ctx **xrp)
{
int retval = -1;
@ -263,7 +391,7 @@ xp_eval_step(xp_ctx *xc0,
if (xc->xc_descendant){
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, &vec, &veclen) < 0)
if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0)
goto done;
}
xc->xc_descendant = 0;
@ -280,7 +408,7 @@ xp_eval_step(xp_ctx *xc0,
x = NULL;
while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) {
/* xs->xs_c0 is nodetest */
if (nodetest == NULL || nodetest_eval(x, nodetest))
if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1)
if (cxvec_append(x, &vec, &veclen) < 0)
goto done;
}
@ -292,7 +420,7 @@ xp_eval_step(xp_ctx *xc0,
case A_DESCENDANT_OR_SELF:
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, &vec, &veclen) < 0)
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, &vec, &veclen) < 0)
goto done;
}
ctx_nodeset_replace(xc, vec, veclen);
@ -331,7 +459,7 @@ xp_eval_step(xp_ctx *xc0,
break;
}
if (xs->xs_c1){
if (xp_eval(xc, xs->xs_c1, xrp) < 0)
if (xp_eval(xc, xs->xs_c1, nsc, xrp) < 0)
goto done;
}
else{
@ -351,6 +479,7 @@ xp_eval_step(xp_ctx *xc0,
* pred -> pred expr
* @param[in] xc Incoming context
* @param[in] xs XPATH node tree
* @param[in] nsc XML Namespace context
* @param[out] xrp Resulting context
*
* A predicate filters a node-set with respect to an axis to produce a new
@ -371,6 +500,7 @@ xp_eval_step(xp_ctx *xc0,
static int
xp_eval_predicate(xp_ctx *xc,
xpath_tree *xs,
cvec *nsc,
xp_ctx **xrp)
{
int retval = -1;
@ -386,7 +516,7 @@ xp_eval_predicate(xp_ctx *xc,
goto done;
}
else{ /* eval previous predicates */
if (xp_eval(xc, xs->xs_c0, &xr0) < 0)
if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0)
goto done;
}
if (xs->xs_c1){
@ -415,7 +545,7 @@ xp_eval_predicate(xp_ctx *xc,
* evaluated with that node as the context node */
if (cxvec_append(x, &xcc->xc_nodeset, &xcc->xc_size) < 0)
goto done;
if (xp_eval(xcc, xs->xs_c1, &xrc) < 0)
if (xp_eval(xcc, xs->xs_c1, nsc, &xrc) < 0)
goto done;
if (xcc)
ctx_free(xcc);
@ -834,13 +964,16 @@ xp_union(xp_ctx *xc1,
* Each node in that set is used as a context node for the following step.
* @param[in] xc Incoming context
* @param[in] xs XPATH node tree
* @param[in] nsc XML Namespace context
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
*/
static int
xp_eval(xp_ctx *xc,
xpath_tree *xs,
cvec *nsc,
xp_ctx **xrp)
{
int retval = -1;
cxobj *x;
@ -880,12 +1013,12 @@ xp_eval(xp_ctx *xc,
break;
case XP_STEP: /* XP_NODE is first argument -not called explicitly */
if (xp_eval_step(xc, xs, xrp) < 0)
if (xp_eval_step(xc, xs, nsc, xrp) < 0)
goto done;
goto ok;
break;
case XP_PRED:
if (xp_eval_predicate(xc, xs, xrp) < 0)
if (xp_eval_predicate(xc, xs, nsc, xrp) < 0)
goto done;
goto ok;
break;
@ -895,7 +1028,7 @@ xp_eval(xp_ctx *xc,
/* Eval first child c0
*/
if (xs->xs_c0){
if (xp_eval(xc, xs->xs_c0, &xr0) < 0)
if (xp_eval(xc, xs->xs_c0, nsc, &xr0) < 0)
goto done;
}
/* Actions between first and second child
@ -973,7 +1106,7 @@ xp_eval(xp_ctx *xc,
* Note, some operators like locationpath, need transitive context (use_xr0)
*/
if (xs->xs_c1)
if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, &xr1) < 0)
if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, &xr1) < 0)
goto done;
/* Actions after second child
*/
@ -1032,19 +1165,20 @@ xp_eval(xp_ctx *xc,
if (xr0)
ctx_free(xr0);
return retval;
}
} /* xp_eval */
/*! Given XML tree and xpath, returns xpath context
/*! 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] xcur XML-tree where to search
* @param[in] xpath String with XPATH 1.0 syntax
* @param[in] nsc XML Namespace context
* @param[out] xrp Return XPATH context
* @retval 0 OK
* @retval -1 Error
* @code
* xp_ctx *xc = NULL;
* if (xpath_vec_ctx(x, xpath, &xc) < 0)
* if (xpath_vec_ctx(x, NULL, xpath, &xc) < 0)
* err;
* if (xc)
* ctx_free(xc);
@ -1052,6 +1186,7 @@ xp_eval(xp_ctx *xc,
*/
int
xpath_vec_ctx(cxobj *xcur,
cvec *nsc,
char *xpath,
xp_ctx **xrp)
{
@ -1084,7 +1219,7 @@ xpath_vec_ctx(cxobj *xcur,
xc.xc_initial = xcur;
if (cxvec_append(xcur, &xc.xc_nodeset, &xc.xc_size) < 0)
goto done;
if (xp_eval(&xc, xy.xy_top, xrp) < 0)
if (xp_eval(&xc, xy.xy_top, nsc, xrp) < 0)
goto done;
if (xc.xc_nodeset){
free(xc.xc_nodeset);
@ -1102,23 +1237,27 @@ xpath_vec_ctx(cxobj *xcur,
/*! Xpath nodeset function where only the first matching entry is returned
* args:
* @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax
* @retval xml-tree of first match
* @retval NULL Error or not found
* @param[in] xcur XML tree where to search
* @param[in] nsc XML Namespace context
* @param[in] format string with XPATH syntax
* @retval xml-tree XML tree of first match
* @retval NULL Error or not found
*
* @code
* cxobj *x;
* if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) {
* cvec *nsc; // namespace context
* if ((x = xpath_first(xtop, nsc, "//symbol/foo")) != NULL) {
* ...
* }
* @endcode
* @note the returned pointer points into the original tree so should not be freed fter use.
* @note return value does not see difference between error and not found
* @see also xpath_vec.
* @experimental
*/
cxobj *
xpath_first(cxobj *xcur,
cvec *nsc,
char *format,
...)
{
@ -1144,9 +1283,8 @@ xpath_first(cxobj *xcur,
goto done;
}
va_end(ap);
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET && xr->xc_size)
cx = xr->xc_nodeset[0];
done:
@ -1157,18 +1295,21 @@ xpath_first(cxobj *xcur,
return cx;
}
/*! Given XML tree and xpath, returns nodeset as xml node vector
* If result is not nodeset, return empty nodeset
* @param[in] xcur xml-tree where to search
* @param[in] xpath stdarg string with XPATH 1.0 syntax
* @param[in] format stdarg string with XPATH 1.0 syntax
* @param[in] nsc XML Namespace context for XPATH
* @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value
* @retval 0 OK
* @retval -1 Error
* @code
* cvec *nsc; // namespace context
* cxobj **vec;
* size_t veclen;
* if (xpath_vec(xcur, "//symbol/foo", &vec, &veclen) < 0)
* if (xpath_vec(xcur, nsc, "//symbol/foo", &vec, &veclen) < 0)
* goto err;
* for (i=0; i<veclen; i++){
* xn = vec[i];
@ -1176,9 +1317,11 @@ xpath_first(cxobj *xcur,
* }
* free(vec);
* @endcode
* @note Namespace prefix checking is not properly implemented
*/
int
xpath_vec(cxobj *xcur,
cvec *nsc,
char *format,
cxobj ***vec,
size_t *veclen,
@ -1188,8 +1331,8 @@ xpath_vec(cxobj *xcur,
va_list ap;
size_t len;
char *xpath = NULL;
xp_ctx *xr = NULL;
xp_ctx *xr = NULL;
va_start(ap, veclen);
len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
@ -1208,7 +1351,7 @@ xpath_vec(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
*vec = xr->xc_nodeset;
@ -1227,6 +1370,7 @@ xpath_vec(cxobj *xcur,
/* Xpath that returns a vector of matches (only nodes marked with flags)
* @param[in] xcur xml-tree where to search
* @param[in] xpath string with XPATH syntax
* @param[in] nsc External XML namespace context, or NULL
* @param[in] flags Set of flags that return nodes must match (0 if all)
* @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value
@ -1235,7 +1379,8 @@ xpath_vec(cxobj *xcur,
* @code
* cxobj **vec;
* size_t veclen;
* if (xpath_vec_flag(xcur, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0)
* cvec *nsc; // namespace context (not NULL)
* if (xpath_vec_flag(xcur, nsc, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0)
* goto err;
* for (i=0; i<veclen; i++){
* xn = vec[i];
@ -1249,8 +1394,9 @@ xpath_vec(cxobj *xcur,
*/
int
xpath_vec_flag(cxobj *xcur,
cvec *nsc,
char *format,
uint16_t flags,
uint16_t flags,
cxobj ***vec,
size_t *veclen,
...)
@ -1281,7 +1427,7 @@ xpath_vec_flag(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
for (i=0; i<xr->xc_size; i++){
@ -1303,14 +1449,16 @@ xpath_vec_flag(cxobj *xcur,
/*! Given XML tree and xpath, returns boolean
* Returns true if the nodeset is non-empty
* @param[in] xcur xml-tree where to search
* @param[in] nsc External XML namespace context, or NULL
* @param[in] xpath stdarg string with XPATH 1.0 syntax
* @retval 1 True
* @retval 0 False
* @retval -1 Error
*/
int
xpath_vec_bool(cxobj *xcur,
char *format,
xpath_vec_bool(cxobj *xcur,
cvec *nsc,
char *format,
...)
{
int retval = -1;
@ -1335,7 +1483,7 @@ xpath_vec_bool(cxobj *xcur,
goto done;
}
va_end(ap);
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
if (xpath_vec_ctx(xcur, nsc, xpath, &xr) < 0)
goto done;
if (xr)
retval = ctx2boolean(xr);