diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c8b794..1035646e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,14 @@ ## 5.8.0 Planned: July 2022 +### New features + +* Text syntax parser/loader + * Added new text syntax parsing and loading from CLI + * Text output format changed: + * Namespace/modulename added to top-level + * See [Support performant load_config_file(...) for TEXT format](https://github.com/clicon/clixon/issues/324) + ### C/CLI-API changes on existing features Developers may need to change their code @@ -52,7 +60,8 @@ Developers may need to change their code * Replace `xml2json_cb(...)` with `xml2json_file(..., 0)` * `clicon_xml2cbuf()`: Added `skiptop` parameter: `clicon_xml2cbuf(..., int skiptop)` * `xml2cli()`: Added `skiptop` parameter: `xml2cli(..., int skiptop)` - + * Merged `cli_xml2txt()` and `xml2txt_cb()` with `xml2txt()` + ## 5.7.0 17 May 2022 diff --git a/apps/cli/cli_auto.c b/apps/cli/cli_auto.c index c6227b2f..9ffc2692 100644 --- a/apps/cli/cli_auto.c +++ b/apps/cli/cli_auto.c @@ -125,28 +125,6 @@ cvec_append(cvec *cvv0, return cvv2; } -/*! x is element and has exactly one child which in turn has none - * @see child_type in clixon_json.c - */ -static int -tleaf(cxobj *x) -{ - cxobj *xc; - - if (xml_type(x) != CX_ELMNT) - return 0; - if (xml_child_nr_notype(x, CX_ATTR) != 1) - return 0; - /* From here exactly one noattr child, get it */ - xc = NULL; - while ((xc = xml_child_each(x, xc, -1)) != NULL) - if (xml_type(xc) != CX_ATTR) - break; - if (xc == NULL) - return -1; /* n/a */ - return (xml_child_nr_notype(xc, CX_ATTR) == 0); -} - /*! Print an XML tree structure from an auto-cli env to stdout and encode chars "<>&" * * @param[in] xn clicon xml tree @@ -256,64 +234,6 @@ cli_xml2file(cxobj *xn, return retval; } -/*! Translate XML to a "pseudo-code" textual format using a callback - internal function - * @param[in] xn XML object to print - * @param[in] fn Callback to make print function - * @param[in] level print 4 spaces per level in front of each line - */ -int -cli_xml2txt(cxobj *xn, - clicon_output_cb *fn, - int level) -{ - cxobj *xc = NULL; - int children=0; - int retval = -1; - int exist = 0; - - if (xn == NULL || fn == NULL){ - clicon_err(OE_XML, EINVAL, "xn or fn is NULL"); - goto done; - } - if (yang_extension_value(xml_spec(xn), "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0) - goto done; - if (exist) - goto ok; - xc = NULL; /* count children (elements and bodies, not attributes) */ - while ((xc = xml_child_each(xn, xc, -1)) != NULL) - if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) - children++; - if (!children){ /* If no children print line */ - switch (xml_type(xn)){ - case CX_BODY: - (*fn)(stdout, "%s;\n", xml_value(xn)); - break; - case CX_ELMNT: - (*fn)(stdout, "%*s%s;\n", 4*level, "", xml_name(xn)); - break; - default: - break; - } - goto ok; - } - (*fn)(stdout, "%*s", 4*level, ""); - (*fn)(stdout, "%s ", xml_name(xn)); - if (!tleaf(xn)) - (*fn)(stdout, "{\n"); - xc = NULL; - while ((xc = xml_child_each(xn, xc, -1)) != NULL){ - if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) - if (cli_xml2txt(xc, fn, level+1) < 0) - break; - } - if (!tleaf(xn)) - (*fn)(stdout, "%*s}\n", 4*level, ""); - ok: - retval = 0; - done: - return retval; -} - /*! Enter a CLI edit mode * @param[in] h CLICON handle * @param[in] cvv Vector of variables from CLIgen command-line @@ -657,10 +577,10 @@ cli_auto_show(clicon_handle h, break; case FORMAT_TEXT: if (isroot) - cli_xml2txt(xp, cligen_output, 0); /* tree-formed text */ + xml2txt(xp, cligen_output, stdout, 0); /* tree-formed text */ else while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) - cli_xml2txt(xc, cligen_output, 0); /* tree-formed text */ + xml2txt(xc, cligen_output, stdout, 0); /* tree-formed text */ break; case FORMAT_CLI: if (xml2cli(h, stdout, xp, prefix, cligen_output, !isroot) < 0) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 0b2ba8dc..1bc1b3b7 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -698,7 +698,7 @@ compare_xmls(cxobj *xc1, xc = NULL; if (astext) while ((xc = xml_child_each(xc1, xc, -1)) != NULL) - xml2txt_cb(f, xc, cligen_output); + xml2txt(xc, cligen_output, f, 0); else while ((xc = xml_child_each(xc1, xc, -1)) != NULL) clicon_xml2file_cb(f, xc, 0, 1, cligen_output); @@ -715,7 +715,7 @@ compare_xmls(cxobj *xc1, xc = NULL; if (astext) while ((xc = xml_child_each(xc2, xc, -1)) != NULL) - xml2txt_cb(f, xc, cligen_output); + xml2txt(xc, cligen_output, f, 0); else while ((xc = xml_child_each(xc2, xc, -1)) != NULL) clicon_xml2file_cb(f, xc, 0, 1, cligen_output); @@ -882,6 +882,14 @@ load_config_file(clicon_handle h, goto done; } break; + case FORMAT_TEXT: + if ((ret = clixon_text_syntax_parse_file(fp, YB_NONE, yspec, &xt, &xerr)) < 0) + goto done; + if (ret == 0){ + clixon_netconf_error(xerr, "Loading", filename); + goto done; + } + break; case FORMAT_CLI: { char *mode = cli_syntax_mode(h); @@ -1034,7 +1042,7 @@ save_config_file(clicon_handle h, goto done; break; case FORMAT_TEXT: - if (xml2txt(f, xt, 0) < 0) + if (xml2txt(xt, fprintf, f, 0) < 0) goto done; break; case FORMAT_CLI: @@ -1165,7 +1173,7 @@ cli_notification_cb(int s, goto done; break; case FORMAT_TEXT: - if (xml2txt_cb(stdout, x, cligen_output) < 0) + if (xml2txt(x, cligen_output, stdout, 0) < 0) goto done; break; default: diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2dc0acb2..ca42233e 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -505,7 +505,7 @@ cli_show_config1(clicon_handle h, case FORMAT_TEXT: xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, -1)) != NULL) - cli_xml2txt(xc, cligen_output, 0); /* tree-formed text */ + xml2txt(xc, cligen_output, stdout, 0); /* tree-formed text */ break; case FORMAT_CLI: if (xml2cli(h, stdout, xt, prefix, cligen_output, 1) < 0) @@ -789,7 +789,7 @@ cli_show_generated(clicon_handle h, cli_xml2file(xp_helper, 0, 1, fprintf); break; case FORMAT_TEXT: - cli_xml2txt(xp_helper, cligen_output, 0); /* tree-formed text */ + xml2txt(xp_helper, cligen_output, stdout, 0); /* tree-formed text */ break; default: /* see cli_show_config() */ break; @@ -986,7 +986,7 @@ cli_pagination(clicon_handle h, xml2json_file(stdout, xc, 1, cligen_output, 0); break; case FORMAT_TEXT: - xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ + xml2txt(xc, cligen_output, stdout, 0); /* tree-formed text */ break; case FORMAT_CLI: /* hardcoded to compress and list-keyword = nokey */ diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 58befa85..a270b69b 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -136,7 +136,6 @@ int cli_help(clicon_handle h, cvec *vars, cvec *argv); int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, cvec *commands, cvec *helptexts); int cli_xml2file (cxobj *xn, int level, int prettyprint, clicon_output_cb *fn); -int cli_xml2txt(cxobj *xn, clicon_output_cb *fn, int level); int xml2cli(clicon_handle h, FILE *f, cxobj *xn, char *prepend, clicon_output_cb *fn, int skiptop); /* cli_show.c: CLIgen new vector arg callbacks */ diff --git a/example/main/example_cli.c b/example/main/example_cli.c index 566add05..3d086aec 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -122,7 +122,7 @@ example_client_rpc(clicon_handle h, fprintf(stdout,"\n"); /* pretty-print: - xml2txt_cb(stdout, xml_child_i(xret, 0), cligen_output); + xml2txt(xml_child_i(xret, 0), cligen_output, stdout, 0); */ retval = 0; done: diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index 47b10817..7e2c2e06 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -111,11 +111,13 @@ load("Load configuration from XML file") ("Filename (local file cli("Replace candidate with file containing CLI commands"), load_config_file("","filename", "replace", "cli"); xml("Replace candidate with file containing XML"), load_config_file("","filename", "replace", "xml"); json("Replace candidate with file containing JSON"), load_config_file("","filename", "replace", "json"); + text("Replace candidate with file containing TEXT"), load_config_file("","filename", "replace", "text"); } merge("Merge file with existent candidate"), load_config_file("filename", "merge");{ cli("Merge candidate with file containing CLI commands"), load_config_file("","filename", "merge", "cli"); xml("Merge candidate with file containing XML"), load_config_file("","filename", "merge", "xml"); json("Merge candidate with file containing JSON"), load_config_file("","filename", "merge", "json"); + text("Merge candidate with file containing TEXT"), load_config_file("","filename", "merge", "text"); } } example("This is a comment") ("Just a random number"), mycallback("myarg"); diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 23a49eb5..f395927c 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -103,6 +103,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_text_syntax.h b/lib/clixon/clixon_text_syntax.h new file mode 100644 index 00000000..fb2c3599 --- /dev/null +++ b/lib/clixon/clixon_text_syntax.h @@ -0,0 +1,47 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * TEXT / curly-brace syntax parsing and translations + */ +#ifndef _CLIXON_TEXT_SYNTAX_H +#define _CLIXON_TEXT_SYNTAX_H + +/* + * Prototypes + */ +int xml2txt(cxobj *xn, clicon_output_cb *fn, FILE *f, int level); +int clixon_text_syntax_parse_string(char *str, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr); +int clixon_text_syntax_parse_file(FILE *fp, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr); + +#endif /* _CLIXON_TEXT_SYNTAX_H */ + diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 1e24edce..ebeca724 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -225,6 +225,7 @@ cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); int xml_child_insert_pos(cxobj *x, cxobj *xc, int i); int xml_childvec_set(cxobj *x, int len); cxobj **xml_childvec_get(cxobj *x); +int clixon_child_xvec_append(cxobj *x, clixon_xvec *xv); cxobj *xml_new(char *name, cxobj *xn_parent, enum cxobj_type type); cxobj *xml_new_body(char *name, cxobj *parent, char *val); yang_stmt *xml_spec(cxobj *x); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 1ebec813..3691e7c8 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -50,8 +50,6 @@ typedef enum yang_class yang_class; * Prototypes */ int isxmlns(cxobj *x); -int xml2txt_cb(FILE *f, cxobj *x, clicon_output_cb *fn); -int xml2txt(FILE *f, cxobj *x, int level); int xmlns_assign(cxobj *x); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); diff --git a/lib/clixon/clixon_xml_vec.h b/lib/clixon/clixon_xml_vec.h index 2117a2c2..f9ac3c5d 100644 --- a/lib/clixon/clixon_xml_vec.h +++ b/lib/clixon/clixon_xml_vec.h @@ -41,7 +41,7 @@ /* * Types */ -typedef struct clixon_xml_vec clixon_xvec; /* struct defined in clicon_xvec.c */ +typedef struct clixon_xml_vec clixon_xvec; /* struct defined in clicon_xml_vec.c */ /* * Prototypes @@ -51,7 +51,7 @@ clixon_xvec *clixon_xvec_dup(clixon_xvec *xv0); int clixon_xvec_free(clixon_xvec *xv); int clixon_xvec_len(clixon_xvec *xv); cxobj *clixon_xvec_i(clixon_xvec *xv, int i); -int clixon_xvec_extract(clixon_xvec *xv, cxobj ***xvec, int *xlen); +int clixon_xvec_extract(clixon_xvec *xv, cxobj ***xvcec, int *xlen, int *xmax); int clixon_xvec_append(clixon_xvec *xv, cxobj *x); int clixon_xvec_prepend(clixon_xvec *xv, cxobj *x); int clixon_xvec_insert_pos(clixon_xvec *xv, cxobj *x, int i); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 26936eb7..3d577404 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -85,14 +85,15 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_xpath_optimize.c clixon_xpath_yang.c \ clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c \ - clixon_dispatcher.c + clixon_dispatcher.c clixon_text_syntax.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_xpath_parse.o clixon_xpath_parse.tab.o \ lex.clixon_api_path_parse.o clixon_api_path_parse.tab.o \ - lex.clixon_instance_id_parse.o clixon_instance_id_parse.tab.o + lex.clixon_instance_id_parse.o clixon_instance_id_parse.tab.o \ + lex.clixon_text_syntax_parse.o clixon_text_syntax_parse.tab.o # Generated src GENSRC = build.c @@ -123,6 +124,7 @@ clean: rm -f clixon_xpath_parse.tab.[ch] clixon_xpath_parse.[co] rm -f clixon_api_path_parse.tab.[ch] clixon_api_path_parse.[co] rm -f clixon_instance_id_parse.tab.[ch] clixon_instance_id_parse.[co] + rm -f clixon_text_syntax_parse.tab.[ch] clixon_text_syntax_parse.[co] rm -f lex.clixon_xml_parse.c rm -f lex.clixon_yang_parse.c rm -f lex.clixon_json_parse.c @@ -221,6 +223,19 @@ clixon_instance_id_parse.tab.c: clixon_instance_id_parse.tab.h lex.clixon_instance_id_parse.o : lex.clixon_instance_id_parse.c clixon_instance_id_parse.tab.h $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< +# text syntax parser +lex.clixon_text_syntax_parse.c : clixon_text_syntax_parse.l clixon_text_syntax_parse.tab.h + $(LEX) -Pclixon_text_syntax_parse clixon_text_syntax_parse.l # -d is debug + +clixon_text_syntax_parse.tab.h: clixon_text_syntax_parse.y + $(YACC) -l -d -b clixon_text_syntax_parse -p clixon_text_syntax_parse clixon_text_syntax_parse.y # -t is debug + +# extra rule to avoid parallell yaccs +clixon_text_syntax_parse.tab.c: clixon_text_syntax_parse.tab.h + +lex.clixon_text_syntax_parse.o : lex.clixon_text_syntax_parse.c clixon_text_syntax_parse.tab.h + $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< + distclean: clean rm -f Makefile *~ .depend diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index e4a4f4f9..30b6fc8d 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -87,7 +87,7 @@ /* Size of json read buffer when reading from file*/ #define BUFLEN 1024 -/* Name of xml top object created by xml parse functions */ +/* Name of xml top object created by parse functions */ #define JSON_TOP_SYMBOL "top" enum array_element_type{ @@ -1545,6 +1545,7 @@ clixon_json_parse_string(char *str, * * @param[in] fp File descriptor to the JSON file (ASCII string) * @param[in] rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG + * @param[in] yb How to bind yang to XML top-level when parsing * @param[in] yspec Yang specification, or NULL * @param[in,out] xt Pointer to (XML) parse tree. If empty, create. * @param[out] xerr Reason for invalid returned as netconf err msg diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index 2dc2bc83..21dfb7e4 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -1652,7 +1652,7 @@ clixon_xml_find_api_path(cxobj *xt, if (ret == 0) goto fail; /* Convert to api xvec format */ - if (clixon_xvec_extract(xv, xvec, xlen) < 0) + if (clixon_xvec_extract(xv, xvec, xlen, NULL) < 0) goto done; retval = 1; done: @@ -1745,7 +1745,7 @@ clixon_xml_find_instance_id(cxobj *xt, if (ret == 0) goto fail; /* Convert to api xvec format */ - if (xv && clixon_xvec_extract(xv, xvec, xlen) < 0) + if (xv && clixon_xvec_extract(xv, xvec, xlen, NULL) < 0) goto done; retval = 1; done: diff --git a/lib/src/clixon_text_syntax.c b/lib/src/clixon_text_syntax.c new file mode 100644 index 00000000..abc29e98 --- /dev/null +++ b/lib/src/clixon_text_syntax.c @@ -0,0 +1,421 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * TEXT / curly-brace syntax parsing and translations + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#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_options.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_io.h" +#include "clixon_xml_sort.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xml_bind.h" +#include "clixon_text_syntax.h" +#include "clixon_text_syntax_parse.h" + +/* Size of json read buffer when reading from file*/ +#define BUFLEN 1024 + +/* Name of xml top object created by parse functions */ +#define TOP_SYMBOL "top" + +/*! x is element and has eactly one child which in turn has none + * @see child_type in clixon_json.c + */ +static int +tleaf(cxobj *x) +{ + cxobj *xc; + + if (xml_type(x) != CX_ELMNT) + return 0; + if (xml_child_nr_notype(x, CX_ATTR) != 1) + return 0; + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -1; /* n/a */ + return (xml_child_nr_notype(xc, CX_ATTR) == 0); +} + +/*! Translate XML to a "pseudo-code" textual format using a callback - internal function + * @param[in] xn XML object to print + * @param[in] fn Callback to make print function + * @param[in] f File to print to + * @param[in] level print 4 spaces per level in front of each line + * @see xml2txt XXX why are these different? + */ +int +xml2txt(cxobj *xn, + clicon_output_cb *fn, + FILE *f, + int level) +{ + cxobj *xc = NULL; + int children=0; + int retval = -1; + int exist = 0; + yang_stmt *yn; + yang_stmt *yp; + yang_stmt *ymod; + yang_stmt *ypmod; + char *prefix = NULL; + + if (xn == NULL || fn == NULL){ + clicon_err(OE_XML, EINVAL, "xn or fn is NULL"); + goto done; + } + if ((yn = xml_spec(xn)) != NULL){ + if (yang_extension_value(yn, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0) + goto done; + if (exist) + goto ok; + /* Find out prefix if needed: topmost or new module a la API-PATH */ + if (ys_real_module(yn, &ymod) < 0) + goto done; + if ((yp = yang_parent_get(yn)) != NULL && + yp != ymod){ + if (ys_real_module(yp, &ypmod) < 0) + goto done; + if (ypmod != ymod) + prefix = yang_argument_get(ymod); + } + else + prefix = yang_argument_get(ymod); + } + xc = NULL; /* count children (elements and bodies, not attributes) */ + while ((xc = xml_child_each(xn, xc, -1)) != NULL) + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + children++; + if (!children){ /* If no children print line */ + switch (xml_type(xn)){ + case CX_BODY: + (*fn)(f, "%s;\n", xml_value(xn)); + break; + case CX_ELMNT: + (*fn)(f, "%*s%s;\n", 4*level, "", xml_name(xn)); + break; + default: + break; + } + goto ok; + } + (*fn)(f, "%*s", 4*level, ""); + if (prefix) + (*fn)(f, "%s:", prefix); + (*fn)(f, "%s ", xml_name(xn)); + if (!tleaf(xn)) + (*fn)(f, "{\n"); + xc = NULL; + while ((xc = xml_child_each(xn, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + if (xml2txt(xc, fn, f, level+1) < 0) + break; + } + if (!tleaf(xn)) + (*fn)(f, "%*s}\n", 4*level, ""); + ok: + retval = 0; + done: + return retval; +} + +/*! Parse a string containing text syntax and return an XML tree + * + + * @param[in] str Input string containing JSON + * @param[in] rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG + * @param[in] yb How to bind yang to XML top-level when parsing (if rfc7951) + * @param[in] yspec Yang specification (if rfc 7951) + * @param[out] xt XML top of tree typically w/o children on entry (but created) + * @param[out] xerr Reason for invalid returned as netconf err msg + * + * @see _xml_parse for XML variant + * @retval 1 OK and valid + * @retval 0 Invalid (only if yang spec) + * @retval -1 Error with clicon_err called + */ +static int +_text_syntax_parse(char *str, + yang_bind yb, + yang_stmt *yspec, + cxobj *xt, + cxobj **xerr) +{ + int retval = -1; + clixon_text_syntax_yacc ts = {0,}; + int ret; + cxobj *x; + cbuf *cberr = NULL; + int failed = 0; /* yang assignment */ + + clicon_debug(1, "%s %d %s", __FUNCTION__, yb, str); + ts.ts_parse_string = str; + ts.ts_linenum = 1; + ts.ts_xtop = xt; + ts.ts_yspec = yspec; + if (clixon_text_syntax_parsel_init(&ts) < 0) + goto done; + if (clixon_text_syntax_parseparse(&ts) != 0) { /* yacc returns 1 on error */ + clicon_log(LOG_NOTICE, "TEXT SYNTAX error: line %d", ts.ts_linenum); + if (clicon_errno == 0) + clicon_err(OE_JSON, 0, "TEXT SYNTAX parser error with no error code (should not happen)"); + goto done; + } + + x = NULL; + while ((x = xml_child_each(ts.ts_xtop, x, CX_ELMNT)) != NULL) { + /* Populate, ie associate xml nodes with yang specs + */ + switch (yb){ + case YB_NONE: + break; + case YB_PARENT: + /* xt:n Has spec + * x: <-- populate from parent + */ + if ((ret = xml_bind_yang0(x, YB_PARENT, NULL, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + break; + case YB_MODULE_NEXT: + if ((ret = xml_bind_yang(x, YB_MODULE, yspec, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + break; + case YB_MODULE: + /* xt: nospec + * x: <-- populate from modules + */ + if ((ret = xml_bind_yang0(x, YB_MODULE, yspec, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + break; + case YB_RPC: + if ((ret = xml_bind_yang_rpc(x, yspec, xerr)) < 0) + goto done; + if (ret == 0){ /* Add message-id */ + if (*xerr && clixon_xml_attr_copy(x, *xerr, "message-id") < 0) + goto done; + failed++; + } + break; + } /* switch */ + } + if (failed) + goto fail; + /* Sort the complete tree after parsing. Sorting is not really meaningful if Yang + not bound */ + if (yb != YB_NONE) + if (xml_sort_recurse(xt) < 0) + goto done; + retval = 1; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cberr) + cbuf_free(cberr); + clixon_text_syntax_parsel_exit(&ts); + return retval; + fail: /* invalid */ + retval = 0; + goto done; +} + +/*! Parse string containing TEXT syntax and return an XML tree + * + * @param[in] str String containing TEXT syntax + * @param[in] yb How to bind yang to XML top-level when parsing + * @param[in] yspec Yang specification, mandatory to make module->xmlns translation + * @param[in,out] xt Top object, if not exists, on success it is created with name 'top' + * @param[out] xerr Reason for invalid returned as netconf err msg + * @retval 1 OK and valid + * @retval 0 Invalid (only if yang spec) w xerr set + * @retval -1 Error with clicon_err called + * + * @code + * cxobj *x = NULL; + * if (clixon_text_syntax_parse_string(str, YB_MODULE, yspec, &x, &xerr) < 0) + * err; + * xml_free(x); + * @endcode + * @note you need to free the xml parse tree after use, using xml_free() + * @see clixon_text_syntax_parse_file From a file + */ +int +clixon_text_syntax_parse_string(char *str, + yang_bind yb, + yang_stmt *yspec, + cxobj **xt, + cxobj **xerr) +{ + clicon_debug(1, "%s", __FUNCTION__); + if (xt==NULL){ + clicon_err(OE_XML, EINVAL, "xt is NULL"); + return -1; + } + if (*xt == NULL){ + if ((*xt = xml_new("top", NULL, CX_ELMNT)) == NULL) + return -1; + } + return _text_syntax_parse(str, yb, yspec, *xt, xerr); +} + +/*! Read a TEXT syntax definition from file and parse it into a parse-tree. + * + * File will be parsed as follows: + * (1) parsed according to TEXT syntax; # Only this check if yspec is NULL + * (2) sanity checked wrt yang + * (4) an xml parse tree will be returned + * + * @param[in] fp File descriptor to the TEXT syntax file + * @param[in] yb How to bind yang to XML top-level when parsing + * @param[in] yspec Yang specification, or NULL + * @param[in,out] xt Pointer to (XML) parse tree. If empty, create. + * @param[out] xerr Reason for invalid returned as netconf err msg + * + * @code + * cxobj *xt = NULL; + * if (clixon_text_syntax_parse_file(stdin, YB_MODULE, yspec, &xt) < 0) + * err; + * xml_free(xt); + * @endcode + * @note you need to free the xml parse tree after use, using xml_free() + * @note, If xt empty, a top-level symbol will be added so that will be: + * @note May block on file I/O + * + * @retval 1 OK and valid + * @retval 0 Invalid (only if yang spec) w xerr set + * @retval -1 Error with clicon_err called + * + * @see clixon_text_syntax_parse_string + */ +int +clixon_text_syntax_parse_file(FILE *fp, + yang_bind yb, + yang_stmt *yspec, + cxobj **xt, + cxobj **xerr) +{ + int retval = -1; + int ret; + char *textbuf = NULL; + int textbuflen = BUFLEN; /* start size */ + int oldtextbuflen; + char *ptr; + char ch; + int len = 0; + + if (xt==NULL){ + clicon_err(OE_XML, EINVAL, "xt is NULL"); + return -1; + } + if ((textbuf = malloc(textbuflen)) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(textbuf, 0, textbuflen); + ptr = textbuf; + while (1){ + if ((ret = fread(&ch, 1, 1, fp)) < 0){ + clicon_err(OE_XML, errno, "read"); + break; + } + if (ret != 0) + textbuf[len++] = ch; + if (ret == 0){ + if (*xt == NULL) + if ((*xt = xml_new(TOP_SYMBOL, NULL, CX_ELMNT)) == NULL) + goto done; + if (len){ + if ((ret = _text_syntax_parse(ptr, yb, yspec, *xt, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + } + break; + } + if (len >= textbuflen-1){ /* Space: one for the null character */ + oldtextbuflen = textbuflen; + textbuflen *= 2; + if ((textbuf = realloc(textbuf, textbuflen)) == NULL){ + clicon_err(OE_XML, errno, "realloc"); + goto done; + } + memset(textbuf+oldtextbuflen, 0, textbuflen-oldtextbuflen); + ptr = textbuf; + } + } + retval = 1; + done: + if (retval < 0 && *xt){ + free(*xt); + *xt = NULL; + } + if (textbuf) + free(textbuf); + return retval; + fail: + retval = 0; + goto done; +} diff --git a/lib/src/clixon_text_syntax_parse.h b/lib/src/clixon_text_syntax_parse.h new file mode 100644 index 00000000..56588a6a --- /dev/null +++ b/lib/src/clixon_text_syntax_parse.h @@ -0,0 +1,69 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * TEXT / curly-brace syntax parsing and translations + */ +#ifndef _CLIXON_TEXT_SYNTAX_PARSE_H_ +#define _CLIXON_TEXT_SYNTAX_PARSE_H_ + +/* + * Types + */ +/*! XML parser yacc handler struct */ +struct clixon_text_syntax_parse_yacc { + char *ts_parse_string; /* original (copy of) parse string */ + int ts_linenum; /* Number of \n in parsed buffer */ + void *ts_lexbuf; /* internal parse buffer from lex */ + cxobj *ts_xtop; /* Vector of created top-level nodes (to know which are created) */ + int ts_xlen; /* Length of ts_xvec */ + int ts_lex_state; /* lex return state */ + yang_stmt *ts_yspec; /* Yang spec */ +}; +typedef struct clixon_text_syntax_parse_yacc clixon_text_syntax_yacc; + +/* + * Variables + */ +extern char *clixon_text_syntax_parsetext; + +/* + * Prototypes + */ +int clixon_text_syntax_parsel_init(clixon_text_syntax_yacc *ya); +int clixon_text_syntax_parsel_exit(clixon_text_syntax_yacc *ya); + +int clixon_text_syntax_parsel_linenr(void); +int clixon_text_syntax_parselex(void *); +int clixon_text_syntax_parseparse(void *); + +#endif /* _CLIXON_TEXT_SYNTAX_PARSE_H_ */ diff --git a/lib/src/clixon_text_syntax_parse.l b/lib/src/clixon_text_syntax_parse.l new file mode 100644 index 00000000..eb85cb60 --- /dev/null +++ b/lib/src/clixon_text_syntax_parse.l @@ -0,0 +1,124 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * TEXT / curly-brace syntax parsing and translations + */ + +%{ + +#include "clixon_config.h" + +#include +#include +#include + +#include "clixon_text_syntax_parse.tab.h" /* generated file */ + +/* cligen */ +#include + +/* clicon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_text_syntax_parse.h" + +/* Redefine main lex function so that you can send arguments to it: _ts is added to arg list */ +#define YY_DECL int clixon_text_syntax_parselex(void *_ts) + +/* Dont use input function (use user-buffer) */ +#define YY_NO_INPUT + +/* typecast macro */ +#define _TS ((clixon_text_syntax_yacc *)_ts) + +#undef clixon_xml_parsewrap +int clixon_text_syntax_parsewrap(void) +{ + return 1; +} + +/* + */ + +%} + +%s COMMENT + +%% + +[ \t]* +\n { _TS->ts_linenum++; } +\r +<> { return MY_EOF; } +# { _TS->ts_lex_state =INITIAL; BEGIN(COMMENT); } +\{ { return *yytext; } +\} { return *yytext; } +\[ { return *yytext; } +\] { return *yytext; } +\; { return *yytext; } +\: { return *yytext; } +[^\n\r \t\[\]\{\}\;\:]+ { + clixon_text_syntax_parselval.string = strdup(yytext); + return TOKEN; } +. { return -1; } + +\n { _TS->ts_linenum++; BEGIN(_TS->ts_lex_state);} +<> { return MY_EOF; } +[^\n]+ + +%% + +/*! Initialize XML scanner. + */ +int +clixon_text_syntax_parsel_init(clixon_text_syntax_yacc *ts) +{ + BEGIN(INITIAL); + ts->ts_lexbuf = yy_scan_string (ts->ts_parse_string); + if (0) + yyunput(0, ""); /* XXX: just to use unput to avoid warning */ + return 0; +} + +/*! Exit xml scanner */ +int +clixon_text_syntax_parsel_exit(clixon_text_syntax_yacc *ts) +{ + yy_delete_buffer(ts->ts_lexbuf); + clixon_text_syntax_parselex_destroy(); /* modern */ + + return 0; +} diff --git a/lib/src/clixon_text_syntax_parse.y b/lib/src/clixon_text_syntax_parse.y new file mode 100644 index 00000000..bf2da9d9 --- /dev/null +++ b/lib/src/clixon_text_syntax_parse.y @@ -0,0 +1,195 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * TEXT / curly-brace syntax parsing and translations + */ +%union { + char *string; + void *stack; +} + +%token MY_EOF +%token TOKEN + +%type stmts +%type stmt +%type id +%type value + +%start top + +%lex-param {void *_ts} /* Add this argument to parse() and lex() function */ +%parse-param {void *_ts} + +%{ + +/* typecast macro */ +#define _TS ((clixon_text_syntax_yacc *)_ts) + +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_string.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_vec.h" +#include "clixon_data.h" +#include "clixon_text_syntax_parse.h" + +/* Enable for debugging, steals some cycles otherwise */ +#if 0 +#define _PARSE_DEBUG(s) clicon_debug(1,(s)) +#else +#define _PARSE_DEBUG(s) +#endif + +void +clixon_text_syntax_parseerror(void *arg, + char *s) +{ + clixon_text_syntax_yacc *ts = (clixon_text_syntax_yacc *)arg; + + clicon_err(OE_XML, XMLPARSE_ERRNO, "text_syntax_parse: line %d: %s: at or before: %s", + ts->ts_linenum, + s, + clixon_text_syntax_parsetext); + return; +} + +static int +text_add_value(cxobj *xn, + char *value) +{ + int retval = -1; + cxobj *xb = NULL; + + if ((xb = xml_new("body", xn, CX_BODY)) == NULL) + goto done; + if (xml_value_set(xb, value) < 0){ + xml_free(xb); + goto done; + } + retval = 0; + done: + return retval; +} + +static cxobj* +text_create_node(clixon_text_syntax_yacc *ts, + char *module, + char *name) +{ + cxobj *xn; + yang_stmt *ymod; + char *ns; + + if ((xn = xml_new(name, NULL, CX_ELMNT)) == NULL) + goto done; + if (module && ts->ts_yspec){ + /* Silently ignore if module name not found */ + if ((ymod = yang_find(ts->ts_yspec, Y_MODULE, NULL)) != NULL){ + if ((ns = yang_find_mynamespace(ymod)) == NULL){ + clicon_err(OE_YANG, 0, "No namespace"); + goto done; + } + /* Set default namespace */ + if (xmlns_set(xn, NULL, ns) < 0) + goto done; + } + } + done: + return xn; +} + +%} + +%% + +top : stmt MY_EOF { _PARSE_DEBUG("top->stmt"); + if (xml_addsub(_TS->ts_xtop, $1) < 0) YYERROR; + YYACCEPT; } + ; + +stmts : stmts stmt { _PARSE_DEBUG("stmts->stmts stmt"); + if (clixon_xvec_append($1, $2) < 0) YYERROR; + $$ = $1; + } + | { _PARSE_DEBUG("stmts->stmt"); + if (($$ = clixon_xvec_new()) == NULL) YYERROR; + } + ; + +stmt : id value ';' { _PARSE_DEBUG("stmt-> id value ;"); + if (text_add_value($1, $2) < 0) YYERROR; + $$ = $1; + } + | id '{' stmts '}' { _PARSE_DEBUG("stmt-> id { stmts }"); + if (clixon_child_xvec_append($1, $3) < 0) YYERROR; + clixon_xvec_free($3); + $$ = $1; + } + + | id '[' values ']' { _PARSE_DEBUG("stmt-> id [ values ]"); } + ; + +id : TOKEN { _PARSE_DEBUG("id->TOKEN"); + if (($$ = text_create_node(_TS, NULL, $1)) == NULL) YYERROR;; + } + | TOKEN ':' TOKEN { _PARSE_DEBUG("id->TOKEN : TOKEN"); + if (($$ = text_create_node(_TS, $1, $3)) == NULL) YYERROR;; + } + ; + +values : values TOKEN { _PARSE_DEBUG("values->values TOKEN"); } + | { _PARSE_DEBUG("values->"); } + ; + +value : TOKEN { _PARSE_DEBUG("value->TOKEN"); $$ = $1; } + ; + + +%% + diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index d27c9d65..2027a55a 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -936,7 +936,6 @@ xml_child_each(cxobj *xparent, return xn; } - /*! Extend child vector with one and insert xml node there * @note does not do anything with child, you may need to set its parent, etc * @see xml_child_insert_pos @@ -1038,6 +1037,33 @@ xml_childvec_get(cxobj *x) return x->x_childvec; } +/*! Given an XML object and a vector of children xvec, append the children to the object + * + * @param[in] x XML node + * @param[in] xv Clixon xml vector + * @retval 0 OK + * @retval -1 Error + * @note xv is not same type as x_childvec + */ +int +clixon_child_xvec_append(cxobj *xn, + clixon_xvec *xv) +{ + int retval = -1; + cxobj *xc; + int i; + + for (i=0; ixv_vec; *xlen = xv->xv_len; + if (xmax) + *xmax = xv->xv_max; if (xv->xv_vec != NULL){ xv->xv_len = 0; xv->xv_max = 0; diff --git a/lib/src/clixon_xpath_optimize.c b/lib/src/clixon_xpath_optimize.c index f07e4108..02c60cb0 100644 --- a/lib/src/clixon_xpath_optimize.c +++ b/lib/src/clixon_xpath_optimize.c @@ -342,7 +342,7 @@ xpath_optimize_check(xpath_tree *xs, if ((ret = xpath_list_optimize_fn(xs, xv, xvec)) < 0) return -1; if (ret == 1){ - if (clixon_xvec_extract(xvec, xvec0, xlen0) < 0) + if (clixon_xvec_extract(xvec, xvec0, xlen0, NULL) < 0) return -1; clixon_xvec_free(xvec); _optimize_hits++; diff --git a/test/test_helloworld.sh b/test/test_helloworld.sh index b211298e..a13db400 100755 --- a/test/test_helloworld.sh +++ b/test/test_helloworld.sh @@ -133,8 +133,8 @@ ret=$($clixon_cli -1 -f $cfg show config) if [ $? -ne 0 ]; then err 0 $r fi -if [ "$ret" != "hello world;" ]; then - err "$ret" "hello world;" +if [ "$ret" != "clixon-hello:hello world;" ]; then + err "$ret" "clixon-hello:hello world;" fi new "netconf edit-config" diff --git a/util/Makefile.in b/util/Makefile.in index e53760a5..c0444a9e 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -85,6 +85,7 @@ LIBDEPS += $(top_srcdir)/lib/src/$(CLIXON_LIB) APPSRC = clixon_util_xml.c APPSRC += clixon_util_xml_mod.c APPSRC += clixon_util_json.c +APPSRC += clixon_util_text_syntax.c APPSRC += clixon_util_yang.c APPSRC += clixon_util_xpath.c APPSRC += clixon_util_path.c @@ -152,6 +153,9 @@ clixon_util_validate: clixon_util_validate.c $(LIBDEPS) clixon_util_dispatcher: clixon_util_dispatcher.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ -l clixon_backend -o $@ $(LIBS) +clixon_util_text_syntax: clixon_util_text_syntax.c $(LIBDEPS) + $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ + ifdef with_restconf clixon_util_stream: clixon_util_stream.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@ diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c index 3fdff966..532147bd 100644 --- a/util/clixon_util_json.c +++ b/util/clixon_util_json.c @@ -32,8 +32,7 @@ ***** END LICENSE BLOCK ***** - * JSON support functions. - * JSON syntax is according to: + * JSON utility command * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf */ diff --git a/util/clixon_util_text_syntax.c b/util/clixon_util_text_syntax.c new file mode 100644 index 00000000..249ec730 --- /dev/null +++ b/util/clixon_util_text_syntax.c @@ -0,0 +1,169 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC (Netgate) + + 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 ***** + + * Text syntax utility command + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +/* Command line options passed to getopt(3) */ +#define UTIL_TEXT_SYNTAX_OPTS "hD:f:tl:y:" + +/* + * Text syntax parse and pretty print test program + * Example compile: +*/ +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options] JSON as input on stdin\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-f \tTEXT syntax input file (overrides stdin)\n" + "\t-t \t\tOutput as TEXT syntax (default is as XML)\n" + "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n" + "\t-y \tyang filename to parse (must be stand-alone)\n" , + argv0); + exit(0); +} + +int +main(int argc, + char **argv) +{ + int retval = -1; + cxobj *xt = NULL; + cxobj *xc; + cbuf *cb = cbuf_new(); + int c; + int logdst = CLICON_LOG_STDERR; + int text_syntax_output = 0; + char *yang_filename = NULL; + yang_stmt *yspec = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + int dbg = 0; + char *input_filename = NULL; + FILE *fp = stdin; /* base file, stdin */ + + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, UTIL_TEXT_SYNTAX_OPTS)) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &dbg) != 1) + usage(argv[0]); + break; + case 'f': + input_filename = optarg; + break; + case 't': + text_syntax_output++; + break; + case 'l': /* Log destination: s|e|o|f */ + if ((logdst = clicon_log_opt(optarg[0])) < 0) + usage(argv[0]); + break; + case 'y': + yang_filename = optarg; + break; + default: + usage(argv[0]); + break; + } + clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst); + clicon_debug_init(dbg, NULL); + + if (yang_filename){ + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse_filename(yang_filename, yspec) == NULL){ + fprintf(stderr, "yang parse error %s\n", clicon_err_reason); + return -1; + } + } + if (input_filename){ + if ((fp = fopen(input_filename, "r")) == NULL){ + clicon_err(OE_YANG, errno, "open(%s)", input_filename); + goto done; + } + } + if ((ret = clixon_text_syntax_parse_file(fp, yspec?YB_MODULE:YB_NONE, yspec, &xt, &xerr)) < 0) + goto done; + if (ret == 0){ + xml_print(stderr, xerr); + goto done; + } + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL){ + if (text_syntax_output) + xml2txt(xc, fprintf, stdout, 0); + else{ + clicon_xml2cbuf(cb, xc, 0, 1, -1); /* print xml */ + fprintf(stdout, "%s", cbuf_get(cb)); + } + } + fflush(stdout); + retval = 0; + done: + if (yspec) + ys_free(yspec); + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + return retval; +} diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index cd57a4e0..74c1756e 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -67,7 +67,7 @@ #include "clixon/clixon.h" /* Command line options passed to getopt(3) */ -#define UTIL_XML_OPTS "hD:f:Jjl:pvoy:Y:t:T:u" +#define UTIL_XML_OPTS "hD:f:JjXl:pvoy:Y:t:T:u" static int validate_tree(clicon_handle h, @@ -118,6 +118,7 @@ usage(char *argv0) "\t-f \tXML input file (overrides stdin)\n" "\t-J \t\tInput as JSON\n" "\t-j \t\tOutput as JSON\n" + "\t-X \t\tOutput as TEXT \n" "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n" "\t-o \t\tOutput the file\n" "\t-v \t\tValidate the result in terms of Yang model (requires -y)\n" @@ -144,6 +145,7 @@ main(int argc, int logdst = CLICON_LOG_STDERR; int jsonin = 0; int jsonout = 0; + int textout = 0; char *input_filename = NULL; char *top_input_filename = NULL; char *yang_file_dir = NULL; @@ -196,6 +198,9 @@ main(int argc, case 'j': jsonout++; break; + case 'X': + textout++; + break; case 'l': /* Log destination: s|e|o|f */ if ((logdst = clicon_log_opt(optarg[0])) < 0) usage(argv[0]); @@ -334,9 +339,16 @@ main(int argc, if (validate_tree(h, xt, yspec) < 0) goto done; } - /* 4. Output data (xml/json) */ + /* 4. Output data (xml/json/text) */ if (output){ - if (jsonout) + if (textout){ + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL){ + if (xml2txt(xc, fprintf, stdout, 0) < 0) + goto done; + } + } + else if (jsonout) xml2json_cbuf(cb, xt, pretty, 1); /* print json */ else clicon_xml2cbuf(cb, xt, 0, pretty, -1, 1); /* print xml */