diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6b3214..6218c127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c index 1b677183..0e90b8bf 100644 --- a/lib/src/clixon_xpath_eval.c +++ b/lib/src/clixon_xpath_eval.c @@ -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; diff --git a/lib/src/clixon_xpath_function.c b/lib/src/clixon_xpath_function.c index a322253f..2bd19473 100644 --- a/lib/src/clixon_xpath_function.c +++ b/lib/src/clixon_xpath_function.c @@ -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, ®exp) < 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) @@ -542,12 +666,12 @@ xp_function_name(xp_ctx *xc, int localonly, xp_ctx **xrp) { - int retval = -1; - xp_ctx *xr = NULL; - xp_ctx *xr0 = NULL; - char *s0 = NULL; - int i; - cxobj *x; + int retval = -1; + xp_ctx *xr = NULL; + xp_ctx *xr0 = NULL; + char *s0 = NULL; + int i; + cxobj *x; if (xs == NULL || xs->xs_c0 == NULL){ clixon_err(OE_XML, EINVAL, "not expects but did not get one argument"); @@ -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,18 +719,75 @@ 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) { - int retval = -1; - xp_ctx *xr0 = NULL; - xp_ctx *xr1 = NULL; - xp_ctx *xr = NULL; - char *s0 = NULL; - char *s1 = NULL; + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr = NULL; + char *s0 = NULL; + char *s1 = NULL; if (xs == NULL || xs->xs_c0 == NULL || xs->xs_c1 == NULL){ clixon_err(OE_XML, EINVAL, "contains expects but did not get two arguments"); @@ -627,7 +808,10 @@ xp_function_contains(xp_ctx *xc, } memset(xr, 0, sizeof(*xr)); xr->xc_type = XT_BOOL; - xr->xc_bool = (strstr(s0, s1) != NULL); + if (starts) + xr->xc_bool = (strncmp(s0, s1, strlen(s1)) == 0); + else + xr->xc_bool = (strstr(s0, s1) != NULL); *xrp = xr; xr = NULL; retval = 0; @@ -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; ixc_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: diff --git a/lib/src/clixon_xpath_function.h b/lib/src/clixon_xpath_function.h index 23f0774b..64206eb0 100644 --- a/lib/src/clixon_xpath_function.h +++ b/lib/src/clixon_xpath_function.h @@ -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); diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index d0642682..ac2d28ec 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -120,7 +120,7 @@ #include "clixon_queue.h" #include "clixon_hash.h" -#include "clixon_handle.h" +#include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_err.h" @@ -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: diff --git a/test/test_xpath.sh b/test/test_xpath.sh index e58ca4ab..7224c010 100755 --- a/test/test_xpath.sh +++ b/test/test_xpath.sh @@ -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 < $xml @@ -71,6 +70,8 @@ cat < $xml2 ethernet 1500 urn:example:foo + 7 + 10.22.33.44 EOF @@ -167,7 +168,6 @@ expectpart "$($clixon_util_xpath -D $DBG -f $xml -p aaa)" 0 "^nodeset:0:42 1:99$" @@ -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 diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index dbc44fa8..fcbff2fa 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -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: