* Much better support for XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex

* NOTE: Due to an error in the previous implementation, all XPATH calls on the form `x[a=str]` where `str` is a string (not a number or XML symbol), must be changed to: `x[a='str'] or x[a="str"]`
    * This includes all calls to `xpath_vec, xpath_first`, etc.
    * All calls to cli_copy_config in CLI spec files must replace 2nd argument from `x[%s=%s]` to `x[%s='%s']`
  * The old API is stillenabled. To define the new, define XPATH_USE_NEW in include/clixon_custom.h and recompile
This commit is contained in:
Olof hagsand 2018-07-17 16:59:32 +02:00
parent 5d7c4a8d18
commit ba7f84afee
29 changed files with 395 additions and 79 deletions

View file

@ -3,9 +3,11 @@
## 3.7.0 (Upcoming)
### Major changes:
* Full support of XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex
* The previous XPATH imlementation was very restricted.
* The only function implemented is the Yang extension "current()". No other functions are implemented (eg last(), count()).
* Much better support for XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex
* NOTE: Due to an error in the previous implementation, all XPATH calls on the form `x[a=str]` where `str` is a string (not a number or XML symbol), must be changed to: `x[a='str'] or x[a="str"]`
* This includes all calls to `xpath_vec, xpath_first`, etc.
* All calls to cli_copy_config in CLI spec files must replace 2nd argument from `x[%s=%s]` to `x[%s='%s']`
* The old API is stillenabled. To define the new, define XPATH_USE_NEW in include/clixon_custom.h and recompile
* Conformance of restconf(RFC-8040) operations where prefix was used instead of module name.
* Proper specification for an operation is POST /restconf/operations/<module_name>:<rpc_procedure> HTTP/1.1
* See https://github.com/clicon/clixon/issues/31, https://github.com/clicon/clixon/pull/32 and https://github.com/clicon/clixon/issues/30

View file

@ -985,7 +985,13 @@ nacm_access(clicon_handle h,
if (username == NULL)
goto step10;
/* User's group */
if (xpath_vec(xacm, "groups/group[user-name=%s]", &gvec, &glen, username) < 0)
if (xpath_vec(xacm,
#ifdef XPATH_USE_NEW
"groups/group[user-name='%s']",
#else
"groups/group[user-name=%s]",
#endif
&gvec, &glen, username) < 0)
goto done;
/* 5. If no groups are found, continue with step 10. */
if (glen == 0)
@ -1002,7 +1008,13 @@ nacm_access(clicon_handle h,
for (j=0; j<glen; j++){
char *gname;
gname = xml_find_body(gvec[j], "name");
if (xpath_first(xrlist,".[group=%s]", gname)!=NULL)
if (xpath_first(xrlist,
#ifdef XPATH_USE_NEW
".[group='%s']",
#else
".[group=%s]",
#endif
gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */

View file

@ -78,7 +78,7 @@
/*! Register log notification stream
* @param[in] h Clicon handle
* @param[in] stream Event stream. CLICON is predefined, others are application-defined
* @param[in] filter Filter. For xml notification ie xpath: .[name=kalle]
* @param[in] filter Filter. For xml notification ie xpath: .[name="kalle"]
* @param[in] status 0 for stop, 1 to start
* @param[in] fn Callback function called when notification occurs
* @param[in] arg Argument to function note
@ -1189,7 +1189,7 @@ cli_unlock(clicon_handle h,
* tovar: Name of variable containing name of object to copy to.
* @code
* cli spec:
* copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s=%s]", "from", "n1", "n2");
* copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s='%s']", "from", "n1", "n2");
* cli command:
* copy snd from to to
* @endcode
@ -1244,17 +1244,34 @@ cli_copy_config(clicon_handle h,
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
/* Sanity check that xpath contains exactly one %s */
/* Sanity check that xpath contains exactly two %s, ie [%s='%s'] */
j = 0;
for (i=0; i<strlen(xpath); i++)
for (i=0; i<strlen(xpath); i++){
if (xpath[i] == '%')
j++;
#ifndef XPATH_USE_NEW
/* This is a horrible kludge due to:
* (1) old xpath implementation wrongly did: a[b=x] instead of a[b='x']
* (2) cli_copy_config has as 2nd argument such an xpath provided by user.
*/
if (j==2){
int k;
if ((xpath[i-1] == '\'' || xpath[i-1] == '\"') &&
(xpath[i+2] == '\'' || xpath[i+2] == '\"')){
for (k=i-1;k<i+2;k++)
xpath[k] = xpath[k+1];
for (k=i+1;k<strlen(xpath)+1;k++)
xpath[k] = xpath[k+2];
i-=1;
}
}
#endif
}
if (j != 2){
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'", xpath);
goto done;
}
cprintf(cb, xpath, keyname, fromname);
/* Get from object configuration and store in x1 */
if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0)
goto done;

View file

@ -285,7 +285,7 @@ yang2cli_var_sub(clicon_handle h,
goto done;
}
}
cprintf(cb, "%s]", r);
cprintf(cb, "%s]", r); /* range */
free(r);
r = NULL;
}

View file

@ -148,7 +148,7 @@ expand_dbvar(void *h,
api_path_fmt = cv_string_get(cv);
/* api_path_fmt = /interface/%s/address/%s
--> ^/interface/eth0/address/.*$
--> /interface/[name=eth0]/address
--> /interface/[name="eth0"]/address
*/
if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0)
goto done;
@ -417,10 +417,10 @@ show_yang(clicon_handle h,
* Format of argv:
* <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name=%s]"
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, cli_show_config("running","xml","iface[name=%s]","n");
* show config id <n:string>, cli_show_config("running","xml","iface[name="%s"]","n");
* @endcode
*/
int

View file

@ -82,12 +82,15 @@
static int
netconf_hello(cxobj *xn)
{
#ifdef nyi
cxobj *x;
x = NULL;
while ((x = xpath_each(xn, "//capability", x)) != NULL) {
//fprintf(stderr, "cap: %s\n", xml_body(x));
}
#endif
return 0;
}

View file

@ -95,6 +95,9 @@ Mapping netconf error-tag -> status code
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

View file

@ -64,7 +64,7 @@
*
* Alternative for xmlkeyfmt would be eg:
* RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used)
* XPATH: /interfaces/interface[name=%s]/ipv4/address/[ip=%s]
* XPATH: /interfaces/interface[name='%s']/ipv4/address/[ip'=%s']
*
* Paths through the code (for coverage)
* cli_callback_generate +----------------+
@ -375,7 +375,7 @@ get(char *dbname,
arg = valvec[j++];
if (uri_percent_decode(arg, &argdec) < 0)
goto done;
cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec);
cprintf(cb, "[%s='%s']", cv_string_get(cvi), argdec);
free(argdec);
argdec=NULL;
}

View file

@ -67,7 +67,12 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
cli_output(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */
/* Show eth0 interfaces config using XPATH */
if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]",
if (clicon_rpc_get_config(h, "running",
#ifdef XPATH_USE_NEW
"/interfaces/interface[name='eth0']",
#else
"/interfaces/interface[name=eth0]",
#endif
&xret) < 0)
goto done;

View file

