1115 lines
28 KiB
C
1115 lines
28 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2017 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 *****
|
|
|
|
* Limited XML XPATH and XSLT functions.
|
|
* NOTE: there is a main function at the end of this file where you can test out
|
|
* different xpath expressions.
|
|
* Look at the end of the file for a test unit program
|
|
*/
|
|
/*
|
|
https://www.w3.org/TR/xpath/
|
|
|
|
Implementation of a limited xslt xpath syntax. Some examples. Given the following
|
|
xml tree:
|
|
<aaa>
|
|
<bbb x="hello"><ccc>42</ccc></bbb>
|
|
<bbb x="bye"><ccc>99</ccc></bbb>
|
|
<ddd><ccc>22</ccc></ddd>
|
|
</aaa>
|
|
|
|
With the following xpath examples. There are some diffs and many limitations compared
|
|
to the xml standards:
|
|
/ whole tree <aaa>...</aaa>
|
|
/bbb
|
|
/aaa/bbb <bbb x="hello"><ccc>42</ccc></bbb>
|
|
<bbb x="bye"><ccc>99</ccc></bbb>
|
|
//bbb as above
|
|
//b?b as above
|
|
//b\* as above
|
|
//b\*\/ccc <ccc>42</ccc>
|
|
<ccc>99</ccc>
|
|
//\*\/ccc <ccc>42</ccc>
|
|
<ccc>99</ccc>
|
|
<ccc>22</ccc>
|
|
-- //bbb@x x="hello"
|
|
//bbb[@x] <bbb x="hello"><ccc>42</ccc></bbb>
|
|
<bbb x="bye"><ccc>99</ccc></bbb>
|
|
//bbb[@x=hello] <bbb x="hello"><ccc>42</ccc></bbb>
|
|
//bbb[@x="hello"] as above
|
|
//bbb[0] <bbb x="hello"><ccc>42</ccc></bbb>
|
|
//bbb[ccc=99] <bbb x="bye"><ccc>99</ccc></bbb>
|
|
--- //\*\/[ccc=99] same as above
|
|
'//bbb | //ddd' <bbb><ccc>42</ccc></bbb>
|
|
<bbb x="hello"><ccc>99</ccc></bbb>
|
|
<ddd><ccc>22</ccc></ddd> (NB spaces)
|
|
etc
|
|
For xpath v1.0 see http://www.w3.org/TR/xpath/
|
|
record[name=c][time=d]
|
|
in
|
|
<record>
|
|
<name>c</name>
|
|
<time>d</time>
|
|
<userid>45654df4-2292-45d3-9ca5-ee72452568a8</userid>
|
|
</record>
|
|
|
|
|
|
*/
|
|
#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>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_xsl.h"
|
|
|
|
/* Constants */
|
|
#define XPATH_VEC_START 128
|
|
|
|
|
|
/*
|
|
* Types
|
|
*/
|
|
struct searchvec{
|
|
cxobj **sv_v0; /* here is result */
|
|
int sv_v0len;
|
|
cxobj **sv_v1; /* this is tmp storage */
|
|
int sv_v1len;
|
|
int sv_max;
|
|
};
|
|
typedef struct searchvec searchvec;
|
|
|
|
/* Local types
|
|
*/
|
|
enum axis_type{
|
|
A_SELF,
|
|
A_CHILD,
|
|
A_PARENT,
|
|
A_ROOT,
|
|
A_ANCESTOR,
|
|
A_DESCENDANT_OR_SELF, /* actually descendant-or-self */
|
|
};
|
|
|
|
/* Mapping between axis type string <--> int */
|
|
static const map_str2int axismap[] = {
|
|
{"self", A_SELF},
|
|
{"child", A_CHILD},
|
|
{"parent", A_PARENT},
|
|
{"root", A_ROOT},
|
|
{"ancestor", A_ANCESTOR},
|
|
{"descendant-or-self", A_DESCENDANT_OR_SELF},
|
|
{NULL, -1}
|
|
};
|
|
|
|
struct xpath_predicate{
|
|
struct xpath_predicate *xp_next;
|
|
char *xp_expr;
|
|
};
|
|
|
|
struct xpath_element{
|
|
struct xpath_element *xe_next;
|
|
enum axis_type xe_type;
|
|
char *xe_prefix; /* eg for namespaces */
|
|
char *xe_str; /* eg for child */
|
|
struct xpath_predicate *xe_predicate; /* eg within [] */
|
|
};
|
|
|
|
static int xpath_split(char *xpathstr, char **pathexpr);
|
|
|
|
static int
|
|
xpath_print(FILE *f, struct xpath_element *xplist)
|
|
{
|
|
struct xpath_element *xe;
|
|
struct xpath_predicate *xp;
|
|
|
|
for (xe=xplist; xe; xe=xe->xe_next){
|
|
fprintf(f, "\t:%s %s ", clicon_int2str(axismap, xe->xe_type),
|
|
xe->xe_str?xe->xe_str:"");
|
|
for (xp=xe->xe_predicate; xp; xp=xp->xp_next)
|
|
fprintf(f, "[%s]", xp->xp_expr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xpath_parse_predicate(struct xpath_element *xe,
|
|
char *pred)
|
|
{
|
|
int retval = -1;
|
|
struct xpath_predicate *xp;
|
|
char *s;
|
|
int i;
|
|
int len;
|
|
|
|
len = strlen(pred);
|
|
for (i=len-1; i>=0; i--){ /* -1 since we search for ][ */
|
|
s = &pred[i];
|
|
if (i==0 ||
|
|
(*(s)==']' && *(s+1)=='[')){
|
|
if (i) {
|
|
*(s)= '\0';
|
|
s += 2;
|
|
}
|
|
if ((xp = malloc(sizeof(*xp))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
memset(xp, 0, sizeof(*xp));
|
|
if ((xp->xp_expr = strdup(s)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
xp->xp_next = xe->xe_predicate;
|
|
xe->xe_predicate = xp;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
xpath_element_new(enum axis_type atype,
|
|
char *str,
|
|
struct xpath_element ***xpnext)
|
|
{
|
|
int retval = -1;
|
|
struct xpath_element *xe;
|
|
char *str1 = NULL;
|
|
char *pred;
|
|
char *local;
|
|
|
|
if ((xe = malloc(sizeof(*xe))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
memset(xe, 0, sizeof(*xe));
|
|
xe->xe_type = atype;
|
|
if (str){
|
|
if ((str1 = strdup(str)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
if (xpath_split(str1, &pred) < 0) /* Can be more predicates */
|
|
goto done;
|
|
if (strlen(str1)){
|
|
/* Split into prefix and localname */
|
|
if ((local = index(str1, ':')) != NULL){
|
|
*local = '\0';
|
|
local++;
|
|
if ((xe->xe_prefix = strdup(str1)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
}
|
|
else
|
|
local = str1;
|
|
if ((xe->xe_str = strdup(local)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if ((xe->xe_str = strdup("*")) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
}
|
|
if (pred && strlen(pred)){
|
|
if (xpath_parse_predicate(xe, pred) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
(**xpnext) = xe;
|
|
*xpnext = &xe->xe_next;
|
|
retval = 0;
|
|
done:
|
|
if (str1)
|
|
free(str1);
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
xpath_element_free(struct xpath_element *xe)
|
|
{
|
|
struct xpath_predicate *xp;
|
|
|
|
if (xe->xe_str)
|
|
free(xe->xe_str);
|
|
if (xe->xe_prefix)
|
|
free(xe->xe_prefix);
|
|
while ((xp = xe->xe_predicate) != NULL){
|
|
xe->xe_predicate = xp->xp_next;
|
|
if (xp->xp_expr)
|
|
free(xp->xp_expr);
|
|
free(xp);
|
|
}
|
|
free(xe);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xpath_free(struct xpath_element *xplist)
|
|
{
|
|
struct xpath_element *xe, *xe_next;
|
|
|
|
for (xe=xplist; xe; xe=xe_next){
|
|
xe_next = xe->xe_next;
|
|
xpath_element_free(xe);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* // is short for /descendant-or-self::node()/
|
|
*/
|
|
static int
|
|
xpath_parse(char *xpath,
|
|
struct xpath_element **xplist0)
|
|
{
|
|
int retval = -1;
|
|
int nvec = 0;
|
|
char *s;
|
|
char *s0;
|
|
int i;
|
|
struct xpath_element *xplist = NULL;
|
|
struct xpath_element **xpnext = &xplist;
|
|
int esc = 0;
|
|
|
|
if ((s0 = strdup(xpath)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
s = s0;
|
|
if (strlen(s))
|
|
nvec = 1;
|
|
/* Chop up LocationPath in Steps delimited by '/' (unless [] predicate)
|
|
* Eg, "/a/b[/c]/d" -> "a" "b[/c]" "d"
|
|
*/
|
|
esc = 0;
|
|
while (*s != '\0'){
|
|
switch (*s){
|
|
case '/':
|
|
if (esc)
|
|
break;
|
|
nvec++;
|
|
*s = '\0';
|
|
break;
|
|
case '[':
|
|
esc++;
|
|
break;
|
|
case ']':
|
|
esc--;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
s++;
|
|
}
|
|
s = s0;
|
|
for (i=0; i<nvec; i++){
|
|
if ((i==0 && strcmp(s,"")==0)) /* Initial / or // */
|
|
xpath_element_new(A_ROOT, NULL, &xpnext);
|
|
else if (i!=nvec-1 && strcmp(s,"")==0)
|
|
xpath_element_new(A_DESCENDANT_OR_SELF, NULL, &xpnext);
|
|
else if (strncmp(s,"descendant-or-self::", strlen("descendant-or-self::"))==0){
|
|
xpath_element_new(A_DESCENDANT_OR_SELF, s+strlen("descendant-or-self::"), &xpnext);
|
|
}
|
|
#if 1
|
|
else if (strncmp(s,"..", strlen(".."))==0) /* abbreviatedstep */
|
|
xpath_element_new(A_PARENT, s+strlen(".."), &xpnext);
|
|
#else
|
|
else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */
|
|
xpath_element_new(A_PARENT, NULL, &xpnext);
|
|
#endif
|
|
#if 1 /* Problems with .[userid=1321] */
|
|
else if (strncmp(s,".", strlen("."))==0)
|
|
xpath_element_new(A_SELF, s+strlen("."), &xpnext);
|
|
#else
|
|
else if (strncmp(s,".", strlen(s))==0) /* abbreviatedstep */
|
|
xpath_element_new(A_SELF, NULL, &xpnext);
|
|
#endif
|
|
|
|
else if (strncmp(s,"self::", strlen("self::"))==0)
|
|
xpath_element_new(A_SELF, s+strlen("self::"), &xpnext);
|
|
|
|
else if (strncmp(s,"parent::", strlen("parent::"))==0)
|
|
xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext);
|
|
else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0)
|
|
xpath_element_new(A_ANCESTOR, s+strlen("ancestor::"), &xpnext);
|
|
else if (strncmp(s,"child::", strlen("child::"))==0)
|
|
xpath_element_new(A_CHILD, s+strlen("child::"), &xpnext);
|
|
else
|
|
xpath_element_new(A_CHILD, s, &xpnext);
|
|
s += strlen(s) + 1;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (s0)
|
|
free(s0);
|
|
if (retval == 0)
|
|
*xplist0 = xplist;
|
|
return retval;
|
|
}
|
|
|
|
/*! Find a node 'deep' in an XML tree
|
|
*
|
|
* The xv_* arguments are filled in nodes found earlier.
|
|
* args:
|
|
* @param[in] xn_parent Base XML object
|
|
* @param[in] name shell wildcard pattern to match with node name
|
|
* @param[in] node_type CX_ELMNT, CX_ATTR or CX_BODY
|
|
* @param[in,out] vec1 internal buffers with results
|
|
* @param[in,out] vec0 internal buffers with results
|
|
* @param[in,out] vec_len internal buffers with length of vec0,vec1
|
|
* @param[in,out] vec_max internal buffers with max of vec0,vec1
|
|
* returns:
|
|
* 0 on OK, -1 on error
|
|
*/
|
|
static int
|
|
recursive_find(cxobj *xn,
|
|
char *pattern,
|
|
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 (fnmatch(pattern, xml_name(xsub), 0) == 0){
|
|
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 (recursive_find(xsub, pattern, node_type, flags, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
*vec0 = vec;
|
|
*vec0len = veclen;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! XPath predicate expression check
|
|
* @param[in] predicate_expression xpath expression as a string
|
|
* @param[in] flags Extra xml flag checks that must match (apart from predicate)
|
|
* @param[in,out] vec0 Vector or xml nodes that are checked. Not matched are filtered
|
|
* @param[in,out] vec0len Length of vector or matches
|
|
* On input, vec0 contains a list of xml nodes to match.
|
|
* On output, vec0 contains only the subset that matched the expression.
|
|
* The predicate expression is a subset of the standard, namely:
|
|
* - @<attr>=<value>
|
|
* - <number>
|
|
* - <name>=<value> # RelationalExpr '=' RelationalExpr
|
|
* - <name>=current()<xpath> XXX
|
|
* @see https://www.w3.org/TR/xpath/#predicates
|
|
*/
|
|
static int
|
|
xpath_expr(char *predicate_expression,
|
|
uint16_t flags,
|
|
cxobj ***vec0,
|
|
size_t *vec0len)
|
|
{
|
|
char *e_a;
|
|
char *e_v;
|
|
int i;
|
|
int retval = -1;
|
|
cxobj *x;
|
|
cxobj *xv;
|
|
cxobj **vec = NULL;
|
|
size_t veclen = 0;
|
|
int oplen;
|
|
char *tag;
|
|
char *val;
|
|
char *e0;
|
|
char *e;
|
|
|
|
if ((e0 = strdup(predicate_expression)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
e = e0;
|
|
if (*e == '@'){ /* @ attribute */
|
|
e++;
|
|
e_v=e;
|
|
e_a = strsep(&e_v, "=");
|
|
if (e_a == NULL){
|
|
clicon_err(OE_XML, errno, "%s: malformed expression: [@%s]",
|
|
__FUNCTION__, e);
|
|
goto done;
|
|
}
|
|
for (i=0; i<*vec0len; i++){
|
|
xv = (*vec0)[i];
|
|
if ((x = xml_find(xv, e_a)) != NULL &&
|
|
(xml_type(x) == CX_ATTR)){
|
|
if (!e_v || strcmp(xml_value(x), e_v) == 0){
|
|
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
|
|
if (flags==0x0 || xml_flag(xv, flags)){
|
|
if (cxvec_append(xv, &vec, &veclen) < 0)
|
|
goto done;
|
|
break; /* xv added */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else{ /* either <n> or <tag><op><value>, where <op>='=' for now */
|
|
oplen = strcspn(e, "=");
|
|
if (strlen(e+oplen)==0){ /* no operator */
|
|
if (sscanf(e, "%d", &i) == 1){ /* number */
|
|
if (i < *vec0len){
|
|
xv = (*vec0)[i]; /* XXX: cant compress: gcc breaks */
|
|
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
|
|
if (flags==0x0 || xml_flag(xv, flags))
|
|
if (cxvec_append(xv, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
clicon_err(OE_XML, errno, "%s: malformed expression: [%s]",
|
|
__FUNCTION__, e);
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if ((tag = strsep(&e, "=")) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: malformed expression: [%s]",
|
|
__FUNCTION__, e);
|
|
goto done;
|
|
}
|
|
for (i=0; i<*vec0len; i++){
|
|
xv = (*vec0)[i];
|
|
/* Check if more may match,... */
|
|
x = NULL;
|
|
while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) {
|
|
if (strcmp(tag, xml_name(x)) != 0)
|
|
continue;
|
|
if ((val = xml_body(x)) != NULL &&
|
|
strcmp(val, e) == 0){
|
|
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
|
|
if (flags==0x0 || xml_flag(xv, flags))
|
|
if (cxvec_append(xv, &vec, &veclen) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* copy the array from 1 to 0 */
|
|
free(*vec0);
|
|
*vec0 = vec;
|
|
*vec0len = veclen;
|
|
retval = 0;
|
|
done:
|
|
if (e0)
|
|
free(e0);
|
|
return retval;
|
|
}
|
|
|
|
/*! Given vec0, add matches to vec1
|
|
* @param[in] xe XPATH in structured (parsed) form
|
|
* @param[in] descendants0
|
|
* @param[in] vec0 vector of XML trees
|
|
* @param[in] vec0len length of XML trees
|
|
* @param[in] flags if != 0, only match xml nodes matching flags
|
|
* @param[out] vec2 Result XML node vector
|
|
* @param[out] vec2len Length of result vector.
|
|
*/
|
|
static int
|
|
xpath_find(struct xpath_element *xe,
|
|
int descendants0,
|
|
cxobj **vec0,
|
|
size_t vec0len,
|
|
uint16_t flags,
|
|
cxobj ***vec2,
|
|
size_t *vec2len
|
|
)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
int j;
|
|
cxobj *x = NULL;
|
|
cxobj *xv;
|
|
int descendants = 0;
|
|
cxobj **vec1 = NULL;
|
|
cxobj *xparent;
|
|
size_t vec1len = 0;
|
|
struct xpath_predicate *xp;
|
|
|
|
if (xe == NULL){
|
|
/* append */
|
|
for (i=0; i<vec0len; i++){
|
|
xv = vec0[i];
|
|
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags));
|
|
if (flags==0x0 || xml_flag(xv, flags))
|
|
cxvec_append(xv, vec2, vec2len);
|
|
}
|
|
free(vec0);
|
|
return 0;
|
|
}
|
|
#if 0
|
|
fprintf(stderr, "%s: %s: \"%s\"\n", __FUNCTION__,
|
|
clicon_int2str(axismap, xe->xe_type), xe->xe_str?xe->xe_str:"");
|
|
#endif
|
|
switch (xe->xe_type){
|
|
case A_SELF:
|
|
break;
|
|
case A_PARENT:
|
|
j = 0;
|
|
for (i=0; i<vec0len; i++){
|
|
xv = vec0[i];
|
|
if ((xparent = xml_parent(xv)) != NULL)
|
|
vec0[j++] = xparent;
|
|
}
|
|
vec0len = j;
|
|
break;
|
|
case A_ROOT: /* set list to NULL */
|
|
x = vec0[0];
|
|
assert(x != NULL);
|
|
while (xml_parent(x) != NULL)
|
|
x = xml_parent(x);
|
|
free(vec0);
|
|
if ((vec0 = calloc(1, sizeof(cxobj *))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "calloc");
|
|
goto done;
|
|
}
|
|
vec0[0] = x;
|
|
vec0len = 1;
|
|
break;
|
|
case A_CHILD:
|
|
if (descendants0){
|
|
for (i=0; i<vec0len; i++){
|
|
xv = vec0[i];
|
|
if (recursive_find(xv, xe->xe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
else
|
|
for (i=0; i<vec0len; i++){
|
|
xv = vec0[i];
|
|
x = NULL;
|
|
while ((x = xml_child_each(xv, x, -1)) != NULL) {
|
|
if (fnmatch(xe->xe_str, xml_name(x), 0) == 0){
|
|
clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(x, flags));
|
|
if (flags==0x0 || xml_flag(x, flags))
|
|
if (cxvec_append(x, &vec1, &vec1len) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
free(vec0);
|
|
vec0 = vec1;
|
|
vec0len = vec1len;
|
|
break;
|
|
case A_DESCENDANT_OR_SELF:
|
|
/* Instead of collecting all descendants (which we could)
|
|
just set a flag and treat that in the next operation */
|
|
descendants++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* remove duplicates */
|
|
for (i=0; i<vec0len; i++){
|
|
for (j=i+1; j<vec0len; j++){
|
|
if (vec0[i] == vec0[j]){
|
|
memmove(vec0[j], vec0[j+1], (vec0len-j)*sizeof(cxobj*));
|
|
vec0len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (xp = xe->xe_predicate; xp; xp = xp->xp_next){
|
|
if (xpath_expr(xp->xp_expr, flags, &vec0, &vec0len) < 0)
|
|
goto done;
|
|
}
|
|
if (xpath_find(xe->xe_next, descendants,
|
|
vec0, vec0len, flags,
|
|
vec2, vec2len) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Transform eg "a/b[kalle]" -> "a/b" e="kalle"
|
|
* @param[in,out] xpathstr Eg "a/b[kalle]" -> "a/b"
|
|
* @param[out] pathexpr Eg "kalle"
|
|
* Which also means:
|
|
* "a/b[foo][bar]" -> pathexpr: "foo][bar"
|
|
* @note destructively modify xpathstr, no new strings allocated
|
|
*/
|
|
static int
|
|
xpath_split(char *xpathstr,
|
|
char **pathexpr)
|
|
{
|
|
int retval = -1;
|
|
int last;
|
|
char *pe = NULL;
|
|
|
|
if (strlen(xpathstr)){
|
|
last = strlen(xpathstr) - 1; /* XXX: this could be -1.. */
|
|
if (xpathstr[last] == ']'){
|
|
xpathstr[last] = '\0';
|
|
if (strlen(xpathstr)){
|
|
if ((pe = index(xpathstr,'[')) != NULL){
|
|
*pe = '\0';
|
|
pe++;
|
|
}
|
|
}
|
|
if (pe==NULL){
|
|
clicon_err(OE_XML, errno, "%s: mismatched []: %s", __FUNCTION__, xpathstr);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
*pathexpr = pe;
|
|
return retval;
|
|
}
|
|
|
|
/*! Process single xpath expression on xml tree
|
|
* @param[in] xpath string with XPATH syntax
|
|
* @param[in] vec0 vector of XML trees
|
|
* @param[in] vec0len length of XML trees
|
|
* @param[in] flags if != 0, only match xml nodes matching flags
|
|
* @param[out] vec2 Result XML node vector
|
|
* @param[out] vec2len Length of result vector.
|
|
*/
|
|
static int
|
|
xpath_exec(char *xpath,
|
|
cxobj **vec0,
|
|
size_t vec0len,
|
|
uint16_t flags,
|
|
cxobj ***vec2,
|
|
size_t *vec2len)
|
|
{
|
|
struct xpath_element *xplist;
|
|
cxobj **vec1;
|
|
size_t vec1len;
|
|
|
|
if (cxvec_dup(vec0, vec0len, &vec1, &vec1len) < 0)
|
|
goto done;
|
|
if (xpath_parse(xpath, &xplist) < 0)
|
|
goto done;
|
|
if (debug > 1)
|
|
xpath_print(stderr, xplist);
|
|
if (xpath_find(xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0)
|
|
goto done;
|
|
if (xpath_free(xplist) < 0)
|
|
goto done;
|
|
done:
|
|
return 0;
|
|
} /* xpath_exec */
|
|
|
|
|
|
/*! Intermediate xpath function to handle 'conditional' cases.
|
|
* For example: xpath = //a | //b.
|
|
* xpath_first+ splits xpath up in several subcalls
|
|
* (eg xpath=//a and xpath=//b) and collects the results.
|
|
* Note: if a match is found in both, two (or more) same results will be
|
|
* returned.
|
|
* Note, this could be 'folded' into xpath1 but I judged it too complex.
|
|
*/
|
|
static int
|
|
xpath_choice(cxobj *xtop,
|
|
char *xpath0,
|
|
uint16_t flags,
|
|
cxobj ***vec1,
|
|
size_t *vec1len)
|
|
{
|
|
int retval = -1;
|
|
char *s0;
|
|
char *s1;
|
|
char *s2;
|
|
char *xpath;
|
|
cxobj **vec0 = NULL;
|
|
size_t vec0len = 0;
|
|
|
|
|
|
if ((s0 = strdup(xpath0)) == NULL){
|
|
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
s2 = s1 = s0;
|
|
if ((vec0 = calloc(1, sizeof(cxobj *))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "calloc");
|
|
goto done;
|
|
}
|
|
vec0[0] = xtop;
|
|
vec0len++;
|
|
while (s1 != NULL){
|
|
s2 = strstr(s1, " | ");
|
|
if (s2 != NULL){
|
|
*s2 = '\0'; /* terminate xpath */
|
|
s2 += 3;
|
|
}
|
|
xpath = s1;
|
|
s1 = s2;
|
|
if (xpath_exec(xpath, vec0, vec0len, flags, vec1, vec1len) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (s0)
|
|
free(s0);
|
|
if (vec0)
|
|
free(vec0);
|
|
return retval;
|
|
}
|
|
|
|
static cxobj *
|
|
xpath_first0(cxobj *cxtop,
|
|
char *xpath)
|
|
{
|
|
cxobj **vec0 = NULL;
|
|
size_t vec0len = 0;
|
|
cxobj *xn = NULL;
|
|
|
|
if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0)
|
|
goto done;
|
|
if (vec0len)
|
|
xn = vec0[0];
|
|
else
|
|
xn = NULL;
|
|
done:
|
|
if (vec0)
|
|
free(vec0);
|
|
return xn;
|
|
}
|
|
|
|
/*! A restricted xpath function where the first matching entry is returned
|
|
* See xpath1() on details for subset.
|
|
* args:
|
|
* @param[in] cxtop xml-tree where to search
|
|
* @param[in] xpath string with XPATH syntax
|
|
* @retval xml-tree of first match, or NULL on error.
|
|
*
|
|
* @code
|
|
* cxobj *x;
|
|
* if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) {
|
|
* ...
|
|
* }
|
|
* @endcode
|
|
* Note that the returned pointer points into the original tree so should not be freed
|
|
* after use.
|
|
* @see also xpath_vec.
|
|
*/
|
|
cxobj *
|
|
xpath_first(cxobj *cxtop,
|
|
char *format,
|
|
...)
|
|
{
|
|
cxobj *retval = NULL;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath;
|
|
|
|
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);
|
|
retval = xpath_first0(cxtop, xpath);
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*! A restricted xpath iterator that loops over all matching entries. Dont use.
|
|
*
|
|
* See xpath1() on details for subset.
|
|
* @param[in] cxtop xml-tree where to search
|
|
* @param[in] xpath string with XPATH syntax
|
|
* @param[in] xprev iterator/result should be initiated to NULL
|
|
* @retval xml-tree of n:th match, or NULL on error.
|
|
*
|
|
* @code
|
|
* cxobj *x = NULL;
|
|
* while ((x = xpath_each(cxtop, "//symbol/foo", x)) != NULL) {
|
|
* ...
|
|
* }
|
|
* @endcode
|
|
*
|
|
* Note that the returned pointer points into the original tree so should not be freed
|
|
* after use.
|
|
* @see also xpath, xpath_vec.
|
|
* NOTE: uses a static variable: consider replacing with xpath_vec() instead
|
|
*/
|
|
cxobj *
|
|
xpath_each(cxobj *cxtop,
|
|
char *xpath,
|
|
cxobj *xprev)
|
|
{
|
|
static cxobj **vec0 = NULL; /* XXX */
|
|
static size_t vec0len = 0;
|
|
cxobj *xn = NULL;
|
|
int i;
|
|
|
|
if (xprev == NULL){
|
|
if (vec0) // XXX
|
|
free(vec0); // XXX
|
|
vec0len = 0;
|
|
if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0)
|
|
goto done;
|
|
}
|
|
if (vec0len){
|
|
if (xprev==NULL)
|
|
xn = vec0[0];
|
|
else{
|
|
for (i=0; i<vec0len; i++)
|
|
if (vec0[i] == xprev)
|
|
break;
|
|
if (i>=vec0len-1)
|
|
xn = NULL;
|
|
else
|
|
xn = vec0[i+1];
|
|
}
|
|
}
|
|
else
|
|
xn = NULL;
|
|
done:
|
|
return xn;
|
|
}
|
|
|
|
/*! A restricted xpath that returns a vector of matches
|
|
*
|
|
* See xpath1() on details for subset
|
|
. * @param[in] cxtop xml-tree where to search
|
|
* @param[in] xpath string with XPATH 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 **xvec;
|
|
* size_t xlen;
|
|
* if (xpath_vec(cxtop, "//symbol/foo", &xvec, &xlen) < 0)
|
|
* goto err;
|
|
* for (i=0; i<xlen; i++){
|
|
* xn = xvec[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_first, xpath_each.
|
|
*/
|
|
int
|
|
xpath_vec(cxobj *cxtop,
|
|
char *format,
|
|
cxobj ***vec,
|
|
size_t *veclen,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath;
|
|
|
|
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;
|
|
retval = xpath_choice(cxtop, xpath, 0x0, vec, veclen);
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/* A restricted xpath that returns a vector of matches (only nodes marked with flags)
|
|
* @param[in] cxtop 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(cxtop, "//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 *cxtop,
|
|
char *format,
|
|
uint16_t flags,
|
|
cxobj ***vec,
|
|
size_t *veclen,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath;
|
|
|
|
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;
|
|
retval = xpath_choice(cxtop, xpath, flags, vec, veclen);
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Turn this on to get an xpath test program
|
|
* Usage: xpath [<xpath>]
|
|
* read xml from input
|
|
* Example compile:
|
|
gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen
|
|
* Example run:
|
|
echo "<a><b/></a>" | xpath "a"
|
|
*/
|
|
#if 0 /* Test program */
|
|
|
|
|
|
static int
|
|
usage(char *argv0)
|
|
{
|
|
fprintf(stderr, "usage:%s <xpath>.\n\tInput on stdin\n", argv0);
|
|
exit(0);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int i;
|
|
cxobj **xv
|
|
cxobj *x;
|
|
cxobj *xn;
|
|
size_t xlen = 0;
|
|
|
|
if (argc != 2){
|
|
usage(argv[0]);
|
|
return 0;
|
|
}
|
|
if (clicon_xml_parse_file(0, &x, "</clicon>") < 0){
|
|
fprintf(stderr, "parsing 2\n");
|
|
return -1;
|
|
}
|
|
printf("\n");
|
|
|
|
if (xpath_vec(x, argv[1], &xv, &xlen) < 0)
|
|
return -1;
|
|
if (xv){
|
|
for (i=0; i<xlen; i++){
|
|
xn = xv[i];
|
|
fprintf(stdout, "[%d]:\n", i);
|
|
clicon_xml2file(stdout, xn, 0, 1);
|
|
}
|
|
free(xv);
|
|
}
|
|
if (x)
|
|
xml_free(x);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* Test program */
|
|
|