More XPath function support

* `count`, `name`, `contains`, `not`, as defined in [xpath 1.0](https://www.w3.org/TR/xpath-10)
  * `deref`, `derived-from` and `derived-from-or-self` from RFC7950 Section 10.
    * in particular in augment/when statements
  * Improved error handling
    * Verification of XPath functions is done at startup when yang modules are loaded, not when XPaths are evaluated.
    * Separation of "not found" and "not implemented" XPath functions
    * Both give a fatal error (backend does not start).
This commit is contained in:
Olof hagsand 2020-09-23 15:35:01 +02:00
parent 2994d2f9a9
commit 21ac47915b
14 changed files with 715 additions and 132 deletions

View file

@ -60,7 +60,6 @@
%token <string> APOST
%token <string> CHARS
%token <string> NAME
%token <string> NODETYPE
%token <string> DOUBLEDOT
%token <string> DOUBLESLASH
%token <string> FUNCTIONNAME
@ -75,6 +74,7 @@
%type <stack> addexpr
%type <stack> unionexpr
%type <stack> pathexpr
%type <stack> filterexpr
%type <stack> locationpath
%type <stack> abslocpath
%type <stack> rellocpath
@ -122,6 +122,7 @@
#include "clixon_xml.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xpath_function.h"
#include "clixon_xpath_parse.h"
@ -135,7 +136,8 @@ void
clixon_xpath_parseerror(void *_xpy,
char *s)
{
clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'",
errno = 0;
clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'", /* Note lineno here is xpath, not yang */
_XPY->xpy_name,
_XPY->xpy_linenum ,
s,
@ -155,7 +157,17 @@ xpath_parse_exit(clixon_xpath_yacc *xpy)
{
return 0;
}
/*! Generic creator function for an xpath tree object
*
* @param[in] type XPATH tree node type
* @param[in] i0 step-> axis_type
* @param[in] numstr original string xs_double: numeric value
* @param[in] s0 String 0 set if XP_PRIME_STR, XP_PRIME_FN, XP_NODE[_FN] prefix
* @param[in] s1 String 1 set if XP_NODE NAME
* @param[in] c0 Child 0
* @param[in] c1 Child 1
*/
static xpath_tree *
xp_new(enum xp_type type,
int i0,
@ -191,6 +203,158 @@ xp_new(enum xp_type type,
return xs;
}
/*! Specialized xpath-tree creation for xpath functions (wrapper for functions around xp_new)
*
* Sanity check xpath functions before adding them
* @param[in] xpy XPath parse handle
* @param[in] name Name of function
* @param[in] xpt Sub-parse-tree
*/
static xpath_tree *
xp_primary_function(clixon_xpath_yacc *xpy,
char *name,
xpath_tree *xpt)
{
xpath_tree *xtret = NULL;
enum clixon_xpath_function fn;
cbuf *cb = NULL;
if ((fn = xp_fnname_str2int(name)) < 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unknown xpath function \"%s\"", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
}
switch (fn){
case XPATHFN_RE_MATCH: /* Group of NOT IMPLEMENTED xpath functions */
case XPATHFN_ENUM_VALUE:
case XPATHFN_BIT_IS_SET:
case XPATHFN_LAST:
case XPATHFN_POSITION:
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_BOOLEAN:
case XPATHFN_TRUE:
case XPATHFN_FALSE:
case XPATHFN_LANG:
case XPATHFN_NUMBER:
case XPATHFN_SUM:
case XPATHFN_FLOOR:
case XPATHFN_CEILING:
case XPATHFN_ROUND:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "XPATH function \"%s\" is not implemented", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
break;
case XPATHFN_CURRENT: /* Group of implemented xpath functions */
case XPATHFN_DEREF:
case XPATHFN_DERIVED_FROM:
case XPATHFN_DERIVED_FROM_OR_SELF:
case XPATHFN_COUNT:
case XPATHFN_NAME:
case XPATHFN_CONTAINS:
case XPATHFN_NOT:
break;
default:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unknown xpath function \"%s\"", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
break;
}
if (cb)
cbuf_free(cb);
xtret = xp_new(XP_PRIME_FN, fn, NULL, name, NULL, xpt, NULL);
name = NULL;
done:
if (name)
free(name);
if (cb)
cbuf_free(cb);
return xtret;
}
/*! Specialized xpath-tree creation for xpath nodetest functions (wrapper for functions around xp_new)
*
* Sanity check xpath functions before adding them
* @param[in] xpy XPath parse handle
* @param[in] name Name of function
* @param[in] xpt Sub-parse-tree
*/
static xpath_tree *
xp_nodetest_function(clixon_xpath_yacc *xpy,
char *name,
xpath_tree *xpt)
{
xpath_tree *xtret = NULL;
enum clixon_xpath_function fn;
cbuf *cb = NULL;
if ((fn = xp_fnname_str2int(name)) < 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unknown xpath function \"%s\"", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
}
switch (fn){
case XPATHFN_COMMENT: /* Group of not implemented node functions */
case XPATHFN_PROCESSING_INSTRUCTIONS:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "XPATH function \"%s\" is not implemented", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
break;
case XPATHFN_TEXT: /* Group of implemented node functions */
case XPATHFN_NODE:
break;
default:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unknown xpath nodetest function \"%s\"", name);
clixon_xpath_parseerror(xpy, cbuf_get(cb));
goto done;
break;
}
if (cb)
cbuf_free(cb);
xtret = xp_new(XP_NODE_FN, fn, NULL, name, NULL, xpt, NULL);
name = NULL; /* Consumed by xp_new */
done:
if (cb)
cbuf_free(cb);
if (name)
free(name);
return xtret;
}
%}
%%
@ -224,7 +388,13 @@ unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,NULL,NULL,NULL,$
;
pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"pathexpr-> locationpath"); }
| primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"pathexpr-> primaryexpr"); }
| filterexpr { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"pathexpr-> filterexpr"); }
| filterexpr '/' rellocpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, $3);clicon_debug(3,"pathexpr-> filterexpr / rellocpath"); }
/* Filterexpr // relativelocationpath */
;
filterexpr : primaryexpr { $$=xp_new(XP_FILTEREXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"filterexpr-> primaryexpr"); }
/* Filterexpr predicate */
;
/* location path returns a node-set */
@ -251,28 +421,27 @@ step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,NULL, NULL, NUL
axisspec : AXISNAME { clicon_debug(3,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}
| '@' { $$=A_ATTRIBUTE; clicon_debug(3,"axisspec-> @"); }
| { clicon_debug(3,"axisspec-> "); $$=A_CHILD;}
;
;
nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,NULL, NULL, NULL, NULL, NULL); clicon_debug(3,"nodetest-> *"); }
| NAME { $$=xp_new(XP_NODE,A_NAN,NULL, NULL, $1, NULL, NULL); clicon_debug(3,"nodetest-> name(%s)",$1); }
| NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,NULL, $1, $3, NULL, NULL);clicon_debug(3,"nodetest-> name(%s) : name(%s)", $1, $3); }
| NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,NULL, $1, NULL, NULL, NULL);clicon_debug(3,"nodetest-> name(%s) : *", $1); }
| NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,NULL, $1, NULL, NULL, NULL); clicon_debug(3,"nodetest-> nodetype():%s", $1); }
| FUNCTIONNAME ')' { if (($$ = xp_nodetest_function(_XPY, $1, NULL)) == NULL) YYERROR;; clicon_debug(3,"nodetest-> nodetype():%s", $1); }
;
/* evaluates to boolean */
predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,NULL, NULL, NULL, $1, $3); clicon_debug(3,"predicates-> [ expr ]"); }
| { $$=xp_new(XP_PRED,A_NAN,NULL, NULL, NULL, NULL, NULL); clicon_debug(3,"predicates->"); }
;
primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,NULL, NULL, NULL, $2, NULL); clicon_debug(3,"primaryexpr-> ( expr )"); }
| NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> NUMBER(%s)", $1); /*XXX*/}
| QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,NULL, $2, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> \" string \""); }
| QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,NULL, NULL, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> \" \""); }
| APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,NULL, $2, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> ' string '"); }
| APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,NULL, NULL, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> ' '"); }
| FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,NULL, $1, NULL, NULL, NULL);clicon_debug(3,"primaryexpr-> functionname ( arguments )"); }
| FUNCTIONNAME '(' args ')' { $$=xp_new(XP_PRIME_FN,A_NAN,NULL, $1, NULL, $3, NULL);clicon_debug(3,"primaryexpr-> functionname ( arguments )"); }
| FUNCTIONNAME ')' { if (($$ = xp_primary_function(_XPY, $1, NULL)) == NULL) YYERROR; clicon_debug(3,"primaryexpr-> functionname ()"); }
| FUNCTIONNAME args ')' { if (($$ = xp_primary_function(_XPY, $1, $2)) == NULL) YYERROR; clicon_debug(3,"primaryexpr-> functionname (arguments)"); }
;
args : args ',' expr { $$=xp_new(XP_EXP,A_NAN,NULL,NULL,NULL,$1, $3);