@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
}
copy("Copy and create a new object") {
interface("Copy interface"){
<name:string expand_dbvar("candidate","/interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s=%s]","name","name","toname");
<name:string expand_dbvar("candidate","/interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
}
}
discard("Discard edits (rollback 0)"), discard_changes();

View file

@ -55,3 +55,8 @@ int strverscmp (__const char *__s1, __const char *__s2);
/* Full xmlns validation check is made only if XML has associated YANG spec
*/
#define XMLNS_YANG_ONLY 1
/* Set if you want all old xpath functions in clixon_xsl.* to use the new
* xpath functions in clixon_xpath.*
*/
#undef XPATH_USE_NEW

View file

@ -81,9 +81,9 @@
#include <clixon/clixon_options.h>
#include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_db.h>
#include <clixon/clixon_xsl.h>
#include <clixon/clixon_xpath_ctx.h>
#include <clixon/clixon_xpath.h>
#include <clixon/clixon_xsl.h>
#include <clixon/clixon_json.h>
#include <clixon/clixon_netconf_lib.h>

View file

@ -85,11 +85,30 @@ extern const map_str2int xpopmap[];
*/
#if defined(__GNUC__) && __GNUC__ >= 3
int xpath_vec_nodeset(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 5)));
int xpath_vec_nodeset_flag(cxobj *xcur, char *format, uint16_t flags,
cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 6)));
cxobj *xpath_first_nodeset(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3)));
int xpath_vec_bool(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3)));
#else
int xpath_vec_nodeset(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...);
int xpath_vec_nodeset_flag(cxobj *xcur, char *format, uint16_t flags,
cxobj ***vec, size_t *veclen, ...);
cxobj *xpath_first_nodeset(cxobj *xcur, char *format, ...);
int xpath_vec_bool(cxobj *xcur, char *format, ...);
#endif
int xpath_vec_ctx(cxobj *xcur, char *xpath, xp_ctx **xrp);
/* backward compatible */
#ifdef XPATH_USE_NEW
#define xpath_first(cxtop, format, args...) xpath_first_nodeset(cxtop, format, ##args)
#define xpath_vec(cxtop, format, vec, veclen, args...) xpath_vec_nodeset(cxtop, format, vec, veclen, ##args)
#define xpath_vec_flag(cxtop, format, flags, vec, veclen, args...) xpath_vec_nodeset_flag(cxtop, format, flags, vec, veclen, ##args)
#else
#define xpath_first(cxtop, format, args...) xpath_first_xsl(cxtop, format, ##args)
#define xpath_vec(cxtop, format, vec, veclen, args...) xpath_vec_xsl(cxtop, format, vec, veclen, ##args)
#define xpath_vec_flag(cxtop, format, flags, vec, veclen, args...) xpath_vec_flag_xsl(cxtop, format, flags, vec, veclen, ##args)
#endif
#endif /* _CLIXON_XPATH_H */

View file

