diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1a1db6..f1bd40ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 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()). * Support for YANG identity and identityref according to RFC 7950 Sec 7.18 and 9.10 * Previous support did no validation of values. * Validation of types and CLI expansion @@ -9,7 +12,7 @@ * Applications which have not strictly enforced the identities may now have problems with validation and may need to be modified. ### Minor changes: -* Dedicated xml,json,yang and xsl parser utility programs added +* Dedicated xml,json,yang and xpath parser utility programs added * CDATA xml support (patch by David Cornejo, Netgate) * Encode and decode (parsing) support * Validation of yang bits type space-separated list value @@ -47,7 +50,7 @@ * See FAQ and example ### Corrected Bugs -* Prefix of rpc was ignored +* Prefix of rpc was ignored (thanks Dmitri at netgate) * https://github.com/clicon/clixon/issues/30 * Added cli returna value also for single commands (eg -1) * Fixed JSON unbalanced braces resulting in assert. diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 825fbc84..bd10b8f8 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -457,7 +457,7 @@ main(int argc, char **argv) } if (!cli_syntax_mode(h)){ - fprintf (stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); + fprintf(stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); goto done; } if (cligen_tree_find(cli_cligen(h), cli_syntax_mode(h)) == NULL) diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 64f94bd8..f5781f87 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -97,7 +97,7 @@ syntax_mode_find(cli_syntax_t *stx, perror("malloc"); return NULL; } - memset (m, 0, sizeof (*m)); + memset(m, 0, sizeof(*m)); strncpy(m->csm_name, mode, sizeof(m->csm_name)-1); strncpy(m->csm_prompt, CLI_DEFAULT_PROMPT, sizeof(m->csm_prompt)-1); INSQ(m, stx->stx_modes); @@ -314,7 +314,7 @@ done: * @param[in] h Clicon handle */ int -cli_syntax_load (clicon_handle h) +cli_syntax_load(clicon_handle h) { int retval = -1; char *plugin_dir = NULL; @@ -343,7 +343,7 @@ cli_syntax_load (clicon_handle h) clicon_err(OE_UNIX, errno, "malloc"); goto done; } - memset (stx, 0, sizeof (*stx)); /* Zero out all */ + memset(stx, 0, sizeof(*stx)); /* Zero out all */ cli_syntax_set(h, stx); @@ -676,9 +676,9 @@ cli_set_prompt(clicon_handle h, * @param[in] fmt Stdarg fmt string */ static int -prompt_fmt (char *prompt, - size_t plen, - char *fmt, ...) +prompt_fmt(char *prompt, + size_t plen, + char *fmt, ...) { va_list ap; char *s = fmt; @@ -698,7 +698,7 @@ prompt_fmt (char *prompt, if (*s == '%' && *++s) { switch(*s) { case 'H': /* Hostname */ - if (gethostname (hname, sizeof (hname)) != 0) + if (gethostname(hname, sizeof(hname)) != 0) strncpy(hname, "unknown", sizeof(hname)-1); cprintf(cb, "%s", hname); break; diff --git a/datastore/keyvalue/clixon_chunk.c b/datastore/keyvalue/clixon_chunk.c index cb197724..80db76da 100644 --- a/datastore/keyvalue/clixon_chunk.c +++ b/datastore/keyvalue/clixon_chunk.c @@ -92,7 +92,7 @@ chunk_initialize () chunk_pagesz = getpagesize(); - bzero (&chunk_heads, sizeof (chunk_heads)); + bzero (&chunk_heads, sizeof(chunk_heads)); for (idx = 0; idx < CHUNK_HEADS; idx++) { chunk_head_t *chead = &chunk_heads[idx]; @@ -121,7 +121,7 @@ chunk_initialize () */ chead->ch_size = (chead->ch_blksz / chead->ch_nchkperblk) - - sizeof (chunk_t); + - sizeof(chunk_t); } @@ -150,22 +150,22 @@ chunk_new_block (chunk_head_t *chead) PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (blk == MAP_FAILED) return -1; - memset ((void *)blk, 0, sizeof(*blk)); + memset((void *)blk, 0, sizeof(*blk)); /* Allocate chunk block */ blk->cb_blk = (void *) mmap(NULL, chead->ch_blksz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (blk->cb_blk == MAP_FAILED) { - munmap (blk, chead->ch_blksz); + munmap(blk, chead->ch_blksz); return -1; } - memset (blk->cb_blk, 0, chead->ch_blksz); + memset(blk->cb_blk, 0, chead->ch_blksz); /* Initialize chunk header */ blk->cb_head = chead; - INSQ (blk, chead->ch_blks); + INSQ(blk, chead->ch_blks); chead->ch_nblks++; /* Initialize chunks */ @@ -174,10 +174,10 @@ chunk_new_block (chunk_head_t *chead) cnk = (chunk_t *)c; cnk->c_blk = blk; - INSQ (cnk, chead->ch_free); + INSQ(cnk, chead->ch_free); chead->ch_nfree++; - c += (chead->ch_size + sizeof (chunk_t)); + c += (chead->ch_size + sizeof(chunk_t)); } @@ -188,7 +188,7 @@ chunk_new_block (chunk_head_t *chead) * chunk_release_block() - Unqueue a block, it's chunks and free mem */ static void -chunk_release_block (chunk_block_t *cblk) +chunk_release_block(chunk_block_t *cblk) { int idx; char *c; @@ -201,7 +201,7 @@ chunk_release_block (chunk_block_t *cblk) /* * Dequeue block */ - DELQ (cblk, chead->ch_blks, chunk_block_t *); + DELQ(cblk, chead->ch_blks, chunk_block_t *); chead->ch_nblks--; /* @@ -211,17 +211,17 @@ chunk_release_block (chunk_block_t *cblk) for (idx = 0; idx < chead->ch_nchkperblk; idx++) { cnk = (chunk_t *)c; - DELQ (cnk, chead->ch_free, chunk_t *); + DELQ(cnk, chead->ch_free, chunk_t *); chead->ch_nfree--; - c += (chead->ch_size + sizeof (chunk_t)); + c += (chead->ch_size + sizeof(chunk_t)); } /* * Free block */ - munmap ((void *)cblk->cb_blk, chead->ch_blksz); - munmap ((void *)cblk, sizeof(*cblk)); + munmap((void *)cblk->cb_blk, chead->ch_blksz); + munmap((void *)cblk, sizeof(*cblk)); } @@ -230,7 +230,7 @@ chunk_release_block (chunk_block_t *cblk) * chunk_alloc() - Map new chunk of memory */ static void * -chunk_alloc (size_t len) +chunk_alloc(size_t len) { register int idx; chunk_head_t *chead; @@ -258,21 +258,21 @@ chunk_alloc (size_t len) /* Get new block if necessary */ if (!chead->ch_nfree) - if (chunk_new_block (chead)) + if (chunk_new_block(chead)) return (void *)NULL; /* Move a free chunk to the in-use list */ cnk = chead->ch_free; - DELQ (cnk, chead->ch_free, chunk_t *); + DELQ(cnk, chead->ch_free, chunk_t *); chead->ch_nfree--; - INSQ (cnk, chead->ch_cnks); + INSQ(cnk, chead->ch_cnks); /* Add reference to the corresponding block */ cnk->c_blk->cb_ref++; #ifdef CHUNK_DIAG /* Clear diag info */ - bzero ((void *)&cnk->c_diag, sizeof (cnk->c_diag)); + bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag)); #endif /* CHUNK_DIAG */ /* Return pointer to first byte after the chunk header */ @@ -285,9 +285,9 @@ chunk_alloc (size_t len) */ void * #ifdef CHUNK_DIAG -_chunk (size_t len, const char *name, const char *file, int line) +_chunk(size_t len, const char *name, const char *file, int line) #else -chunk (size_t len, const char *name) +chunk(size_t len, const char *name) #endif { int newgrp = 0; @@ -306,10 +306,10 @@ chunk (size_t len, const char *name) /* Get actual chunk */ - ptr = chunk_alloc (len); + ptr = chunk_alloc(len); if (!ptr) goto error; - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); #ifdef CHUNK_DIAG /* Store who reuested us @@ -329,7 +329,7 @@ chunk (size_t len, const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } @@ -343,26 +343,26 @@ chunk (size_t len, const char *name) */ if ( !grp ) { - grp = (chunk_group_t *) chunk_alloc (sizeof (chunk_group_t)); + grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t)); if (!grp) goto error; - bzero (grp, sizeof (chunk_group_t)); + bzero(grp, sizeof(chunk_group_t)); - grp->cg_name = (char *) chunk_alloc (strlen (name) + 1); + grp->cg_name = (char *) chunk_alloc(strlen(name) + 1); if (!grp->cg_name) goto error; - bcopy (name, grp->cg_name, strlen(name)+1); + bcopy(name, grp->cg_name, strlen(name)+1); newgrp = 1; } /* Get new entry. */ - ent = (chunk_grpent_t *) chunk_alloc (sizeof (chunk_grpent_t)); + ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t)); if (!ent) goto error; - bzero (ent, sizeof (chunk_grpent_t)); + bzero(ent, sizeof(chunk_grpent_t)); /* Now put everything together */ @@ -371,22 +371,22 @@ chunk (size_t len, const char *name) ent->ce_cnk = cnk; ent->ce_grp = grp; - INSQ (ent, grp->cg_ent); + INSQ(ent, grp->cg_ent); if (newgrp) - INSQ (grp, chunk_grp); + INSQ(grp, chunk_grp); return (ptr); error: if (grp && newgrp) { if (grp->cg_name) - unchunk (grp->cg_name); - unchunk (grp); + unchunk(grp->cg_name); + unchunk(grp); } if (ent) - unchunk (ent); + unchunk(ent); if (ptr) - unchunk (ptr); + unchunk(ptr); return (void *) NULL; } @@ -397,9 +397,9 @@ chunk (size_t len, const char *name) */ void * #ifdef CHUNK_DIAG -_rechunk (void *ptr, size_t len, const char *name, const char *file, int line) +_rechunk(void *ptr, size_t len, const char *name, const char *file, int line) #else -rechunk (void *ptr, size_t len, const char *name) +rechunk(void *ptr, size_t len, const char *name) #endif { int idx; @@ -414,22 +414,22 @@ rechunk (void *ptr, size_t len, const char *name) */ if (!ptr) { #ifdef CHUNK_DIAG - return _chunk (len, name, file, line); + return _chunk(len, name, file, line); #else - return chunk (len, name); + return chunk(len, name); #endif } /* Zero length, free chunk */ if (len == 0) { - unchunk (ptr); + unchunk(ptr); return (void *) NULL; } /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); chead = cnk->c_blk->cb_head; /* Find sufficient sized block head @@ -451,22 +451,22 @@ rechunk (void *ptr, size_t len, const char *name) /* Get new chunk */ #ifdef CHUNK_DIAG - new = _chunk (len, name, file, line); + new = _chunk(len, name, file, line); #else - new = chunk (len, name); + new = chunk(len, name); #endif if (!new) return (void *) NULL; - newcnk = (chunk_t *) (((char *)new) - sizeof (chunk_t)); + newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t)); newchead = newcnk->c_blk->cb_head; /* Copy contents to new chunk */ - bcopy (ptr, new, MIN(newchead->ch_size, chead->ch_size)); + bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size)); /* Free old chunk */ - unchunk (ptr); + unchunk(ptr); return (new); @@ -476,7 +476,7 @@ rechunk (void *ptr, size_t len, const char *name) * unchunk() - Release chunk */ void -unchunk (void *ptr) +unchunk(void *ptr) { chunk_t *cnk; chunk_head_t *chead; @@ -489,14 +489,14 @@ unchunk (void *ptr) /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); cblk = cnk->c_blk; chead = cblk->cb_head; /* Move chunk back to free list */ - DELQ (cnk, chead->ch_cnks, chunk_t *); - INSQ (cnk, chead->ch_free); + DELQ(cnk, chead->ch_cnks, chunk_t *); + INSQ(cnk, chead->ch_free); chead->ch_nfree++; /* If chunk is grouped, remove from group. @@ -504,13 +504,13 @@ unchunk (void *ptr) ent = cnk->c_grpent; if (ent) { grp = ent->ce_grp; - DELQ (ent, grp->cg_ent, chunk_grpent_t *); - unchunk (ent); + DELQ(ent, grp->cg_ent, chunk_grpent_t *); + unchunk(ent); cnk->c_grpent = NULL; /* Group empty? */ if (!dont_unchunk_group && !grp->cg_ent) { - DELQ (grp, chunk_grp, chunk_group_t *); + DELQ(grp, chunk_grp, chunk_group_t *); unchunk(grp->cg_name); unchunk(grp); } @@ -528,7 +528,7 @@ unchunk (void *ptr) * unchunk_group() - Release all group chunks. */ void -unchunk_group (const char *name) +unchunk_group(const char *name) { chunk_group_t *tmp; chunk_group_t *grp = NULL; @@ -542,7 +542,7 @@ unchunk_group (const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } @@ -560,16 +560,16 @@ unchunk_group (const char *name) dont_unchunk_group = 1; while (grp->cg_ent) { cnk = grp->cg_ent->ce_cnk; - unchunk ((chunk_t *)(((char *)cnk) + sizeof (chunk_t))); + unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t))); } dont_unchunk_group = 0; /* Remove group from list and free it */ - DELQ (grp, chunk_grp, chunk_group_t *); - unchunk (grp->cg_name); - unchunk (grp); + DELQ(grp, chunk_grp, chunk_group_t *); + unchunk(grp->cg_name); + unchunk(grp); } /* @@ -577,9 +577,9 @@ unchunk_group (const char *name) */ void * #ifdef CHUNK_DIAG -_chunkdup (const void *ptr, size_t len, const char *name, const char *file, int line) +_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line) #else -chunkdup (const void *ptr, size_t len, const char *name) +chunkdup(const void *ptr, size_t len, const char *name) #endif { void *new; @@ -592,9 +592,9 @@ chunkdup (const void *ptr, size_t len, const char *name) /* Get new chunk */ #ifdef CHUNK_DIAG - new = _chunk (len, name, file, line); + new = _chunk(len, name, file, line); #else - new = chunk (len, name); + new = chunk(len, name); #endif if (!new) return (void *)NULL; @@ -610,7 +610,7 @@ chunkdup (const void *ptr, size_t len, const char *name) * chunksize() - Return size of memory chunk. */ size_t -chunksize (void *ptr) +chunksize(void *ptr) { chunk_t *cnk; chunk_head_t *chead; @@ -621,7 +621,7 @@ chunksize (void *ptr) /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); cblk = cnk->c_blk; chead = cblk->cb_head; @@ -635,10 +635,10 @@ chunksize (void *ptr) */ char * #ifdef CHUNK_DIAG -_chunk_strncat (const char *dst, const char *src, size_t n, const char *name, +_chunk_strncat(const char *dst, const char *src, size_t n, const char *name, const char *file, int line) #else -chunk_strncat (const char *dst, const char *src, size_t n, const char *name) +chunk_strncat(const char *dst, const char *src, size_t n, const char *name) #endif { size_t len; @@ -651,7 +651,7 @@ chunk_strncat (const char *dst, const char *src, size_t n, const char *name) #ifdef CHUNK_DIAG ptr = _rechunk(ptr, len, name, file, line); #else - ptr = rechunk (ptr, len, name); + ptr = rechunk(ptr, len, name); #endif if (ptr == NULL) return NULL; @@ -670,10 +670,10 @@ chunk_strncat (const char *dst, const char *src, size_t n, const char *name) */ char * #ifdef CHUNK_DIAG -_chunk_sprintf (const char *name, const char *file, +_chunk_sprintf(const char *name, const char *file, int line, const char *fmt, ...) #else -chunk_sprintf (const char *name, char *fmt, ...) +chunk_sprintf(const char *name, char *fmt, ...) #endif { size_t len; @@ -683,13 +683,13 @@ chunk_sprintf (const char *name, char *fmt, ...) /* Calculate formatted string length */ va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args) + 1; - va_end (args); + va_end(args); /* get chunk */ #ifdef CHUNK_DIAG - str = _chunk (len, name, file, line); + str = _chunk(len, name, file, line); #else - str = chunk (len, name); + str = chunk(len, name); #endif if (str == NULL) return NULL; @@ -697,7 +697,7 @@ chunk_sprintf (const char *name, char *fmt, ...) /* Format string */ va_start(args, fmt); len = vsnprintf(str, len, fmt, args); - va_end (args); + va_end(args); return str; } @@ -756,7 +756,7 @@ chunk_check(FILE *fout, const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } diff --git a/datastore/keyvalue/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c index d0b72b63..b80daf71 100644 --- a/datastore/keyvalue/clixon_qdb.c +++ b/datastore/keyvalue/clixon_qdb.c @@ -436,7 +436,7 @@ db_regexp(char *file, goto quit; } pair = &newpairs[npairs]; - memset (pair, 0, sizeof(*pair)); + memset(pair, 0, sizeof(*pair)); pair->dp_key = chunk_sprintf(label, "%s", key); if (regexp) @@ -451,7 +451,7 @@ db_regexp(char *file, } if ( ! noval) { if (vlen){ - pair->dp_val = chunkdup (val, vlen, label); + pair->dp_val = chunkdup(val, vlen, label); if (pair->dp_val == NULL) { clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__); goto quit; diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 05432227..25cc653c 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -81,6 +81,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 311702b5..5b5789dd 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -46,6 +46,8 @@ static const map_str2int atmap[] = { {NULL, -1} }; * @endcode + * @see clicon_int2str + * @see clicon_str2int */ struct map_str2int{ char *ms_str; diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h new file mode 100644 index 00000000..c045237d --- /dev/null +++ b/lib/clixon/clixon_xpath.h @@ -0,0 +1,95 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + */ +#ifndef _CLIXON_XPATH_H +#define _CLIXON_XPATH_H + +/* + * Types + */ +enum xp_op{ + XO_AND, + XO_OR, + XO_DIV, + XO_MOD, + XO_ADD, + XO_MULT, + XO_SUB, + XO_EQ, + XO_NE, + XO_GE, + XO_LE, + XO_LT, + XO_GT, + XO_UNION, +}; + +/* Axis specifiers according to https://www.w3.org/TR/xpath-10/#NT-AxisName */ +enum axis_type{ + A_NAN = 0, /* Not set */ + A_ANCESTOR, + A_ANCESTOR_OR_SELF, + A_ATTRIBUTE, + A_CHILD, + A_DESCENDANT, + A_DESCENDANT_OR_SELF, + A_FOLLOWING, + A_FOLLOWING_SIBLING, + A_NAMESPACE, + A_PARENT, + A_PRECEEDING, + A_PRECEEDING_SIBLING, + A_SELF, + A_ROOT /* XXX Not in https://www.w3.org/TR/xpath-10 */ +}; + +/* + * Variables + */ +extern const map_str2int xpopmap[]; + +/* + * Prototypes + */ +#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_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_bool(cxobj *xcur, char *format, ...); +#endif +int xpath_vec_ctx(cxobj *xcur, char *xpath, xp_ctx **xrp); + +#endif /* _CLIXON_XPATH_H */ diff --git a/lib/clixon/clixon_xpath_ctx.h b/lib/clixon/clixon_xpath_ctx.h new file mode 100644 index 00000000..03854495 --- /dev/null +++ b/lib/clixon/clixon_xpath_ctx.h @@ -0,0 +1,103 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * This file defines XPATH contexts using in traversing the XPATH parse tree. + */ +#ifndef _CLIXON_XPATH_CTX_H +#define _CLIXON_XPATH_CTX_H + +/* + * Types + */ + +/*! XPATH expression type + * An expression is evaluated to yield an object, which has one of the following four basic types: + * node-set (an unordered collection of nodes without duplicates) + * boolean (true or false) + * number (a floating-point number) + * string (a sequence of UCS characters) + */ +enum xp_objtype{ + XT_NODESET, + XT_BOOL, + XT_NUMBER, + XT_STRING +}; + +/* Expression evaluation occurs with respect to a context. XSLT and XPointer specify how the context is + * determined for XPath expressions used in XSLT and XPointer respectively. The context consists of: + * a node (the context node) + * a pair of non-zero positive integers (the context position and the context size) + * a set of variable bindings + * a function library + * the set of namespace declarations in scope for the expression + + * For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node, with the number of nodes + * in the node-set as the context size, and with the proximity position + * of the node in the node-set with respect to the axis as the context + * position; if PredicateExpr evaluates to true for that node, the node + * is included in the new node-set; otherwise, it is not included. + */ +struct xp_ctx{ + enum xp_objtype xc_type; + cxobj **xc_nodeset; /* if type XT_NODESET */ + size_t xc_size; /* Length of nodeset */ + int xc_bool; /* if xc_type XT_BOOL */ + double xc_number; /* if xc_type XT_NUMBER */ + char *xc_string; /* if xc_type XT_STRING */ + cxobj *xc_node; /* Node in nodeset XXX maybe not needed*/ + cxobj *xc_initial; /* RFC 7960 10.1.1 extension: for current() */ + int xc_descendant; /* // */ + /* NYI: a set of variable bindings, set of namespace declarations */ +}; +typedef struct xp_ctx xp_ctx; + +/* + * Variables + */ +extern const map_str2int ctxmap[]; + +/* + * Prototypes + */ +int ctx_free(xp_ctx *xc); +xp_ctx *ctx_dup(xp_ctx *xc); +int ctx_nodeset_replace(xp_ctx *xc, cxobj **vec, size_t veclen); +int ctx_print(cbuf *cb, int id, xp_ctx *xc, char *str); +int ctx2boolean(xp_ctx *xc); +int ctx2string(xp_ctx *xc, char **str0); +int ctx2number(xp_ctx *xc, double *n0); + +#endif /* _CLIXON_XPATH_CTX_H */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 8da9b087..931bd41d 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -72,18 +72,19 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_json.c clixon_yang.c clixon_yang_type.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_netconf_lib.c + clixon_xsl.c clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ + clixon_xml_db.c clixon_netconf_lib.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ - lex.clixon_json_parse.o clixon_json_parse.tab.o - + lex.clixon_json_parse.o clixon_json_parse.tab.o \ + lex.clixon_xpath_parse.o clixon_xpath_parse.tab.o # Extra applications. Utilities, unit testings. Not installed. APPSRC = clixon_util_xml.c APPSRC += clixon_util_json.c APPSRC += clixon_util_yang.c -APPSRC += clixon_util_xsl.c +APPSRC += clixon_util_xpath.c APPS = $(APPSRC:.c=) @@ -107,9 +108,11 @@ clean: rm -f clixon_xml_parse.tab.[ch] clixon_xml_parse.yy.[co] rm -f clixon_yang_parse.tab.[ch] clixon_yang_parse.[co] rm -f clixon_json_parse.tab.[ch] clixon_json_parse.[co] + rm -f clixon_xpath_parse.tab.[ch] clixon_xpath_parse.[co] rm -f lex.clixon_xml_parse.c rm -f lex.clixon_yang_parse.c rm -f lex.clixon_json_parse.c + rm -f lex.clixon_xpath_parse.c ############################################################################# # Implicit rules for lex and yacc. @@ -159,6 +162,18 @@ clixon_json_parse.tab.c clixon_json_parse.tab.h: clixon_json_parse.y lex.clixon_json_parse.o : lex.clixon_json_parse.c clixon_json_parse.tab.h $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< +# xpath parser +lex.clixon_xpath_parse.c : clixon_xpath_parse.l clixon_xpath_parse.tab.h + $(LEX) -Pclixon_xpath_parse clixon_xpath_parse.l # -d is debug + +clixon_xpath_parse.tab.c clixon_xpath_parse.tab.h: clixon_xpath_parse.y + $(YACC) -l -d -p clixon_xpath_parse clixon_xpath_parse.y # -t is debug + mv y.tab.c clixon_xpath_parse.tab.c + mv y.tab.h clixon_xpath_parse.tab.h + +lex.clixon_xpath_parse.o : lex.clixon_xpath_parse.c clixon_xpath_parse.tab.h + $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< + # APPS clixon_util_xml: clixon_util_xml.c $(MYLIB) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $^ $(LIBS) -o $@ @@ -169,7 +184,7 @@ clixon_util_json: clixon_util_json.c $(MYLIB) clixon_util_yang: clixon_util_yang.c $(MYLIB) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $^ $(LIBS) -o $@ -clixon_util_xsl: clixon_util_xsl.c $(MYLIB) +clixon_util_xpath: clixon_util_xpath.c $(MYLIB) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $^ $(LIBS) -o $@ distclean: clean diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index 4debe9c2..aa397d4d 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -52,24 +52,24 @@ * clicon_hash_t *hash = hash_init(); * * n = 234; - * hash_add (hash, "APA", &n, sizeof(n)); + * hash_add(hash, "APA", &n, sizeof(n)); * hash_dump(hash, stdout); * * puts("----"); * - * hash_add (hash, "BEPA", "hoppla Polle!", strlen("hoppla Polle!")+1); + * hash_add(hash, "BEPA", "hoppla Polle!", strlen("hoppla Polle!")+1); * puts((char *)hash_value(hash, "BEPA", NULL)); * hash_dump(hash, stdout); * * puts("----"); * * n = 33; - * hash_add (hash, "CEPA", &n, sizeof(n)); + * hash_add(hash, "CEPA", &n, sizeof(n)); * hash_dump(hash, stdout); * * puts("----"); * - * hash_del (hash, "APA"); + * hash_del(hash, "APA"); * hash_dump(hash, stdout); * * hash_free(hash); @@ -118,11 +118,11 @@ hash_init (void) { clicon_hash_t *hash; - if ((hash = (clicon_hash_t *)malloc (sizeof (clicon_hash_t) * HASH_SIZE)) == NULL){ + if ((hash = (clicon_hash_t *)malloc(sizeof(clicon_hash_t) * HASH_SIZE)) == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); return NULL; } - memset (hash, 0, sizeof(clicon_hash_t)*HASH_SIZE); + memset(hash, 0, sizeof(clicon_hash_t)*HASH_SIZE); return hash; } @@ -167,7 +167,7 @@ hash_lookup(clicon_hash_t *hash, h = hash[bkt]; if (h) { do { - if (!strcmp (h->h_key, key)) + if (!strcmp(h->h_key, key)) return h; h = NEXTQ(clicon_hash_t, h); } while (h != hash[bkt]); @@ -218,15 +218,15 @@ hash_add(clicon_hash_t *hash, clicon_hash_t new = NULL; /* If variable exist, don't allocate a new. just replace value */ - h = hash_lookup (hash, key); + h = hash_lookup(hash, key); if (h == NULL) { - if ((new = (clicon_hash_t)malloc (sizeof (*new))) == NULL){ + if ((new = (clicon_hash_t)malloc(sizeof(*new))) == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); goto catch; } - memset (new, 0, sizeof (*new)); + memset(new, 0, sizeof(*new)); - new->h_key = strdup (key); + new->h_key = strdup(key); if (new->h_key == NULL){ clicon_err(OE_UNIX, errno, "strdup: %s", strerror(errno)); goto catch; @@ -241,11 +241,11 @@ hash_add(clicon_hash_t *hash, clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); goto catch; } - memcpy (newval, val, vlen); + memcpy(newval, val, vlen); /* Free old value if existing variable */ if (h->h_val) - free (h->h_val); + free(h->h_val); h->h_val = newval; h->h_vlen = vlen; @@ -258,8 +258,8 @@ hash_add(clicon_hash_t *hash, catch: if (new) { if (new->h_key) - free (new->h_key); - free (new); + free(new->h_key); + free(new); } return NULL; @@ -279,15 +279,15 @@ hash_del(clicon_hash_t *hash, { clicon_hash_t h; - h = hash_lookup (hash, key); + h = hash_lookup(hash, key); if (h == NULL) return -1; DELQ(h, hash[hash_bucket(key)], clicon_hash_t); - free (h->h_key); - free (h->h_val); - free (h); + free(h->h_key); + free(h->h_val); + free(h); return 0; } diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 2386d561..655bc9f1 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -140,11 +140,11 @@ clicon_strjoin(int argc, len += 1; /* '\0' */ if ((str = malloc(len)) == NULL) return NULL; - memset (str, '\0', len); + memset(str, '\0', len); for (i = 0; i < argc; i++) { if (i != 0) - strncat (str, delim, len - strlen(str)); - strncat (str, argv[i], len - strlen(str)); + strncat(str, delim, len - strlen(str)); + strncat(str, argv[i], len - strlen(str)); } return str; } @@ -518,21 +518,21 @@ clicon_str2int(const map_str2int *mstab, */ #ifndef HAVE_STRNDUP char * -clicon_strndup (const char *str, - size_t len) +clicon_strndup(const char *str, + size_t len) { char *new; size_t slen; - slen = strlen (str); + slen = strlen(str); len = (len < slen ? len : slen); - new = malloc (len + 1); + new = malloc(len + 1); if (new == NULL) return NULL; new[len] = '\0'; - memcpy (new, str, len); + memcpy(new, str, len); return new; } diff --git a/lib/src/clixon_util_xpath.c b/lib/src/clixon_util_xpath.c new file mode 100644 index 00000000..7bc849de --- /dev/null +++ b/lib/src/clixon_util_xpath.c @@ -0,0 +1,231 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + +See https://www.w3.org/TR/xpath/ + + * Turn this on to get an xpath test program + * Usage: xpath [] + * read xpath on first line and xml on rest of lines from input + * Example compile: + gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen + * Example run: +echo "a\n" | xpath +*/ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options]\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-f \tXML file\n" + "\t-p \tPrimary XPATH string\n" + "\t-i \t(optional) Initial XPATH string\n" + "and the following extra rules:\n" + "\tif -f is not given, XML input is expected on stdin\n" + "\tif -p is not given, is expected as the first line on stdin\n" + "This means that with no arguments, and XML is expected on stadin.\n", + argv0 + ); + exit(0); +} + +static int +ctx_print2(cbuf *cb, + xp_ctx *xc) +{ + int i; + + cprintf(cb, "%s:", (char*)clicon_int2str(ctxmap, xc->xc_type)); + switch (xc->xc_type){ + case XT_NODESET: + for (i=0; ixc_size; i++){ + cprintf(cb, "%d:", i); + clicon_xml2cbuf(cb, xc->xc_nodeset[i], 0, 0); + } + break; + case XT_BOOL: + cprintf(cb, "%s", xc->xc_bool?"true":"false"); + break; + case XT_NUMBER: + cprintf(cb, "%lf", xc->xc_number); + break; + case XT_STRING: + cprintf(cb, "%s", xc->xc_string); + break; + } + return 0; +} + +int +main(int argc, char **argv) +{ + int retval = -1; + char *argv0 = argv[0]; + int i; + cxobj **xv = NULL; + cxobj *x0 = NULL; + cxobj *x; + char c; + int len; + char *buf = NULL; + int ret; + int fd = 0; /* unless overriden by argv[1] */ + char *xpath = NULL; + char *xpath0 = NULL; + char *filename; + xp_ctx *xc = NULL; + cbuf *cb; + + clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "hDf:p:i:")) != -1) + switch (c) { + case 'h': + usage(argv0); + break; + case 'D': + debug++; + break; + case 'f': /* XML file */ + filename = optarg; + if ((fd = open(filename, O_RDONLY)) < 0){ + clicon_err(OE_UNIX, errno, "open(%s)", argv[1]); + goto done; + } + break; + case 'p': /* Primary XPATH string */ + xpath = optarg; + break; + case 'i': /* Optional initial XPATH string */ + xpath0 = optarg; + break; + default: + usage(argv[0]); + break; + } + if (xpath==NULL){ + /* First read xpath */ + len = 1024; /* any number is fine */ + if ((buf = malloc(len)) == NULL){ + perror("pt_file malloc"); + return -1; + } + memset(buf, 0, len); + i = 0; + while (1){ + if ((ret = read(0, &c, 1)) < 0){ + perror("read"); + goto done; + } + if (ret == 0) + break; + if (c == '\n') + break; + if (len==i){ + if ((buf = realloc(buf, 2*len)) == NULL){ + fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); + return -1; + } + memset(buf+len, 0, len); + len *= 2; + } + buf[i++] = (char)(c&0xff); + } + xpath = buf; + } + /* + * If fd=0, then continue reading from stdin (after CR) + * If fd>0, reading from file opened as argv[1] + */ + if (xml_parse_file(fd, "", NULL, &x0) < 0){ + fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); + return -1; + } + /* If xpath0 given, position current x */ + if (xpath0){ + if ((x = xpath_first(x0, "%s", xpath0)) == NULL){ + fprintf(stderr, "Error: xpath0 returned NULL\n"); + return -1; + } + } + else + x = x0; + + /* Parse XML */ + if (xpath_vec_ctx(x, xpath, &xc) < 0) + return -1; + /* Print results */ + cb = cbuf_new(); + ctx_print2(cb, xc); + fprintf(stdout, "%s\n", cbuf_get(cb)); + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xc) + ctx_free(xc); + if (xv) + free(xv); + if (buf) + free(buf); + if (x0) + xml_free(x0); + if (fd > 0) + close(fd); + return retval; +} diff --git a/lib/src/clixon_util_xsl.c b/lib/src/clixon_util_xsl.c deleted file mode 100644 index b23d1c52..00000000 --- a/lib/src/clixon_util_xsl.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - -See https://www.w3.org/TR/xpath/ - - * Turn this on to get an xpath test program - * Usage: xpath [] - * read xpath on first line and xml on rest of lines from input - * Example compile: - gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen - * Example run: -echo "a\n" | xpath -*/ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clixon */ -#include - - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:%s .\n\tInput on stdin\n", argv0); - exit(0); -} - -int -main(int argc, char **argv) -{ - int retval = -1; - int i; - cxobj **xv; - cxobj *x; - cxobj *xn; - size_t xlen = 0; - int c; - int len; - char *buf = NULL; - int ret; - - if (argc != 1){ - usage(argv[0]); - return -1; - } - /* First read xpath */ - len = 1024; /* any number is fine */ - if ((buf = malloc(len)) == NULL){ - perror("pt_file malloc"); - return -1; - } - memset(buf, 0, len); - i = 0; - while (1){ - if ((ret = read(0, &c, 1)) < 0){ - perror("read"); - goto done; - } - if (ret == 0) - break; - if (c == '\n') - break; - if (len==i){ - if ((buf = realloc(buf, 2*len)) == NULL){ - fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); - return -1; - } - memset(buf+len, 0, len); - len *= 2; - } - buf[i++] = (char)(c&0xff); - } - x = NULL; - if (xml_parse_file(0, "", NULL, &x) < 0){ - fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); - return -1; - } - if (xpath_vec(x, "%s", &xv, &xlen, buf) < 0) - return -1; - if (xv){ - for (i=0; ix_value = strdup(x0->x_value)) == NULL){ diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 7e1b69e4..8a2b2aa7 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -424,13 +424,15 @@ xml_yang_validate_all(cxobj *xt, void *arg) { int retval = -1; - yang_stmt *ys; - yang_stmt *ytype; + yang_stmt *ys; /* yang node */ + yang_stmt *yc; /* yang child */ + char *xpath; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ + /* Node-specific validation */ switch (ys->ys_keyword){ case Y_LEAF: /* fall thru */ @@ -438,20 +440,29 @@ xml_yang_validate_all(cxobj *xt, /* Special case if leaf is leafref, then first check against current xml tree */ - if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){ - if (strcmp(ytype->ys_argument, "leafref") == 0){ - if (validate_leafref(xt, ytype) < 0) + if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){ + if (strcmp(yc->ys_argument, "leafref") == 0){ + if (validate_leafref(xt, yc) < 0) goto done; } - else if (strcmp(ytype->ys_argument, "identityref") == 0){ - if (validate_identityref(xt, ys, ytype) < 0) + else if (strcmp(yc->ys_argument, "identityref") == 0){ + if (validate_identityref(xt, ys, yc) < 0) goto done; } } break; + case Y_MUST: /* RFC 7950 Sec 7.5.3 */ + break; default: break; } + /* "when" sub-node RFC 7950 Sec 7.21.5 */ + if ((yc = yang_find((yang_node*)ys, Y_WHEN, NULL)) != NULL){ + xpath = yc->ys_argument; /* "when" has xpath argument */ + if (xpath_first(xt, "%s", xpath)) + ; + fprintf(stderr, "%s %s\n", __FUNCTION__, xpath); + } } retval = 0; done: @@ -1372,7 +1383,6 @@ xml_spec_populate(cxobj *x, return retval; } - /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c new file mode 100644 index 00000000..96825c7d --- /dev/null +++ b/lib/src/clixon_xpath.c @@ -0,0 +1,1183 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, indicate + your decision by deleting the provisions above and replace them with the + notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xpath_parse.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" + +/* + * Variables + */ +/* Mapping between XPATH operator string <--> int */ +const map_str2int xpopmap[] = { + {"and", XO_AND}, + {"or", XO_OR}, + {"div", XO_DIV}, + {"mod", XO_MOD}, + {"+", XO_ADD}, + {"*", XO_MULT}, + {"-", XO_SUB}, + {"=", XO_EQ}, + {"!=", XO_NE}, + {">=", XO_GE}, + {"<=", XO_LE}, + {"<", XO_LT}, + {">", XO_GT}, + {"|", XO_UNION}, + {NULL, -1} +}; + +/* Mapping between axis type string <--> int */ +static const map_str2int axismap[] = { + {"self", A_SELF}, + {"child", A_CHILD}, + {"parent", A_PARENT}, + {"root", A_ROOT}, + {"ancestor", A_ANCESTOR}, + {"descendant-or-self", A_DESCENDANT_OR_SELF}, + {NULL, -1} +}; + +static const map_str2int xpath_tree_map[] = { + {"expr", XP_EXP}, + {"andexpr", XP_AND}, + {"relexpr", XP_RELEX}, + {"addexpr", XP_ADD}, + {"unionexpr", XP_UNION}, + {"pathexpr", XP_PATHEXPR}, + {"locationpath", XP_LOCPATH}, + {"abslocpath", XP_ABSPATH}, + {"rellocpath", XP_RELLOCPATH}, + {"step", XP_STEP}, + {"nodetest", XP_NODE}, + {"nodetest fn", XP_NODE_FN}, + {"predicates", XP_PRED}, + {"primaryexpr", XP_PRI0}, + {"primaryexpr nr", XP_PRIME_NR}, + {"primaryexpr str", XP_PRIME_STR}, + {"primaryexpr fn", XP_PRIME_FN}, + {NULL, -1} +}; + +/* + * XPATH parse tree type + */ +/*! Print XPATH parse tree */ +static int +xpath_tree_print0(cbuf *cb, + xpath_tree *xs, + int level) +{ + cprintf(cb, "%*s%s:", level*3, "", clicon_int2str(xpath_tree_map, xs->xs_type)); + if (xs->xs_s0){ + cprintf(cb, "\"%s\" ", xs->xs_s0); + if (xs->xs_s1) + cprintf(cb,"\"%s\" ", xs->xs_s1); + } + cprintf(cb, "\n"); + if (xs->xs_c0) + xpath_tree_print0(cb, xs->xs_c0,level+1); + if (xs->xs_c1) + xpath_tree_print0(cb, xs->xs_c1, level+1); + return 0; +} + +static int +xpath_tree_print(cbuf *cb, + xpath_tree *xs) +{ + xpath_tree_print0(cb, xs, 0); + return 0; +} + +static int +xpath_tree_free( + xpath_tree *xs) +{ + if (xs->xs_s0) + free(xs->xs_s0); + if (xs->xs_s1) + free(xs->xs_s1); + if (xs->xs_c0) + xpath_tree_free(xs->xs_c0); + if (xs->xs_c1) + xpath_tree_free(xs->xs_c1); + free(xs); + return 0; +} + +/*! Make a nodetest + * @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN + * @retval 0 Match + * @retval 1 No match + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +nodetest_eval(cxobj *x, + xpath_tree *xs) +{ + char *fn; + + if (xs->xs_type == XP_NODE){ + /* Namespaces is s0, name is s1 */ + if (strcmp(xs->xs_s1, "*")==0) + return 1; + else if (strcmp(xml_name(x), xs->xs_s1)==0) + return 1; + else + return 0; + } + else if (xs->xs_type == XP_NODE_FN){ + fn = xs->xs_s0; + if (strcmp(fn, "node")==0) + return 1; + else if (strcmp(fn, "text")==0) + return 1; + } + return 0; +} + +int +nodetest_recursive(cxobj *xn, + xpath_tree *nodetest, + int node_type, + uint16_t flags, + cxobj ***vec0, + size_t *vec0len) +{ + int retval = -1; + cxobj *xsub; + cxobj **vec = *vec0; + size_t veclen = *vec0len; + + xsub = NULL; + while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { + if (nodetest_eval(xsub, nodetest) == 1){ + clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags)); + if (flags==0x0 || xml_flag(xsub, flags)) + if (cxvec_append(xsub, &vec, &veclen) < 0) + goto done; + // continue; /* Dont go deeper */ + } + if (nodetest_recursive(xsub, nodetest, node_type, flags, &vec, &veclen) < 0) + goto done; + } + retval = 0; + *vec0 = vec; + *vec0len = veclen; + done: + return retval; +} + +static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp); + +/*! Evaluate xpath step rule of an XML tree + * + * @param[in] xc0 Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + * + * - A node test that is a QName is true if and only if the type of the node (see [5 Data Model]) + * is the principal node type and has an expanded-name equal to the expanded-name specified by the QName. + * - A node test * is true for any node of the principal node type. + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +xp_eval_step(xp_ctx *xc0, + xpath_tree *xs, + xp_ctx **xrp) +{ + int retval = -1; + int i; + cxobj *x; + cxobj *xv; + cxobj *xp; + cxobj **vec = NULL; + size_t veclen = 0; + xpath_tree *nodetest = xs->xs_c0; + xp_ctx *xr0 = NULL; + xp_ctx *xc = NULL; + + /* Create new xc */ + if ((xc = ctx_dup(xc0)) == NULL) + goto done; + switch (xs->xs_int){ + case A_ANCESTOR: + break; + case A_ANCESTOR_OR_SELF: + break; + case A_ATTRIBUTE: /* principal node type is attribute */ + break; + case A_CHILD: + if (xc->xc_descendant){ + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, &vec, &veclen) < 0) + goto done; + } + xc->xc_descendant = 0; + } + else{ + if (nodetest->xs_type==XP_NODE_FN && + nodetest->xs_s0 && + strcmp(nodetest->xs_s0,"current")==0){ + if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0) + goto done; + } + else for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + x = NULL; + while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { + /* xs->xs_c0 is nodetest */ + if (nodetest == NULL || nodetest_eval(x, nodetest)) + if (cxvec_append(x, &vec, &veclen) < 0) + goto done; + } + } + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_DESCENDANT: + case A_DESCENDANT_OR_SELF: + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, &vec, &veclen) < 0) + goto done; + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_FOLLOWING: + break; + case A_FOLLOWING_SIBLING: + break; + 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; ixc_size; i++){ + x = xc->xc_nodeset[i]; + if ((xp = xml_parent(x)) != NULL) + if (cxvec_append(xp, &xr0->xc_nodeset, &xr0->xc_size) < 0) + goto done; + } + break; + case A_PRECEEDING: + break; + 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; + } + assert(*xrp); + retval = 0; + done: + if (xc) + ctx_free(xc); + return retval; +} + +/*! Evaluate xpath predicates rule + * + * pred -> pred expr + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + * + * A predicate filters a node-set with respect to an axis to produce a new + * node-set. For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node, with the number of nodes in + * the node-set as the context size, and with the proximity position of the node + * in the node-set with respect to the axis as the context position; if + * PredicateExpr evaluates to true for that node, the node is included in the + * new node-set; otherwise, it is not included. + * A PredicateExpr is evaluated by evaluating the Expr and converting the result + * to a boolean. If the result is a + * - number, the result will be converted to true if the number is equal to the + * context position and will be converted to false otherwise; + * - if the result is not a number, then the result will be converted as if by a + * call to the boolean function. + * Thus a location path para[3] is equivalent to para[position()=3]. + */ +static int +xp_eval_predicate(xp_ctx *xc, + xpath_tree *xs, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xrc = NULL; + int i; + cxobj *x; + xp_ctx *xcc; + + if (xs->xs_c0 == NULL){ /* empty */ + if ((xr0 = ctx_dup(xc)) == NULL) + goto done; + } + else{ /* eval previous predicates */ + if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + goto done; + } + if (xs->xs_c1){ + /* Loop over each node in the nodeset */ + assert (xr0->xc_type == XT_NODESET); + if ((xr1 = malloc(sizeof(*xr1))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr1, 0, sizeof(*xr1)); + xr1->xc_type = XT_NODESET; + xr1->xc_node = xc->xc_node; + xr1->xc_initial = xc->xc_initial; + for (i=0; ixc_size; i++){ + x = xr0->xc_nodeset[i]; + /* Create new context */ + if ((xcc = malloc(sizeof(*xcc))) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xcc, 0, sizeof(*xcc)); + xcc->xc_type = XT_NODESET; + xcc->xc_initial = xc->xc_initial; + xcc->xc_node = x; + /* For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node */ + if (cxvec_append(x, &xcc->xc_nodeset, &xcc->xc_size) < 0) + goto done; + if (xp_eval(xcc, xs->xs_c1, &xrc) < 0) + goto done; + if (xcc) + ctx_free(xcc); + if (xrc->xc_type == XT_NUMBER){ + /* If the result is a number, the result will be converted to true + if the number is equal to the context position */ + if ((int)xrc->xc_number == i) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + else { + /* if PredicateExpr evaluates to true for that node, the node is + included in the new node-set */ + if (ctx2boolean(xrc)) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + if (xrc) + ctx_free(xrc); + } + + } + assert(xr0||xr1); + if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + retval = 0; + done: + assert(retval==0); + if (xr0) + ctx_free(xr0); + if (xr1) + ctx_free(xr1); + return retval; +} + +/*! Given two XPATH contexts, eval logical operations: or,and + * The logical operators convert their operands to booleans + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_logop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int b1; + int b2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + if ((b1 = ctx2boolean(xc1)) < 0) + goto done; + if ((b2 = ctx2boolean(xc2)) < 0) + goto done; + switch (op){ + case XO_AND: + xr->xc_bool = b1 && b2; + break; + case XO_OR: + xr->xc_bool = b1 || b2; + break; + default: + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval numeric operations: +-*,div,mod + * The numeric operators convert their operands to numbers as if by + * calling the number function. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_numop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + double n1; + double n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NUMBER; + if (ctx2number(xc1, &n1) < 0) + goto done; + if (ctx2number(xc2, &n2) < 0) + goto done; + if (isnan(n1) || isnan(n2)) + xr->xc_number = NAN; + else + switch (op){ + case XO_DIV: + xr->xc_number = n1/n2; + break; + case XO_MOD: + xr->xc_number = ((int)n1)%((int)n2); + break; + case XO_ADD: + xr->xc_number = n1+n2; + break; + case XO_MULT: + xr->xc_number = n1*n2; + break; + case XO_SUB: + xr->xc_number = n1-n2; + break; + default: + clicon_err(OE_UNIX, errno, "Invalid operator %s in this context", + clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval relational operations: <>= + * A RelationalExpr is evaluated by comparing the objects that result from + * evaluating the two operands. + * This is covered: + * (a) Both are INTs, BOOLs, STRINGs. Result type is boolean + * (b) Both are nodesets and one is empty. Result type is boolean. + * (c) One is nodeset and other is INT or STRING. Result type is nodeset + * (d) All others (eg two nodesets, BOOL+STRING) are not supported. + * Op is = EQ + * From XPATH 1.0 standard, the evaluation has three variants: + * (1) comparisons that involve node-sets are defined in terms of comparisons that + * do not involve node-sets; this is defined uniformly for =, !=, <=, <, >= and >. + * (2) comparisons that do not involve node-sets are defined for = and !=. + * (3) comparisons that do not involve node-sets are defined for <=, <, >= and >. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_relop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + xp_ctx *xc; + cxobj *x; + int i; + int j; + int b; + char *s1; + char *s2; + int reverse = 0; + double n1, n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + 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 */ + for (i=0; ixc_size; i++){ + if ((s1 = xml_body(xc1->xc_nodeset[i])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + for (j=0; jxc_size; j++){ + if ((s2 = xml_body(xc2->xc_nodeset[j])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + switch(op){ + case XO_EQ: + xr->xc_bool = (strcmp(s1, s2)==0); + break; + case XO_NE: + xr->xc_bool = (strcmp(s1, s2)!=0); + break; + case XO_GE: + xr->xc_bool = (strcmp(s1, s2)>=0); + break; + case XO_LE: + xr->xc_bool = (strcmp(s1, s2)<=0); + break; + case XO_LT: + xr->xc_bool = (strcmp(s1, s2)<0); + break; + case XO_GT: + xr->xc_bool = (strcmp(s1, s2)>0); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op)); + goto done; + break; + } + } + } + break; + case XT_BOOL: + xr->xc_bool = (xc1->xc_bool == xc2->xc_bool); + break; + case XT_NUMBER: + xr->xc_bool = (xc1->xc_number == xc2->xc_number); + break; + case XT_STRING: + xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0); + break; + } + } + else if (xc1->xc_type != XT_NODESET && + xc2->xc_type != XT_NODESET){ + clicon_err(OE_XML, 0, "Mixed types not supported, %d %d", xc1->xc_type, xc2->xc_type); + goto done; + } + else{ /* one is nodeset, ie (1) above */ + if (xc2->xc_type == XT_NODESET){ + xc = xc2; + xc2 = xc1; + xc1 = xc; + reverse++; /* reverse */ + } + /* xc1 is nodeset + * xc2 is something else */ + switch (xc2->xc_type){ + case XT_BOOL: + /* comparison on the boolean and the result of converting the + node-set to a boolean using the boolean function is true. */ + b = ctx2boolean(xc); + switch(op){ + case XO_EQ: + xr->xc_bool = (b == xc2->xc_bool); + break; + case XO_NE: + xr->xc_bool = (b != xc2->xc_bool); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and bool", clicon_int2str(xpopmap,op)); + goto done; + break; + } /* switch op */ + break; + case XT_STRING: + /* If one object to be compared is a node-set and the + other is a string, then the comparison will be true if and only + 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.*/ + for (i=0; ixc_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); + break; + case XO_NE: + xr->xc_bool = (strcmp(s1, s2)); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op)); + goto done; + break; + } + } + break; + case XT_NUMBER: + for (i=0; ixc_size; i++){ + x = xc1->xc_nodeset[i]; /* node in nodeset */ + if (sscanf(xml_body(x), "%lf", &n1) != 1) + n1 = NAN; + n2 = xc2->xc_number; + switch(op){ + case XO_EQ: + xr->xc_bool = (n1 == n2); + break; + case XO_NE: + xr->xc_bool = (n1 != n2); + break; + case XO_GE: + xr->xc_bool = reverse?(n2 >= n1):(n1 >= n2); + break; + case XO_LE: + xr->xc_bool = reverse?(n2 <= n1):(n1 <= n2); + break; + case XO_LT: + xr->xc_bool = reverse?(n2 < n1):(n1 < n2); + break; + case XO_GT: + xr->xc_bool = reverse?(n2 > n1):(n1 > n2); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op)); + goto done; + break; + } + } + break; + default: + clicon_err(OE_XML, 0, "Type %d not supported", xc2->xc_type); + } /* switch type */ + } + ok: + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval union operation + * Both operands must be nodesets, otherwise empty nodeset is returned + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_union(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int i; + + if (op != XO_UNION){ + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NODESET; + + for (i=0; ixc_size; i++) + if (cxvec_append(xc1->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + for (i=0; ixc_size; i++){ + if (cxvec_append(xc2->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + + +/*! Evaluate an XPATH on an XML tree + + * The initial sequence of steps selects a set of nodes relative to a context node. + * Each node in that set is used as a context node for the following step. + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + */ +static int +xp_eval(xp_ctx *xc, + xpath_tree *xs, + xp_ctx **xrp) + +{ + int retval = -1; + cxobj *x; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr2 = NULL; + int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */ + + assert(xc->xc_initial); + if (debug){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); + clicon_debug(1, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + /* Pre-actions before check first child c0 + */ + switch (xs->xs_type){ + case XP_RELLOCPATH: + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_ABSPATH: + /* Set context node to top node, and nodeset to that node only */ + x = xc->xc_node; + while (xml_parent(x) != NULL) + x = xml_parent(x); + // xc->xc_node = x; + xc->xc_nodeset[0] = x; + xc->xc_size=1; + /* // is short for /descendant-or-self::node()/ */ + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + + break; + case XP_STEP: /* XP_NODE is first argument -not called explicitly */ + if (xp_eval_step(xc, xs, xrp) < 0) + goto done; + goto ok; + break; + case XP_PRED: + if (xp_eval_predicate(xc, xs, xrp) < 0) + goto done; + goto ok; + break; + default: + break; + } + /* Eval first child c0 + */ + if (xs->xs_c0){ + if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + goto done; + } + /* Actions between first and second child + */ + switch (xs->xs_type){ + case XP_EXP: + break; + case XP_AND: + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + break; + case XP_ADD: /* combine mult and add ops */ + break; + case XP_UNION: + break; + case XP_PATHEXPR: + break; + case XP_LOCPATH: + break; + case XP_ABSPATH: + use_xr0++; + /* Special case, no c0 or c1, single "/" */ + if (xs->xs_c0 == NULL){ + 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; + xr0->xc_type = XT_NODESET; + x = NULL; + 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; + case XP_RELLOCPATH: + use_xr0++; + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_NODE: + break; + case XP_NODE_FN: + break; + case XP_PRI0: + break; + case XP_PRIME_NR: /* primaryexpr -> [] */ + 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; + xr0->xc_type = XT_NUMBER; + xr0->xc_number = xs->xs_double; + break; + case XP_PRIME_STR: + 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; + xr0->xc_type = XT_STRING; + xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL; + break; + case XP_PRIME_FN: + break; + default: + break; + } + /* Eval second child c0 + * Note, some operators 8like locationpath, need transitive context (use_xr0) + */ + if (xs->xs_c1) + if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, &xr1) < 0) + goto done; + /* Actions after second child + */ + if (xs->xs_c1) + switch (xs->xs_type){ + case XP_AND: /* combine and and or ops */ + if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + if (xp_relop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_ADD: /* combine mult and add ops */ + if (xp_numop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_UNION: /* combine and and or ops */ + if (xp_union(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + default: + break; + } + xc->xc_descendant = 0; + assert(xr0||xr1||xr2); + if (xr2){ + *xrp = xr2; + xr2 = NULL; + } + else if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + ok: + if (debug){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); + clicon_debug(1, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + retval = 0; + done: + if (xr2) + ctx_free(xr2); + if (xr1) + ctx_free(xr1); + if (xr0) + ctx_free(xr0); + return retval; +} + +/*! 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 + * @retval 0 OK + * @retval -1 Error + */ +int +xpath_vec_ctx(cxobj *xcur, + char *xpath, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx xc = {0,}; + struct clicon_xpath_yacc_arg xy = {0,}; + + xy.xy_parse_string = xpath; + xy.xy_name = "xpath parser"; + xy.xy_linenum = 1; + if (xpath_scan_init(&xy) < 0) + goto done; + if (xpath_parse_init(&xy) < 0) + goto done; + if (clixon_xpath_parseparse(&xy) != 0) { /* yacc returns 1 on error */ + clicon_log(LOG_NOTICE, "XPATH error: on line %d", xy.xy_linenum); + if (clicon_errno == 0) + clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)"); + goto done; + } + if (debug){ + cbuf *cb = cbuf_new(); + xpath_tree_print(cb, xy.xy_top); + clicon_debug(1, "xpath parse tree:\n%s", cbuf_get(cb)); + cbuf_free(cb); + } + xc.xc_type = XT_NODESET; + xc.xc_node = xcur; + xc.xc_initial = xcur; + if (cxvec_append(xcur, &xc.xc_nodeset, &xc.xc_size) < 0) + goto done; + if (xp_eval(&xc, xy.xy_top, xrp) < 0) + goto done; + if (xc.xc_nodeset) + free(xc.xc_nodeset); + /* done: */ + xpath_parse_exit(&xy); + xpath_scan_exit(&xy); + retval = 0; + done: + if (xy.xy_top) + xpath_tree_free(xy.xy_top); + return retval; +} + +/*! Given XML tree and xpath, returns nodeset as xml node vector + * If result is not nodeset, return empty nodeset + * @param[in] xcur xml-tree where to search + * @param[in] xpath stdarg string with XPATH 1.0 syntax + * @param[out] vec vector of xml-trees. Vector must be free():d after use + * @param[out] veclen returns length of vector in return value + * @retval 0 OK + * @retval -1 Error + */ +int +xpath_vec_nodeset(cxobj *xcur, + char *format, + cxobj ***vec, + size_t *veclen, + ...) +{ + int retval = -1; + va_list ap; + size_t len; + char *xpath = NULL; + xp_ctx *xr = NULL; + + va_start(ap, veclen); + len = vsnprintf(NULL, 0, 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){ + *vec = xr->xc_nodeset; + xr->xc_nodeset = NULL; + *veclen = xr->xc_size; + } + retval = 0; + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return retval; +} + +/*! 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 + * @retval 1 True + * @retval 0 False + * @retval -1 Error + */ +int +xpath_vec_bool(cxobj *xcur, + char *format, + ...) +{ + int retval = -1; + 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) + retval = ctx2boolean(xr); + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return retval; +} + diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c new file mode 100644 index 00000000..39221a37 --- /dev/null +++ b/lib/src/clixon_xpath_ctx.c @@ -0,0 +1,294 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, indicate + your decision by deleting the provisions above and replace them with the + notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * This file defines XPATH contexts using in traversing the XPATH parse tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xpath_parse.h" +#include "clixon_xpath_ctx.h" + +/* + * Variables + */ +const map_str2int ctxmap[] = { + {"nodeset", XT_NODESET}, + {"bool", XT_BOOL}, + {"number", XT_NUMBER}, + {"string", XT_STRING}, + {NULL, -1} +}; + +/*! Free xpath context */ +int +ctx_free(xp_ctx *xc) +{ + if (xc->xc_nodeset) + free(xc->xc_nodeset); + if (xc->xc_string) + free(xc->xc_string); + free(xc); + return 0; +} + +/*! Duplicate xpath context */ +xp_ctx * +ctx_dup(xp_ctx *xc0) +{ + static xp_ctx *xc = NULL; + + if ((xc = malloc(sizeof(*xc))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xc, 0, sizeof(*xc)); + *xc = *xc0; + if (xc0->xc_size){ + if ((xc->xc_nodeset = calloc(xc0->xc_size, sizeof(cxobj*))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + memcpy(xc->xc_nodeset, xc0->xc_nodeset, xc->xc_size*sizeof(cxobj*)); + } + if (xc0->xc_string) + if ((xc->xc_string = strdup(xc0->xc_string)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + done: + return xc; +} + +/*! Print XPATH context */ +int +ctx_print(cbuf *cb, + int id, + xp_ctx *xc, + char *str) +{ + static int ident = 0; + int i; + + if (id<0) + ident += id; + cprintf(cb, "%*s%s ", ident, "", str?str:""); + if (id>0) + ident += id; + if (xc){ + cprintf(cb, "%s: ", (char*)clicon_int2str(ctxmap, xc->xc_type)); + switch (xc->xc_type){ + case XT_NODESET: + for (i=0; ixc_size; i++) + cprintf(cb, "%s ", xml_name(xc->xc_nodeset[i])); + break; + case XT_BOOL: + cprintf(cb, "%s", xc->xc_bool?"true":"false"); + break; + case XT_NUMBER: + cprintf(cb, "%lf", xc->xc_number); + break; + case XT_STRING: + cprintf(cb, "%s", xc->xc_string); + break; + } + } + return 0; +} + +/*! Convert xpath context to boolean according to boolean() function in XPATH spec + * @param[in] xc XPATH context + * @retval 0 False + * @retval 1 True + * a number is true if and only if it is neither positive or negative zero nor NaN + * a node-set is true if and only if it is non-empty + * a string is true if and only if its length is non-zero + * an object of a type other than the four basic types is converted to a boolean + * in a way that is dependent on that type + */ +int +ctx2boolean(xp_ctx *xc) +{ + int b; + switch (xc->xc_type){ + case XT_NODESET: + b = (xc->xc_size != 0); + break; + case XT_BOOL: + b = xc->xc_bool; + break; + case XT_NUMBER: + b = (xc->xc_number != 0.0 && xc->xc_number != NAN); + break; + case XT_STRING: + b = (xc->xc_string && strlen(xc->xc_string)); + break; + } + return b; +} + +/*! Convert xpath context to string according to string() function in XPATH spec + * @param[in] xc XPATH context + * @param[out] str0 Malloced result string + * @retval 0 OK + * @retval -1 Error + * @note string malloced. + */ +int +ctx2string(xp_ctx *xc, + char **str0) +{ + int retval = -1; + char *str = NULL; + int len; + char *b; + + switch (xc->xc_type){ + case XT_NODESET: + if (xc->xc_size && (b = xml_body(xc->xc_nodeset[0]))){ + if ((str = strdup(b)) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + } + else + if ((str = strdup("")) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + case XT_BOOL: + if ((str = strdup(xc->xc_bool == 0?"false":"true")) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + case XT_NUMBER: + len = snprintf(NULL, 0, "%0lf", xc->xc_number); + len++; + if ((str = malloc(len)) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + snprintf(str, len, "%0lf", xc->xc_number); + break; + case XT_STRING: + if ((str = strdup(xc->xc_string)) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + } + *str0 = str; + retval = 0; + done: + return retval; +} + +/*! Convert xpath context to number according to number() function in XPATH spec + * @param[in] xc XPATH context + * @param[out] n0 Floating point or NAN + * @retval 0 OK + * @retval -1 Error + */ +int +ctx2number(xp_ctx *xc, + double *n0) +{ + int retval = -1; + char *str = NULL; + double n; + + switch (xc->xc_type){ + case XT_NODESET: + if (ctx2string(xc, &str) < 0) + goto done; + if (sscanf(str, "%lf",&n) != 1) + n = NAN; + break; + case XT_BOOL: + n = (double)xc->xc_bool; + break; + case XT_NUMBER: + n = xc->xc_number; + break; + case XT_STRING: + if (sscanf(xc->xc_string, "%lf",&n) != 1) + n = NAN; + break; + } + *n0 = n; + retval = 0; + done: + if (str) + free(str); + return retval; +} + +/*! Replace a nodeset of a XPATH context with a new nodeset + */ +int +ctx_nodeset_replace(xp_ctx *xc, + cxobj **vec, + size_t veclen) +{ + if (xc->xc_nodeset) + free(xc->xc_nodeset); + xc->xc_nodeset = vec; + xc->xc_size = veclen; + return 0; +} + diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h new file mode 100644 index 00000000..1affce3c --- /dev/null +++ b/lib/src/clixon_xpath_parse.h @@ -0,0 +1,101 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ +#ifndef _CLIXON_XPATH_PARSE_H_ +#define _CLIXON_XPATH_PARSE_H_ + +/* + * Types + */ +/* used as non-terminal type in yacc rules */ +enum xp_type{ + XP_EXP, + XP_AND, + XP_RELEX, + XP_ADD, + XP_UNION, + XP_PATHEXPR, + XP_LOCPATH, + XP_ABSPATH, + XP_RELLOCPATH, + XP_STEP, + XP_NODE, + XP_NODE_FN, + XP_PRED, + XP_PRI0, + XP_PRIME_NR, + XP_PRIME_STR, + XP_PRIME_FN, +}; + +/*! XPATH Parsing generates a tree of nodes that is later traversed + */ +struct xpath_tree{ + enum xp_type xs_type; + int xs_int; + double xs_double; + char *xs_s0; + char *xs_s1; + struct xpath_tree *xs_c0; /* child 0 */ + struct xpath_tree *xs_c1; /* child 1 */ +}; +typedef struct xpath_tree xpath_tree; + +struct clicon_xpath_yacc_arg{ /* XXX: mostly unrelevant */ + const char *xy_name; /* Name of syntax (for error string) */ + int xy_linenum; /* Number of \n in parsed buffer */ + char *xy_parse_string; /* original (copy of) parse string */ + void *xy_lexbuf; /* internal parse buffer from lex */ + xpath_tree *xy_top; +}; + +/* + * Variables + */ +extern char *clixon_xpath_parsetext; + +/* + * Prototypes + */ +int xpath_scan_init(struct clicon_xpath_yacc_arg *jy); +int xpath_scan_exit(struct clicon_xpath_yacc_arg *jy); + +int xpath_parse_init(struct clicon_xpath_yacc_arg *jy); +int xpath_parse_exit(struct clicon_xpath_yacc_arg *jy); + +int clixon_xpath_parselex(void *); +int clixon_xpath_parseparse(void *); +void clixon_xpath_parseerror(void *, char*); + +#endif /* _CLIXON_XPATH_PARSE_H_ */ diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l new file mode 100644 index 00000000..5ac54644 --- /dev/null +++ b/lib/src/clixon_xpath_parse.l @@ -0,0 +1,174 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +%{ + +#include "clixon_config.h" + +#include +#include +#include +#include +#include + +#include "clixon_xpath_parse.tab.h" /* generated */ + +#include + +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_parse.h" + +/* Redefine main lex function so that you can send arguments to it: _yy is added to arg list */ +#define YY_DECL int clixon_xpath_parselex(void *_yy) + +/* Dont use input function (use user-buffer) */ +#define YY_NO_INPUT + +/* typecast macro */ +#define _XY ((struct clicon_xpath_yacc_arg *)_yy) + +#define MAXBUF 4*4*64*1024 + +#undef clixon_xpath_parsewrap +int +clixon_xpath_parsewrap(void) +{ + return 1; +} + + + +%} + +digit [0-9] +integer {digit}+ +real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+) + +%x TOKEN +%s QLITERAL +%s ALITERAL + +%% +[ \t] +\n { _XY->xy_linenum++; } +\r { } +<> { return X_EOF; } +".." { return DOUBLEDOT; } +[()\[\]\.@,/:|] { return *yytext; } +"::" { return DOUBLECOLON; } +and { clixon_xpath_parselval.intval = clicon_str2int(xpopmap, yytext); return LOGOP; } +or { clixon_xpath_parselval.intval = clicon_str2int(xpopmap, yytext); return LOGOP; } +div { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +mod { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +[+*\-] { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +\? { return *yytext; } +"//" { return DOUBLESLASH; } +"!=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return RELOP; } +">=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +"<=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +[<>=] { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +last { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +position { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +count { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +ancestor { clixon_xpath_parselval.intval = A_ANCESTOR; return AXISNAME; } +ancestor-or-self { clixon_xpath_parselval.intval = A_ANCESTOR_OR_SELF; return AXISNAME; } +attribute { clixon_xpath_parselval.intval = A_ATTRIBUTE; return AXISNAME; } +child { clixon_xpath_parselval.intval = A_CHILD; return AXISNAME; } +descendant { clixon_xpath_parselval.intval = A_DESCENDANT; return AXISNAME; } +descendant-or-self { clixon_xpath_parselval.intval = A_DESCENDANT_OR_SELF; return AXISNAME; } +following { clixon_xpath_parselval.intval = A_FOLLOWING; return AXISNAME; } +following-sibling { clixon_xpath_parselval.intval = A_FOLLOWING_SIBLING; return AXISNAME; } +namespace { clixon_xpath_parselval.intval = A_NAMESPACE; return AXISNAME; } +parent { clixon_xpath_parselval.intval = A_PARENT; return AXISNAME; } +preceding { clixon_xpath_parselval.intval = A_PRECEEDING; return AXISNAME; } +preceding-sibling { clixon_xpath_parselval.intval = A_PRECEEDING_SIBLING; return AXISNAME; } +self { clixon_xpath_parselval.intval = A_SELF; return AXISNAME; } +current { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +comment { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +text { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +processing-instructions { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +node { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +\" { BEGIN(QLITERAL); return QUOTE; } +\' { BEGIN(ALITERAL); return APOST; } +\-?({integer}|{real}) { sscanf(yytext,"%lf",&clixon_xpath_parselval.dval); return NUMBER;} +[0-9A-Za-z_\-]+ { clixon_xpath_parselval.string = strdup(yytext); + return NAME; /* rather be catch-all */ + } +. { fprintf(stderr,"LEXICAL ERROR\n"); return -1; } +\" { BEGIN(TOKEN); return QUOTE; } +. { clixon_xpath_parselval.string = strdup(yytext); + return CHAR;} +\' { BEGIN(TOKEN); return APOST; } +. { clixon_xpath_parselval.string = strdup(yytext); + return CHAR;} + +%% + + +/*! Initialize scanner. + */ +int +xpath_scan_init(struct clicon_xpath_yacc_arg *xy) +{ + BEGIN(TOKEN); + xy->xy_lexbuf = yy_scan_string (xy->xy_parse_string); +#if 1 /* XXX: just to use unput to avoid warning */ + if (0) + yyunput(0, ""); +#endif + + return 0; +} + +/* + * free buffers + * Even within Flex version 2.5 (this is assumed), freeing buffers is different. + */ +int +xpath_scan_exit(struct clicon_xpath_yacc_arg *xy) +{ + yy_delete_buffer(xy->xy_lexbuf); + clixon_xpath_parselex_destroy(); /* modern */ + return 0; +} + diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y new file mode 100644 index 00000000..e7eca0a1 --- /dev/null +++ b/lib/src/clixon_xpath_parse.y @@ -0,0 +1,289 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * XPATH Parser + * From https://www.w3.org/TR/xpath-10/ + * 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) + * Lexical structure is defined by ExprToken, see + * see https://www.w3.org/TR/xpath-10/#exprlex + */ + +%start start + +%union { + int intval; + double dval; + char *string; + void *stack; /* xpath_tree */ +} + +%token AXISNAME +%token LOGOP +%token ADDOP +%token RELOP + +%token NUMBER + +%token X_EOF +%token QUOTE +%token APOST +%token CHAR +%token NAME +%token NODETYPE +%token DOUBLEDOT +%token DOUBLECOLON +%token DOUBLESLASH +%token FUNCTIONNAME + +%type axisspec + +%type string +%type expr +%type andexpr +%type relexpr +%type addexpr +%type unionexpr +%type pathexpr +%type locationpath +%type abslocpath +%type rellocpath +%type step +%type nodetest +%type predicates +%type primaryexpr + +%lex-param {void *_xy} /* Add this argument to parse() and lex() function */ +%parse-param {void *_xy} + +%{ +/* Here starts user C-code */ + +/* typecast macro */ +#define _XY ((struct clicon_xpath_yacc_arg *)_xy) + +#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;} + +/* add _yy to error paramaters */ +#define YY_(msgid) msgid + +#include "clixon_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_queue.h" +#include "clixon_string.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" + +#include "clixon_xpath_parse.h" + +extern int clixon_xpath_parseget_lineno (void); + +/* + also called from yacc generated code * +*/ + +void +clixon_xpath_parseerror(void *_xy, + char *s) +{ + clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'", + _XY->xy_name, + _XY->xy_linenum , + s, + clixon_xpath_parsetext); + return; +} + +int +xpath_parse_init(struct clicon_xpath_yacc_arg *xy) +{ + // clicon_debug_init(2, NULL); + return 0; +} + +int +xpath_parse_exit(struct clicon_xpath_yacc_arg *xy) +{ + return 0; +} + +static xpath_tree * +xp_new(enum xp_type type, + int i0, + double d0, + char *s0, + char *s1, + xpath_tree *c0, + xpath_tree *c1) +{ + xpath_tree *xs = NULL; + + if ((xs = malloc(sizeof(xpath_tree))) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xs, 0, sizeof(*xs)); + xs->xs_type = type; + xs->xs_int = i0; + xs->xs_double = d0; + xs->xs_s0 = s0; + xs->xs_s1 = s1; + xs->xs_c0 = c0; + xs->xs_c1 = c1; + done: + return xs; +} + +%} + +%% + +/* +*/ + +start : expr X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->expr"); YYACCEPT; } + | locationpath X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->locationpath"); YYACCEPT; } + ; + +expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"expr->expr or andexpr"); } + | andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"expr-> andexpr"); } + ; + +andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"andexpr-> andexpr and relexpr"); } + | relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"andexpr-> relexpr"); } + ; + +relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"relexpr-> relexpr relop addexpr"); } + | addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"relexpr-> addexpr"); } + ; + +addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"addexpr-> addexpr ADDOP unionexpr"); } + | unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"addexpr-> unionexpr"); } + ; + +/* node-set */ +unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"unionexpr-> unionexpr | pathexpr"); } + | pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"unionexpr-> pathexpr"); } + ; + +pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> locationpath"); } + | primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> primaryexpr"); } + ; + +/* location path returns a node-set */ +locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> rellocpath"); } + | abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> abslocpath"); } + ; + +abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(1,"abslocpath-> /"); } + | '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(1,"abslocpath->/ rellocpath");} + /* // is short for /descendant-or-self::node()/ */ + | DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(1,"abslocpath-> // rellocpath"); } + ; + +rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"rellocpath-> step"); } + | rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"rellocpath-> rellocpath / step"); } + | rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(1,"rellocpath-> rellocpath // step"); } + ; + +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-> .."); } + ; + +axisspec : AXISNAME DOUBLECOLON { clicon_debug(1,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;} + | '@' { $$=A_ATTRIBUTE; clicon_debug(1,"axisspec-> @"); } + | { clicon_debug(1,"axisspec-> "); $$=A_CHILD;} + ; + +nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"nodetest-> *"); } + | NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(1,"nodetest-> name(%s)",$1); } + | NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : name(%s)", $1, $3); } + | NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : *", $1); } + | NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(1,"nodetest-> nodetype()"); } + ; + +/* evaluates to boolean */ +predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(1,"predicates-> [ expr ]"); } + | { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"predicates->"); } + ; + +primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(1,"primaryexpr-> ( expr )"); } + | NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> NUMBER(%lf)", $1); } + | QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" string \""); } + | QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" \""); } + | APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' string '"); } + | APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' '"); } + | FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> functionname ( arguments )"); } + ; + +/* XXX Adding this between FUNCTIONNAME() breaks parser,.. +arguments : arguments expr { clicon_debug(1,"arguments-> arguments expr"); } + | { clicon_debug(1,"arguments-> "); } + ; +*/ +string : string CHAR { + int len = strlen($1); + $$ = realloc($1, len+strlen($2) + 1); + sprintf($$+len, "%s", $2); + free($2); + clicon_debug(1,"string-> string CHAR"); + } + | CHAR { clicon_debug(1,"string-> "); } + ; + + +%% + diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index a758be52..ef47eab3 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -35,54 +35,12 @@ * NOTE: there is a main function at the end of this file where you can test out * different xpath expressions. * Look at the end of the file for a test unit program - */ -/* -See https://www.w3.org/TR/xpath/ - -Implementation of a limited xslt xpath syntax. Some examples. Given the following -xml tree: - - 42 - 99 - 22 - - -With the following xpath examples. There are some diffs and many limitations compared -to the xml standards: - / whole tree ... - /bbb - /aaa/bbb 42 - 99 - //bbb as above - //b?b as above - //b\* as above - //b\*\/ccc 42 - 99 - //\*\/ccc 42 - 99 - 22 --- //bbb@x x="hello" - //bbb[@x] 42 - 99 - //bbb[@x=hello] 42 - //bbb[@x="hello"] as above - //bbb[0] 42 - //bbb[ccc=99] 99 ---- //\*\/[ccc=99] same as above - '//bbb | //ddd' 42 - 99 - 22 (NB spaces) - etc - For xpath v1.0 see http://www.w3.org/TR/xpath/ -record[name=c][time=d] -in - - c - - 45654df4-2292-45d3-9ca5-ee72452568a8 - +The code is implemented according to XPATH 1.0: + https://www.w3.org/TR/xpath-10/ +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) */ #include #include @@ -95,6 +53,7 @@ in #include #include #include +#include /* cligen */ #include @@ -108,14 +67,18 @@ in #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_xsl.h" + /* Constants */ #define XPATH_VEC_START 128 /* * Types */ + struct searchvec{ cxobj **sv_v0; /* here is result */ int sv_v0len; @@ -127,13 +90,22 @@ typedef struct searchvec searchvec; /* Local types */ -enum axis_type{ - A_SELF, - A_CHILD, - A_PARENT, - A_ROOT, - A_ANCESTOR, - A_DESCENDANT_OR_SELF, /* actually descendant-or-self */ + +struct xpath_predicate{ + struct xpath_predicate *xp_next; + char *xp_expr; +}; + +/* XPATH Axis according to https://www.w3.org/TR/xpath-10/#NT-Step + * Axis ::= AxisSpecifier NodeTest Predicate* + * Eg "child:: + */ +struct xpath_element{ + struct xpath_element *xe_next; + enum axis_type xe_type; + char *xe_prefix; /* eg for namespaces */ + char *xe_str; /* eg for child */ + struct xpath_predicate *xe_predicate; /* eg within [] */ }; /* Mapping between axis type string <--> int */ @@ -147,23 +119,12 @@ static const map_str2int axismap[] = { {NULL, -1} }; -struct xpath_predicate{ - struct xpath_predicate *xp_next; - char *xp_expr; -}; - -struct xpath_element{ - struct xpath_element *xe_next; - enum axis_type xe_type; - char *xe_prefix; /* eg for namespaces */ - char *xe_str; /* eg for child */ - struct xpath_predicate *xe_predicate; /* eg within [] */ -}; - static int xpath_split(char *xpathstr, char **pathexpr); +/*! Print xpath structure for debug */ static int -xpath_print(FILE *f, struct xpath_element *xplist) +xpath_print(FILE *f, + struct xpath_element *xplist) { struct xpath_element *xe; struct xpath_predicate *xp; @@ -217,6 +178,11 @@ xpath_parse_predicate(struct xpath_element *xe, return retval; } +/*! XPATH parse, create new child element + * @param[in] atype Axis type, see https://www.w3.org/TR/xpath-10/#axes + * @param[in] str + * @param[out] xpnext + */ static int xpath_element_new(enum axis_type atype, char *str, @@ -309,8 +275,17 @@ xpath_free(struct xpath_element *xplist) return 0; } -/* - * // is short for /descendant-or-self::node()/ +/*! Parse xpath to xpath_element structure + + * + * [1] LocationPath ::= RelativeLocationPath + * | AbsoluteLocationPath + * [2] AbsoluteLocationPath ::= '/' RelativeLocationPath? + * | AbbreviatedAbsoluteLocationPath + * [3] RelativeLocationPath ::= Step + | RelativeLocationPath '/' Step + | AbbreviatedRelativeLocationPath + * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath */ static int xpath_parse(char *xpath, @@ -355,6 +330,7 @@ xpath_parse(char *xpath, } s++; } + /* Iterate through steps (s), see https://www.w3.org/TR/xpath-10/#NT-Step */ s = s0; for (i=0; ixe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0) + if (xpath_recursive_find(xv, xe->xe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0) goto done; } } @@ -738,7 +701,6 @@ xpath_find(cxobj *xcur, } } } - for (xp = xe->xe_predicate; xp; xp = xp->xp_next){ if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0) goto done; @@ -798,6 +760,7 @@ xpath_split(char *xpathstr, * @param[in] flags if != 0, only match xml nodes matching flags * @param[out] vec2 Result XML node vector * @param[out] vec2len Length of result vector. + * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath */ static int xpath_exec(cxobj *xcur, @@ -831,14 +794,20 @@ xpath_exec(cxobj *xcur, * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[in] flags if != 0, only match xml nodes matching flags - * @param[in] vec1 vector of XML trees - * @param[in] vec1len length of XML trees + * @param[out] vec1 vector of XML trees + * @param[out] vec1len length of XML trees * For example: xpath = //a | //b. * xpath_first+ splits xpath up in several subcalls * (eg xpath=//a and xpath=//b) and collects the results. * Note: if a match is found in both, two (or more) same results will be * returned. * Note, this could be 'folded' into xpath1 but I judged it too complex. + * @see https://www.w3.org/TR/xpath-10/#NT-Expr + * An 'Expr' is composed of compositions of and, or, =, +, -, down to: + * PathExpr ::= LocationPath + * | FilterExpr + * | FilterExpr '/' RelativeLocationPath + * | FilterExpr '//' RelativeLocationPath */ static int xpath_choice(cxobj *xcur, @@ -848,13 +817,13 @@ xpath_choice(cxobj *xcur, size_t *vec1len) { int retval = -1; - char *s0; + char *s0 = NULL; char *s1; char *s2; char *xpath; cxobj **vec0 = NULL; size_t vec0len = 0; - + if ((s0 = strdup(xpath0)) == NULL){ clicon_err(OE_XML, errno, "strdup"); goto done; @@ -878,7 +847,7 @@ xpath_choice(cxobj *xcur, goto done; } retval = 0; - done: + done: if (s0) free(s0); if (vec0) @@ -1020,7 +989,7 @@ xpath_each(cxobj *xcur, /*! A restricted xpath that returns a vector of matches * * See xpath1() on details for subset -. * @param[in] xcur xml-tree where to search + * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] veclen returns length of vector in return value @@ -1052,7 +1021,7 @@ xpath_vec(cxobj *xcur, int retval = -1; va_list ap; size_t len; - char *xpath; + char *xpath = NULL; va_start(ap, veclen); len = vsnprintf(NULL, 0, format, ap); @@ -1079,6 +1048,7 @@ 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 @@ -1140,96 +1110,3 @@ xpath_vec_flag(cxobj *xcur, return retval; } -/* - * Turn this on to get an xpath test program - * Usage: xpath [] - * read xpath on first line and xml on rest of lines from input - * Example compile: - gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen - * Example run: -echo "a\n" | xpath -*/ -#if 0 /* Test program */ - - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:%s .\n\tInput on stdin\n", argv0); - exit(0); -} - -int -main(int argc, char **argv) -{ - int retval = -1; - int i; - cxobj **xv; - cxobj *x; - cxobj *xn; - size_t xlen = 0; - int c; - int len; - char *buf = NULL; - char *filename; - int fd; - - if (argc != 2){ - usage(argv[0]); - goto done; - } - filename = argv[1]; - if ((fd = open(filename, O_RDONLY)) < 0){ - clicon_err(OE_UNIX, errno, "open(%s)", filename); - goto done; - } - /* Read xpath */ - len = 1024; /* any number is fine */ - if ((buf = malloc(len)) == NULL){ - perror("pt_file malloc"); - return -1; - } - memset(buf, 0, len); - i = 0; - while (1){ /* read the whole file */ - if ((c = fgetc(stdin)) == EOF) - return -1; - if (c == '\n') - break; - if (len==i){ - if ((buf = realloc(buf, 2*len)) == NULL){ - fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); - return -1; - } - memset(buf+len, 0, len); - len *= 2; - } - buf[i++] = (char)(c&0xff); - } - x = NULL; - if (xml_parse_file(fd, "", NULL, &x) < 0){ - fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); - return -1; - } - close (fd); - printf("\n"); - - if (xpath_vec(x, "%s", &xv, &xlen, buf) < 0) - return -1; - if (xv){ - for (i=0; i $dir/clixon-ret echo "$expect"| od -t c > $dir/clixon-expect - diff $dir/clixon-ret $dir/clixon-expect + diff $dir/clixon-expect $dir/clixon-ret exit $testnr } diff --git a/test/test_xpath.sh b/test/test_xpath.sh new file mode 100755 index 00000000..35ee692a --- /dev/null +++ b/test/test_xpath.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# Test: XPATH tests +PROG=../lib/src/clixon_util_xpath + +# include err() and new() functions and creates $dir +. ./lib.sh + +# XML file (alt provide it in stdin after xpath) +xml=$dir/xml.xml +xml2=$dir/xml2.xml + +cat < $xml + + 42 + 99 + 22 + +EOF + +cat < $xml2 + + + e0 + + true + + + +e0 +myfamily + + v6ur:ipv6-unicast + foo + + + + + bar + myfamily + + + + 22 + 99 + responder-only + rt:static + bar + 0 + + ethernet + 1500 + + +EOF + +new "xpath /" +expecteof "$PROG -f $xml -p /" 0 "" "^nodeset:0:429922$" + +new "xpath /aaa" +expecteof "$PROG -f $xml -p /aaa" 0 "" "^nodeset:0:429922$" + +new "xpath /bbb" +expecteof "$PROG -f $xml -p /bbb" 0 "" "^nodeset:$" + +new "xpath /aaa/bbb" +expecteof "$PROG -f $xml -p /aaa/bbb" 0 "" "^0:42 +1:99$" + +new "xpath //bbb" +expecteof "$PROG -f $xml -p //bbb" 0 "" "0:42 +1:99" + +new "xpath //b?b" +#expecteof "$PROG -f $xml" 0 "//b?b" "" + +new "xpath //b*" +#expecteof "$PROG -f $xml" 0 "//b*" "" + +new "xpath //b*/ccc" +#expecteof "$PROG -f $xml" 0 "//b*/ccc" "" + +new "xpath //bbb[0]" +expecteof "$PROG -f $xml -p //bbb[0]" 0 "" "^nodeset:0:42$" + +new "xpath //bbb[ccc=99]" +expecteof "$PROG -f $xml -p //bbb[ccc=99]" 0 "" "^nodeset:0:99$" + +new "xpath ../connection-type = 'responder-only'" +expecteof "$PROG -f $xml2 -p ../connection-type='responder-only' -i /aaa/bbb/here" 0 "" "^bool:true$" + +new "xpath ../connection-type = 'no-responder'" +expecteof "$PROG -f $xml2 -p ../connection-type='no-responder' -i /aaa/bbb/here" 0 "" "^bool:false$" + +new "xpath . <= 0.75 * ../max-rtr-adv-interval" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". <= 0.75 * ../max-rtr-adv-interval" "^bool:true$" + +new "xpath . > 0.75 * ../max-rtr-adv-interval" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". > 0.75 * ../max-rtr-adv-interval" "^bool:false$" + +new "xpath . <= ../valid-lifetime" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". <= ../valid-lifetime" "^bool:true$" + +new "xpath ../../rt:address-family = 'v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 "../../rt:address-family = 'v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath ../../../rt:address-family = 'v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here2/here" 0 "../../../rt:address-family = 'v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath /if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" +expecteof "$PROG -f $xml2" 0 "/if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" "^bool:true$" + +new "xpath rt:address-family='v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa" 0 "rt:address-family='v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath ../type='rt:static'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 "../type='rt:static'" "^bool:true$" + +new "xpath rib-name != ../../name" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "rib-name != ../../name" "^bool:true$" + +new "xpath routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" "^bool:true$" + +new "xpath ifType = \"ethernet\" or ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" or ifMTU = 1500" "^bool:true$" + +new "xpath ifType != \"ethernet\" or ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" or ifMTU = 1500" "^bool:true$" + +new "xpath ifType = \"ethernet\" or ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" or ifMTU = 1400" "^bool:true$" + +new "xpath ifType != \"ethernet\" or ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" or ifMTU = 1400" "^bool:false$" + +new "xpath ifType = \"ethernet\" and ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" and ifMTU = 1500" "^bool:true$" + +new "xpath ifType != \"ethernet\" and ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" and ifMTU = 1500" "^bool:false$" + +new "xpath ifType = \"ethernet\" and ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" and ifMTU = 1400" "^bool:false$" + +new "xpath ifType != \"ethernet\" and ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" and ifMTU = 1400" "^bool:false$" + +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$" + +rm -rf $dir diff --git a/test/test_xsl.sh b/test/test_xsl.sh deleted file mode 100755 index b7e322ad..00000000 --- a/test/test_xsl.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# Test: XSL tests -PROG=../lib/src/clixon_util_xsl - -# include err() and new() functions and creates $dir -. ./lib.sh - -new "xsl test" -expecteof $PROG 0 "a -" "^0:$" - -rm -rf $dir diff --git a/test/test_yang.sh b/test/test_yang.sh index 1e542501..3522500c 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Test4: Yang specifics: multi-keys and empty type +# Yang specifics: multi-keys and empty type APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh