XPath: refactored XPath match, documented localonly and prefixonly api

This commit is contained in:
Olof hagsand 2024-12-15 11:27:58 +01:00
parent 6c73c36fb7
commit 081a541c6b
13 changed files with 384 additions and 105 deletions

View file

@ -153,7 +153,7 @@ static inline int clixon_debug_isset(unsigned n)
}
/* Is detail set ?, return detail level 0-7 */
static inline int clixon_debug_detail()
static inline int clixon_debug_detail(void)
{
unsigned level = clixon_debug_get();

View file

@ -60,7 +60,7 @@ int xml_nsctx_node(cxobj *x, cvec **ncp);
int xml_nsctx_yang(yang_stmt *yn, cvec **ncp);
int xml_nsctx_yangspec(yang_stmt *yspec, cvec **ncp);
int xml_nsctx_cbuf(cbuf *cb, cvec *nsc);
int xml2ns(cxobj *x, char *localname, char **ns);
int xml2ns(cxobj *x, char *prefix, char **ns);
int xml2ns_recurse(cxobj *x);
int xmlns_set(cxobj *x, char *prefix, char *ns);
int xmlns_set_all(cxobj *x, cvec *nsc);

View file

@ -134,7 +134,7 @@ int xpath_tree_eq(xpath_tree *xt1, xpath_tree *xt2, xpath_tree ***vec, size_t
xpath_tree *xpath_tree_traverse(xpath_tree *xt, ...);
int xpath_tree_free(xpath_tree *xs);
int xpath_parse(const char *xpath, xpath_tree **xptree);
int xpath_vec_ctx(cxobj *xcur, cvec *nsc, const char *xpath, int localonly, xp_ctx **xrp);
int xpath_vec_ctx(cxobj *xcur, cvec *nsc, const char *xpath, int localonly, xp_ctx **xrp);
int xpath_vec_bool(cxobj *xcur, cvec *nsc, const char *xpformat, ...) __attribute__ ((format (printf, 3, 4)));
int xpath_vec_flag(cxobj *xcur, cvec *nsc, const char *xpformat, uint16_t flags,

View file

@ -58,7 +58,9 @@ enum xp_objtype{
XT_STRING
};
/* Expression evaluation occurs with respect to a context. XSLT and XPointer specify how the
/*! XPath context and result
*
* Expression evaluation occurs with respect to a context. XSLT and XPointer specify how the
* context is determined for XPath expressions used in XSLT and XPointer respectively. The
* context consists of:
* a node (the context node)

View file

@ -60,7 +60,10 @@
* 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.
*
* 3) localonly flag refers to https://www.w3.org/TR/REC-xml-names/, where :
* PrefixedName ::= Prefix ':' LocalPart
* "localonly" means to skip the "Prefix" part, ie namespaces
* see nodetest_eval_node() for the semantics of prefix/namespace processing
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
@ -328,8 +331,8 @@ xpath_tree2cbuf(xpath_tree *xs,
cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int));
break;
case XP_PATHEXPR:
/* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath
| FilterExpr '//' RelativeLocationPath
/* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath
| FilterExpr '//' RelativeLocationPath
*/
if (xs->xs_s0)
cprintf(xcb, "%s", xs->xs_s0);
@ -608,7 +611,7 @@ xpath_parse(const char *xpath,
* @param[in] xcur XML-tree where to search
* @param[in] nsc External XML namespace context, or NULL
* @param[in] xpath String with XPath 1.0 syntax
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[in] localonly Skip prefix and namespace tests
* @param[out] xrp Return XPath context
* @retval 0 OK
* @retval -1 Error

View file

@ -44,9 +44,9 @@
* 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?
* 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.
* 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?
@ -86,6 +86,7 @@
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_ctx.h"
#include "clixon_string.h"
#include "clixon_xpath.h"
#include "clixon_xpath_optimize.h"
#include "clixon_xpath_function.h"
@ -110,99 +111,148 @@ const map_str2int xpopmap[] = {
{NULL, -1}
};
/*! Eval an XPath nodetest
/*! Eval an XPath nodetest with full namespace test
*
* @retval 1 Match
* @retval 0 No match
* @retval -1 Error XXX: retval -1 not properly handled
* XML x -> prefix1 + name1
* XPATH xs -> prefix2 + name2
* Unless name2=*, if name1 != name2 -> fail
* Lookup(prefix1, XML) -> ns1
* Lookup(prefix2, NSC) -> ns2
* if ns1 = NULL -> fail
* if ns2 = NULL -> fail (see XPATH_NS_ACCEPT_UNRESOLVED)
* if ns1 = ns2 -> match
* otherwise fail
* @param[in] x XML sub-tree given by the the context node
* @param[in] xs XPath stack of type XP_NODE or XP_NODE_FN
* @param[in] nsc XML Namespace context as given by xpath_vec_ctx()
* @retval 1 Match
* @retval 0 No match
* @retval -1 Error
*/
static int
nodetest_eval_node(cxobj *x,
xpath_tree *xs,
cvec *nsc)
nodetest_eval_namespace(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;
int retval = -1;
char *name1;
char *prefix1;
char *prefix2;
char *name2;
char *ns1 = NULL; /* xml namespace */
char *ns2 = NULL; /* xpath namespace */
/* 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;
/* XML x -> prefix1 + name1 */
prefix1 = xml_prefix(x);
name1 = xml_name(x);
/* XPATH xs -> prefix2 + name2 */
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 */
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "%s %s", name1, name2);
if (strcmp(name2, "*") != 0){
/* if name1 != name2 -> fail */
if (strcmp(name1, name2) != 0)
goto fail;
}
/* get namespace of xml tree */
if (xml2ns(x, prefix1, &ns1) < 0)
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 if (nsxpath == NULL){
/* We have a namespace from xml, but none in yang.
* This can happen in eg augments and ../foo, where foo is
* augmented from another namespace
*/
retval = 1;
}
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 0 /* debugging */
/* 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 (ns1 == NULL)
goto fail;
ns2 = xml_nsctx_get(nsc, prefix2);
if (ns2 == NULL){
#ifdef XPATH_NS_ACCEPT_UNRESOLVED
goto ok;
#else
goto fail;
#endif
}
else if (strcmp(ns1, ns2) != 0)
goto fail;
#ifdef XPATH_NS_ACCEPT_UNRESOLVED
ok:
#endif
retval = 1;
done: /* retval set in preceding statement */
return retval;
fail:
retval = 0;
goto done;
}
/*! Eval an XPath nodetest but skip namespace tests
*
* XML x -> prefix1 + name1
* XPATH xs -> prefix2 + name2
* Unless name2=*, if name1 != name2 -> fail
* if prefix1 and prefix2 are string-equal or both NULL -> match
* else -> fail
* @param[in] x XML node
* @param[in] xs XPath stack of type XP_NODE or XP_NODE_FN
* @retval 1 Match
* @retval 0 No match
* @retval -1 Error XXX: retval -1 not properly handled
*/
static int
nodetest_eval_prefixonly(cxobj *x,
xpath_tree *xs)
{
int retval = -1;
char *name1;
char *prefix1;
char *prefix2;
char *name2;
int ret;
/* XML x -> prefix1 + name1 */
prefix1 = xml_prefix(x);
name1 = xml_name(x);
/* XPATH xs -> prefix2 + name2 */
prefix2 = xs->xs_s0;
name2 = xs->xs_s1;
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "%s:%s %s:%s", prefix1, name1, prefix2, name2);
if (strcmp(name2, "*") != 0){
/* if name1 != name2 -> fail */
if (strcmp(name1, name2) != 0)
goto fail;
}
ret = clicon_strcmp(prefix1, prefix2);
if (ret != 0)
goto fail;
retval = 1;
done: /* retval set in preceding statement */
return retval;
fail:
retval = 0;
goto done;
}
/*! Eval an XPath nodetest but skip prefix and namespace tests
*
* This is NOT according to standard
* If name2 = "*" -> match # A node test * is true for any node of the principal node type.
* If name1 = name2 -> match (string-equal or both NULL)
* @param[in] x XML node
* @param[in] xs XPath stack of type XP_NODE or XP_NODE_FN
* @retval 1 Match
* @retval 0 No match
* @retval -1 Error
*/
static int
nodetest_eval_node_localonly(cxobj *x,
xpath_tree *xs,
cvec *nsc)
nodetest_eval_localonly(cxobj *x,
xpath_tree *xs)
{
int retval = -1;
char *name1 = xml_name(x);
char *name2 = NULL;
char *name1;
char *name2;
/* Namespaces is s0, name is s1 */
if (strcmp(xs->xs_s1, "*")==0){
name1 = xml_name(x);
name2 = xs->xs_s1;
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "%s %s", name1, name2);
if (strcmp(name2, "*")==0){
retval = 1;
goto done;
}
name2 = xs->xs_s1;
/* Before going into namespaces, check name equality and filter out noteq */
/* Check name only */
if (strcmp(name1, name2) == 0){
retval = 1;
goto done;
@ -219,7 +269,7 @@ nodetest_eval_node_localonly(cxobj *x,
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @retval 1 Match
* @retval 0 No match
* @retval 0 No match
* @retval -1 Error
* - node() is true for any node of any type whatsoever.
* - text() is true for any text node.
@ -234,9 +284,11 @@ nodetest_eval(cxobj *x,
if (xs->xs_type == XP_NODE){
if (localonly)
retval = nodetest_eval_node_localonly(x, xs, nsc);
retval = nodetest_eval_localonly(x, xs);
else if (nsc == NULL)
retval = nodetest_eval_prefixonly(x, xs);
else
retval = nodetest_eval_node(x, xs, nsc);
retval = nodetest_eval_namespace(x, xs, nsc);
}
else if (xs->xs_type == XP_NODE_FN){
switch (xs->xs_int){
@ -295,7 +347,7 @@ nodetest_recursive(cxobj *xn,
retval = 0;
*vec0 = vec;
*vec0len = veclen;
done:
done:
return retval;
}
@ -309,7 +361,7 @@ nodetest_recursive(cxobj *xn,
* @retval 0 OK
* @retval -1 Error
*
* - A node test that is a QName is true if and only if the type of the node (see [5 Data Model])
* - A node test that is a QName is true if and only if the type of the node (see [5 Data Model])
* is the principal node type and has an expanded-name equal to the expanded-name specified by the QName.
* - A node test * is true for any node of the principal node type.
* - node() is true for any node of any type whatsoever.
@ -1019,7 +1071,7 @@ xp_relop(xp_ctx *xc1,
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op));
goto done;
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
@ -1055,7 +1107,7 @@ xp_relop(xp_ctx *xc1,
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op));
goto done;
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
@ -1203,7 +1255,7 @@ xp_eval(xp_ctx *xc,
goto ok;
break;
case XPATHFN_DEREF:
if (xp_function_deref(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
if (xp_function_deref(xc, xs->xs_c0, nsc, xrp) < 0)
goto done;
goto ok;
break;
@ -1223,7 +1275,7 @@ xp_eval(xp_ctx *xc,
goto ok;
break;
case XPATHFN_POSITION:
if (xp_function_position(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
if (xp_function_position(xc, xs->xs_c0, nsc, xrp) < 0)
goto done;
goto ok;
break;
@ -1288,12 +1340,12 @@ xp_eval(xp_ctx *xc,
goto ok;
break;
case XPATHFN_TRUE:
if (xp_function_true(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
if (xp_function_true(xc, xs->xs_c0, nsc, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_FALSE:
if (xp_function_false(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
if (xp_function_false(xc, xs->xs_c0, nsc, xrp) < 0)
goto done;
goto ok;
break;

View file

@ -280,7 +280,6 @@ xp_function_re_match(xp_ctx *xc,
* @param[in] xc0 Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
@ -290,7 +289,6 @@ int
xp_function_deref(xp_ctx *xc0,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
@ -587,7 +585,6 @@ xp_function_bit_is_set(xp_ctx *xc,
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
@ -596,7 +593,6 @@ int
xp_function_position(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
@ -1253,7 +1249,6 @@ int
xp_function_true(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
@ -1280,7 +1275,6 @@ int
xp_function_false(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;

View file

@ -95,10 +95,10 @@ const char *xp_fnname_int2str(enum clixon_xpath_function code);
int xp_function_current(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_re_match(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_deref(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_deref(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, xp_ctx **xrp);
int xp_function_derived_from(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, int self, xp_ctx **xrp);
int xp_function_bit_is_set(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_position(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_position(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, xp_ctx **xrp);
int xp_function_count(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_name(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_string(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
@ -109,7 +109,7 @@ int xp_function_string_length(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int
int xp_function_translate(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_boolean(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_not(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_true(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_false(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_true(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, xp_ctx **xrp);
int xp_function_false(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, xp_ctx **xrp);
#endif /* _CLIXON_XPATH_FUNCTION_H */