New: [feature request: support xpath functions for strings](https://github.com/clicon/clixon/issues/556)

Added: re-match, substring, string, string-length, translate, substring-before, substring-after, starts-with
This commit is contained in:
Olof hagsand 2024-11-01 15:11:28 +01:00
parent 3188e3cc59
commit 515d30bdd7
7 changed files with 741 additions and 45 deletions

View file

@ -15,6 +15,10 @@
## 7.3.0
Expected: January 2025
### Features
* New: [feature request: support xpath functions for strings](https://github.com/clicon/clixon/issues/556)
* Added: re-match, substring, string, string-length, translate, substring-before, substring-after, starts-with
### Corrected Bugs
* Fixed: [string length validation doesn't work for the entry "" in case it has default value specified](https://github.com/clicon/clixon/issues/563)

View file

@ -1197,6 +1197,11 @@ xp_eval(xp_ctx *xc,
goto done;
goto ok;
break;
case XPATHFN_RE_MATCH:
if (xp_function_re_match(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_DEREF:
if (xp_function_deref(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
@ -1232,8 +1237,43 @@ xp_eval(xp_ctx *xc,
goto done;
goto ok;
break;
case XPATHFN_STRING:
if (xp_function_string(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_STARTS_WITH:
if (xp_function_contains(xc, xs->xs_c0, nsc, 1, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_CONTAINS:
if (xp_function_contains(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
if (xp_function_contains(xc, xs->xs_c0, nsc, 0, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_SUBSTRING_BEFORE:
if (xp_function_substring_str(xc, xs->xs_c0, nsc, 1, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_SUBSTRING_AFTER:
if (xp_function_substring_str(xc, xs->xs_c0, nsc, 0, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_SUBSTRING:
if (xp_function_substring(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_STRING_LENGTH:
if (xp_function_string_length(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;
case XPATHFN_TRANSLATE:
if (xp_function_translate(xc, xs->xs_c0, nsc, localonly, xrp) < 0)
goto done;
goto ok;
break;

View file

@ -66,6 +66,7 @@
#include "clixon_log.h"
#include "clixon_debug.h"
#include "clixon_options.h"
#include "clixon_regex.h"
#include "clixon_yang_type.h"
#include "clixon_xml_map.h"
#include "clixon_yang_module.h"
@ -82,7 +83,7 @@
static const map_str2int xpath_fnname_map[] = { /* alphabetic order */
{"bit-is-set", XPATHFN_BIT_IS_SET},
{"boolean", XPATHFN_BOOLEAN},
{"eiling", XPATHFN_CEILING},
{"ceiling", XPATHFN_CEILING},
{"comment", XPATHFN_COMMENT},
{"concat", XPATHFN_CONCAT},
{"contains", XPATHFN_CONTAINS},
@ -110,6 +111,7 @@ static const map_str2int xpath_fnname_map[] = { /* alphabetic order */
{"round", XPATHFN_ROUND},
{"starts-with", XPATHFN_STARTS_WITH},
{"string", XPATHFN_STRING},
{"string-length", XPATHFN_STRING_LENGTH},
{"substring", XPATHFN_SUBSTRING},
{"substring-after", XPATHFN_SUBSTRING_AFTER},
{"substring-before", XPATHFN_SUBSTRING_BEFORE},
@ -136,6 +138,17 @@ xp_fnname_int2str(enum clixon_xpath_function code)
return clicon_int2str(xpath_fnname_map, code);
}
/*! Returns a node set with the initial context node as its only member.
*
* @param[in] xc0 Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see RFC 7950 10.1.1
*/
int
xp_function_current(xp_ctx *xc0,
struct xpath_tree *xs,
@ -162,6 +175,117 @@ xp_function_current(xp_ctx *xc0,
return retval;
}
/*! Returns "true" if the "subject" string matches the regular expression "pattern";
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see RFC 7950 10.2.1
* @note Uses xml2 regexp if libxml2 enabled, otherwise posix
* This means for xml2, you have to configure BOTH cligen and clixon with --with-libxml2
* @note Compiling regexp takes a lot of resources, no caching is made of re here
* as is done for eg YANG patterns
* Example: re-match("1.22.333", "\d{1,3}\.\d{1,3}\.\d{1,3}") returns true
*/
int
xp_function_re_match(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
char *regexp = NULL;
char *posix = NULL;
void *re = NULL;
int ret;
if (xs == NULL || xs->xs_c0 == NULL || xs->xs_c1 == NULL){
clixon_err(OE_XML, EINVAL, "contains expects but did not get two arguments");
goto done;
}
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
if (xp_eval(xc, xs->xs_c1, nsc, localonly, &xr1) < 0)
goto done;
if (ctx2string(xr1, &regexp) < 0)
goto done;
#ifdef HAVE_LIBXML2
if ((ret = cligen_regex_libxml2_compile(regexp, &re)) < 0)
goto done;
#else
if (regexp_xsd2posix(regexp, &posix) < 0)
goto done;
if ((ret = cligen_regex_posix_compile(posix, &re)) < 0)
goto done;
#endif
if (ret == 0){
clixon_err(OE_YANG, 0, "regexp compile fail: \"%s\"", regexp);
goto done;
}
// s0 = "1.22.333";
#ifdef HAVE_LIBXML2
if ((ret = cligen_regex_libxml2_exec(re, s0)) < 0)
goto done;
#else
if ((ret = cligen_regex_posix_exec(re, s0)) < 0)
goto done;
#endif
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_BOOL;
xr->xc_bool = ret;
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (re){
#ifdef HAVE_LIBXML2
cligen_regex_libxml2_free(re);
#else
cligen_regex_posix_free(re);
free(re);
#endif
}
if (xr0)
ctx_free(xr0);
if (xr1)
ctx_free(xr1);
if (s0)
free(s0);
if (regexp)
free(regexp);
if (posix)
free(posix);
return retval;
}
/*! Follows reference defined by the first node and returns nodes it refers to
*
* @param[in] xc0 Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see RFC 7950 10.3.1
*/
int
xp_function_deref(xp_ctx *xc0,
struct xpath_tree *xs,
@ -316,7 +440,7 @@ derived_from_one(char *baseidentity,
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see rfc7950 10.4.1
* @see RFC7950 10.4.1
* Returns "true" if any node in the argument "nodes" is a node of type "identityref" and its
* value is an identity that is derived from (see Section 7.18.2) the identity "identity"
* boolean derived-from(node-set nodes, string identity)
@ -583,7 +707,7 @@ xp_function_name(xp_ctx *xc,
return retval;
}
/*! Eval xpath function contains
/*! Eval xpath function converts an object to a string
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
@ -595,9 +719,66 @@ xp_function_name(xp_ctx *xc,
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
*/
int
xp_function_string(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr0 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
if (xs != NULL && xs->xs_c0){
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
}
else {
if ((s0 = strdup("")) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_STRING;
xr->xc_string = s0;
s0 = NULL;
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr0)
ctx_free(xr0);
if (s0)
free(s0);
return retval;
}
/*! Eval xpath function contains sub-string
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] starts 0: contains, 1: starts with
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
*/
int
xp_function_contains(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int starts,
int localonly,
xp_ctx **xrp)
{
@ -627,6 +808,9 @@ xp_function_contains(xp_ctx *xc,
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_BOOL;
if (starts)
xr->xc_bool = (strncmp(s0, s1, strlen(s1)) == 0);
else
xr->xc_bool = (strstr(s0, s1) != NULL);
*xrp = xr;
xr = NULL;
@ -643,6 +827,363 @@ xp_function_contains(xp_ctx *xc,
return retval;
}
/*! Eval xpath function contains sub-string
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] before 0:Return substring after, 1: before
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
* Example: substring-before("1999/04/01","/") returns "1999"
*/
int
xp_function_substring_str(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int before,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
char *s1 = NULL;
char *sp;
if (xs == NULL || xs->xs_c0 == NULL || xs->xs_c1 == NULL){
clixon_err(OE_XML, EINVAL, "contains expects but did not get two arguments");
goto done;
}
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
if (xp_eval(xc, xs->xs_c1, nsc, localonly, &xr1) < 0)
goto done;
if (ctx2string(xr1, &s1) < 0)
goto done;
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_STRING;
sp = strstr(s0, s1);
if (before) {
if (sp != NULL)
*sp = '\0';
else
*s0 = '\0';
if ((xr->xc_string = strdup(s0)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else {
if (sp)
sp += strlen(s1);
if (sp == NULL)
sp = "";
if ((xr->xc_string = strdup(sp)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr0)
ctx_free(xr0);
if (xr1)
ctx_free(xr1);
if (s0)
free(s0);
if (s1)
free(s1);
return retval;
}
/*! Eval xpath function return substring
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
*/
int
xp_function_substring(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
struct xpath_tree *a0;
struct xpath_tree *a1;
struct xpath_tree *a2;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xr2 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
char *s0p;
double d1;
double d2;
int32_t i1;
int32_t i10;
int32_t i2;
int32_t it;
if (xs == NULL || xs->xs_c0 == NULL || xs->xs_c1 == NULL){
clixon_err(OE_XML, EINVAL, "contains expects but did not get two arguments");
goto done;
}
if (xs->xs_c0->xs_c1 != NULL){
a0 = xs->xs_c0->xs_c0;
a1 = xs->xs_c0->xs_c1;
a2 = xs->xs_c1;
}
else {
a0 = xs->xs_c0;
a1 = xs->xs_c1;
a2 = NULL;
}
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, a0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
if (xp_eval(xc, a1, nsc, localonly, &xr1) < 0)
goto done;
if (ctx2number(xr1, &d1) < 0)
goto done;
if ((i10 = round(d1)-1) < 0)
i1 = 0;
else
i1 = i10;
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_STRING;
if (i1 < strlen(s0))
s0p = &s0[i1];
else
s0p = "";
if (a2) {
if (xp_eval(xc, a2, nsc, localonly, &xr2) < 0)
goto done;
if (ctx2number(xr2, &d2) < 0)
goto done;
if ((i2 = round(d2)) < 0)
i2 = 0;
it = i10+i2;
if (it < (int)strlen(s0)){
if (it < 0)
*s0p = '\0';
else
*(s0+i10+i2) = '\0';
}
}
if ((xr->xc_string = strdup(s0p)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr0)
ctx_free(xr0);
if (xr1)
ctx_free(xr1);
if (xr2)
ctx_free(xr2);
if (s0)
free(s0);
return retval;
}
/*! Eval xpath function returns the number of characters in the string
*
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
* XXX Dont know how to implement string-length() without arg
*/
int
xp_function_string_length(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
xp_ctx *xr0 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
if (xs != NULL && xs->xs_c0){
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
}
else {
if ((s0 = strdup("")) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_NUMBER;
xr->xc_number = strlen(s0);
s0 = NULL;
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr0)
ctx_free(xr0);
if (s0)
free(s0);
return retval;
}
/*! Eval xpath function replaces characters in the first string
*
* Returns the first argument string with occurrences of characters in the second argument string
* replaced by the character at the corresponding position in the third argument string.
* @param[in] xc Incoming context
* @param[in] xs XPath node tree
* @param[in] nsc XML Namespace context
* @param[in] localonly Skip prefix and namespace tests (non-standard)
* @param[out] xrp Resulting context
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions
*/
int
xp_function_translate(xp_ctx *xc,
struct xpath_tree *xs,
cvec *nsc,
int localonly,
xp_ctx **xrp)
{
int retval = -1;
struct xpath_tree *a0;
struct xpath_tree *a1;
struct xpath_tree *a2;
xp_ctx *xr0 = NULL;
xp_ctx *xr1 = NULL;
xp_ctx *xr2 = NULL;
xp_ctx *xr = NULL;
char *s0 = NULL;
char *s1 = NULL;
char *s2 = NULL;
cbuf *cb = NULL;
char *p1;
char ch;
int i;
int j;
if (xs == NULL || xs->xs_c0 == NULL || xs->xs_c1 == NULL){
clixon_err(OE_XML, EINVAL, "contains expects but did not get two arguments");
goto done;
}
if (xs->xs_c0->xs_c1 != NULL){
a0 = xs->xs_c0->xs_c0;
a1 = xs->xs_c0->xs_c1;
a2 = xs->xs_c1;
}
else {
a0 = xs->xs_c0;
a1 = xs->xs_c1;
a2 = NULL;
}
/* contains two arguments in xs: boolean contains(string, string) */
if (xp_eval(xc, a0, nsc, localonly, &xr0) < 0)
goto done;
if (ctx2string(xr0, &s0) < 0)
goto done;
if (xp_eval(xc, a1, nsc, localonly, &xr1) < 0)
goto done;
if (ctx2string(xr1, &s1) < 0)
goto done;
if (a2) {
if (xp_eval(xc, a2, nsc, localonly, &xr2) < 0)
goto done;
if (ctx2string(xr2, &s2) < 0)
goto done;
}
if ((xr = malloc(sizeof(*xr))) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xr, 0, sizeof(*xr));
xr->xc_type = XT_STRING;
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
for (i=0; i<strlen(s0); i++){
ch = s0[i];
if ((p1 = strchr(s1, s0[i])) != NULL){
j = p1 - s1;
if (j < strlen(s2))
cprintf(cb, "%c", s2[j]);
}
else
cprintf(cb, "%c", ch);
}
if ((xr->xc_string = strdup(cbuf_get(cb))) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
*xrp = xr;
xr = NULL;
retval = 0;
done:
if (xr0)
ctx_free(xr0);
if (xr1)
ctx_free(xr1);
if (xr2)
ctx_free(xr2);
if (s0)
free(s0);
if (s1)
free(s1);
if (s2)
free(s2);
if (cb)
cbuf_free(cb);
return retval;
}
/*! The boolean function converts its argument to a boolean
*
* Conversion is as follows:

View file

@ -42,13 +42,13 @@
* Types
*/
/*
* XPath functions from Xpath 1.0 spec or YANG
* XPath functions from XPath 1.0 spec or YANG
* @see xp_eval,xp_primary_function where they are parsed and checked
* @see clixon_xpath_function.ch] for implementation
* @see clixon_xpath_function.ch for implementation
*/
enum clixon_xpath_function{
XPATHFN_CURRENT, /* RFC 7950 10.1.1 */
XPATHFN_RE_MATCH, /* RFC 7950 10.2.1 NYI */
XPATHFN_RE_MATCH, /* RFC 7950 10.2.1 */
XPATHFN_DEREF, /* RFC 7950 10.3.1 */
XPATHFN_DERIVED_FROM, /* RFC 7950 10.4.1 */
XPATHFN_DERIVED_FROM_OR_SELF, /* RFC 7950 10.4.2 */
@ -61,16 +61,16 @@ enum clixon_xpath_function{
XPATHFN_LOCAL_NAME, /* XPATH 1.0 4.1 NYI */
XPATHFN_NAMESPACE_URI, /* XPATH 1.0 4.1 NYI */
XPATHFN_NAME, /* XPATH 1.0 4.1 */
XPATHFN_STRING, /* XPATH 1.0 4.2 NYI */
XPATHFN_STRING, /* XPATH 1.0 4.2 */
XPATHFN_CONCAT, /* XPATH 1.0 4.2 NYI */
XPATHFN_STARTS_WITH, /* XPATH 1.0 4.2 NYI */
XPATHFN_STARTS_WITH, /* XPATH 1.0 4.2 */
XPATHFN_CONTAINS, /* XPATH 1.0 4.2 */
XPATHFN_SUBSTRING_BEFORE, /* XPATH 1.0 4.2 NYI */
XPATHFN_SUBSTRING_AFTER, /* XPATH 1.0 4.2 NYI */
XPATHFN_SUBSTRING, /* XPATH 1.0 4.2 NYI */
XPATHFN_STRING_LENGTH, /* XPATH 1.0 4.2 NYI */
XPATHFN_SUBSTRING_BEFORE, /* XPATH 1.0 4.2 */
XPATHFN_SUBSTRING_AFTER, /* XPATH 1.0 4.2 */
XPATHFN_SUBSTRING, /* XPATH 1.0 4.2 */
XPATHFN_STRING_LENGTH, /* XPATH 1.0 4.2 */
XPATHFN_NORMALIZE_SPACE, /* XPATH 1.0 4.2 NYI */
XPATHFN_TRANSLATE, /* XPATH 1.0 4.2 NYI */
XPATHFN_TRANSLATE, /* XPATH 1.0 4.2 */
XPATHFN_BOOLEAN, /* XPATH 1.0 4.3 */
XPATHFN_NOT, /* XPATH 1.0 4.3 */
XPATHFN_TRUE, /* XPATH 1.0 4.3 */
@ -94,13 +94,19 @@ int xp_fnname_str2int(char *fnname);
const char *xp_fnname_int2str(enum clixon_xpath_function code);
int xp_function_current(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_re_match(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_deref(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_derived_from(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, int self, xp_ctx **xrp);
int xp_function_bit_is_set(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_position(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_count(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_name(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_contains(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_string(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_contains(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int starts, int localonly, xp_ctx **xrp);
int xp_function_substring_str(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int before, int localonly, xp_ctx **xrp);
int xp_function_substring(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_string_length(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_translate(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_boolean(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_not(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);
int xp_function_true(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp);

View file

@ -251,21 +251,13 @@ xp_primary_function(clixon_xpath_yacc *xpy,
}
fn = ret;
switch (fn){
case XPATHFN_RE_MATCH: /* Group of NOT IMPLEMENTED xpath functions */
case XPATHFN_ENUM_VALUE:
case XPATHFN_ENUM_VALUE: /* Group of NOT IMPLEMENTED xpath functions */
case XPATHFN_LAST:
case XPATHFN_ID:
case XPATHFN_LOCAL_NAME:
case XPATHFN_NAMESPACE_URI:
case XPATHFN_STRING:
case XPATHFN_CONCAT:
case XPATHFN_STARTS_WITH:
case XPATHFN_SUBSTRING_BEFORE:
case XPATHFN_SUBSTRING_AFTER:
case XPATHFN_SUBSTRING:
case XPATHFN_STRING_LENGTH:
case XPATHFN_NORMALIZE_SPACE:
case XPATHFN_TRANSLATE:
case XPATHFN_LANG:
case XPATHFN_NUMBER:
case XPATHFN_SUM:
@ -280,7 +272,8 @@ xp_primary_function(clixon_xpath_yacc *xpy,
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
break;
case XPATHFN_CURRENT: /* Group of implemented xpath functions */
case XPATHFN_RE_MATCH: /* Group of implemented xpath functions */
case XPATHFN_CURRENT:
case XPATHFN_DEREF:
case XPATHFN_DERIVED_FROM:
case XPATHFN_BIT_IS_SET:
@ -288,7 +281,14 @@ xp_primary_function(clixon_xpath_yacc *xpy,
case XPATHFN_POSITION:
case XPATHFN_COUNT:
case XPATHFN_NAME:
case XPATHFN_STRING:
case XPATHFN_STARTS_WITH:
case XPATHFN_CONTAINS:
case XPATHFN_SUBSTRING_BEFORE:
case XPATHFN_SUBSTRING_AFTER:
case XPATHFN_SUBSTRING:
case XPATHFN_STRING_LENGTH:
case XPATHFN_TRANSLATE:
case XPATHFN_BOOLEAN:
case XPATHFN_NOT:
case XPATHFN_TRUE:

View file

@ -1,9 +1,9 @@
#!/usr/bin/env bash
# XPATH tests
# Basic XPATH tests
# See also test_xpath_functions.sh for XPaths with YANG conditionals
# Some XPATH cases clixon cannot handle
# - /aaa/bbb/comment, where "comment" is nodetype
# - //b*, combinations of // and "*"
# For more (outdated info): https://github.com/clicon/clixon/issues/54
# Test has three parts:
# - Only XML no YANG
# - negative tests with YANG
@ -23,7 +23,6 @@ xmlfn=$dir/xmlfn.xml
fyang=$dir/clixon-example.yang
cat <<EOF > $xml
<aaa>
<bbb x="hello">
@ -71,6 +70,8 @@ cat <<EOF > $xml2
<ifType>ethernet</ifType>
<ifMTU>1500</ifMTU>
<namespace>urn:example:foo</namespace>
<mylength>7</mylength>
<myaddr>10.22.33.44</myaddr>
</bbb>
</aaa>
EOF
@ -167,7 +168,6 @@ expectpart "$($clixon_util_xpath -D $DBG -f $xml -p aaa)" 0 "^nodeset:0:<aaa><bb
new "xpath /bbb"
expectpart "$($clixon_util_xpath -D $DBG -f $xml -p /bbb)" 0 "^nodeset:$"
new "xpath /aaa/bbb"
expectpart "$($clixon_util_xpath -D $DBG -f $xml -p /aaa/bbb)" 0 "^0:<bbb x=\"hello\"><ccc>42</ccc></bbb>
1:<bbb x=\"bye\"><ccc>99</ccc></bbb>$"
@ -309,8 +309,112 @@ expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p 'derived-from(../../change-
new "xpath derived-from-or-self"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p 'derived-from-or-self(../../change-operation,"modify")')" 0 "bool:false"
new "xpath contains"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "contains(../../objectClass,'BTSFunction') or contains(../../objectClass,'RNCFunction')")" 0 "bool:false"
# re-match
new "xpath re-match match true" # example from rfc 7950
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p 're-match("1.22.333", "\d{1,3}\.\d{1,3}\.\d{1,3}")')" 0 "bool:true"
new "xpath re-match match path"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p 're-match(aaa/bbb/myaddr, "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")')" 0 "bool:true"
new "xpath re-match match path fail"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p 're-match(aaa/bbb/myaddr, "\d{1,3}\.\d{1,3}\.\d{1,3}")')" 0 "bool:false"
# string
new "xpath string empty"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "string()")" 0 "string:$"
new "xpath string path"
expectpart "$($clixon_util_xpath -D $DBG -f $xml4 -p "string(root/y/a)")" 0 "string:222$"
# starts-with
new "xpath starts-with true"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "starts-with('kalle','kal')")" 0 "bool:true"
new "xpath starts-with false"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "starts-with('kalle','all')")" 0 "bool:false"
new "xpath starts-with empty"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "starts-with('kalle','')")" 0 "bool:true"
new "xpath starts-with too long"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "starts-with('kalle','kalle42')")" 0 "bool:false"
new "xpath contains direct strings true"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "contains('kalle','all')")" 0 "bool:true"
new "xpath contains direct strings false"
expectpart "$($clixon_util_xpath -D $DBG -f $xml3 -p "contains('kalle','ball')")" 0 "bool:false"
new "xpath contains path true"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "contains(aaa/bbb/namespace,aaa/name)")" 0 "bool:true"
new "xpath contains path false"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "contains(aaa/bbb/ifMTU,aaa/name)")" 0 "bool:false"
# substring-before / after
new "xpath substring-before 1"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-before(\"1999/04/01\",\"/\")")" 0 "string:1999" --not-- "1999/"
new "xpath substring-before 2"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-before(\"1999/04/01\",\"04\")")" 0 "string:1999/" --not-- "04"
new "xpath substring-before 3"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-before(\"1999/04/01\",\"zzz\")")" 0 "string:" --not-- "string:1"
new "xpath substring-after 1"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-after(\"1999/04/01\",\"/\")")" 0 "string:04/01"
new "xpath substring-after 2"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-after(\"1999/04/01\",\"19\")")" 0 "string:99/04/01"
new "xpath substring-after 3"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring-after(\"1999/04/01\",\"z\")")" 0 "string:" --not-- "1999"
# substring, see examples in https://www.w3.org/TR/xpath-10/ Sections 4.2
new "xpath substring 1"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",2,3)")" 0 "string:234" --not-- "45"
new "xpath substring 2"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",1.5,2.6)")" 0 "string:234" --not-- "45"
new "xpath substring 3"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",0,3)")" 0 "string:12" --not-- "123"
new "xpath substring 4"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",0 div 0,3)")" 0 "string:" --not-- "12"
new "xpath substring 5"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",1, 0 div 0)")" 0 "string:" --not-- "12"
# XXX cornercase does not work
#new "xpath substring 6"
#expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",-42, 1 div 0)")" 0 "string:12345"
new "xpath substring 7"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(\"12345\",-1, 1 div 0)")" 0 "string:" --not-- "string:1"
new "xpath substring paths"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "substring(aaa/bbb/namespace,5,aaa/bbb/mylength)")" 0 "string:example" --not-- "example:"
# string-length
new "xpath string-length empty" # XXX without args not supported
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "string-length()")" 0 "number:0"
new "xpath string-length direct"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "string-length(\"12345\")")" 0 "number:5"
new "xpath string-length path"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "string-length(aaa/name)")" 0 "number:3"
# translate
new "xpath translate" # modified
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "translate(\"bar\", \"abc\",\"DEF\")")" 0 "string:EDr$"
new "xpath translate remove"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "translate(\"--aaa--\", \"abc-\",\"DEF\")")" 0 "string:DDD$"
new "xpath translate none"
expectpart "$($clixon_util_xpath -D $DBG -f $xml2 -p "translate(\"bar\", \"cde-\",\"fgh\")")" 0 "string:bar$"
# Nodetests

View file

@ -1,5 +1,6 @@
#!/usr/bin/env bash
# test xpath functions within YANG conditionals
# For basic XPath tests see test_xpath.sh
# XPATH base https://www.w3.org/TR/xpath-10/
# YANG XPATH functions: https://tools.ietf.org/html/rfc7950
# Test of xpath functions: