clixon/lib/src/clixon_xpath_eval.c

1415 lines
45 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2, indicate
your decision by deleting the provisions above and replace them with the
notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** 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?
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <math.h> /* NaN */
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_debug.h"
#include "clixon_yang_type.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xpath_optimize.h"
#include "clixon_xpath_function.h"
#include "clixon_xpath_eval.h"
/* Mapping between XPath operator string <--> int */
const map_str2int xpopmap[] = {
{"and", XO_AND},
{"or", XO_OR},
{"div", XO_DIV},
{"mod", XO_MOD},
{"+", XO_ADD},
{"*", XO_MULT},
{"-", XO_SUB},
{"=", XO_EQ},
{"!=", XO_NE},
{">=", XO_GE},
{"<=", XO_LE},
{"<", XO_LT},
{">", XO_GT},
{"|", XO_UNION},
{NULL, -1}
};
/*! Eval an XPath nodetest
*
* @retval 1 Match
* @retval 0 No match
* @retval -1 Error XXX: retval -1 not properly handled
*/
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 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);
}
#endif
done: /* retval set in preceding statement */
return retval;
}
/*! Eval an XPath nodetest but skip prefix and namespace tests
*
* This is NOT according to standard
*/
static int
nodetest_eval_node_localonly(cxobj *x,
xpath_tree *xs,
cvec *nsc)
{
int retval = -1;
char *name1 = xml_name(x);
char *name2 = NULL;
/* Namespaces is s0, name is s1 */
if (strcmp(xs->xs_s1, "*")==0){
retval = 1;
goto done;
}
name2 = xs->xs_s1;
/* Before going into namespaces, check name equality and filter out noteq */
if (strcmp(name1, name2) == 0){
retval = 1;
goto done;
}
retval = 0; /* no match */
done: /* retval set in preceding statement */
return retval;
}
/*! Make a nodetest
*
* @param[in] x XML node
* @param[in] xs XPath stack of type XP_NODE or XP_NODE_FN
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @retval 1 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.
*/
static int
nodetest_eval(cxobj *x,
xpath_tree *xs,
cvec *nsc,
int localonly)
{
int retval = 0; /* NB: no match is default (not error) */
if (xs->xs_type == XP_NODE){
if (localonly)
retval = nodetest_eval_node_localonly(x, xs, nsc);
else
retval = nodetest_eval_node(x, xs, nsc);
}
else if (xs->xs_type == XP_NODE_FN){
switch (xs->xs_int){
case XPATHFN_NODE:
case XPATHFN_TEXT:
retval = 1;
break;
default:
break;
}
}
/* note, retval set by previous statement */
return retval;
}
/*! test node recursive
*
* @param[in] xn
* @param[in] nodetest XPath stack
* @param[in] node_type
* @param[in] flags
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] vec0
* @param[out] vec0len
* @retval 0 OK
* @retval -1 Error
*/
int
nodetest_recursive(cxobj *xn,
xpath_tree *nodetest,
int node_type,
uint16_t flags,
cvec *nsc,
int localonly,
cxobj ***vec0,
int *vec0len)
{
int retval = -1;
cxobj *xsub;
cxobj **vec = *vec0;
int veclen = *vec0len;
xsub = NULL;
while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) {
if (nodetest_eval(xsub, nodetest, nsc, localonly) == 1){
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "%x %x", flags, xml_flag(xsub, flags));
if (flags==0x0 || xml_flag(xsub, flags))
if (cxvec_append(xsub, &vec, &veclen) < 0)
goto done;
// continue; /* Don't go deeper */
}
if (nodetest_recursive(xsub, nodetest, node_type, flags, nsc, localonly, &vec, &veclen) < 0)
goto done;
}
retval = 0;
*vec0 = vec;
*vec0len = veclen;
done:
return retval;
}
/*! 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[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @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])
* 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.
* - text() is true for any text node.
*/
static int
xp_eval_step(xp_ctx *xc0,
xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
int i;
cxobj *x;
cxobj *xv;
cxobj *xp;
cxobj **vec = NULL;
int veclen = 0;
xpath_tree *nodetest = xs->xs_c0;
xp_ctx *xc = NULL;
int ret;
/* Create new xc */
if ((xc = ctx_dup(xc0)) == NULL)
goto done;
switch (xs->xs_int){
case A_ANCESTOR:
break;
case A_ANCESTOR_OR_SELF:
break;
case A_ATTRIBUTE: /* principal node type is attribute */
break;
case A_CHILD:
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, nsc, localonly, &vec, &veclen) < 0)
goto done;
}
xc->xc_descendant = 0;
}
else{
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
x = NULL;
if ((ret = xpath_optimize_check(xs, xv, &vec, &veclen)) < 0)
goto done;
if (ret == 0){/* regular code, no optimization made */
while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) {
/* xs->xs_c0 is nodetest */
if (nodetest == NULL ||
nodetest_eval(x, nodetest, nsc, localonly) == 1){
if (cxvec_append(x, &vec, &veclen) < 0)
goto done;
}
}
}
}
}
ctx_nodeset_replace(xc, vec, veclen);
if (vec)
vec = NULL;
break;
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, nsc, localonly, &vec, &veclen) < 0)
goto done;
}
for (i=0; i<veclen; i++){
x = vec[i];
if (cxvec_append(x, &xc->xc_nodeset, &xc->xc_size) < 0)
goto done;
}
if (vec){
free(vec);
vec = NULL;
}
break;
case A_DESCENDANT:
for (i=0; i<xc->xc_size; i++){
xv = xc->xc_nodeset[i];
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, localonly, &vec, &veclen) < 0)
goto done;
}
ctx_nodeset_replace(xc, vec, veclen);
break;
case A_FOLLOWING:
break;
case A_FOLLOWING_SIBLING:
break;
case A_NAMESPACE: /* principal node type is namespace */
break;
case A_PARENT:
veclen = xc->xc_size;
vec = xc->xc_nodeset;
xc->xc_size = 0;
xc->xc_nodeset = NULL;
for (i=0; i<veclen; i++){
x = vec[i];
if ((xp = xml_parent(x)) != NULL
#ifdef XML_PARENT_CANDIDATE
/* Also check "candidate" parent for special when use-case */
|| (xp = xml_parent_candidate(x)) != NULL
#endif /* XML_PARENT_CANDIDATE */
)
if (cxvec_append(xp, &xc->xc_nodeset, &xc->xc_size) < 0)
goto done;
}
if (vec){
free(vec);
vec = NULL;
}
break;
case A_PRECEDING:
break;
case A_PRECEDING_SIBLING:
break;
case A_SELF:
break;
default:
clixon_err(OE_XML, 0, "No such axisname: %d", xs->xs_int);
goto done;
break;
}
if (xs->xs_c1){
if (xp_eval(xc, xs->xs_c1, nsc, localonly, xrp) < 0)
goto done;
}
else{
*xrp = xc;
xc = NULL;
}
if (*xrp == NULL){
clixon_err(OE_XML, 0, "Internal error xrp is NULL");
goto done;
}
retval = 0;
done:
if (xc)
ctx_free(xc);
return retval;
}
/*! Evaluate xpath predicates rule
*
* pred -> pred expr
* @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
*
* A predicate filters a node-set with respect to an axis to produce a new
* node-set. For each node in the node-set to be filtered, the PredicateExpr is
* evaluated with that node as the context node, with the number of nodes in
* the node-set as the context size, and with the proximity position of the node
* in the node-set with respect to the axis as the context position; if
* PredicateExpr evaluates to true for that node, the node is included in the
* new node-set; otherwise, it is not included.
* A PredicateExpr is evaluated by evaluating the Expr and converting the result
* to a boolean. If the result is a
* - number, the result will be converted to true if the number is equal to the
* context position and will be converted to false otherwise;
* - if the result is not a number, then the result will be converted as if by a
* call to the boolean function.
* Thus a location path para[3] is equivalent to para[position()=3].
*/
static int
xp_eval_predicate(xp_ctx *xc,
xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xrc = NULL;
int i;
cxobj *x;
xp_ctx *xcc = NULL;
if (xs->xs_c0 != NULL){ /* eval previous predicates */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
}
else{ /* empty */
if ((xr0 = ctx_dup(xc)) == NULL)
goto done;
}
// alt set nodeset to NULL
if (xs->xs_c1 && xr0->xc_type == XT_NODESET){ /* Second child */
/* Loop over each node in the nodeset
* XXX: alt to check xr0 is nodeset: set new var nodeset to NULL
*/
if ((xr1 = malloc(sizeof(*xr1))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr1, 0, sizeof(*xr1));
xr1->xc_type = XT_NODESET;
xr1->xc_node = xc->xc_node;
xr1->xc_initial = xc->xc_initial;
for (i=0; i<xr0->xc_size; i++){
x = xr0->xc_nodeset[i];
/* Create new context */
if ((xcc = malloc(sizeof(*xcc))) == NULL){
clixon_err(OE_XML, errno, "malloc");
goto done;
}
memset(xcc, 0, sizeof(*xcc));
xcc->xc_type = XT_NODESET;
xcc->xc_initial = xc->xc_initial;
xcc->xc_node = x;
xcc->xc_position = i;
/* For each node in the node-set to be filtered, the PredicateExpr is
* 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, nsc, localonly, &xrc) < 0)
goto done;
ctx_free(xcc);
xcc = NULL;
if (xrc->xc_type == XT_NUMBER){
/* If the result is a number, the result will be converted to true
if the number is equal to the context position */
if ((int)xrc->xc_number == i)
if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0)
goto done;
}
else {
/* if PredicateExpr evaluates to true for that node, the node is
included in the new node-set */
if (ctx2boolean(xrc))
if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0)
goto done;
}
if (xrc)
ctx_free(xrc);
}
}
if (xr0 == NULL && xr1 == NULL){
clixon_err(OE_XML, EFAULT, "Internal error: no result produced");
goto done;
}
if (xr1){
*xrp = xr1;
xr1 = NULL;
}
else if (xr0){
*xrp = xr0;
xr0 = NULL;
}
retval = 0;
done:
if (xcc)
ctx_free(xcc);
if (xr0)
ctx_free(xr0);
if (xr1)
ctx_free(xr1);
return retval;
}
/*! Given two XPath contexts, eval logical operations: or,and
*
* The logical operators convert their operands to booleans
* @param[in] xc1 Context of operand1
* @param[in] xc2 Context of operand2
* @param[in] op Relational operator
* @param[out] xrp Result context
* @retval 0 OK
* @retval -1 Error
*/
static int
xp_logop(xp_ctx *xc1,
xp_ctx *xc2,
enum xp_op op,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr = NULL;
int b1;
int b2;
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_initial = xc1->xc_initial;
xr->xc_type = XT_BOOL;
if ((b1 = ctx2boolean(xc1)) < 0)
goto done;
if ((b2 = ctx2boolean(xc2)) < 0)
goto done;
switch (op){
case XO_AND:
xr->xc_bool = b1 && b2;
break;
case XO_OR:
xr->xc_bool = b1 || b2;
break;
default:
clixon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context",
__FUNCTION__, clicon_int2str(xpopmap,op));
goto done;
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr)
ctx_free(xr);
return retval;
}
/*! Given two XPath contexts, eval numeric operations: +-*,div,mod
*
* The numeric operators convert their operands to numbers as if by
* calling the number function.
* @param[in] xc1 Context of operand1
* @param[in] xc2 Context of operand2
* @param[in] op Relational operator
* @param[out] xrp Result context
* @retval 0 OK
* @retval -1 Error
*/
static int
xp_numop(xp_ctx *xc1,
xp_ctx *xc2,
enum xp_op op,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr = NULL;
double n1;
double n2;
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_initial = xc1->xc_initial;
xr->xc_type = XT_NUMBER;
if (ctx2number(xc1, &n1) < 0)
goto done;
if (ctx2number(xc2, &n2) < 0)
goto done;
if (isnan(n1) || isnan(n2))
xr->xc_number = NAN;
else
switch (op){
case XO_DIV:
xr->xc_number = n1/n2;
break;
case XO_MOD:
xr->xc_number = ((int)n1)%((int)n2);
break;
case XO_ADD:
xr->xc_number = n1+n2;
break;
case XO_MULT:
xr->xc_number = n1*n2;
break;
case XO_SUB:
xr->xc_number = n1-n2;
break;
default:
clixon_err(OE_UNIX, errno, "Invalid operator %s in this context",
clicon_int2str(xpopmap,op));
goto done;
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr)
ctx_free(xr);
return retval;
}
/*! Get xml body value as cligen variable
*
* @param[in] x XML node (body and leaf/leaf-list)
* @param[out] cvp Pointer to cligen variable containing value of x body
* @retval 0 OK, cvp contains cv or NULL
* @retval -1 Error
* @note only applicable if x is body and has yang-spec and is leaf or leaf-list
* Move to clixon_xml.c?
* As a side-effect sets the cache.
* Clear cache with xml_cv_set(x, NULL)
* @see xml_cv_cache.c duplicated function
*/
static int
xml_cv_cache(cxobj *x,
cg_var **cvp)
{
int retval = -1;
cg_var *cv = NULL;
yang_stmt *y;
yang_stmt *yrestype;
enum cv_type cvtype;
int ret;
char *reason=NULL;
int options = 0;
uint8_t fraction = 0;
char *body;
if ((body = xml_body(x)) == NULL)
body="";
if ((cv = xml_cv(x)) != NULL)
goto ok;
if ((y = xml_spec(x)) == NULL){
clixon_err(OE_XML, EFAULT, "Yang binding missing for xml symbol %s, body:%s", xml_name(x), body);
goto done;
}
if (yang_type_get(y, NULL, &yrestype, &options, NULL, NULL, NULL, &fraction) < 0)
goto done;
yang2cv_type(yang_argument_get(yrestype), &cvtype);
if (cvtype==CGV_ERR){
clixon_err(OE_YANG, errno, "yang->cligen type %s mapping failed",
yang_argument_get(yrestype));
goto done;
}
if ((cv = cv_new(cvtype)) == NULL){
clixon_err(OE_YANG, errno, "cv_new");
goto done;
}
if (cvtype == CGV_DEC64)
cv_dec64_n_set(cv, fraction);
if ((ret = cv_parse1(body, cv, &reason)) < 0){
clixon_err(OE_YANG, errno, "cv_parse1");
goto done;
}
if (ret == 0){
clixon_err(OE_YANG, EINVAL, "cv parse error: %s\n", reason);
goto done;
}
if (xml_cv_set(x, cv) < 0)
goto done;
ok:
*cvp = cv;
cv = NULL;
retval = 0;
done:
if (reason)
free(reason);
if (cv)
cv_free(cv);
return retval;
}
/*! Given two XPath contexts, eval relational operations: <>=
*
* A RelationalExpr is evaluated by comparing the objects that result from
* evaluating the two operands.
* This is covered:
* (a) Both are INTs, BOOLs, STRINGs. Result type is boolean
* (b) Both are nodesets and one is empty. Result type is boolean.
* (c) One is nodeset and other is INT or STRING. Result type is nodeset
* (d) All others (eg two nodesets, BOOL+STRING) are not supported.
* Op is = EQ
* From XPath 1.0 standard, the evaluation has three variants:
* (1) comparisons that involve node-sets are defined in terms of comparisons that
* do not involve node-sets; this is defined uniformly for =, !=, <=, <, >= and >.
* (2) comparisons that do not involve node-sets are defined for = and !=.
* (3) comparisons that do not involve node-sets are defined for <=, <, >= and >.
* @param[in] xc1 Context of operand1
* @param[in] xc2 Context of operand2
* @param[in] op Relational operator
* @param[out] xrp Result context
* @retval 0 OK
* @retval -1 Error
*/
static int
xp_relop(xp_ctx *xc1,
xp_ctx *xc2,
enum xp_op op,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr = NULL;
xp_ctx *xc;
cxobj *x1;
cxobj *x2;
int i;
int j;
int b;
char *s1;
char *s2;
int reverse = 0;
double n1, n2;
char *xb;
cg_var *cv1, *cv2;
int ret;
if (xc1 == NULL || xc2 == NULL){
clixon_err(OE_UNIX, EINVAL, "xc1 or xc2 NULL");
goto done;
}
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_initial = xc1->xc_initial;
xr->xc_type = XT_BOOL;
if (xc1->xc_type == xc2->xc_type){ /* cases (2-3) above */
switch (xc1->xc_type){
case XT_NODESET:
/* If both are node-sets, then it is true iff the string value of one
node in the first node-set and one in the second node-set is true */
for (i=0; i<xc1->xc_size; i++){
/* node in nodeset */
if ((x1 = xc1->xc_nodeset[i]) == NULL ||
(s1 = xml_body(x1)) == NULL){
xr->xc_bool = 0;
goto ok;
}
for (j=0; j<xc2->xc_size; j++){
if ((x2 = xc2->xc_nodeset[j]) == NULL ||
(s2 = xml_body(x2)) == NULL){
xr->xc_bool = 0;
goto ok;
}
/* YANG bound, use cv evaluation, else strcmp */
if (xml_spec(x1) && xml_spec(x2)){
if (xml_cv_cache(x1, &cv1) < 0) /* error case */
goto done;
if (xml_cv_cache(x2, &cv2) < 0) /* error case */
goto done;
if (cv1 != NULL && cv2 != NULL)
ret = cv_cmp(cv1, cv2);
else if (cv1 == NULL && cv2 == NULL)
ret = 0;
else if (cv1 == NULL)
ret = -1;
else
ret = 1;
switch(op){
case XO_EQ:
xr->xc_bool = (ret == 0);
break;
case XO_NE:
xr->xc_bool = (ret != 0);
break;
case XO_GE:
xr->xc_bool = (ret >= 0);
break;
case XO_LE:
xr->xc_bool = (ret <= 0);
break;
case XO_LT:
xr->xc_bool = (ret < 0);
break;
case XO_GT:
xr->xc_bool = (ret > 0);
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op));
goto done;
break;
}
}
else{
switch(op){
case XO_EQ:
xr->xc_bool = (strcmp(s1, s2)==0);
break;
case XO_NE:
xr->xc_bool = (strcmp(s1, s2)!=0);
break;
case XO_GE:
xr->xc_bool = (strcmp(s1, s2)>=0);
break;
case XO_LE:
xr->xc_bool = (strcmp(s1, s2)<=0);
break;
case XO_LT:
xr->xc_bool = (strcmp(s1, s2)<0);
break;
case XO_GT:
xr->xc_bool = (strcmp(s1, s2)>0);
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op));
goto done;
break;
}
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
break;
case XT_BOOL:
xr->xc_bool = (xc1->xc_bool == xc2->xc_bool);
break;
case XT_NUMBER:
switch(op){
case XO_EQ:
xr->xc_bool = (xc1->xc_number == xc2->xc_number);
break;
case XO_NE:
xr->xc_bool = (xc1->xc_number != xc2->xc_number);
break;
case XO_GE:
xr->xc_bool = (xc1->xc_number >= xc2->xc_number);
break;
case XO_LE:
xr->xc_bool = (xc1->xc_number <= xc2->xc_number);
break;
case XO_LT:
xr->xc_bool = (xc1->xc_number < xc2->xc_number);
break;
case XO_GT:
xr->xc_bool = (xc1->xc_number > xc2->xc_number);
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op));
goto done;
break;
}
break;
case XT_STRING:
xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0);
break;
} /* switch xc1 */
}
else if (xc1->xc_type != XT_NODESET &&
xc2->xc_type != XT_NODESET){
clixon_err(OE_XML, 0, "Mixed types not supported, %d %d", xc1->xc_type, xc2->xc_type);
goto done;
}
else{ /* one is nodeset, ie (1) above */
if (xc2->xc_type == XT_NODESET){
xc = xc2;
xc2 = xc1;
xc1 = xc;
reverse++; /* reverse */
}
/* xc1 is nodeset
* xc2 is something else */
switch (xc2->xc_type){
case XT_BOOL:
/* comparison on the boolean and the result of converting the
node-set to a boolean using the boolean function is true. */
b = ctx2boolean(xc1);
switch(op){
case XO_EQ:
xr->xc_bool = (b == xc2->xc_bool);
break;
case XO_NE:
xr->xc_bool = (b != xc2->xc_bool);
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset and bool", clicon_int2str(xpopmap,op));
goto done;
break;
} /* switch op */
break;
case XT_STRING:
/* If one object to be compared is a node-set and the
other is a string, then the comparison will be true if and only
if there is a node in the node-set such that the result of
performing the comparison on the string-value of the node and
the other string is true.*/
s2 = xc2->xc_string;
for (i=0; i<xc1->xc_size; i++){
/* node in nodeset */
if ((x1 = xc1->xc_nodeset[i]) == NULL)
s1 = NULL;
else
s1 = xml_body(x1);
switch(op){
case XO_EQ:
if (s1 == NULL && s2 == NULL)
xr->xc_bool = 1;
if (s1 == NULL){
if (strlen(s2) == 0)
xr->xc_bool = 1;
else
xr->xc_bool = 0;
}
else if (s2 == NULL){
if (strlen(s1) == 0)
xr->xc_bool = 1;
else
xr->xc_bool = 0;
}
else
xr->xc_bool = (strcmp(s1, s2)==0);
break;
case XO_NE:
if (s1 == NULL || s2 == NULL)
xr->xc_bool = !(s1==NULL && s2 == NULL);
else
xr->xc_bool = (strcmp(s1, s2));
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op));
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
break;
case XT_NUMBER:
for (i=0; i<xc1->xc_size; i++){
/* node in nodeset */
if ((x1 = xc1->xc_nodeset[i]) == NULL ||
(xb = xml_body(x1)) == NULL ||
sscanf(xb, "%lf", &n1) != 1)
n1 = NAN;
n2 = xc2->xc_number;
switch(op){
case XO_EQ:
xr->xc_bool = (n1 == n2);
break;
case XO_NE:
xr->xc_bool = (n1 != n2);
break;
case XO_GE:
xr->xc_bool = reverse?(n2 >= n1):(n1 >= n2);
break;
case XO_LE:
xr->xc_bool = reverse?(n2 <= n1):(n1 <= n2);
break;
case XO_LT:
xr->xc_bool = reverse?(n2 < n1):(n1 < n2);
break;
case XO_GT:
xr->xc_bool = reverse?(n2 > n1):(n1 > n2);
break;
default:
clixon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op));
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
break;
default:
clixon_err(OE_XML, 0, "Type %d not supported", xc2->xc_type);
} /* switch type */
}
ok:
/* Just ensure bool is 0 or 1 */
if (xr->xc_type == XT_BOOL && xr->xc_bool != 0)
xr->xc_bool = 1;
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr)
ctx_free(xr);
return retval;
}
/*! Given two XPath contexts, eval union operation
*
* Both operands must be nodesets, otherwise empty nodeset is returned
* @param[in] xc1 Context of operand1
* @param[in] xc2 Context of operand2
* @param[in] op Relational operator
* @param[out] xrp Result context
* @retval 0 OK
* @retval -1 Error
*/
static int
xp_union(xp_ctx *xc1,
xp_ctx *xc2,
enum xp_op op,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr = NULL;
int i;
if (op != XO_UNION){
clixon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context",
__FUNCTION__, clicon_int2str(xpopmap,op));
goto done;
}
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_initial = xc1->xc_initial;
xr->xc_type = XT_NODESET;
for (i=0; i<xc1->xc_size; i++)
if (cxvec_append(xc1->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0)
goto done;
for (i=0; i<xc2->xc_size; i++){
if (cxvec_append(xc2->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0)
goto done;
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr)
ctx_free(xr);
return retval;
}
/*! Evaluate an XPath on an XML tree
*
* The initial sequence of steps selects a set of nodes relative to a context node.
* 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[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
*/
int
xp_eval(xp_ctx *xc,
xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
cxobj *x;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xr2 = NULL;
int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */
// ctx_print(stderr, xc, xpath_tree_int2str(xs->xs_type));
/* Pre-actions before check first child c0
*/
switch (xs->xs_type){
case XP_RELLOCPATH:
if (xs->xs_int == A_DESCENDANT_OR_SELF)
xc->xc_descendant = 1; /* XXX need to set to 0 in sub */
break;
case XP_ABSPATH:
/* Set context node to top node, and nodeset to that node only */
x = xc->xc_node;
#ifdef XML_PARENT_CANDIDATE
while (xml_parent(x) != NULL || xml_parent_candidate(x) != NULL)
x = xml_parent(x)?xml_parent(x):xml_parent_candidate(x);
#else
while (xml_parent(x) != NULL)
x = xml_parent(x);
#endif
xc->xc_node = x;
xc->xc_nodeset[0] = x;
xc->xc_size=1;
/* // is short for /descendant-or-self::node()/ */
if (xs->xs_int == A_DESCENDANT_OR_SELF)
xc->xc_descendant = 1; /* XXX need to set to 0 in sub */
break;
case XP_STEP: /* XP_NODE is first argument -not called explicitly */
if (xp_eval_step(xc, xs, nsc, localonly, xrp) < 0)
goto done;
goto ok; /* Skip generic child traverse */
break;
case XP_PRED:
if (xp_eval_predicate(xc, xs, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XP_PRIME_FN:
if (xs->xs_s0){
switch (xs->xs_int){
case XPATHFN_CURRENT:
if (xp_function_current(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_DEREF:
if (xp_function_deref(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_DERIVED_FROM:
if (xp_function_derived_from(xc, xs->xs_c0, nsc, localonly, 0, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_DERIVED_FROM_OR_SELF:
if (xp_function_derived_from(xc, xs->xs_c0, nsc, localonly, 1, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_BIT_IS_SET:
if (xp_function_bit_is_set(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_POSITION:
if (xp_function_position(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_COUNT:
if (xp_function_count(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_NAME:
if (xp_function_name(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_CONTAINS:
if (xp_function_contains(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_BOOLEAN:
if (xp_function_boolean(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_NOT:
if (xp_function_not(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_TRUE:
if (xp_function_true(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_FALSE:
if (xp_function_false(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
default:
clixon_err(OE_XML, EFAULT, "XPath function not implemented: %s", xs->xs_s0);
goto done;
break;
}
}
break;
default:
break;
}
/* Eval first child c0
*/
if (xs->xs_c0){
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
}
/* Actions between first and second child
*/
switch (xs->xs_type){
case XP_EXP:
break;
case XP_AND:
break;
case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */
break;
case XP_ADD: /* combine mult and add ops */
break;
case XP_UNION:
break;
case XP_PATHEXPR:
if (xs->xs_c1)
use_xr0++;
break;
case XP_FILTEREXPR:
break;
case XP_LOCPATH:
break;
case XP_ABSPATH:
use_xr0++;
/* Special case, no c0 or c1, single "/" */
if (xs->xs_c0 == NULL){
if ((xr0 = malloc(sizeof(*xr0))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr0, 0, sizeof(*xr0));
xr0->xc_initial = xc->xc_initial;
xr0->xc_type = XT_NODESET;
x = NULL;
while ((x = xml_child_each(xc->xc_node, x, CX_ELMNT)) != NULL) {
if (cxvec_append(x, &xr0->xc_nodeset, &xr0->xc_size) < 0)
goto done;
}
}
break;
case XP_RELLOCPATH:
use_xr0++;
if (xs->xs_int == A_DESCENDANT_OR_SELF){
if (use_xr0)
xr0->xc_descendant = 1; /* XXX need to set to 0 in sub */
else
xc->xc_descendant = 1; /* XXX need to set to 0 in sub */
}
break;
case XP_NODE:
break;
case XP_NODE_FN:
break;
case XP_PRI0:
break;
case XP_PRIME_NR: /* primaryexpr -> [<number>] */
if ((xr0 = malloc(sizeof(*xr0))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr0, 0, sizeof(*xr0));
xr0->xc_initial = xc->xc_initial;
xr0->xc_type = XT_NUMBER;
xr0->xc_number = xs->xs_double;
break;
case XP_PRIME_STR:
if ((xr0 = malloc(sizeof(*xr0))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr0, 0, sizeof(*xr0));
xr0->xc_initial = xc->xc_initial;
xr0->xc_type = XT_STRING;
xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL;
break;
default:
break;
}
/* Eval second child c1
* Note, some operators like locationpath, need transitive context (use_xr0)
*/
if (xs->xs_c1){
if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, localonly, &xr1) < 0)
goto done;
/* Actions after second child
*/
switch (xs->xs_type){
case XP_AND: /* combine and and or ops */
if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0)
goto done;
break;
case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */
if (xp_relop(xr0, xr1, xs->xs_int, &xr2) < 0)
goto done;
break;
case XP_ADD: /* combine mult and add ops */
if (xp_numop(xr0, xr1, xs->xs_int, &xr2) < 0)
goto done;
break;
case XP_UNION: /* combine and and or ops */
if (xp_union(xr0, xr1, xs->xs_int, &xr2) < 0)
goto done;
default:
break;
}
}
if (use_xr0)
xr0->xc_descendant = 0;
else
xc->xc_descendant = 0;
if (xr0 == NULL && xr1 == NULL && xr2 == NULL){
clixon_err(OE_XML, EFAULT, "Internal error: no result produced");
goto done;
}
if (xr2){
*xrp = xr2;
xr2 = NULL;
}
else if (xr1){
*xrp = xr1;
xr1 = NULL;
}
else
if (xr0){
*xrp = xr0;
xr0 = NULL;
}
ok:
// ctx_print(stderr, *xrp, xpath_tree_int2str(xs->xs_type));
retval = 0;
done:
if (xr2)
ctx_free(xr2);
if (xr1)
ctx_free(xr1);
if (xr0)
ctx_free(xr0);
return retval;
} /* xp_eval */