1546 lines
45 KiB
C
1546 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
|
|
*
|
|
* Note on xcur parameter to most xpath functions:
|
|
* The W3 standard defines the document root / element as the top-level.
|
|
* In the clixon xpath API, the document root is defined as the top of the xml tree.
|
|
* The xcur argument of xpath_first and others is the "current" xml node (xcur) which can
|
|
* be any node in that tree, not necessarily the document root.
|
|
* This is convenient if you want to use relative xpaths from any location in the tree (eg ../../foo/bar).
|
|
* It may be confusing if you expect xcur to be the root node.
|
|
*
|
|
* Some notes on namespace extensions in Netconf/Yang
|
|
* 1) The xpath is not "namespace-aware" in the sense that if you look for a path, eg
|
|
* "n:a/n:b", those must match the XML, so they need to match prefixes AND name in the xml
|
|
* such as <n:a><n:b>. An xml with <m:a><m:b> (or <a><b>) will NOT match EVEN IF they have the
|
|
* same namespace given by xmlns settings.
|
|
* 2) RFC6241 8.9.1
|
|
* In the scope of get-.config, 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>
|
|
* 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.
|
|
*
|
|
*/
|
|
#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_xml_nsctx.h"
|
|
#include "clixon_netconf_lib.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_yang_schema_mount.h"
|
|
#include "clixon_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
#include "clixon_xpath_parse.h"
|
|
#include "clixon_xpath_eval.h"
|
|
|
|
/* Use apostrophe(') in xpath literals, eg a/[x='foo'], not double-quotes(")
|
|
* If not set, use ": a/[x="foo"]
|
|
* Advantage with ' is it works well in clispecs, " must be escaped
|
|
* @see https://www.w3.org/TR/xpath-10/#NT-Literal
|
|
*/
|
|
#define XPATH_USE_APOSTROPHE
|
|
|
|
/*
|
|
* Variables
|
|
*/
|
|
|
|
/* Mapping between xpath_tree node name string <--> int
|
|
* @see xpath_tree_int2str
|
|
*/
|
|
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},
|
|
{"filterexpr", XP_FILTEREXPR},
|
|
{"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}
|
|
};
|
|
|
|
/* Mapping between axis_type string <--> int
|
|
* @see axis_type_int2str
|
|
*/
|
|
static const map_str2int axis_type_map[] = {
|
|
{"NaN", A_NAN},
|
|
{"ancestor", A_ANCESTOR},
|
|
{"ancestor-or-self", A_ANCESTOR_OR_SELF},
|
|
{"attribute", A_ATTRIBUTE},
|
|
{"child", A_CHILD},
|
|
{"descendant", A_DESCENDANT},
|
|
{"descendant-or-self", A_DESCENDANT_OR_SELF},
|
|
{"following", A_FOLLOWING},
|
|
{"following-sibling", A_FOLLOWING_SIBLING},
|
|
{"namespace", A_NAMESPACE},
|
|
{"parent", A_PARENT},
|
|
{"preceding", A_PRECEDING},
|
|
{"preceding-sibling", A_PRECEDING_SIBLING},
|
|
{"self", A_SELF},
|
|
{"root", A_ROOT},
|
|
{NULL, -1}
|
|
};
|
|
|
|
|
|
/*
|
|
* XPath parse tree type
|
|
*/
|
|
|
|
/*! Map from axis-type int to string
|
|
*/
|
|
char*
|
|
axis_type_int2str(int axis_type)
|
|
{
|
|
return (char*)clicon_int2str(axis_type_map, axis_type);
|
|
}
|
|
|
|
int
|
|
axis_type_str2int(char *name)
|
|
{
|
|
return clicon_str2int(axis_type_map, name);
|
|
}
|
|
|
|
/*! Map from xpath_tree node name int to string
|
|
*/
|
|
char*
|
|
xpath_tree_int2str(int nodetype)
|
|
{
|
|
return (char*)clicon_int2str(xpath_tree_map, nodetype);
|
|
}
|
|
|
|
/*! Print XPath parse tree
|
|
*
|
|
* @note uses "" instead of '' in printing literals, rule [29] in https://www.w3.org/TR/xpath-10
|
|
*/
|
|
static int
|
|
xpath_tree_print0(cbuf *cb,
|
|
xpath_tree *xs,
|
|
int level)
|
|
{
|
|
cprintf(cb, "%*s%s:", level*3, "", xpath_tree_int2str(xs->xs_type));
|
|
if (xs->xs_s0)
|
|
cprintf(cb, "\"%s\" ", xs->xs_s0);
|
|
if (xs->xs_s1)
|
|
cprintf(cb,"\"%s\" ", xs->xs_s1);
|
|
if (xs->xs_int)
|
|
switch (xs->xs_type){
|
|
case XP_STEP:
|
|
cprintf(cb, "%s", axis_type_int2str(xs->xs_int));
|
|
break;
|
|
default:
|
|
cprintf(cb, "%d ", xs->xs_int);
|
|
break;
|
|
}
|
|
if (xs->xs_strnr)
|
|
cprintf(cb,"%s ", xs->xs_strnr);
|
|
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;
|
|
}
|
|
|
|
/*! Print a xpath_tree to CLIgen buf
|
|
*
|
|
* @param[out] cb CLIgen buffer
|
|
* @param[in] xs XPath tree
|
|
*/
|
|
int
|
|
xpath_tree_print_cb(cbuf *cb,
|
|
xpath_tree *xs)
|
|
{
|
|
xpath_tree_print0(cb, xs, 0);
|
|
return 0;
|
|
}
|
|
|
|
/*! Print a xpath_tree
|
|
*
|
|
* @param[in] f UNIX output stream
|
|
* @param[in] xs XPath tree
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see xpath_tree2str
|
|
*/
|
|
int
|
|
xpath_tree_print(FILE *f,
|
|
xpath_tree *xs)
|
|
{
|
|
int retval = -1;
|
|
cbuf *cb = NULL;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (xpath_tree_print0(cb, xs, 0) < 0)
|
|
goto done;
|
|
fprintf(f, "%s", cbuf_get(cb));
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Create an xpath string from an xpath tree, ie "unparsing"
|
|
*
|
|
* @param[in] xs XPath tree
|
|
* @param[out] xpath XPath string as CLIgen buf
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see xpath_tree_print
|
|
*/
|
|
int
|
|
xpath_tree2cbuf(xpath_tree *xs,
|
|
cbuf *xcb)
|
|
{
|
|
int retval = -1;
|
|
|
|
/* 1. Before first child */
|
|
switch (xs->xs_type){
|
|
case XP_ABSPATH:
|
|
if (xs->xs_int == A_DESCENDANT_OR_SELF)
|
|
cprintf(xcb, "/");
|
|
cprintf(xcb, "/");
|
|
break;
|
|
case XP_STEP:
|
|
switch (xs->xs_int){
|
|
case A_SELF:
|
|
cprintf(xcb, ".");
|
|
break;
|
|
case A_PARENT:
|
|
cprintf(xcb, "..");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case XP_NODE: /* s0 is namespace prefix, s1 is name */
|
|
if (xs->xs_s0)
|
|
cprintf(xcb, "%s:", xs->xs_s0);
|
|
cprintf(xcb, "%s", xs->xs_s1);
|
|
break;
|
|
case XP_PRIME_NR:
|
|
cprintf(xcb, "%s", xs->xs_strnr?xs->xs_strnr:"0");
|
|
break;
|
|
case XP_PRIME_STR:
|
|
#ifdef XPATH_USE_APOSTROPHE
|
|
cprintf(xcb, "'%s'", xs->xs_s0?xs->xs_s0:"");
|
|
#else
|
|
cprintf(xcb, "\"%s\"", xs->xs_s0?xs->xs_s0:"");
|
|
#endif
|
|
break;
|
|
case XP_PRIME_FN:
|
|
if (xs->xs_s0)
|
|
cprintf(xcb, "%s(", xs->xs_s0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* 2. First child */
|
|
if (xs->xs_c0 && xpath_tree2cbuf(xs->xs_c0, xcb) < 0)
|
|
goto done;
|
|
/* 3. Between first and second child */
|
|
switch (xs->xs_type){
|
|
case XP_AND: /* and or */
|
|
case XP_ADD: /* div mod + * - */
|
|
if (xs->xs_c1)
|
|
cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int));
|
|
break;
|
|
case XP_RELEX: /* !=, >= <= < > = */
|
|
case XP_UNION: /* | */
|
|
if (xs->xs_c1)
|
|
cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int));
|
|
break;
|
|
case XP_PATHEXPR:
|
|
/* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath
|
|
| FilterExpr '//' RelativeLocationPath
|
|
*/
|
|
if (xs->xs_s0)
|
|
cprintf(xcb, "%s", xs->xs_s0);
|
|
break;
|
|
case XP_RELLOCPATH:
|
|
if (xs->xs_c1){
|
|
if (xs->xs_int == A_DESCENDANT_OR_SELF)
|
|
cprintf(xcb, "/");
|
|
cprintf(xcb, "/");
|
|
}
|
|
break;
|
|
case XP_PRED:
|
|
if (xs->xs_c1)
|
|
cprintf(xcb, "[");
|
|
break;
|
|
case XP_EXP:
|
|
if (xs->xs_c0 && xs->xs_c1) /* Function name and two arguments, insert , */
|
|
cprintf(xcb, ",");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* 4. Second child */
|
|
if (xs->xs_c1 && xpath_tree2cbuf(xs->xs_c1, xcb) < 0)
|
|
goto done;
|
|
/* 5. After second child */
|
|
switch (xs->xs_type){
|
|
case XP_PRED:
|
|
if (xs->xs_c1)
|
|
cprintf(xcb, "]");
|
|
break;
|
|
case XP_PRIME_FN:
|
|
if (xs->xs_s0)
|
|
cprintf(xcb, ")");
|
|
default:
|
|
break;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
xpath_tree_append(xpath_tree *xt,
|
|
xpath_tree ***vec,
|
|
size_t *len)
|
|
{
|
|
int retval = -1;
|
|
|
|
if ((*vec = realloc(*vec, sizeof(xpath_tree *) * (*len+1))) == NULL){
|
|
clixon_err(OE_XML, errno, "realloc");
|
|
goto done;
|
|
}
|
|
(*vec)[(*len)++] = xt;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Check if two xpath-trees (parsed xpaths) ar equal
|
|
*
|
|
* @param[in] xt1 XPath parse 1
|
|
* @param[in] xt2 XPath parse 2
|
|
* @param[in,out] vec XPath tree vector
|
|
* @param[in,out] len Length of XML XPath vector
|
|
* @retval 1 Equal
|
|
* @retval 0 Not equal
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xpath_tree_eq(xpath_tree *xt1, /* pattern */
|
|
xpath_tree *xt2,
|
|
xpath_tree ***vec,
|
|
size_t *len)
|
|
{
|
|
int retval = -1; /* Error */
|
|
xpath_tree *xc1;
|
|
xpath_tree *xc2;
|
|
int ret;
|
|
|
|
/* node type */
|
|
if (xt1->xs_type != xt2->xs_type
|
|
#if 1 /* special case that they are expressions but of different types */
|
|
&& !((xt1->xs_type == XP_PRIME_NR || xt1->xs_type == XP_PRIME_STR) &&
|
|
(xt2->xs_type == XP_PRIME_NR || xt2->xs_type == XP_PRIME_STR))
|
|
#endif
|
|
){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "type %s vs %s",
|
|
xpath_tree_int2str(xt1->xs_type),
|
|
xpath_tree_int2str(xt2->xs_type));
|
|
goto neq;
|
|
}
|
|
/* check match, if set, store and go directly to ok */
|
|
if (xt1->xs_match){
|
|
if (xpath_tree_append(xt2, vec, len) < 0)
|
|
goto done;
|
|
goto eq;
|
|
}
|
|
if (xt1->xs_int != xt2->xs_int){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "int");
|
|
goto neq;
|
|
}
|
|
if (xt1->xs_double != xt2->xs_double){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "double");
|
|
goto neq;
|
|
}
|
|
if (clicon_strcmp(xt1->xs_s0, xt2->xs_s0)){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "s0");
|
|
goto neq;
|
|
}
|
|
if (clicon_strcmp(xt1->xs_s1, xt2->xs_s1)){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "s1");
|
|
goto neq;
|
|
}
|
|
xc1 = xt1->xs_c0;
|
|
xc2 = xt2->xs_c0;
|
|
if (xc1 == NULL && xc2 == NULL)
|
|
;
|
|
else{
|
|
if (xc1 == NULL || xc2 == NULL){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "NULL");
|
|
goto neq;
|
|
}
|
|
if ((ret = xpath_tree_eq(xc1, xc2, vec, len)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto neq;
|
|
}
|
|
xc1 = xt1->xs_c1;
|
|
xc2 = xt2->xs_c1;
|
|
if (xc1 == NULL && xc2 == NULL)
|
|
;
|
|
else{
|
|
if (xc1 == NULL || xc2 == NULL){
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "NULL");
|
|
goto neq;
|
|
}
|
|
if ((ret = xpath_tree_eq(xc1, xc2, vec, len)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto neq;
|
|
}
|
|
eq:
|
|
retval = 1; /* equal */
|
|
done:
|
|
return retval;
|
|
neq:
|
|
retval = 0; /* not equal */
|
|
goto done;
|
|
}
|
|
|
|
/*! Traverse through an xpath-tree using indexes
|
|
*
|
|
* @param[in] xt Start-tree
|
|
* @param[in] i List of indexes terminated by -1: 0 means c0, 1 means c1
|
|
* @retval xc End-tree
|
|
*/
|
|
xpath_tree *
|
|
xpath_tree_traverse(xpath_tree *xt,
|
|
...)
|
|
{
|
|
va_list ap;
|
|
int i;
|
|
xpath_tree *xs = xt;
|
|
|
|
va_start(ap, xt);
|
|
for (i = va_arg(ap, int); i >= 0; i = va_arg(ap, int)){
|
|
switch (i){
|
|
case 0:
|
|
xs = xs->xs_c0;
|
|
break;
|
|
case 1:
|
|
xs = xs->xs_c1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
va_end(ap);
|
|
return xs;
|
|
}
|
|
|
|
/*! Free a xpath_tree
|
|
*
|
|
* @param[in] xs XPath tree
|
|
* @see xpath_parse creates a xpath_tree
|
|
*/
|
|
int
|
|
xpath_tree_free(xpath_tree *xs)
|
|
{
|
|
if (xs->xs_strnr)
|
|
free(xs->xs_strnr);
|
|
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;
|
|
}
|
|
|
|
/*! Given xpath, parse it, and return structured xpath tree
|
|
*
|
|
* @param[in] xpath String with XPath 1.0 syntax
|
|
* @param[out] xptree XPath-tree, parsed, structured XPath, free:xpath_tree_free
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* xpath_tree *xpt = NULL;
|
|
* if (xpath_parse(xpath, &xpt) < 0)
|
|
* err;
|
|
* if (xpt)
|
|
* xpath_tree_free(xpt);
|
|
* @endcode
|
|
* @see xpath_tree_free
|
|
* @see xpath_tree2cbuf for unparsing, ie producing an original xpath string
|
|
*/
|
|
int
|
|
xpath_parse(const char *xpath,
|
|
xpath_tree **xptree)
|
|
{
|
|
int retval = -1;
|
|
clixon_xpath_yacc xpy = {0,};
|
|
cbuf *cb = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "xpath %s", xpath);
|
|
if (xpath == NULL){
|
|
clixon_err(OE_XML, EINVAL, "XPath is NULL");
|
|
goto done;
|
|
}
|
|
xpy.xpy_parse_string = xpath;
|
|
xpy.xpy_name = "xpath parser";
|
|
xpy.xpy_linenum = 1;
|
|
if (xpath_scan_init(&xpy) < 0)
|
|
goto done;
|
|
if (xpath_parse_init(&xpy) < 0)
|
|
goto done;
|
|
if (clixon_xpath_parseparse(&xpy) != 0) { /* yacc returns 1 on error */
|
|
clixon_log(NULL, LOG_NOTICE, "XPath error: on line %d", xpy.xpy_linenum);
|
|
if (clixon_err_category() == 0)
|
|
clixon_err(OE_XML, 0, "XPath parser error with no error code (should not happen)");
|
|
xpath_scan_exit(&xpy);
|
|
goto done;
|
|
}
|
|
if (clixon_debug_isset(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL)){
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
xpath_tree_print_cb(cb, xpy.xpy_top);
|
|
clixon_debug(CLIXON_DBG_ALWAYS, "xpath parse tree:\n%s", cbuf_get(cb));
|
|
}
|
|
xpath_parse_exit(&xpy);
|
|
xpath_scan_exit(&xpy);
|
|
if (xptree){
|
|
*xptree = xpy.xpy_top;
|
|
xpy.xpy_top = NULL;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (xpy.xpy_top)
|
|
xpath_tree_free(xpy.xpy_top);
|
|
return retval;
|
|
}
|
|
|
|
/*! Given XML tree and xpath, parse xpath, eval it and return xpath context,
|
|
*
|
|
* This is a raw form of xpath where you can do type conversion of the return
|
|
* value, etc, not just a nodeset.
|
|
* @param[in] xcur XML-tree where to search
|
|
* @param[in] nsc External XML namespace context, or NULL
|
|
* @param[in] xpath String with XPath 1.0 syntax
|
|
* @param[in] localonly Skip prefix and namespace tests (non-standard)
|
|
* @param[out] xrp Return XPath context
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @code
|
|
* xp_ctx *xc = NULL;
|
|
* if (xpath_vec_ctx(x, NULL, xpath, 0, &xc) < 0)
|
|
* err;
|
|
* if (xc)
|
|
* ctx_free(xc);
|
|
* @endcode
|
|
*/
|
|
int
|
|
xpath_vec_ctx(cxobj *xcur,
|
|
cvec *nsc,
|
|
const char *xpath,
|
|
int localonly,
|
|
xp_ctx **xrp)
|
|
{
|
|
int retval = -1;
|
|
xpath_tree *xptree = NULL;
|
|
xp_ctx xc = {0,};
|
|
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "%s", xpath);
|
|
if (xpath_parse(xpath, &xptree) < 0)
|
|
goto done;
|
|
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, xptree, nsc, localonly, xrp) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (xc.xc_nodeset){
|
|
free(xc.xc_nodeset);
|
|
xc.xc_nodeset = NULL;
|
|
}
|
|
if (xptree)
|
|
xpath_tree_free(xptree);
|
|
return retval;
|
|
}
|
|
|
|
/*! XPath nodeset function where only the first matching entry is returned
|
|
*
|
|
* @param[in] xcur XML tree where to search
|
|
* @param[in] nsc External XML namespace context, or NULL
|
|
* @param[in] xpformat Format string for XPath syntax
|
|
* @retval xml-tree XML tree of first match
|
|
* @retval NULL Error or not found
|
|
*
|
|
* @code
|
|
* cxobj *x;
|
|
* cvec *nsc = NULL; // namespace context
|
|
* if (xml_nsctx_node(xtop, &nsc) < 0)
|
|
* err;
|
|
* if ((x = xpath_first(xtop, nsc, "//symbol/foo")) != NULL) {
|
|
* ...
|
|
* }
|
|
* @endcode
|
|
* @note the returned pointer points into the original tree so should not be freed after use.
|
|
* @note return value does not see difference between error and not found
|
|
* @see also xpath_vec.
|
|
*/
|
|
cxobj *
|
|
xpath_first(cxobj *xcur,
|
|
cvec *nsc,
|
|
const char *xpformat,
|
|
...)
|
|
{
|
|
cxobj *cx = NULL;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, xpformat);
|
|
len = vsnprintf(NULL, 0, xpformat, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, xpformat);
|
|
if (vsnprintf(xpath, len+1, xpformat, ap) < 0){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (xpath_vec_ctx(xcur, nsc, xpath, 0, &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;
|
|
}
|
|
|
|
/*! XPath nodeset function where prefixes are skipped, only first matching is returned
|
|
*
|
|
* Reason for skipping prefix/namespace check may be with incomplete tree, for example.
|
|
* @param[in] xcur XML tree where to search
|
|
* @param[in] xpformat Format string for XPath syntax
|
|
* @retval xml-tree XML tree of first match
|
|
* @retval NULL Error or not found
|
|
*
|
|
* @code
|
|
* cxobj *x;
|
|
* cvec *nsc; // namespace context
|
|
* if ((x = xpath_first_localonly(xtop, "//symbol/foo")) != NULL) {
|
|
* ...
|
|
* }
|
|
* @endcode
|
|
* @note the returned pointer points into the original tree so should not be freed after use.
|
|
* @note return value does not see difference between error and not found
|
|
* @note Prefixes and namespaces are ignored so this is NOT according to standard
|
|
* @see also xpath_first.
|
|
*/
|
|
cxobj *
|
|
xpath_first_localonly(cxobj *xcur,
|
|
const char *xpformat,
|
|
...)
|
|
{
|
|
cxobj *cx = NULL;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, xpformat);
|
|
len = vsnprintf(NULL, 0, xpformat, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, xpformat);
|
|
if (vsnprintf(xpath, len+1, xpformat, ap) < 0){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (xpath_vec_ctx(xcur, NULL, xpath, 1, &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] nsc External XML namespace context, or NULL
|
|
* @param[in] xpformat Format string for 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
|
|
* cvec *nsc; // namespace context
|
|
* cxobj **vec = NULL;
|
|
* size_t veclen;
|
|
* if (xpath_vec(xcur, nsc, "//symbol/foo", &vec, &veclen) < 0)
|
|
* err;
|
|
* for (i=0; i<veclen; i++){
|
|
* xn = vec[i];
|
|
* ...
|
|
* }
|
|
* free(vec);
|
|
* @endcode
|
|
*/
|
|
int
|
|
xpath_vec(cxobj *xcur,
|
|
cvec *nsc,
|
|
const char *xpformat,
|
|
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, xpformat, ap);
|
|
va_end(ap);
|
|
/* allocate an xpath string exactly fitting the length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: actually compute xpath string content */
|
|
va_start(ap, veclen);
|
|
if (vsnprintf(xpath, len+1, xpformat, ap) < 0){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
*vec = NULL;
|
|
*veclen = 0;
|
|
if (xpath_vec_ctx(xcur, nsc, xpath, 0, &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] xpformat Format string for XPath syntax
|
|
* @param[in] nsc External XML namespace context, or NULL
|
|
* @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;
|
|
* cvec *nsc; // namespace context (not NULL)
|
|
* if (xpath_vec_flag(xcur, nsc, "//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,
|
|
cvec *nsc,
|
|
const char *xpformat,
|
|
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;
|
|
int ilen = 0; /* change when cxvec_append uses size_t */
|
|
|
|
va_start(ap, veclen);
|
|
len = vsnprintf(NULL, 0, xpformat, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clixon_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, xpformat, ap) < 0){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
*vec=NULL;
|
|
if (xpath_vec_ctx(xcur, nsc, xpath, 0, &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, &ilen) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
*veclen = ilen;
|
|
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] nsc External XML namespace context, or NULL
|
|
* @param[in] xpformat Format string for XPath syntax
|
|
* @retval 1 True
|
|
* @retval 0 False
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xpath_vec_bool(cxobj *xcur,
|
|
cvec *nsc,
|
|
const char *xpformat,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *xpath = NULL;
|
|
xp_ctx *xr = NULL;
|
|
|
|
va_start(ap, xpformat);
|
|
len = vsnprintf(NULL, 0, xpformat, ap);
|
|
va_end(ap);
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((xpath = malloc(len+1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from reason and args */
|
|
va_start(ap, xpformat);
|
|
if (vsnprintf(xpath, len+1, xpformat, ap) < 0){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (xpath_vec_ctx(xcur, nsc, xpath, 0, &xr) < 0)
|
|
goto done;
|
|
if (xr)
|
|
retval = ctx2boolean(xr);
|
|
done:
|
|
if (xr)
|
|
ctx_free(xr);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes
|
|
*
|
|
* @param[in] xs Parsed xpath - xpath_tree
|
|
* @param[in] yspec Yang spec containing all modules, associated with namespaces
|
|
* @param[in] nsc0 Input namespace context
|
|
* @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free
|
|
* @param[out] reason Error reason if result is 0 - failed
|
|
* @retval 1 OK with nsc1 containing the transformed nsc
|
|
* @retval 0 XPath failure with reason set to why
|
|
* @retval -1 Fatal Error
|
|
*/
|
|
static int
|
|
xpath_traverse_canonical(xpath_tree *xs,
|
|
yang_stmt *yspec,
|
|
cvec *nsc0,
|
|
cvec *nsc1,
|
|
cbuf **reason)
|
|
{
|
|
int retval = -1;
|
|
char *prefix0;
|
|
char *prefix1 = NULL;
|
|
char *namespace;
|
|
yang_stmt *ymod;
|
|
cbuf *cb = NULL;
|
|
int ret;
|
|
// char *name;
|
|
|
|
switch (xs->xs_type){
|
|
case XP_NODE: /* s0 is namespace prefix, s1 is name */
|
|
/* Nodetest = * needs no prefix */
|
|
if (xs->xs_s1 && strcmp(xs->xs_s1, "*") == 0)
|
|
break;
|
|
prefix0 = xs->xs_s0;
|
|
// name = xs->xs_s1;
|
|
if ((namespace = xml_nsctx_get(nsc0, prefix0)) == NULL){
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "No namespace found for prefix: %s", prefix0);
|
|
if (reason)
|
|
*reason = cb;
|
|
goto fail;
|
|
}
|
|
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){
|
|
#if 0 /* Just accept it, see note in xpath2canonical */
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "No yang found for namespace: %s", namespace);
|
|
if (reason)
|
|
*reason = cb;
|
|
goto fail;
|
|
#endif
|
|
}
|
|
if (ymod == NULL)
|
|
prefix1 = prefix0;
|
|
else
|
|
if ((prefix1 = yang_find_myprefix(ymod)) == NULL){
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "No prefix found in module: %s", yang_argument_get(ymod));
|
|
if (reason)
|
|
*reason = cb;
|
|
goto fail;
|
|
}
|
|
if (xml_nsctx_get(nsc1, prefix1) == NULL)
|
|
if (xml_nsctx_add(nsc1, prefix1, namespace) < 0)
|
|
goto done;
|
|
if (prefix0==NULL || strcmp(prefix0, prefix1) != 0){
|
|
if (xs->xs_s0)
|
|
free(xs->xs_s0);
|
|
if ((xs->xs_s0 = strdup(prefix1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (xs->xs_c0){
|
|
if ((ret = xpath_traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
if (xs->xs_c1){
|
|
if ((ret = xpath_traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes
|
|
*
|
|
* @param[in] xpath0 Input xpath
|
|
* @param[in] nsc0 Input namespace context
|
|
* @param[in] yspec Yang spec containing all modules, associated with namespaces
|
|
* @param[out] xpath1 Output xpath. Free after use with free
|
|
* @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free
|
|
* @retval 1 OK, xpath1 and nsc1 allocated
|
|
* @retval 0 XPath failure with reason set to why
|
|
* @retval -1 Fatal Error
|
|
* Example:
|
|
* Module A has prefix a and namespace urn:example:a and symbols x
|
|
* Module B with prefix b and namespace urn:example:b and symbols y
|
|
* Then incoming:
|
|
* xpath0: /x/c:y
|
|
* nsc0: NULL:"urn:example:a"; c:"urn:example:b"
|
|
* will be translated to:
|
|
* xpath1: /a:x/b:y
|
|
* nsc1: a:"urn:example:a"; b:"urn:example:b"
|
|
* @code
|
|
* char *xpath1 = NULL;
|
|
* cvec *nsc1 = NULL;
|
|
* cbuf *reason = NULL;
|
|
* if ((ret = xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1, &reason)) < 0)
|
|
* err;
|
|
* ...
|
|
* if (xpath1) free(xpath1);
|
|
* if (nsc1) xml_nsctx_free(nsc1);
|
|
* if (reason) cbuf_free(reason);
|
|
* @endcode
|
|
* @note Unsolvable issue of mountpoints, eg an xpath of //x:foo where foo is under one or several
|
|
* mountpoints: a well-defined namespace cannot be determined. Therefore just allow
|
|
* inconsistencies and hope that it will be covered by other code
|
|
* @see xpath2xml
|
|
*/
|
|
int
|
|
xpath2canonical(const char *xpath0,
|
|
cvec *nsc0,
|
|
yang_stmt *yspec,
|
|
char **xpath1,
|
|
cvec **nsc1p,
|
|
cbuf **cbreason)
|
|
{
|
|
int retval = -1;
|
|
xpath_tree *xpt = NULL;
|
|
cvec *nsc1 = NULL;
|
|
cbuf *xcb = NULL;
|
|
int ret;
|
|
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "");
|
|
/* Parse input xpath into an xpath-tree */
|
|
if (xpath_parse(xpath0, &xpt) < 0)
|
|
goto done;
|
|
/* Create new nsc */
|
|
if ((nsc1 = xml_nsctx_init(NULL, NULL)) == NULL)
|
|
goto done;
|
|
/* Traverse tree to find prefixes, transform them to canonical form and
|
|
* create a canonical network namespace
|
|
*/
|
|
if ((ret = xpath_traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
/* Print tree with new prefixes */
|
|
if ((xcb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (xpath_tree2cbuf(xpt, xcb) < 0)
|
|
goto done;
|
|
if (xpath1){
|
|
if ((*xpath1 = strdup(cbuf_get(xcb))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
if (nsc1p){
|
|
*nsc1p = nsc1;
|
|
nsc1 = NULL;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
if (xcb)
|
|
cbuf_free(xcb);
|
|
if (nsc1)
|
|
xml_nsctx_free(nsc1);
|
|
if (xpt)
|
|
xpath_tree_free(xpt);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Return a count(xpath)
|
|
*
|
|
* @param[in] xcur xml-tree where to search
|
|
* @param[in] nsc External XML namespace context, or NULL
|
|
* @param[in] xpath XPath syntax
|
|
* @param[oit] count Nr of elements of xpath
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note This function is made for making optimizations in certain circumstances, such as a list
|
|
*/
|
|
int
|
|
xpath_count(cxobj *xcur,
|
|
cvec *nsc,
|
|
const char *xpath,
|
|
uint32_t *count)
|
|
{
|
|
int retval = -1;
|
|
xp_ctx *xc = NULL;
|
|
cbuf *cb = NULL;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "count(%s)", xpath);
|
|
if (xpath_vec_ctx(xcur, nsc, cbuf_get(cb), 0, &xc) < 0)
|
|
goto done;
|
|
if (xc && xc->xc_type == XT_NUMBER && xc->xc_number != NAN)
|
|
*count = (uint32_t)xc->xc_number;
|
|
else
|
|
*count = 0;
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (xc)
|
|
ctx_free(xc);
|
|
return retval;
|
|
}
|
|
|
|
/*! Given an XML node, build an xpath recursively to root, internal function
|
|
*
|
|
* @param[in] x XML object
|
|
* @param[in] nsc Namespace context
|
|
* @param[in] spec If set, recursively continue only to root without spec
|
|
* @param[in] apostrophe If set, use apostrophe in xpath literals, eg a/[x='foo'], not double-quotes(") * @param[out] cb XPath string as cbuf.
|
|
* @retval 0 OK
|
|
* @retval -1 Error. eg XML malformed
|
|
*/
|
|
static int
|
|
xml2xpath1(cxobj *x,
|
|
cvec *nsc,
|
|
int spec,
|
|
int apostrophe,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xp;
|
|
yang_stmt *y = NULL;
|
|
cvec *cvk = NULL; /* vector of index keys */
|
|
cg_var *cvi;
|
|
char *keyname;
|
|
cxobj *xkey;
|
|
cxobj *xb;
|
|
char *b;
|
|
enum rfc_6020 keyword;
|
|
char *prefix = NULL;
|
|
char *namespace;
|
|
|
|
if ((xp = xml_parent(x)) == NULL)
|
|
goto ok;
|
|
y = xml_spec(x);
|
|
|
|
if (spec && y == NULL)
|
|
goto ok;
|
|
/* Strip top-level netconf anydata, eg from get-config protocol processing */
|
|
if (y != NULL){
|
|
if (yang_keyword_get(y) == Y_ANYXML)
|
|
goto ok;
|
|
if (yang_keyword_get(y) == Y_ANYDATA)
|
|
goto ok;
|
|
}
|
|
if (xml2xpath1(xp, nsc, spec, apostrophe, cb) < 0)
|
|
goto done;
|
|
if (nsc){
|
|
if (xml2ns(x, xml_prefix(x), &namespace) < 0)
|
|
goto done;
|
|
if (namespace){
|
|
if (xml_nsctx_get_prefix(nsc, namespace, &prefix) == 0)
|
|
; /* maybe NULL? */
|
|
}
|
|
else
|
|
prefix = xml_prefix(x); /* maybe NULL? */
|
|
}
|
|
else
|
|
prefix = xml_prefix(x);
|
|
/* XXX: sometimes there should be a /, sometimes not */
|
|
cprintf(cb, "/");
|
|
if (prefix)
|
|
cprintf(cb, "%s:", prefix);
|
|
cprintf(cb, "%s", xml_name(x));
|
|
if (y != NULL){
|
|
keyword = yang_keyword_get(y);
|
|
switch (keyword){
|
|
case Y_LEAF_LIST:
|
|
if (apostrophe){
|
|
if ((b = xml_body(x)) != NULL)
|
|
cprintf(cb, "[.='%s']", b);
|
|
else
|
|
cprintf(cb, "[.='']");
|
|
}
|
|
else{
|
|
if ((b = xml_body(x)) != NULL)
|
|
cprintf(cb, "[.=\"%s\"]", b);
|
|
else
|
|
cprintf(cb, "[.=\"\"]");
|
|
}
|
|
break;
|
|
case Y_LIST:
|
|
cvk = yang_cvec_get(y);
|
|
cvi = NULL;
|
|
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
keyname = cv_string_get(cvi);
|
|
if ((xkey = xml_find(x, keyname)) == NULL){
|
|
clixon_err(OE_XML, 0, "No key %s in list %s", keyname, xml_name(x));
|
|
goto done;
|
|
}
|
|
if ((xb = xml_find(x, keyname)) == NULL)
|
|
goto done;
|
|
b = xml_body(xb);
|
|
#if 1
|
|
if (b==NULL || strlen(b)==0)
|
|
continue;
|
|
#endif
|
|
cprintf(cb, "[");
|
|
if (prefix)
|
|
cprintf(cb, "%s:", prefix);
|
|
if (apostrophe)
|
|
cprintf(cb, "%s='%s']", keyname, b?b:"");
|
|
else
|
|
cprintf(cb, "%s=\"%s\"]", keyname, b?b:"");
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given an XML node, build an xpath to root
|
|
*
|
|
* Creates an XPath from an XML node with some limitations, see notes below.
|
|
* The prefixes used are from the given namespace context if any, otherwise the native prefixes are used, if any.
|
|
* Note that this means that prefixes may be translated such as if the XML namespace mapping is different than the once used
|
|
* in the XML.
|
|
* Therefore, if nsc is "canonical", the returned xpath is also "canonical", even though the XML is not.
|
|
* @param[in] x XML object
|
|
* @param[in] nsc Namespace context
|
|
* @param[in] spec If set, recursively continue only to root without spec (added in 6.1 for yang mount)
|
|
* @param[in] apostrophe If set, use apostrophe in xpath literals, eg a/[x='foo'], not double-quotes(")
|
|
* @param[out] xpath Malloced xpath string. Need to free() after use
|
|
* @retval 0 OK
|
|
* @retval -1 Error. (eg XML malformed)
|
|
* @code
|
|
* char *xpath = NULL;
|
|
* cxobj *x;
|
|
* ... x is inside an xml tree ...
|
|
* if (xml2xpath(x, nsc, 0, 0, &xpath) < 0)
|
|
* err;
|
|
* free(xpath);
|
|
* @endcode
|
|
* @note x needs to be bound to YANG, see eg xml_bind_yang()
|
|
* @note namespaces of xpath is not well-defined, follows xml, should be canonical?
|
|
*/
|
|
int
|
|
xml2xpath(cxobj *x,
|
|
cvec *nsc,
|
|
int spec,
|
|
int apostrophe,
|
|
char **xpathp)
|
|
{
|
|
int retval = -1;
|
|
cbuf *cb;
|
|
char *xpath = NULL;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (xml2xpath1(x, nsc, spec, apostrophe, cb) < 0)
|
|
goto done;
|
|
/* XXX: see xpath in test statement,.. */
|
|
xpath = cbuf_get(cb);
|
|
if (xpathp){
|
|
if ((*xpathp = strdup(xpath)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
xpath = NULL;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! Create xml tree from xpath as xpath-tree
|
|
*
|
|
* @param[in] xs Parsed xpath - xpath_tree
|
|
* @param[in] nsc Namespace context for xpath
|
|
* @param[in] x0 XML tree so far
|
|
* @param[out] xbotp Resulting xml tree (end of xpath) (optional)
|
|
* @param[out] xerr Netconf error message (if retval=0)
|
|
* @retval 1 OK
|
|
* @retval 0 Invalid xpath
|
|
* @retval -1 Fatal error, clixon_err called
|
|
* @see xpath_traverse_canonical
|
|
*/
|
|
static int
|
|
xpath2xml_traverse(xpath_tree *xs,
|
|
cvec *nsc,
|
|
cxobj *x0,
|
|
yang_stmt *y0,
|
|
cxobj **xbotp,
|
|
yang_stmt **ybotp,
|
|
cxobj **xerr)
|
|
{
|
|
int retval = -1;
|
|
int ret;
|
|
char *name;
|
|
char *prefix;
|
|
char *namespace;
|
|
char *ns = NULL;
|
|
cbuf *cberr = NULL;
|
|
cxobj *xc;
|
|
yang_stmt *ymod;
|
|
yang_stmt *yc;
|
|
|
|
*xbotp = x0;
|
|
*ybotp = y0;
|
|
switch (xs->xs_type){
|
|
case XP_NODE: /* s0 is namespace prefix, s1 is name */
|
|
prefix = xs->xs_s0;
|
|
name = xs->xs_s1;
|
|
if ((namespace = xml_nsctx_get(nsc, prefix)) == NULL){
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cberr, "No namespace found for prefix: %s", prefix);
|
|
if (xerr &&
|
|
netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if (yang_keyword_get(y0) == Y_SPEC){ /* top-node */
|
|
if ((ymod = yang_find_module_by_namespace(y0, namespace)) == NULL){
|
|
cprintf(cberr, "No such yang module namespace");
|
|
if (xerr &&
|
|
netconf_unknown_element_xml(xerr, "application", namespace, cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
y0 = ymod;
|
|
}
|
|
if ((yc = yang_find_datanode(y0, name)) == NULL){
|
|
if (xerr &&
|
|
netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if ((xc = xml_new(name, x0, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (xml2ns(x0, prefix, &ns) < 0)
|
|
goto done;
|
|
if (ns == NULL)
|
|
if (xmlns_set(xc, NULL, namespace) < 0)
|
|
goto done;
|
|
*xbotp = xc;
|
|
*ybotp = yc;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (xs->xs_c0){
|
|
if ((ret = xpath2xml_traverse(xs->xs_c0, nsc, x0, y0, xbotp, ybotp, xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
goto fail;
|
|
}
|
|
}
|
|
if (xs->xs_c1){
|
|
x0 = *xbotp;
|
|
y0 = *ybotp;
|
|
if ((ret = xpath2xml_traverse(xs->xs_c1, nsc, x0, y0, xbotp, ybotp, xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
goto fail;
|
|
}
|
|
if (xs->xs_type == XP_STEP){
|
|
*xbotp = x0;
|
|
*ybotp = y0;
|
|
}
|
|
}
|
|
retval = 1;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "retval:%d", retval);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Create xml tree from restricted xpath
|
|
*
|
|
* Create an XML tree from "scratch" using xpath.
|
|
* @param[in] xpath (Absolute) XPath
|
|
* @param[in] nsc Namespace context for xpath
|
|
* @param[in,out] xtop Incoming XML tree
|
|
* @param[in] yspec Yang spec for xtop
|
|
* @param[out] xbotp Resulting xml tree (end of xpath) (optional)
|
|
* @param[out] ybotp Yang spec matching xpathp
|
|
* @param[out] xerr Netconf error message (if retval=0)
|
|
* @retval 1 OK
|
|
* @retval 0 Invalid xpath
|
|
* @retval -1 Fatal error, clixon_err called
|
|
* @see api_path2xml
|
|
* @see xml2xpath
|
|
* @note xpath is restricted to absolute paths, and simple expressions, eg as "node-identifier"
|
|
*/
|
|
int
|
|
xpath2xml(char *xpath,
|
|
cvec *nsc,
|
|
cxobj *xtop,
|
|
yang_stmt *ytop,
|
|
cxobj **xbotp,
|
|
yang_stmt **ybotp,
|
|
cxobj **xerr)
|
|
{
|
|
int retval = -1;
|
|
cbuf *cberr = NULL;
|
|
xpath_tree *xpt = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_XPATH | CLIXON_DBG_DETAIL, "xpath:%s", xpath);
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (*xpath != '/'){
|
|
cprintf(cberr, "Invalid absolute xpath: %s (must start with '/')", xpath);
|
|
if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
/* Parse input xpath into an xpath-tree */
|
|
if (xpath_parse(xpath, &xpt) < 0)
|
|
goto done;
|
|
if ((retval = xpath2xml_traverse(xpt, nsc, xtop, ytop, xbotp, ybotp, xerr)) < 1)
|
|
goto done;
|
|
done:
|
|
if (xpt)
|
|
xpath_tree_free(xpt);
|
|
if (cberr)
|
|
cbuf_free(cberr);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|