1351 lines
34 KiB
C
1351 lines
34 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
|
|
|
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
|
|
*/
|
|
#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 <fnmatch.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <syslog.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#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_xpath_parse.h"
|
|
#include "clixon_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
|
|
/*
|
|
* Variables
|
|
*/
|
|
/* 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}
|
|
};
|
|
|
|
static const map_str2int xpath_tree_map[] = {
|
|
{"expr", XP_EXP},
|
|
{"andexpr", XP_AND},
|
|
{"relexpr", XP_RELEX},
|
|
{"addexpr", XP_ADD},
|
|
{"unionexpr", XP_UNION},
|
|
{"pathexpr", XP_PATHEXPR},
|
|
{"locationpath", XP_LOCPATH},
|
|
{"abslocpath", XP_ABSPATH},
|
|
{"rellocpath", XP_RELLOCPATH},
|
|
{"step", XP_STEP},
|
|
{"nodetest", XP_NODE},
|
|
{"nodetest fn", XP_NODE_FN},
|
|
{"predicates", XP_PRED},
|
|
{"primaryexpr", XP_PRI0},
|
|
{"primaryexpr nr", XP_PRIME_NR},
|
|
{"primaryexpr str", XP_PRIME_STR},
|
|
{"primaryexpr fn", XP_PRIME_FN},
|
|
{NULL, -1}
|
|
};
|
|
|
|
/*
|
|
* XPATH parse tree type
|
|
*/
|
|
/*! Print XPATH parse tree */
|
|
static int
|
|
xpath_tree_print0(cbuf *cb,
|
|
xpath_tree *xs,
|
|
int level)
|
|
{
|
|
cprintf(cb, "%*s%s:", level*3, "", clicon_int2str(xpath_tree_map, xs->xs_type));
|
|
if (xs->xs_s0){
|
|
cprintf(cb, "\"%s\" ", xs->xs_s0);
|
|
if (xs->xs_s1)
|
|
cprintf(cb,"\"%s\" ", xs->xs_s1);
|
|
}
|
|
cprintf(cb, "\n");
|
|
if (xs->xs_c0)
|
|
xpath_tree_print0(cb, xs->xs_c0,level+1);
|
|
if (xs->xs_c1)
|
|
xpath_tree_print0(cb, xs->xs_c1, level+1);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xpath_tree_print(cbuf *cb,
|
|
xpath_tree *xs)
|
|
{
|
|
xpath_tree_print0(cb, xs, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xpath_tree_free(
|
|
xpath_tree *xs)
|
|
{
|
|
if (xs->xs_s0)
|
|
free(xs->xs_s0);
|
|
if (xs->xs_s1)
|
|
free(xs->xs_s1);
|
|
if (xs->xs_c0)
|
|
xpath_tree_free(xs->xs_c0);
|
|
if (xs->xs_c1)
|
|
xpath_tree_free(xs->xs_c1);
|
|
free(xs);
|
|
return 0;
|
|
}
|
|
|
|
/*! Make a nodetest
|
|
* @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN
|
|
* @retval 0 Match
|
|
* @retval 1 No 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)
|
|
{
|
|
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;
|
|
}
|
|
else if (xs->xs_type == XP_NODE_FN){
|
|
fn = xs->xs_s0;
|
|
if (strcmp(fn, "node")==0)
|
|
return 1;
|
|
else if (strcmp(fn, "text")==0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
nodetest_recursive(cxobj *xn,
|
|
xpath_tree *nodetest,
|
|
int node_type,
|
|
uint16_t flags,
|
|
cxobj ***vec0,
|
|
size_t *vec0len)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xsub;
|
|
cxobj **vec = *vec0;
|
|
size_t veclen = *vec0len;
|
|
|
|
xsub = NULL;
|
|
while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) {
|
|
if (nodetest_eval(xsub, nodetest) == 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)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
*vec0 = vec;
|
|
*vec0len = veclen;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp);
|
|
|
|
/*! Evaluate xpath step rule of an XML tree
|
|
*
|
|
* @param[in] xc0 Incoming context
|
|
* @param[in] xs XPATH node tree
|
|
* @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])
|
|
* 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,
|
|
xp_ctx **xrp)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
cxobj *x;
|
|
cxobj *xv;
|
|
cxobj *xp;
|
|
cxobj **vec = NULL;
|
|
size_t veclen = 0;
|
|
xpath_tree *nodetest = xs->xs_c0;
|
|
xp_ctx *xc = NULL;
|
|
|
|
/* 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, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
xc->xc_descendant = 0;
|
|
}
|
|
else{
|
|
if (nodetest->xs_type==XP_NODE_FN &&
|
|
nodetest->xs_s0 &&
|
|
strcmp(nodetest->xs_s0,"current")==0){
|
|
if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
else for (i=0; i<xc->xc_size; i++){
|
|
xv = xc->xc_nodeset[i];
|
|
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 (cxvec_append(x, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
ctx_nodeset_replace(xc, vec, veclen);
|
|
break;
|
|
case A_DESCENDANT:
|
|
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)
|
|
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)
|
|
if (cxvec_append(xp, &xc->xc_nodeset, &xc->xc_size) < 0)
|
|
goto done;
|
|
}
|
|
if (vec){
|
|
free(vec);
|
|
vec = NULL;
|
|
}
|
|
break;
|
|
case A_PRECEEDING:
|
|
break;
|
|
case A_PRECEEDING_SIBLING:
|
|
break;
|
|
case A_SELF:
|
|
break;
|
|
default:
|
|
clicon_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, xrp) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
*xrp = xc;
|
|
xc = NULL;
|
|
}
|
|
assert(*xrp);
|
|
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[out] xrp Resulting context
|
|
*
|
|
* 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,
|
|
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;
|
|
|
|
if (xs->xs_c0 == NULL){ /* empty */
|
|
if ((xr0 = ctx_dup(xc)) == NULL)
|
|
goto done;
|
|
}
|
|
else{ /* eval previous predicates */
|
|
if (xp_eval(xc, xs->xs_c0, &xr0) < 0)
|
|
goto done;
|
|
}
|
|
if (xs->xs_c1){
|
|
/* Loop over each node in the nodeset */
|
|
assert (xr0->xc_type == XT_NODESET);
|
|
if ((xr1 = malloc(sizeof(*xr1))) == NULL){
|
|
clicon_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){
|
|
clicon_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;
|
|
/* 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, &xrc) < 0)
|
|
goto done;
|
|
if (xcc)
|
|
ctx_free(xcc);
|
|
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);
|
|
}
|
|
|
|
}
|
|
assert(xr0||xr1);
|
|
if (xr1){
|
|
*xrp = xr1;
|
|
xr1 = NULL;
|
|
}
|
|
else
|
|
if (xr0){
|
|
*xrp = xr0;
|
|
xr0 = NULL;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
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){
|
|
clicon_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:
|
|
clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context",
|
|
__FUNCTION__, clicon_int2str(xpopmap,op));
|
|
goto done;
|
|
}
|
|
*xrp = xr;
|
|
retval = 0;
|
|
done:
|
|
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){
|
|
clicon_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:
|
|
clicon_err(OE_UNIX, errno, "Invalid operator %s in this context",
|
|
clicon_int2str(xpopmap,op));
|
|
goto done;
|
|
}
|
|
*xrp = xr;
|
|
retval = 0;
|
|
done:
|
|
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 *x;
|
|
int i;
|
|
int j;
|
|
int b;
|
|
char *s1;
|
|
char *s2;
|
|
int reverse = 0;
|
|
double n1, n2;
|
|
|
|
if ((xr = malloc(sizeof(*xr))) == NULL){
|
|
clicon_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++){
|
|
if ((s1 = xml_body(xc1->xc_nodeset[i])) == NULL){
|
|
xr->xc_bool = 0;
|
|
goto ok;
|
|
}
|
|
for (j=0; j<xc2->xc_size; j++){
|
|
if ((s2 = xml_body(xc2->xc_nodeset[j])) == NULL){
|
|
xr->xc_bool = 0;
|
|
goto ok;
|
|
}
|
|
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:
|
|
clicon_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:
|
|
xr->xc_bool = (xc1->xc_number == xc2->xc_number);
|
|
break;
|
|
case XT_STRING:
|
|
xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0);
|
|
break;
|
|
}
|
|
}
|
|
else if (xc1->xc_type != XT_NODESET &&
|
|
xc2->xc_type != XT_NODESET){
|
|
clicon_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:
|
|
clicon_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++){
|
|
x = xc1->xc_nodeset[i]; /* node in nodeset */
|
|
s1 = xml_body(x);
|
|
switch(op){
|
|
case XO_EQ:
|
|
if (s1==NULL || s2==NULL){
|
|
clicon_err(OE_XML, EINVAL, "Malformed xpath: empty string");
|
|
goto done;
|
|
}
|
|
xr->xc_bool = (strcmp(s1, s2)==0);
|
|
break;
|
|
case XO_NE:
|
|
if (s1==NULL || s2==NULL){
|
|
clicon_err(OE_XML, EINVAL, "Malformed xpath: empty string");
|
|
goto done;
|
|
}
|
|
xr->xc_bool = (strcmp(s1, s2));
|
|
break;
|
|
default:
|
|
clicon_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++){
|
|
x = xc1->xc_nodeset[i]; /* node in nodeset */
|
|
if (sscanf(xml_body(x), "%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:
|
|
clicon_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:
|
|
clicon_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;
|
|
retval = 0;
|
|
done:
|
|
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){
|
|
clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context",
|
|
__FUNCTION__, clicon_int2str(xpopmap,op));
|
|
goto done;
|
|
}
|
|
if ((xr = malloc(sizeof(*xr))) == NULL){
|
|
clicon_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;
|
|
retval = 0;
|
|
done:
|
|
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[out] xrp Resulting context
|
|
*/
|
|
static int
|
|
xp_eval(xp_ctx *xc,
|
|
xpath_tree *xs,
|
|
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 */
|
|
|
|
if (debug>1){
|
|
cbuf *cb;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
|
|
clicon_debug(2, "%s", cbuf_get(cb));
|
|
cbuf_free(cb);
|
|
}
|
|
/* 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;
|
|
while (xml_parent(x) != NULL)
|
|
x = xml_parent(x);
|
|
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, xrp) < 0)
|
|
goto done;
|
|
goto ok;
|
|
break;
|
|
case XP_PRED:
|
|
if (xp_eval_predicate(xc, xs, xrp) < 0)
|
|
goto done;
|
|
goto ok;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* Eval first child c0
|
|
*/
|
|
if (xs->xs_c0){
|
|
if (xp_eval(xc, xs->xs_c0, &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:
|
|
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){
|
|
clicon_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)
|
|
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){
|
|
clicon_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){
|
|
clicon_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;
|
|
case XP_PRIME_FN:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* Eval second child c0
|
|
* 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)
|
|
goto done;
|
|
/* Actions after second child
|
|
*/
|
|
if (xs->xs_c1)
|
|
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;
|
|
}
|
|
xc->xc_descendant = 0;
|
|
assert(xr0||xr1||xr2);
|
|
if (xr2){
|
|
*xrp = xr2;
|
|
xr2 = NULL;
|
|
}
|
|
else if (xr1){
|
|
*xrp = xr1;
|
|
xr1 = NULL;
|
|
}
|
|
else
|
|
if (xr0){
|
|
*xrp = xr0;
|
|
xr0 = NULL;
|
|
}
|
|
ok:
|
|
if (debug){
|
|
cbuf *cb;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
|
|
clicon_debug(2, "%s", cbuf_get(cb));
|
|
cbuf_free(cb);
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xr2)
|
|
ctx_free(xr2);
|
|
if (xr1)
|
|
ctx_free(xr1);
|
|
if (xr0)
|
|
ctx_free(xr0);
|
|
return retval;
|
|
}
|
|
|
|
/*! Given XML tree and xpath, returns 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[out] xrp Return XPATH context
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* xp_ctx *xc = NULL;
|
|
* if (xpath_vec_ctx(x, xpath, &xc) < 0)
|
|
* err;
|
|
* if (xc)
|
|
* ctx_free(xc);
|
|
* @endcode
|
|
*/
|
|
int
|
|
xpath_vec_ctx(cxobj *xcur,
|
|
char *xpath,
|
|
xp_ctx **xrp)
|
|
{
|
|
int retval = -1;
|
|
xp_ctx xc = {0,};
|
|
struct clicon_xpath_yacc_arg xy = {0,};
|
|
|
|
xy.xy_parse_string = xpath;
|
|
xy.xy_name = "xpath parser";
|
|
xy.xy_linenum = 1;
|
|
if (xpath_scan_init(&xy) < 0)
|
|
goto done;
|
|
if (xpath_parse_init(&xy) < 0)
|
|
goto done;
|
|
clicon_debug(2,"%s",__FUNCTION__);
|
|
if (clixon_xpath_parseparse(&xy) != 0) { /* yacc returns 1 on error */
|
|
clicon_log(LOG_NOTICE, "XPATH error: on line %d", xy.xy_linenum);
|
|
if (clicon_errno == 0)
|
|
clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)");
|
|
goto done;
|
|
}
|
|
if (debug > 1){
|
|
cbuf *cb = cbuf_new();
|
|
xpath_tree_print(cb, xy.xy_top);
|
|
clicon_debug(2, "xpath parse tree:\n%s", cbuf_get(cb));
|
|
cbuf_free(cb);
|
|
}
|
|
xc.xc_type = XT_NODESET;
|
|
xc.xc_node = 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)
|
|
goto done;
|
|
if (xc.xc_nodeset){
|
|
free(xc.xc_nodeset);
|
|
xc.xc_nodeset = NULL;
|
|
}
|
|
/* done: */
|
|
xpath_parse_exit(&xy);
|
|
xpath_scan_exit(&xy);
|
|
retval = 0;
|
|
done:
|
|
if (xy.xy_top)
|
|
xpath_tree_free(xy.xy_top);
|
|
return retval;
|
|
}
|
|
|
|
/*! 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
|
|
*
|
|
* @code
|
|
* cxobj *x;
|
|
* if ((x = xpath_first(xtop, "//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.
|
|
*/
|
|
cxobj *
|
|
xpath_first(cxobj *xcur,
|
|
char *format,
|
|
...)
|
|
{
|
|
cxobj *cx = NULL;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, format);
|
|
len = vsnprintf(NULL, 0, format, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, format);
|
|
if (vsnprintf(xpath, len+1, format, ap) < 0){
|
|
clicon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
|
|
goto done;
|
|
|
|
if (xr && xr->xc_type == XT_NODESET && xr->xc_size)
|
|
cx = xr->xc_nodeset[0];
|
|
done:
|
|
if (xr)
|
|
ctx_free(xr);
|
|
if (xpath)
|
|
free(xpath);
|
|
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[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
|
|
* cxobj **vec;
|
|
* size_t veclen;
|
|
* if (xpath_vec(xcur, "//symbol/foo", &vec, &veclen) < 0)
|
|
* goto err;
|
|
* for (i=0; i<veclen; i++){
|
|
* xn = vec[i];
|
|
* ...
|
|
* }
|
|
* free(vec);
|
|
* @endcode
|
|
*/
|
|
int
|
|
xpath_vec(cxobj *xcur,
|
|
char *format,
|
|
cxobj ***vec,
|
|
size_t *veclen,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, veclen);
|
|
len = vsnprintf(NULL, 0, format, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, veclen);
|
|
if (vsnprintf(xpath, len+1, format, ap) < 0){
|
|
clicon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
*vec=NULL;
|
|
*veclen = 0;
|
|
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
|
|
goto done;
|
|
if (xr && xr->xc_type == XT_NODESET){
|
|
*vec = xr->xc_nodeset;
|
|
xr->xc_nodeset = NULL;
|
|
*veclen = xr->xc_size;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xr)
|
|
ctx_free(xr);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/* 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] 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
|
|
* @retval 0 OK
|
|
* @retval -1 error.
|
|
* @code
|
|
* cxobj **vec;
|
|
* size_t veclen;
|
|
* if (xpath_vec_flag(xcur, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0)
|
|
* goto err;
|
|
* for (i=0; i<veclen; i++){
|
|
* xn = vec[i];
|
|
* ...
|
|
* }
|
|
* free(vec);
|
|
* @endcode
|
|
* @Note that although the returned vector must be freed after use, the returned xml
|
|
* trees need not be.
|
|
* @see also xpath_vec This is a specialized version.
|
|
*/
|
|
int
|
|
xpath_vec_flag(cxobj *xcur,
|
|
char *format,
|
|
uint16_t flags,
|
|
cxobj ***vec,
|
|
size_t *veclen,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
int i;
|
|
cxobj *x;
|
|
|
|
va_start(ap, veclen);
|
|
len = vsnprintf(NULL, 0, format, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, veclen);
|
|
if (vsnprintf(xpath, len+1, format, ap) < 0){
|
|
clicon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
*vec=NULL;
|
|
*veclen = 0;
|
|
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
|
|
goto done;
|
|
if (xr && xr->xc_type == XT_NODESET){
|
|
for (i=0; i<xr->xc_size; i++){
|
|
x = xr->xc_nodeset[i];
|
|
if (flags==0x0 || xml_flag(x, flags))
|
|
if (cxvec_append(x, vec, veclen) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xr)
|
|
ctx_free(xr);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*! 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] 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,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, format);
|
|
len = vsnprintf(NULL, 0, format, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, format);
|
|
if (vsnprintf(xpath, len+1, format, ap) < 0){
|
|
clicon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
|
|
goto done;
|
|
if (xr)
|
|
retval = ctx2boolean(xr);
|
|
done:
|
|
if (xr)
|
|
ctx_free(xr);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|