@ -40,16 +40,17 @@
* Prototypes
*/
#if defined(__GNUC__) && __GNUC__ >= 3
cxobj *xpath_first(cxobj *cxtop, char *format, ...) __attribute__ ((format (printf, 2, 3)));
int xpath_vec(cxobj *cxtop, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 5)));
cxobj *xpath_first_xsl(cxobj *cxtop, char *format, ...) __attribute__ ((format (printf, 2, 3)));
int xpath_vec_xsl(cxobj *cxtop, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 5)));
int xpath_vec_flag(cxobj *cxtop, char *format, uint16_t flags,
cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 6)));
#else
cxobj *xpath_first(cxobj *cxtop, char *format, ...);
int xpath_vec(cxobj *cxtop, char *format, cxobj ***vec, size_t *veclen, ...);
int xpath_vec_flag(cxobj *cxtop, char *xpath, uint16_t flags,
cxobj *xpath_first_xsl(cxobj *cxtop, char *format, ...);
int xpath_vec_xsl(cxobj *cxtop, char *format, cxobj ***vec, size_t *veclen, ...);
int xpath_vec_flag_xsl(cxobj *cxtop, char *xpath, uint16_t flags,
cxobj ***vec, size_t *veclen, ...);
#endif
cxobj *xpath_each(cxobj *xn_top, char *xpath, cxobj *prev);
#endif /* _CLIXON_XSL_H */

View file

@ -774,7 +774,7 @@ json_parse_file(int fd,
/*
* Turn this on to get a json parse and pretty print test program
* Usage: xpath
* Usage: json
* read json from input
* Example compile:
gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen

View file

@ -68,6 +68,8 @@
#include "clixon_xml.h"
#include "clixon_plugin.h"
#include "clixon_xsl.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xml_map.h"
/* Mapping between Clicon startup modes string <--> constants,

View file

@ -65,6 +65,9 @@
#include "clixon_xml.h"
#include "clixon_plugin.h"
#include "clixon_xsl.h"
#include "clixon_string.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_proto.h"
#include "clixon_err.h"
#include "clixon_proto_client.h"

View file

@ -83,6 +83,8 @@
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xsl.h"
#include "clixon_log.h"
#include "clixon_err.h"
@ -995,7 +997,7 @@ api_path_fmt2api_path(char *api_path_fmt,
* @example
* api_path_fmt: /interface/%s/address/%s
* cvv: name=eth0
* xpath: /interface/[name=eth0]/address
* xpath: /interface/[name='eth0']/address
* @example
* api_path_fmt: /ip/me/%s (if key)
* cvv: -
@ -1038,7 +1040,13 @@ api_path_fmt2xpath(char *api_path_fmt,
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
cprintf(cb, "[%s=%s]", cv_name_get(cv), str);
cprintf(cb,
#ifdef XPATH_USE_NEW
"[%s='%s']",
#else
"[%s=%s]",
#endif
cv_name_get(cv), str);
free(str);
}
}
@ -1455,7 +1463,13 @@ api_path2xpath_cvv(yang_spec *yspec,
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath, "[%s=%s]", cv_string_get(cvi), v);
cprintf(xpath,
#ifdef XPATH_USE_NEW
"[%s='%s']",
#else
"[%s=%s]",
#endif
cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)

View file

@ -33,6 +33,10 @@
* 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>
@ -254,7 +258,6 @@ xp_eval_step(xp_ctx *xc0,
cxobj **vec = NULL;
size_t veclen = 0;
xpath_tree *nodetest = xs->xs_c0;
xp_ctx *xr0 = NULL;
xp_ctx *xc = NULL;
/* Create new xc */
@ -312,16 +315,14 @@ xp_eval_step(xp_ctx *xc0,
case A_NAMESPACE: /* principal node type is namespace */
break;
case A_PARENT:
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;
for (i=0; i<xc->xc_size; i++){
x = xc->xc_nodeset[i];
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, &xr0->xc_nodeset, &xr0->xc_size) < 0)
if (cxvec_append(xp, &xc->xc_nodeset, &xc->xc_size) < 0)
goto done;
}
break;
@ -330,19 +331,20 @@ xp_eval_step(xp_ctx *xc0,
case A_PRECEEDING_SIBLING:
break;
case A_SELF:
xr0 = ctx_dup(xc);
break;
default:
clicon_err(OE_XML, 0, "No such axisname: %d", xs->xs_int);
goto done;
break;
}
if (xr0)
*xrp = xr0;
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:
@ -623,8 +625,8 @@ xp_relop(xp_ctx *xc1,
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 a
node in the first node-set and in the second node-set is true */
/* 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;
@ -659,7 +661,11 @@ xp_relop(xp_ctx *xc1,
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:
@ -711,10 +717,10 @@ xp_relop(xp_ctx *xc1,
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);
s2 = xc2->xc_string;
switch(op){
case XO_EQ:
xr->xc_bool = (strcmp(s1, s2)==0);
@ -727,6 +733,8 @@ xp_relop(xp_ctx *xc1,
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
break;
case XT_NUMBER:
@ -759,6 +767,8 @@ xp_relop(xp_ctx *xc1,
goto done;
break;
}
if (xr->xc_bool) /* enough to find a single node */
break;
}
break;
default:
@ -862,7 +872,7 @@ xp_eval(xp_ctx *xc,
x = xc->xc_node;
while (xml_parent(x) != NULL)
x = xml_parent(x);
// xc->xc_node = x;
xc->xc_node = x;
xc->xc_nodeset[0] = x;
xc->xc_size=1;
/* // is short for /descendant-or-self::node()/ */
@ -921,7 +931,6 @@ xp_eval(xp_ctx *xc,
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;
}
}
break;
@ -1027,7 +1036,6 @@ xp_eval(xp_ctx *xc,
}
/*! Given XML tree and xpath, returns xpath context
* @param[in] xcur XML-tree where to search
* @param[in] xpath String with XPATH 1.0 syntax
* @param[out] xrp Return XPATH context
@ -1081,6 +1089,45 @@ xpath_vec_ctx(cxobj *xcur,
return retval;
}
cxobj *
xpath_first_nodeset(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
@ -1135,6 +1182,81 @@ xpath_vec_nodeset(cxobj *xcur,
return retval;
}
/* A restricted 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_nodeset_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);
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
* @param[in] xcur xml-tree where to search
* @param[in] xpath stdarg string with XPATH 1.0 syntax

View file

@ -239,8 +239,8 @@ rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL
;
step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(1,"step->axisspec(%d) nodetest", $1); }
| '.' { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, NULL); clicon_debug(1,"step-> ."); }
| DOUBLEDOT { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, NULL); clicon_debug(1,"step-> .."); }
| '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> ."); }
| DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> .."); }
;
axisspec : AXISNAME DOUBLECOLON { clicon_debug(1,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}

View file

@ -42,6 +42,10 @@ The code is implemented according to XPATH 1.0:
The primary syntactic construct in XPath is the expression. An expression matches
the production Expr (see https://www.w3.org/TR/xpath-10/#NT-Expr)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -896,7 +900,7 @@ xpath_first0(cxobj *xcur,
* @see also xpath_vec.
*/
cxobj *
xpath_first(cxobj *xcur,
xpath_first_xsl(cxobj *xcur,
char *format,
...)
{
@ -1012,7 +1016,7 @@ xpath_each(cxobj *xcur,
* @see also xpath_first, xpath_each.
*/
int
xpath_vec(cxobj *xcur,
xpath_vec_xsl(cxobj *xcur,
char *format,
cxobj ***vec,
size_t *veclen,
@ -1048,7 +1052,6 @@ xpath_vec(cxobj *xcur,
return retval;
}
/* A restricted 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
@ -1073,7 +1076,7 @@ xpath_vec(cxobj *xcur,
* @see also xpath_vec This is a specialized version.
*/
int
xpath_vec_flag(cxobj *xcur,
xpath_vec_flag_xsl(cxobj *xcur,
char *format,
uint16_t flags,
cxobj ***vec,

View file

@ -545,7 +545,6 @@ cv_validate1(cg_var *cv,
if ((vec = clicon_strsep(str, " \t", &nvec)) == NULL)
goto done;
for (i=0; i<nvec; i++){
clicon_log(LOG_NOTICE, "%s: vec[i]: %s", __FUNCTION__, vec[i]);
if ((v = vec[i]) == NULL || !strlen(v))
continue;
found = 0;

View file

@ -5,6 +5,9 @@
testnr=0
testname=
# Set to 1 if new xpath. Set to nothing, or comment if old
#XPATH_USE_NEW=1
# For memcheck
#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli"
clixon_cli=clixon_cli

View file

@ -63,6 +63,7 @@ expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^interfaces interface eth/0/0
new "cli configure using encoded chars data <&"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 ""
new "cli configure using encoded chars name <&"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface fddi&< type ianaift:ethernetCsmacd" 0 ""

View file

@ -95,6 +95,9 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></s
new "leafref base commit"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "leafref get config"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces><interface><name>eth0</name>'
new "leafref add wrong ref"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><default-address><absname>eth3</absname><address>10.0.4.6</address></default-address></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -6,6 +6,7 @@ APPNAME=example
cfg=$dir/conf_yang.xml
fyang=$dir/netconf.yang
tmp=$dir/tmp.x
cat <<EOF > $cfg
<config>
@ -92,8 +93,18 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101"><get-con
new "Add subtree eth/0/0 using none and create which should add eth/0/0"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Too many quotes, (single inside double inside single) need to fool bash
if [ -n "$XPATH_USE_NEW" ]; then
cat <<EOF > $tmp # new
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name='eth/0/0']"/></get-config></rpc>]]>]]>
EOF
else
cat <<EOF > $tmp
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth/0/0]"/></get-config></rpc>]]>]]>
EOF
fi
new "Check eth/0/0 added using xpath"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth/0/0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
new "Re-create same eth/0/0 which should generate error"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
@ -110,11 +121,33 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><can
new "netconf edit config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Too many quotes
if [ -n "$XPATH_USE_NEW" ]; then
cat <<EOF > $tmp # new
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name='eth1']/enabled"/></get-config></rpc>]]>]]>
EOF
else
cat <<EOF > $tmp # new
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>
EOF
fi
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^<rpc-reply><data><interfaces><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
# Too many quotes
if [ -n "$XPATH_USE_NEW" ]; then
cat <<EOF > $tmp # new
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name='eth1']/enabled/../.."/></get-config></rpc>]]>]]>
EOF
else
cat <<EOF > $tmp # old
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>
EOF
fi
new "netconf get config xpath parent"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></data></rpc-reply>]]>]]>$"
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
@ -157,7 +190,7 @@ new "netconf edit CDATA"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name><type>ex:eth</type><description><![CDATA[myeth&]]></description></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
#new "netconf get CDATA"
#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/interfaces/interface[name=eth/0/0]/description\" /></get-config></rpc>]]>]]>" "<rpc-reply><data><interfaces><interface><name>eth/0/0</name><description><![CDATA[myeth&]]></description><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>"
#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/interfaces/interface[name='eth/0/0']/description\" /></get-config></rpc>]]>]]>" "<rpc-reply><data><interfaces><interface><name>eth/0/0</name><description><![CDATA[myeth&]]></description><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>"
new "netconf discard-changes"

View file

@ -121,6 +121,22 @@ fi
new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><c><d>hej</d></c><l>hopp</l><y0>d</y0><y0>b</y0><y0>c</y0><y0>a</y0><y1>a</y1><y1>b</y1><y1>c</y1><y1>d</y1><y2><k>d</k><a>bar</a></y2><y2><k>a</k><a>bar</a></y2><y2><k>c</k><a>bar</a></y2><y2><k>b</k><a>bar</a></y2><y3><k>a</k><a>bar</a></y3><y3><k>b</k><a>bar</a></y3><y3><k>c</k><a>bar</a></y3><y3><k>d</k><a>bar</a></y3></data></rpc-reply>]]>]]>$"
if [ -n "$XPATH_USE_NEW" ]; then #new
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y2[k='a']\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y2><k>a</k><a>bar</a></y2></data></rpc-reply>]]>]]>$"
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y3[k='a']\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y3><k>a</k><a>bar</a></y3></data></rpc-reply>]]>]]>$"
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y2[k='b']\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y2><k>b</k><a>bar</a></y2></data></rpc-reply>]]>]]>$"
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y3[k='b']\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y3><k>b</k><a>bar</a></y3></data></rpc-reply>]]>]]>$"
else # old
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y2[k=a]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y2><k>a</k><a>bar</a></y2></data></rpc-reply>]]>]]>$"
@ -133,6 +149,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><runn
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y3[k=b]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y3><k>b</k><a>bar</a></y3></data></rpc-reply>]]>]]>$"
fi
new "delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><delete-config><target><candidate/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -8,12 +8,20 @@ PROG=../lib/src/clixon_util_xpath
# XML file (alt provide it in stdin after xpath)
xml=$dir/xml.xml
xml2=$dir/xml2.xml
xml3=$dir/xml3.xml
xml4=$dir/xml4.xml
cat <<EOF > $xml
<aaa>
<bbb x="hello"><ccc>42</ccc></bbb>
<bbb x="bye"><ccc>99</ccc></bbb>
<ddd><ccc>22</ccc></ddd>
<bbb x="hello">
<ccc>42</ccc>
</bbb>
<bbb x="bye">
<ccc>99</ccc>
</bbb>
<ddd>
<ccc>22</ccc>
</ddd>
</aaa>
EOF
@ -53,6 +61,19 @@ cat <<EOF > $xml2
</aaa>
EOF
# Multiple leaf-list
cat <<EOF > $xml3
<bbb x="hello">
<ccc>foo</ccc>
<ccc>42</ccc>
<ccc>bar</ccc>
</bbb>
<bbb x="bye">
<ccc>99</ccc>
<ccc>foo</ccc>
</bbb>
EOF
new "xpath /"
expecteof "$PROG -f $xml -p /" 0 "" "^nodeset:0:<aaa><bbb x=\"hello\"><ccc>42</ccc></bbb><bbb x=\"bye\"><ccc>99</ccc></bbb><ddd><ccc>22</ccc></ddd></aaa>$"
@ -148,4 +169,23 @@ expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" and ifMTU = 140
new "xpath ifType != \"atm\" or (ifMTU <= 17966 and ifMTU >= 64)"
expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"atm\" or (ifMTU <= 17966 and ifMTU >= 64)" "^bool:true$"
new "xpath .[name='bar']"
expecteof "$PROG -f $xml2 -p .[name='bar'] -i /aaa/bbb/routing/ribs/rib" 0 "" "^nodeset:0:<rib><name>bar</name><address-family>myfamily</address-family></rib>$"
new "Multiple entries"
new "xpath bbb[ccc='foo']"
expecteof "$PROG -f $xml3 -p bbb[ccc='foo']" 0 "" "^nodeset:0:<bbb x=\"hello\"><ccc>foo</ccc><ccc>42</ccc><ccc>bar</ccc></bbb>1:<bbb x=\"bye\"><ccc>99</ccc><ccc>foo</ccc></bbb>$"
new "xpath bbb[ccc='42']"
expecteof "$PROG -f $xml3 -p bbb[ccc='42']" 0 "" "^nodeset:0:<bbb x=\"hello\"><ccc>foo</ccc><ccc>42</ccc><ccc>bar</ccc></bbb>$"
new "xpath bbb[ccc=99] (number w/o quotes)"
expecteof "$PROG -f $xml3 -p bbb[ccc=99]" 0 "" "^nodeset:0:<bbb x=\"bye\"><ccc>99</ccc><ccc>foo</ccc></bbb>$"
new "xpath bbb[ccc='bar']"
expecteof "$PROG -f $xml3 -p bbb[ccc='bar']" 0 "" "^nodeset:0:<bbb x=\"hello\"><ccc>foo</ccc><ccc>42</ccc><ccc>bar</ccc></bbb>$"
new "xpath bbb[ccc='fie']"
expecteof "$PROG -f $xml3 -p bbb[ccc='fie']" 0 "" "^nodeset:$"
rm -rf $dir

View file

@ -136,8 +136,13 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><can
new "netconf get leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f/e\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><f><e>hej</e><e>hopp</e></f></x></data></rpc-reply>]]>]]>$"
if [ -n "$XPATH_USE_NEW" ]; then # new
new "netconf get leaf-list path"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e='hej']\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><f><e>hej</e><e>hopp</e></f></x></data></rpc-reply>]]>]]>$"
else # old
new "netconf get leaf-list path"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e=hej]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><f><e>hej</e><e>hopp</e></f></x></data></rpc-reply>]]>]]>$"
fi
new "netconf get (should be some)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply><data><x><y><a>1</a><b>2</b><c>5</c><val>one</val></y><d/></x></data></rpc-reply>]]>]]>$"
@ -154,7 +159,10 @@ new "netconf set presence and not present"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><x><nopresence/><presence/></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get presence only"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/x/*presence"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><x><presence/></x></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/x/presence"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><x><presence/></x></data></rpc-reply>]]>]]>$"
new "netconf get presence only"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/x/nopresence"/></get-config></rpc>]]>]]>' "^<rpc-reply><data/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"