diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee775b..be0ebff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,9 @@ Users may have to change how they access the system ### C/CLI-API changes on existing features Developers may need to change their code +* Renamed `clixon_txt2file()` to `clixon_text2file()` +* Changed parameters of example clispec function `compare_dbs()` + * New parameters are: `db1`, `db2`, `format` * Add `fromroot` parameter to `cli_show_common()` * `cli_show_common(...xpath...)` --> `cli_show_common(...xpath,0...)` * Low-level message functions added `descr` argument for better logging @@ -73,6 +76,8 @@ Developers may need to change their code ### Minor features +* CLI show compare example function: + * Improved diff algorithm for XML and TEXT/curly, replaced UNIX diff with structural in-mem algorithm * JSON: Added unicode BMP support for unicode strings as part of fixing (https://github.com/clicon/clixon/issues/453) * Example cli pipe grep command quotes vertical bar for OR function * Added: [Feature request: node's alias for CLI](https://github.com/clicon/clixon/issues/434) @@ -86,6 +91,7 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: ["show compare" and "show compare | display cli" differs #23](https://github.com/clicon/clixon-controller/issues/23) * Fixed: [JSON backslash string decoding/encoding not correct](https://github.com/clicon/clixon/issues/453) * Fixed: [CLI show config | display exits over mountpoints with large YANGs](https://github.com/clicon/clixon-controller/issues/39) * JSON string fixed according to RFC 8259: encoding/decoding of escape as defined in Section 8 diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index d345e42a..0c1d2f94 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -872,6 +872,7 @@ compare_db_names(clicon_handle h, cxobj *xc1 = NULL; cxobj *xc2 = NULL; cxobj *xerr = NULL; + cbuf *cb = NULL; if (clicon_rpc_get_config(h, NULL, db1, "/", NULL, NULL, &xc1) < 0) goto done; @@ -885,10 +886,39 @@ compare_db_names(clicon_handle h, clixon_netconf_error(xerr, "Get configuration", NULL); goto done; } - if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */ - goto done; + /* Note that XML and TEXT uses a (new) structured in-mem algorithm while + * JSON and CLI uses (old) UNIX file diff. + */ + switch (format){ + case FORMAT_XML: + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (clixon_xml_diff2cbuf(cb, xc1, xc2) < 0) + goto done; + cligen_output(stdout, "%s", cbuf_get(cb)); + break; + case FORMAT_TEXT: + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (clixon_text_diff2cbuf(cb, xc1, xc2) < 0) + goto done; + cligen_output(stdout, "%s", cbuf_get(cb)); + break; + case FORMAT_JSON: + case FORMAT_CLI: + if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */ + goto done; + default: + break; + } retval = 0; done: + if (cb) + cbuf_free(cb); if (xc1) xml_free(xc1); if (xc2) @@ -899,25 +929,31 @@ compare_db_names(clicon_handle h, /*! Compare two dbs using XML. Write to file and run diff * @param[in] h Clicon handle * @param[in] cvv - * @param[in] argv arg: 0 as xml, 1: as text + * @param[in] argv */ int compare_dbs(clicon_handle h, cvec *cvv, cvec *argv) { - int retval = -1; + int retval = -1; enum format_enum format; + char *db1; + char *db2; + char *formatstr; - if (cvec_len(argv) > 1){ - clicon_err(OE_PLUGIN, EINVAL, "Requires 0 or 1 element. If given: astext flag 0|1"); + if (cvec_len(argv) != 3){ + clicon_err(OE_PLUGIN, EINVAL, "Expected arguments: "); goto done; } - if (cvec_len(argv) && cv_int32_get(cvec_i(argv, 0)) == 1) - format = FORMAT_TEXT; - else - format = FORMAT_XML; - if (compare_db_names(h, format, "running", "candidate") < 0) + db1 = cv_string_get(cvec_i(argv, 0)); + db2 = cv_string_get(cvec_i(argv, 1)); + formatstr = cv_string_get(cvec_i(argv, 2)); + if ((format = format_str2int(formatstr)) < 0){ + clicon_err(OE_XML, 0, "format not found %s", formatstr); + goto done; + } + if (compare_db_names(h, format, db1, db2) < 0) goto done; retval = 0; done: @@ -1186,7 +1222,7 @@ save_config_file(clicon_handle h, goto done; break; case FORMAT_TEXT: - if (clixon_txt2file(f, xt, 0, fprintf, 0, 1) < 0) + if (clixon_text2file(f, xt, 0, fprintf, 0, 1) < 0) goto done; break; case FORMAT_CLI: @@ -1304,7 +1340,7 @@ cli_notification_cb(int s, if (clixon_json2file(stdout, xt, 1, cligen_output, 1, 1) < 0) goto done; case FORMAT_TEXT: - if (clixon_txt2file(stdout, xt, 0, cligen_output, 1, 1) < 0) + if (clixon_text2file(stdout, xt, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_XML: diff --git a/apps/cli/cli_pipe.c b/apps/cli/cli_pipe.c index c857dfd8..8b7a689c 100644 --- a/apps/cli/cli_pipe.c +++ b/apps/cli/cli_pipe.c @@ -319,7 +319,7 @@ pipe_showas_fn(clicon_handle h, goto done; break; case FORMAT_TEXT: - if (clixon_txt2file(stdout, xt, 0, cligen_output, 1, 1) < 0) + if (clixon_text2file(stdout, xt, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_CLI: diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 085360ec..89e19df5 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -557,7 +557,7 @@ cli_show_common(clicon_handle h, cligen_output(stdout, "\n"); break; case FORMAT_TEXT: /* XXX does not handle multiple leaf-list */ - if (clixon_txt2file(stdout, xp, 0, cligen_output, skiptop, 1) < 0) + if (clixon_text2file(stdout, xp, 0, cligen_output, skiptop, 1) < 0) goto done; break; case FORMAT_CLI: @@ -1299,7 +1299,7 @@ cli_pagination(clicon_handle h, goto done; break; case FORMAT_TEXT: - if (clixon_txt2file(stdout, xc, 0, cligen_output, 0, 1) < 0) + if (clixon_text2file(stdout, xc, 0, cligen_output, 0, 1) < 0) goto done; break; case FORMAT_CLI: @@ -1341,6 +1341,134 @@ cli_pagination(clicon_handle h, return retval; } +/*! Translate to CLI commands in cbuf + * + * Howto: join strings and pass them down. + * Identify unique/index keywords for correct set syntax. + * @param[in] h Clicon handle + * @param[in,out] cb Cligen buffer to write to + * @param[in] xn XML Parse-tree (to translate) + * @param[in] prepend Print this text in front of all commands. + * @retval 0 OK + * @retval -1 Error + * @see clixon_cli2file + */ +static int +cli2cbuf(clicon_handle h, + cbuf *cb, + cxobj *xn, + char *prepend) +{ + int retval = -1; + cxobj *xe = NULL; + cbuf *cbpre = NULL; + yang_stmt *ys; + int match; + char *body; + int compress = 0; + autocli_listkw_t listkw; + int exist = 0; + char *name; + + if (autocli_list_keyword(h, &listkw) < 0) + goto done; + if (xml_type(xn)==CX_ATTR) + goto ok; + if ((ys = xml_spec(xn)) == NULL) + goto ok; + if (yang_extension_value(ys, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0) + goto done; + if (exist) + goto ok; + exist = 0; + if (yang_extension_value(ys, "alias", CLIXON_AUTOCLI_NS, &exist, &name) < 0) + goto done; + if (!exist) + name = xml_name(xn); + /* If leaf/leaf-list or presence container, then print line */ + if (yang_keyword_get(ys) == Y_LEAF || + yang_keyword_get(ys) == Y_LEAF_LIST){ + if (prepend) + cprintf(cb, "%s", prepend); + if (listkw != AUTOCLI_LISTKW_NONE) + cprintf(cb, "%s ", name); + if ((body = xml_body(xn)) != NULL){ + if (index(body, ' ')) + cprintf(cb, "\"%s\"", body); + else + cprintf(cb, "%s", body); + } + cprintf(cb, "\n"); + goto ok; + } + /* Create prepend variable string */ + if ((cbpre = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + if (prepend) + cprintf(cbpre, "%s", prepend); + + /* If non-presence container && HIDE mode && only child is + * a list, then skip container keyword + * See also yang2cli_container */ + if (autocli_compress(h, ys, &compress) < 0) + goto done; + if (!compress) + cprintf(cbpre, "%s ", xml_name(xn)); + + /* If list then first loop through keys */ + if (yang_keyword_get(ys) == Y_LIST){ + xe = NULL; + while ((xe = xml_child_each(xn, xe, -1)) != NULL){ + if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0) + goto done; + if (!match) + continue; + if (listkw == AUTOCLI_LISTKW_ALL) + cprintf(cbpre, "%s ", xml_name(xe)); + cprintf(cbpre, "%s ", xml_body(xe)); + } + } + else if ((yang_keyword_get(ys) == Y_CONTAINER) && + yang_find(ys, Y_PRESENCE, NULL) != NULL){ + /* If presence container, then print as leaf (but continue to children) */ + if (prepend) + cprintf(cb, "%s", prepend); + if (listkw != AUTOCLI_LISTKW_NONE) + cprintf(cb, "%s ", xml_name(xn)); + if ((body = xml_body(xn)) != NULL){ + if (index(body, ' ')) + cprintf(cb, "\"%s\"", body); + else + cprintf(cb, "%s", body); + } + cprintf(cb, "\n"); + } + + /* For lists, print cbpre before its elements */ + if (yang_keyword_get(ys) == Y_LIST) + cprintf(cb, "%s\n", cbuf_get(cbpre)); + /* Then loop through all other (non-keys) */ + xe = NULL; + while ((xe = xml_child_each(xn, xe, -1)) != NULL){ + if (yang_keyword_get(ys) == Y_LIST){ + if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0) + goto done; + if (match) + continue; /* Not key itself */ + } + if (cli2cbuf(h, cb, xe, cbuf_get(cbpre)) < 0) + goto done; + } + ok: + retval = 0; + done: + if (cbpre) + cbuf_free(cbpre); + return retval; +} + /*! Translate from XML to CLI commands, internal * * Howto: join strings and pass them down. @@ -1352,7 +1480,7 @@ cli_pagination(clicon_handle h, * @param[in] fn Callback to make print function */ static int -xml2cli1(clicon_handle h, +cli2file(clicon_handle h, FILE *f, cxobj *xn, char *prepend, @@ -1457,7 +1585,7 @@ xml2cli1(clicon_handle h, if (match) continue; /* Not key itself */ } - if (xml2cli1(h, f, xe, cbuf_get(cbpre), fn) < 0) + if (cli2file(h, f, xe, cbuf_get(cbpre), fn) < 0) goto done; } ok: @@ -1480,6 +1608,7 @@ xml2cli1(clicon_handle h, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @retval 0 OK * @retval -1 Error + * @see clixon_cli2cbuf */ int clixon_cli2file(clicon_handle h, @@ -1497,11 +1626,50 @@ clixon_cli2file(clicon_handle h, if (skiptop){ xc = NULL; while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) - if (xml2cli1(h, f, xc, prepend, fn) < 0) + if (cli2file(h, f, xc, prepend, fn) < 0) goto done; } else { - if (xml2cli1(h, f, xn, prepend, fn) < 0) + if (cli2file(h, f, xn, prepend, fn) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Translate from XML to CLI commands + * + * Howto: join strings and pass them down. + * Identify unique/index keywords for correct set syntax. + * @param[in] h Clicon handle + * @param[in] f Output FILE (eg stdout) + * @param[in] xn XML Parse-tree (to translate) + * @param[in] prepend Print this text in front of all commands. + * @param[in] fn File print function (if NULL, use fprintf) + * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, + * @retval 0 OK + * @retval -1 Error + * @see clixon_cli2file + */ +int +clixon_cli2cbuf(clicon_handle h, + cbuf *cb, + cxobj *xn, + char *prepend, + int skiptop) +{ + int retval = 1; + cxobj *xc; + + if (skiptop){ + xc = NULL; + while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) + if (cli2cbuf(h, cb, xc, prepend) < 0) + goto done; + } + else { + if (cli2cbuf(h, cb, xn, prepend) < 0) goto done; } retval = 0; diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 8cf93221..a81ff692 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -118,7 +118,7 @@ int cli_process_control(clicon_handle h, cvec *vars, cvec *argv); int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, cvec *commands, cvec *helptexts); int clixon_cli2file(clicon_handle h, FILE *f, cxobj *xn, char *prepend, clicon_output_cb *fn, int skiptop); - +int clixon_cli2cbuf(clicon_handle h, cbuf *cb, cxobj *xn, char *prepend, int skiptop); /* cli_show.c: CLIgen new vector arg callbacks */ int cli_show_common(clicon_handle h, char *db, enum format_enum format, int pretty, int state, char *withdefault, char *extdefault, char *prepend, char *xpath, int fromroot, cvec *nsc, int skiptop); diff --git a/example/main/example_cli.c b/example/main/example_cli.c index 9d6a78e8..9a1a5291 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -136,7 +136,7 @@ example_client_rpc(clicon_handle h, fprintf(stdout,"\n"); /* pretty-print: - clixon_txt2file(stdout, xml_child_i(xret, 0), 0, cligen_output, 0); + clixon_text2file(stdout, xml_child_i(xret, 0), 0, cligen_output, 0); */ retval = 0; done: diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index 542a8184..1a049d71 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -88,9 +88,9 @@ show("Show a particular state of the system"){ [("Namespace")], show_conf_xpath("candidate"); version("Show version"), cli_show_version("candidate", "text", "/"); options("Show clixon options"), cli_show_options(); - compare("Compare candidate and running databases"), compare_dbs((int32)0);{ - xml("Show comparison in xml"), compare_dbs((int32)0); - text("Show comparison in text"), compare_dbs((int32)1); + compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{ + xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml"); + text("Show comparison in text"), compare_dbs("running", "candidate", "text"); } pagination("Show list pagination") xpath("Show configuration") ("XPATH expression"){ xml, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "xml", "10"); diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 6ae3e568..34788ec4 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -187,5 +187,6 @@ * Seems to be only an optimization since the config is queried from the backend anyway * The reason this probably should be undef:ed is that the restconf config appears in ps and other in * cleartext + * Plan is to remove this (undef:d) in next release */ #undef RESTCONF_INLINE diff --git a/lib/clixon/clixon_text_syntax.h b/lib/clixon/clixon_text_syntax.h index e19d0509..c0e9896e 100644 --- a/lib/clixon/clixon_text_syntax.h +++ b/lib/clixon/clixon_text_syntax.h @@ -39,7 +39,9 @@ /* * Prototypes */ -int clixon_txt2file(FILE *f, cxobj *xn, int level, clicon_output_cb *fn, int skiptop, int autocliext); +int clixon_text2file(FILE *f, cxobj *xn, int level, clicon_output_cb *fn, int skiptop, int autocliext); +int clixon_text2cbuf(cbuf *cb, cxobj *xn, int level, int skiptop, int autocliext); +int clixon_text_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1); 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); diff --git a/lib/clixon/clixon_xml_io.h b/lib/clixon/clixon_xml_io.h index fdd9a770..07b21cd2 100644 --- a/lib/clixon/clixon_xml_io.h +++ b/lib/clixon/clixon_xml_io.h @@ -53,5 +53,6 @@ int clixon_xml_parse_string(const char *str, yang_bind yb, yang_stmt *yspec, c int clixon_xml_parse_va(yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr, const char *format, ...) __attribute__ ((format (printf, 5, 6))); int clixon_xml_attr_copy(cxobj *xin, cxobj *xout, char *name); +int clixon_xml_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1); #endif /* _CLIXON_XML_IO_H_ */ diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 07673104..841ea081 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -77,6 +77,5 @@ int xml_rpc_isaction(cxobj *xn); int xml_find_action(cxobj *xn, int top, cxobj **xap); int purge_tagged_nodes(cxobj *xn, char *ns, char *name, char *value, int keepnode); int clixon_compare_xmls(cxobj *xc1, cxobj *xc2, enum format_enum format); -int xml_tree_diff_print(cbuf *cb, int level, cxobj *x0, cxobj *x1, enum format_enum format); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 202499a6..e2b8a719 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -228,7 +228,7 @@ clicon_option_dump1(clicon_handle h, goto done; break; case FORMAT_TEXT: - if (clixon_txt2file(f, xc, 0, cligen_output, 0, 0) < 0) + if (clixon_text2file(f, xc, 0, cligen_output, 0, 0) < 0) goto done; break; default: diff --git a/lib/src/clixon_text_syntax.c b/lib/src/clixon_text_syntax.c index 98e7195e..14a1d4d9 100644 --- a/lib/src/clixon_text_syntax.c +++ b/lib/src/clixon_text_syntax.c @@ -78,6 +78,7 @@ #define TEXT_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 @@ -100,6 +101,7 @@ tleaf(cxobj *x) } /*! 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 @@ -110,15 +112,16 @@ tleaf(cxobj *x) * leaflist state: * 0: No leaflist * 1: In leaflist + * @see text2cbuf to buffer (slower) */ static int -xml2txt1(cxobj *xn, - clicon_output_cb *fn, - FILE *f, - int level, - int autocliext, - int *leafl, - char **leaflname) +text2file(cxobj *xn, + clicon_output_cb *fn, + FILE *f, + int level, + int autocliext, + int *leafl, + char **leaflname) { cxobj *xc = NULL; @@ -129,7 +132,7 @@ xml2txt1(cxobj *xn, char *value; cg_var *cvi; cvec *cvk = NULL; /* vector of index keys */ - cbuf *cb = NULL; + cbuf *cbb = NULL; #ifndef TEXT_SYNTAX_NOPREFIX yang_stmt *yp = NULL; yang_stmt *ymod; @@ -184,19 +187,19 @@ xml2txt1(cxobj *xn, if (children == 0){ /* If no children print line */ switch (xml_type(xn)){ case CX_BODY:{ - if ((cb = cbuf_new()) == NULL){ + if ((cbb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } value = xml_value(xn); if (index(value, ' ') != NULL) - cprintf(cb, "\"%s\"", value); + cprintf(cbb, "\"%s\"", value); else - cprintf(cb, "%s", value); + cprintf(cbb, "%s", value); if (*leafl) /* Skip keyword if leaflist */ - (*fn)(f, "%*s%s\n", PRETTYPRINT_INDENT*level, "", cbuf_get(cb)); + (*fn)(f, "%*s%s\n", PRETTYPRINT_INDENT*level, "", cbuf_get(cbb)); else - (*fn)(f, "%s;\n", cbuf_get(cb)); + (*fn)(f, "%s;\n", cbuf_get(cbb)); break; } case CX_ELMNT: @@ -243,7 +246,7 @@ xml2txt1(cxobj *xn, if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){ if (yn && yang_key_match(yn, xml_name(xc), NULL)) continue; /* Skip keys, already printed */ - if (xml2txt1(xc, fn, f, level+1, autocliext, leafl, leaflname) < 0) + if (text2file(xc, fn, f, level+1, autocliext, leafl, leaflname) < 0) break; } } @@ -257,8 +260,200 @@ xml2txt1(cxobj *xn, ok: retval = 0; done: - if (cb) - cbuf_free(cb); + if (cbb) + cbuf_free(cbb); + return retval; +} + +#ifndef TEXT_SYNTAX_NOPREFIX +static char * +get_prefix(yang_stmt *yn) +{ + char *prefix = NULL; + yang_stmt *yp = NULL; + yang_stmt *ymod; + yang_stmt *ypmod; + + /* Find out prefix if needed: topmost or new module a la API-PATH */ + if (ys_real_module(yn, &ymod) < 0) + return NULL; + if ((yp = yang_parent_get(yn)) != NULL && + yp != ymod){ + if (ys_real_module(yp, &ypmod) < 0) + return NULL; + if (ypmod != ymod) + prefix = yang_argument_get(ymod); + } + else + prefix = yang_argument_get(ymod); + return prefix; +} +#endif + +/*! 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 PRETTYPRINT_INDENT spaces per level in front of each line + * @param[in] prefix Add string to beginning of each line (or NULL) + * @param[in] autocliext How to handle autocli extensions: 0: ignore 1: follow + * @param[in,out] leafl Leaflist state for keeping track of when [] ends + * @param[in,out] leaflname Leaflist state for [] + * leaflist state: + * 0: No leaflist + * 1: In leaflist + * @see text2file but to file (faster) + */ +static int +text2cbuf(cbuf *cb, + cxobj *xn, + int level, + char *prepend, + int autocliext, + int *leafl, + char **leaflname) + +{ + cxobj *xc = NULL; + int children=0; + int retval = -1; + int exist = 0; + yang_stmt *yn; + char *value; + cg_var *cvi; + cvec *cvk = NULL; /* vector of index keys */ + cbuf *cbb = NULL; + int level1; + char *prefix = NULL; + + if (xn == NULL || cb == NULL){ + clicon_err(OE_XML, EINVAL, "xn or cb is NULL"); + goto done; + } + level1 = level*PRETTYPRINT_INDENT; + if (prepend) + level1 -= strlen(prepend); + if ((yn = xml_spec(xn)) != NULL){ + if (autocliext){ + if (yang_extension_value(yn, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0) + goto done; + if (exist) + goto ok; + } +#ifndef TEXT_SYNTAX_NOPREFIX + prefix = get_prefix(yn); +#endif + if (yang_keyword_get(yn) == Y_LIST){ + if ((cvk = yang_cvec_get(yn)) == NULL){ + clicon_err(OE_YANG, 0, "No keys"); + goto done; + } + } + } + if (*leafl && yn){ + if (yang_keyword_get(yn) == Y_LEAF_LIST && strcmp(*leaflname, yang_argument_get(yn)) == 0) + ; + else{ + *leafl = 0; + *leaflname = NULL; + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s\n", level1, "]"); + } + } + 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 == 0){ /* If no children print line */ + switch (xml_type(xn)){ + case CX_BODY:{ + if ((cbb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + value = xml_value(xn); + if (index(value, ' ') != NULL) + cprintf(cbb, "\"%s\"", value); + else + cprintf(cbb, "%s", value); + if (*leafl){ /* Skip keyword if leaflist */ + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s%s\n", level1, "", cbuf_get(cbb)); + } + else + cprintf(cb, "%s;\n", cbuf_get(cbb)); + break; + } + case CX_ELMNT: + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s%s", level1, "", xml_name(xn)); + cvi = NULL; /* Lists only */ + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) + cprintf(cb, " %s", xml_body(xc)); + } + cprintf(cb, ";\n"); + break; + default: + break; + } + goto ok; + } + if (*leafl == 0){ + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(xn)); + } + cvi = NULL; /* Lists only */ + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) + cprintf(cb, " %s", xml_body(xc)); + } + if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl){ + ; + } + else if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl == 0){ + *leafl = 1; + *leaflname = yang_argument_get(yn); + cprintf(cb, " [\n"); + } + else if (!tleaf(xn)) + cprintf(cb, " {\n"); + else + cprintf(cb, " "); + xc = NULL; + while ((xc = xml_child_each(xn, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){ + if (yn && yang_key_match(yn, xml_name(xc), NULL)) + continue; /* Skip keys, already printed */ + if (text2cbuf(cb, xc, level+1, prepend, autocliext, leafl, leaflname) < 0) + break; + } + } + /* Stop leaf-list printing (ie []) if no longer leaflist and same name */ + if (yn && yang_keyword_get(yn) != Y_LEAF_LIST && *leafl != 0){ + *leafl = 0; + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s\n", level1 + PRETTYPRINT_INDENT, "]"); + } + if (!tleaf(xn)){ + if (prepend) + cprintf(cb, "%s", prepend); + cprintf(cb, "%*s}\n", level1, ""); + } + ok: + retval = 0; + done: + if (cbb) + cbuf_free(cbb); return retval; } @@ -274,12 +469,12 @@ xml2txt1(cxobj *xn, * @retval -1 Error */ int -clixon_txt2file(FILE *f, - cxobj *xn, - int level, - clicon_output_cb *fn, - int skiptop, - int autocliext) +clixon_text2file(FILE *f, + cxobj *xn, + int level, + clicon_output_cb *fn, + int skiptop, + int autocliext) { int retval = 1; cxobj *xc; @@ -291,11 +486,11 @@ clixon_txt2file(FILE *f, if (skiptop){ xc = NULL; while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) - if (xml2txt1(xc, fn, f, level, autocliext, &leafl, &leaflname) < 0) + if (text2file(xc, fn, f, level, autocliext, &leafl, &leaflname) < 0) goto done; } else { - if (xml2txt1(xn, fn, f, level, autocliext, &leafl, &leaflname) < 0) + if (text2file(xn, fn, f, level, autocliext, &leafl, &leaflname) < 0) goto done; } retval = 0; @@ -303,6 +498,265 @@ clixon_txt2file(FILE *f, return retval; } +/*! Translate internal cxobj tree to a "curly" textual format to cbufs + * + * @param[out] cb Cligen buffer to write to + * @param[in] xn XML object to print + * @param[in] level Print PRETTYPRINT_INDENT spaces per level in front of each line + * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, + * @param[in] autocliext How to handle autocli extensions: 0: ignore 1: follow + */ +int +clixon_text2cbuf(cbuf *cb, + cxobj *xn, + int level, + int skiptop, + int autocliext) +{ + int retval = 1; + cxobj *xc; + int leafl = 0; + char *leaflname = NULL; + + if (skiptop){ + xc = NULL; + while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) + if (text2cbuf(cb, xc, level, NULL, autocliext, &leafl, &leaflname) < 0) + goto done; + } + else { + if (text2cbuf(cb, xn, level, NULL, autocliext, &leafl, &leaflname) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Print list keys + */ +static int +text_diff_keys(cbuf *cb, + cxobj *x, + yang_stmt *y) +{ + cvec *cvk; + cg_var *cvi; + char *keyname; + char *keyval; + + if (y && yang_keyword_get(y) == Y_LIST){ + cvk = yang_cvec_get(y); + cvi = NULL; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + keyval = xml_find_body(x, keyname); + cprintf(cb, " %s", keyval); + } + } + return 0; +} + +/*! Print TEXT diff of two cxobj trees into a cbuf + * + * YANG dependent + * @param[out] cb CLIgen buffer + * @param[in] x0 First XML tree + * @param[in] x1 Second XML tree + * @param[in] level How many spaces to insert before each line + * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, + * @retval 0 Ok + * @retval -1 Error + * @cod + * cbuf *cb = cbuf_new(); + * if (clixon_text_diff2cbuf(cb, 0, x0, x1) < 0) + * err(); + * @endcode + * @see clixon_xml_diff2cbuf + * XXX Leaf-list +/- is not correct + * For example, it should be: + * value [ + * + 97 + * - 99 + * ] + * But is: + * + value [ + * + 97 + * - 99 + */ +static int +text_diff2cbuf(cbuf *cb, + cxobj *x0, + cxobj *x1, + int level, + int skiptop) +{ + int retval = -1; + cxobj *x0c = NULL; /* x0 child */ + cxobj *x1c = NULL; /* x1 child */ + yang_stmt *yc0; + yang_stmt *yc1; + char *b0; + char *b1; + int eq; + int nr=0; + int level1; + yang_stmt *y0; + char *prefix = NULL; + int leafl = 0; // XXX + char *leaflname = NULL; // XXX + + level1 = level*PRETTYPRINT_INDENT; + if ((y0 = xml_spec(x0)) != NULL){ +#ifndef TEXT_SYNTAX_NOPREFIX + prefix = get_prefix(y0); +#endif + } + /* Traverse x0 and x1 in lock-step */ + x0c = x1c = NULL; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + for (;;){ + /* Check if one or both subtrees are NULL */ + if (x0c == NULL && x1c == NULL) + goto ok; + else if (x0c == NULL){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x1)); + text_diff_keys(cb, x1, y0); + cprintf(cb, " {\n"); + nr++; + } + if (text2cbuf(cb, x1c, level+1, "+", 0, &leafl, &leaflname) < 0) + goto done; + x1c = xml_child_each(x1, x1c, CX_ELMNT); + continue; + } + else if (x1c == NULL){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x0)); + text_diff_keys(cb, x0, y0); + cprintf(cb, "{\n"); + nr++; + } + if (text2cbuf(cb, x0c, level+1, "-", 0, &leafl, &leaflname) < 0) + goto done; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + continue; + } + /* Both x0c and x1c exists, check if they are yang-equal. */ + eq = xml_cmp(x0c, x1c, 0, 0, NULL); + yc0 = xml_spec(x0c); + yc1 = xml_spec(x1c); + + if (eq < 0){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x0)); + text_diff_keys(cb, x0, y0); + cprintf(cb, " {\n"); + nr++; + } + if (text2cbuf(cb, x0c, level+1, "-", 0, &leafl, &leaflname) < 0) + goto done; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + continue; + } + else if (eq > 0){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x1)); + text_diff_keys(cb, x1, y0); + cprintf(cb, " {\n"); + nr++; + } + if (text2cbuf(cb, x1c, level+1, "+", 0, &leafl, &leaflname) < 0) + goto done; + x1c = xml_child_each(x1, x1c, CX_ELMNT); + continue; + } + else{ /* equal */ + if (yc0 && yc1 && yc0 != yc1){ /* choice */ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s {\n", xml_name(x0)); + nr++; + } + if (text2cbuf(cb, x0c, level+1, "-", 0, &leafl, &leaflname) < 0) + goto done; + if (text2cbuf(cb, x1c, level+1, "+", 0, &leafl, &leaflname) < 0) + goto done; + } + else if (yc0 && yang_keyword_get(yc0) == Y_LEAF){ + b0 = xml_body(x0c); + b1 = xml_body(x1c); + if (b0 == NULL && b1 == NULL) + ; + else if (b0 == NULL || b1 == NULL + || strcmp(b0, b1) != 0){ + if (nr==0 && skiptop == 0){ + cprintf(cb, "%*s", level1, ""); + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", xml_name(x0)); + text_diff_keys(cb, x0, y0); + cprintf(cb, " {\n"); + nr++; + } + cprintf(cb, "-%*s%s %s;\n", level1+PRETTYPRINT_INDENT-1, "", xml_name(x0c), b0); + cprintf(cb, "+%*s%s %s;\n", level1+PRETTYPRINT_INDENT-1, "", xml_name(x1c), b1); + } + } + else if (text_diff2cbuf(cb, x0c, x1c, level+1, 0) < 0) + goto done; + } + /* Get next */ + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + } /* for */ + ok: + if (nr) + cprintf(cb, "%*s}\n", level1, ""); + retval = 0; + done: + return retval; +} + +/*! Print TEXT diff of two cxobj trees into a cbuf + * + * YANG dependent + * @param[out] cb CLIgen buffer + * @param[in] x0 First XML tree + * @param[in] x1 Second XML tree + * @retval 0 Ok + * @retval -1 Error + * @cod + * cbuf *cb = cbuf_new(); + * if (clixon_text_diff2cbuf(cb, 0, x0, x1) < 0) + * err(); + * @endcode + * @see clixon_xml_diff2cbuf + */ +int +clixon_text_diff2cbuf(cbuf *cb, + cxobj *x0, + cxobj *x1) +{ + return text_diff2cbuf(cb, x0, x1, 0, 1); +} + /*! Look for YANG lists nodes and convert bodies to keys * * This is a compromise between making the text parser (1) YANG aware or (2) not. @@ -367,16 +821,15 @@ text_populate_list(cxobj *xn) /*! 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 - * @retval 1 OK and valid - * @retval 0 Invalid (only if yang spec) - * @retval -1 Error with clicon_err called + * @retval 1 OK and valid + * @retval 0 Invalid (only if yang spec) + * @retval -1 Error with clicon_err called * @see _xml_parse for XML variant * @note Parsing requires YANG, which means yb must be YB_MODULE/_NEXT */ diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c index dcbc9818..77c14b33 100644 --- a/lib/src/clixon_xml_io.c +++ b/lib/src/clixon_xml_io.c @@ -446,11 +446,11 @@ clixon_xml2cbuf1(cbuf *cb, * @param[in] xn Top-level xml object * @param[in] level Indentation level for pretty * @param[in] pretty Insert \n and spaces to make the xml more readable. - * @param[in] prefix Add string to beginning of each line (if pretty) + * @param[in] prefix Add string to beginning of each line (or NULL) (if pretty) * @param[in] depth Limit levels of child resources: -1: all, 0: none, 1: node itself * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @retval 0 OK - * @retval -1 Error + * @retval -1 Error * Depth is used in NACM * @code * cbuf *cb = cbuf_new(); @@ -894,3 +894,200 @@ clixon_xml_attr_copy(cxobj *xin, done: return retval; } + +/*! Print list keys + */ +static int +xml_diff_keys(cbuf *cb, + cxobj *x, + yang_stmt *y, + int level) +{ + cvec *cvk; + cg_var *cvi; + char *keyname; + char *keyval; + + if (y && yang_keyword_get(y) == Y_LIST){ + cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ + cvi = NULL; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + keyval = xml_find_body(x, keyname); + cprintf(cb, "%*s<%s>%s\n", level, "", keyname, keyval, keyname); + } + } + return 0; +} + +/*! Print XML diff of two cxobj trees into a cbuf + * + * YANG dependent + * Uses underlying XML diff algorithm with better result than clixon_compare_xmls + * @param[out] cb CLIgen buffer + * @param[in] x0 First XML tree + * @param[in] x1 Second XML tree + * @param[in] level How many spaces to insert before each line + * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, + * @retval 0 Ok + * @retval -1 Error + * @cod + * cbuf *cb = cbuf_new(); + * if (clixon_xml_diff2cbuf(cb, 0, x0, x1) < 0) + * err(); + * cligen_output(stdout, "%s", cbuf_get(cb)); + * @endcode + * @see xml_diff which returns diff sets + * @see clixon_compare_xmls which uses files and is independent of YANG + */ +int +xml_diff2cbuf(cbuf *cb, + cxobj *x0, + cxobj *x1, + int level, + int skiptop) +{ + int retval = -1; + cxobj *x0c = NULL; /* x0 child */ + cxobj *x1c = NULL; /* x1 child */ + yang_stmt *y0; + yang_stmt *yc0; + yang_stmt *yc1; + char *b0; + char *b1; + int eq; + int nr=0; + int level1; + + level1 = level*PRETTYPRINT_INDENT; + y0 = xml_spec(x0); + /* Traverse x0 and x1 in lock-step */ + x0c = x1c = NULL; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + for (;;){ + if (x0c == NULL && x1c == NULL) + goto ok; + else if (x0c == NULL){ + /* Check if one or both subtrees are NULL */ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x1)); + xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) + goto done; + x1c = xml_child_each(x1, x1c, CX_ELMNT); + continue; + } + else if (x1c == NULL){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x0)); + xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) + goto done; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + continue; + } + /* Both x0c and x1c exists, check if they are yang-equal. */ + eq = xml_cmp(x0c, x1c, 0, 0, NULL); + if (eq < 0){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x0)); + xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) + goto done; + x0c = xml_child_each(x0, x0c, CX_ELMNT); + continue; + } + else if (eq > 0){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x1)); + xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) + goto done; + x1c = xml_child_each(x1, x1c, CX_ELMNT); + continue; + } + else{ /* equal */ + /* xml-spec NULL could happen with anydata children for example, + * if so, continute compare children but without yang + */ + yc0 = xml_spec(x0c); + yc1 = xml_spec(x1c); + if (yc0 && yc1 && yc0 != yc1){ /* choice */ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x0)); + xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) + goto done; + if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) + goto done; + } + else if (yc0 && yang_keyword_get(yc0) == Y_LEAF){ + /* if x0c and x1c are leafs w bodies, then they may be changed */ + b0 = xml_body(x0c); + b1 = xml_body(x1c); + if (b0 == NULL && b1 == NULL) + ; + else if (b0 == NULL || b1 == NULL + || strcmp(b0, b1) != 0){ + if (nr==0 && skiptop==0){ + cprintf(cb, "%*s<%s>\n", level1, "", xml_name(x0)); + xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); + nr++; + } + cprintf(cb, "-%*s%s>%s\n", ((level+1)*PRETTYPRINT_INDENT-1), "<", + xml_name(x0c), b0, xml_name(x0c)); + cprintf(cb, "+%*s%s>%s\n", ((level+1)*PRETTYPRINT_INDENT-1), "<", + xml_name(x1c), b1, xml_name(x1c)); + } + } + else if (xml_diff2cbuf(cb, x0c, x1c, level+1, 0) < 0) + goto done; + } + /* Get next */ + x0c = xml_child_each(x0, x0c, CX_ELMNT); + x1c = xml_child_each(x1, x1c, CX_ELMNT); + } /* for */ + ok: + if (nr) + cprintf(cb, "%*s\n", level*PRETTYPRINT_INDENT, "", xml_name(x0)); + retval = 0; + done: + return retval; +} + +/*! Print XML diff of two cxobj trees into a cbuf + * + * YANG dependent + * Uses underlying XML diff algorithm with better result than clixon_compare_xmls + * @param[out] cb CLIgen buffer + * @param[in] x0 First XML tree + * @param[in] x1 Second XML tree + * @retval 0 Ok + * @retval -1 Error + * @cod + * cbuf *cb = cbuf_new(); + * if (clixon_xml_diff2cbuf(cb, 0, x0, x1) < 0) + * err(); + * cligen_output(stdout, "%s", cbuf_get(cb)); + * @endcode + * @see xml_diff which returns diff sets + * @see clixon_compare_xmls which uses files and is independent of YANG + */ +int +clixon_xml_diff2cbuf(cbuf *cb, + cxobj *x0, + cxobj *x1) +{ + return xml_diff2cbuf(cb, x0, x1, 0, 1); +} diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 945bdab3..261c7066 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -410,6 +410,7 @@ xml_diff1(cxobj *x0, * @retval -1 Error * All xml vectors should be freed after use. * @see xml_tree_equal same algorithm but do not bother with what has changed + * @see clixon_xml_diff_print same algorithm but print in +/- diff format */ int xml_diff(cxobj *x0, @@ -1796,7 +1797,7 @@ purge_tagged_nodes(cxobj *xn, * @param[in] xc1 XML tree 1 * @param[in] xc2 XML tree 2 * @param[in] format "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) - * @see xml_tree_diff_print with better XML in-mem comparison but is YANG dependent + * @see clixon_xml_diff2cbuf with better XML in-mem comparison but is YANG dependent */ int clixon_compare_xmls(cxobj *xc1, @@ -1820,7 +1821,7 @@ clixon_compare_xmls(cxobj *xc1, goto done; switch(format){ case FORMAT_TEXT: - if (clixon_txt2file(f, xc1, 0, cligen_output, 1, 1) < 0) + if (clixon_text2file(f, xc1, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_XML: @@ -1841,7 +1842,7 @@ clixon_compare_xmls(cxobj *xc1, switch(format){ case FORMAT_TEXT: - if (clixon_txt2file(f, xc2, 0, cligen_output, 1, 1) < 0) + if (clixon_text2file(f, xc2, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_XML: @@ -1870,127 +1871,3 @@ clixon_compare_xmls(cxobj *xc1, unlink(filename2); return retval; } - -/*! Print diff of two XML trees in memory. YANG dependent - * - * Uses underlying XML diff algorithm with better result than clixon_compare_xmls - * @param[in] cb CLIgen buffer - * @param[in] level How many spaces to insert before each line - * @param[in] x0 First XML tree - * @param[in] x1 Second XML tree - * @param[in] format "text"|"xml"|"json"|"cli"|"netconf" (NYI: only xml) - * @retval 0 Ok - * @retval -1 Error - * @see xml_diff which returns diff sets - * @see clixon_compare_xmls which uses files and is independent of YANG - * XXX only XML - */ -int -xml_tree_diff_print(cbuf *cb, - int level, - cxobj *x0, - cxobj *x1, - enum format_enum format) -{ - int retval = -1; - cxobj *x0c = NULL; /* x0 child */ - cxobj *x1c = NULL; /* x1 child */ - yang_stmt *yc0; - yang_stmt *yc1; - char *b0; - char *b1; - int eq; - int nr=0; - - /* Traverse x0 and x1 in lock-step */ - x0c = x1c = NULL; - x0c = xml_child_each(x0, x0c, CX_ELMNT); - x1c = xml_child_each(x1, x1c, CX_ELMNT); - for (;;){ - if (x0c == NULL && x1c == NULL) - goto ok; - else if (x0c == NULL){ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x1)); - nr++; - if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) - goto done; - x1c = xml_child_each(x1, x1c, CX_ELMNT); - continue; - } - else if (x1c == NULL){ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x0)); - nr++; - if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) - goto done; - x0c = xml_child_each(x0, x0c, CX_ELMNT); - continue; - } - /* Both x0c and x1c exists, check if they are yang-equal. */ - eq = xml_cmp(x0c, x1c, 0, 0, NULL); - if (eq < 0){ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x0)); - nr++; - if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) - goto done; - x0c = xml_child_each(x0, x0c, CX_ELMNT); - continue; - } - else if (eq > 0){ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x1)); - nr++; - if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) - goto done; - x1c = xml_child_each(x1, x1c, CX_ELMNT); - continue; - } - else{ /* equal */ - /* xml-spec NULL could happen with anydata children for example, - * if so, continute compare children but without yang - */ - yc0 = xml_spec(x0c); - yc1 = xml_spec(x1c); - if (yc0 && yc1 && yc0 != yc1){ /* choice */ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x0)); - nr++; - if (clixon_xml2cbuf(cb, x0c, level+1, 1, "-", -1, 0) < 0) - goto done; - if (clixon_xml2cbuf(cb, x1c, level+1, 1, "+", -1, 0) < 0) - goto done; - } - else - if (yc0 && yang_keyword_get(yc0) == Y_LEAF){ - /* if x0c and x1c are leafs w bodies, then they may be changed */ - b0 = xml_body(x0c); - b1 = xml_body(x1c); - if (b0 == NULL && b1 == NULL) - ; - else if (b0 == NULL || b1 == NULL - || strcmp(b0, b1) != 0 - ){ - if (nr==0) - cprintf(cb, "%*s%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x0)); - nr++; - cprintf(cb, "-%*s%s>%s\n", (level*PRETTYPRINT_INDENT-1), "<", - xml_name(x0c), b0, xml_name(x0c)); - cprintf(cb, "+%*s%s>%s\n", (level*PRETTYPRINT_INDENT-1), "<", - xml_name(x1c), b1, xml_name(x1c)); - } - } - else if (xml_tree_diff_print(cb, level+1, x0c, x1c, format) < 0) - goto done; - } - x0c = xml_child_each(x0, x0c, CX_ELMNT); - x1c = xml_child_each(x1, x1c, CX_ELMNT); - } - ok: - if (nr) - cprintf(cb, "%*s/%s>\n", level*PRETTYPRINT_INDENT, "<", xml_name(x0)); - retval = 0; - done: - return retval; -} diff --git a/test/test_cli.sh b/test/test_cli.sh index f1e80cc3..937891fa 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -129,9 +129,9 @@ debug("Debugging parts of the system"){ } show("Show a particular state of the system"){ xpath("Show configuration") ("XPATH expression") ("Namespace"), show_conf_xpath("candidate"); - compare("Compare candidate and running databases"), compare_dbs((int32)0);{ - xml("Show comparison in xml"), compare_dbs((int32)0); - text("Show comparison in text"), compare_dbs((int32)1); + compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{ + xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml"); + text("Show comparison in text"), compare_dbs("running", "candidate", "text"); } configuration("Show configuration"), cli_show_auto_mode("candidate", "text", true, false);{ cli("Show configuration as CLI commands"), cli_show_auto_mode("candidate", "cli", true, false, "report-all", "set "); @@ -229,7 +229,7 @@ new "cli success validate" expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "^$" new "cli compare diff" -expectpart "$($clixon_cli -1 -f $cfg -l o show compare text)" 0 "+ address 1.2.3.4" +expectpart "$($clixon_cli -1 -f $cfg -l o show compare text)" 0 "+\ *address 1.2.3.4" new "cli start shell" expectpart "$($clixon_cli -1 -f $cfg -l o shell echo foo)" 0 "foo" diff --git a/test/test_cli_diff.sh b/test/test_cli_diff.sh new file mode 100755 index 00000000..9c8bd32c --- /dev/null +++ b/test/test_cli_diff.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +# CLI compare for all formats +# Create a diff by committing one set, then add/remove some parts in candidate and show diff in all formats + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example +# include err() and new() functions and creates $dir + +cfg=$dir/conf_yang.xml +clidir=$dir/cli +fyang=$dir/clixon-example.yang + +test -d ${clidir} || rm -rf ${clidir} +mkdir $clidir + +# Use yang in example + +cat < $cfg + + $cfg + ${YANG_INSTALLDIR} + $IETFRFC + $fyang + /usr/local/lib/$APPNAME/backend + $APPNAME + /usr/local/lib/$APPNAME/cli + $clidir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + +EOF + +cat < $fyang +module clixon-example { + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import clixon-autocli{ + prefix autocli; + } + /* Generic config data */ + container top{ + list section{ + key name; + leaf name{ + type string; + } + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } + container multi{ + list parameter{ + key "first second"; + leaf first{ + type string; + } + leaf second{ + type string; + } + leaf-list value{ + type string; + } + } + } + } + } +} +EOF + +cat < $clidir/ex.cli +# Clixon example specification +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; +CLICON_PLUGIN="example_cli"; +CLICON_PIPETREE="|mypipe"; # Only difference from nodefault + +set @datamodel, cli_auto_set(); +delete("Delete a configuration item") @datamodel, cli_auto_del(); +commit("Commit the changes"), cli_commit(); +show("Show a particular state of the system"){ + compare("Compare candidate and running databases") { + xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml"); + json("Show comparison in xml"), compare_dbs("running", "candidate", "json"); + text("Show comparison in text"), compare_dbs("running", "candidate", "text"); + cli("Show comparison in text"), compare_dbs("running", "candidate", "cli", "set "); + } + configuration("Show configuration") { + candidate, cli_show_auto_mode("candidate", "xml", false, false); { + @|mypipe, cli_show_auto_mode("candidate", "xml", true, false); + } + running, cli_show_auto_mode("running", "xml", false, false);{ + @|mypipe, cli_show_auto_mode("running", "xml", true, false); + } + } +} +EOF + +cat < $clidir/clipipe.cli +CLICON_MODE="|mypipe"; # Must start with | +\| { + show { + xml, pipe_showas_fn("xml"); + json, pipe_showas_fn("json"); + text, pipe_showas_fn("text"); + cli, pipe_showas_fn("cli", true, "set "); + } +} +EOF + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "wait backend" +wait_backend + +new "add a" +expectpart "$($clixon_cli -1 -f $cfg set top section x table parameter a value 17)" 0 "^$" + +new "add b" +expectpart "$($clixon_cli -1 -f $cfg set top section x table parameter b value 42)" 0 "^$" + +new "add d" +expectpart "$($clixon_cli -1 -f $cfg set top section x table parameter d value 98)" 0 "^$" + +new "check compare xml" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^+ " --not-- "\-" "" + +new "check compare text" +expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^+ clixon-example:top {" --not-- "^\-" data + +new "commit" +expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "^$" + +new "check running" +expectpart "$($clixon_cli -1 -f $cfg show config running)" 0 "^
xa17b42d98
$" + +new "delete a" +expectpart "$($clixon_cli -1 -f $cfg delete top section x table parameter a)" 0 "^$" + +new "add c" +expectpart "$($clixon_cli -1 -f $cfg set top section x table parameter c value 72)" 0 "^$" + +new "change d" +expectpart "$($clixon_cli -1 -f $cfg set top section x table parameter d value 99)" 0 "^$" + +new "check candidate" +expectpart "$($clixon_cli -1 -f $cfg show config candidate)" 0 "^
xb42c72d99
$" + +new "check compare xml" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "" "^\-\ *" "^+\ *" "^\-\ *a" "^+\ *c" --not-- "^+\ *a" "^\-\ *c" + +new "check compare text" +expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^\ *table {" "^\-\ *parameter a {" "^+\ *parameter c {" "^\-\ *value 98;" "^+\ *value 99;" + +new "delete section x" +expectpart "$($clixon_cli -1 -f $cfg delete top section x)" 0 "^$" + +# multiple and leaf-list +new "add a12 17" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter a1 a2 value 17)" 0 "^$" + +new "add a12 18" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter a1 a2 value 18)" 0 "^$" + +new "add b12 42" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter b1 b2 value 42)" 0 "^$" + +new "add b12 43" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter b1 b2 value 43)" 0 "^$" + +new "add d12 98" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter d1 d2 value 98)" 0 "^$" + +new "add d12 99" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter d1 d2 value 99)" 0 "^$" + +new "commit" +expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "^$" + +new "delete a12" +expectpart "$($clixon_cli -1 -f $cfg delete top section y multi parameter a1 a2)" 0 "^$" + +new "add c12 72" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter c1 c2 value 72)" 0 "^$" + +new "add c12 73" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter c1 c2 value 73)" 0 "^$" + +new "delete d12 99" +expectpart "$($clixon_cli -1 -f $cfg delete top section y multi parameter d1 d2 value 99)" 0 "^$" + +new "add d12 97" +expectpart "$($clixon_cli -1 -f $cfg set top section y multi parameter d1 d2 value 97)" 0 "^$" + +new "check compare multi xml" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^\-\ *a1" "^\-\ *a2" "^\-\ *17" "^\-\ *18" "^+\ *c1" "^+\ *c2" "^+\ *72" "^+\ *73" "^+\ *97" "^\-\ *99" --not-- "98" + +new "check compare multi text" +expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^\-\ *parameter a1 a2 {" "^\-\ *17" "^\-\ *18" "^+\ *parameter c1 c2 {" "^+\ *72" "^+\ *73" "^+\ *97" "^\-\ *99" "parameter d1 d2 {" --not-- "parameter b1 b2 {" +# XXX --not-- "^+\ *value \[" + +# NYI: json, cli + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + +new "endtest" +endtest diff --git a/test/test_datastore_format.sh b/test/test_datastore_format.sh index eae29819..d72a9c6c 100755 --- a/test/test_datastore_format.sh +++ b/test/test_datastore_format.sh @@ -99,9 +99,9 @@ quit("Quit"), cli_quit(); discard("Discard edits (rollback 0)"), discard_changes(); show("Show a particular state of the system"){ xpath("Show configuration") ("XPATH expression") ("Namespace"), show_conf_xpath("candidate"); - compare("Compare candidate and running databases"), compare_dbs((int32)0);{ - xml("Show comparison in xml"), compare_dbs((int32)0); - text("Show comparison in text"), compare_dbs((int32)1); + compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{ + xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml"); + text("Show comparison in text"), compare_dbs("running", "candidate", "text"); } configuration("Show configuration"), cli_show_auto_mode("candidate", "text", true, false);{ cli("Show configuration as CLI commands"), cli_show_auto_mode("candidate", "cli", true, false, "report-all", "set "); diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 3dcc1af4..c3a80e2a 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -334,7 +334,7 @@ main(int argc, /* 4. Output data (xml/json/text) */ if (output){ if (textout){ - if (clixon_txt2file(stdout, xt, 0, fprintf, 1, 0) < 0) + if (clixon_text2file(stdout, xt, 0, fprintf, 1, 0) < 0) goto done; } else if (jsonout){