1415 lines
45 KiB
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 */
|
|
|
|
|