CLI show compare example function

Improved diff algorithm for XML and TEXT/curly, replaced UNIX diff with structural in-mem algorithm
Fixed: ["show compare" and "show compare | display cli" differs #23](https://github.com/clicon/clixon-controller/issues/23)
Changed parameters of example clispec function `compare_dbs()`
Added show2cbuf functions for TEXT/CLI
Renamed clixon_txt2file to clixon_text2file
This commit is contained in:
Olof hagsand 2023-09-22 10:26:09 +02:00
parent 45f41e3e4d
commit 2603b6f139
19 changed files with 1170 additions and 193 deletions

View file

@ -53,6 +53,9 @@ Users may have to change how they access the system
### C/CLI-API changes on existing features ### C/CLI-API changes on existing features
Developers may need to change their code 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()` * Add `fromroot` parameter to `cli_show_common()`
* `cli_show_common(...xpath...)` --> `cli_show_common(...xpath,0...)` * `cli_show_common(...xpath...)` --> `cli_show_common(...xpath,0...)`
* Low-level message functions added `descr` argument for better logging * Low-level message functions added `descr` argument for better logging
@ -73,6 +76,8 @@ Developers may need to change their code
### Minor features ### 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) * 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 * 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) * 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 ### 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: [JSON backslash string decoding/encoding not correct](https://github.com/clicon/clixon/issues/453)
* Fixed: [CLI show config | display <format> exits over mountpoints with large YANGs](https://github.com/clicon/clixon-controller/issues/39) * Fixed: [CLI show config | display <format> 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 * JSON string fixed according to RFC 8259: encoding/decoding of escape as defined in Section 8

View file

@ -872,6 +872,7 @@ compare_db_names(clicon_handle h,
cxobj *xc1 = NULL; cxobj *xc1 = NULL;
cxobj *xc2 = NULL; cxobj *xc2 = NULL;
cxobj *xerr = NULL; cxobj *xerr = NULL;
cbuf *cb = NULL;
if (clicon_rpc_get_config(h, NULL, db1, "/", NULL, NULL, &xc1) < 0) if (clicon_rpc_get_config(h, NULL, db1, "/", NULL, NULL, &xc1) < 0)
goto done; goto done;
@ -885,10 +886,39 @@ compare_db_names(clicon_handle h,
clixon_netconf_error(xerr, "Get configuration", NULL); clixon_netconf_error(xerr, "Get configuration", NULL);
goto done; goto done;
} }
if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */ /* Note that XML and TEXT uses a (new) structured in-mem algorithm while
goto done; * 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; retval = 0;
done: done:
if (cb)
cbuf_free(cb);
if (xc1) if (xc1)
xml_free(xc1); xml_free(xc1);
if (xc2) if (xc2)
@ -899,25 +929,31 @@ compare_db_names(clicon_handle h,
/*! Compare two dbs using XML. Write to file and run diff /*! Compare two dbs using XML. Write to file and run diff
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] cvv * @param[in] cvv
* @param[in] argv arg: 0 as xml, 1: as text * @param[in] argv <db1> <db2> <format>
*/ */
int int
compare_dbs(clicon_handle h, compare_dbs(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
int retval = -1; int retval = -1;
enum format_enum format; enum format_enum format;
char *db1;
char *db2;
char *formatstr;
if (cvec_len(argv) > 1){ if (cvec_len(argv) != 3){
clicon_err(OE_PLUGIN, EINVAL, "Requires 0 or 1 element. If given: astext flag 0|1"); clicon_err(OE_PLUGIN, EINVAL, "Expected arguments: <db1> <db2> <format>");
goto done; goto done;
} }
if (cvec_len(argv) && cv_int32_get(cvec_i(argv, 0)) == 1) db1 = cv_string_get(cvec_i(argv, 0));
format = FORMAT_TEXT; db2 = cv_string_get(cvec_i(argv, 1));
else formatstr = cv_string_get(cvec_i(argv, 2));
format = FORMAT_XML; if ((format = format_str2int(formatstr)) < 0){
if (compare_db_names(h, format, "running", "candidate") < 0) clicon_err(OE_XML, 0, "format not found %s", formatstr);
goto done;
}
if (compare_db_names(h, format, db1, db2) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
@ -1186,7 +1222,7 @@ save_config_file(clicon_handle h,
goto done; goto done;
break; break;
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_CLI: case FORMAT_CLI:
@ -1304,7 +1340,7 @@ cli_notification_cb(int s,
if (clixon_json2file(stdout, xt, 1, cligen_output, 1, 1) < 0) if (clixon_json2file(stdout, xt, 1, cligen_output, 1, 1) < 0)
goto done; goto done;
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_XML: case FORMAT_XML:

View file

@ -319,7 +319,7 @@ pipe_showas_fn(clicon_handle h,
goto done; goto done;
break; break;
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_CLI: case FORMAT_CLI:

View file

@ -557,7 +557,7 @@ cli_show_common(clicon_handle h,
cligen_output(stdout, "\n"); cligen_output(stdout, "\n");
break; break;
case FORMAT_TEXT: /* XXX does not handle multiple leaf-list */ 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; goto done;
break; break;
case FORMAT_CLI: case FORMAT_CLI:
@ -1299,7 +1299,7 @@ cli_pagination(clicon_handle h,
goto done; goto done;
break; break;
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_CLI: case FORMAT_CLI:
@ -1341,6 +1341,134 @@ cli_pagination(clicon_handle h,
return retval; 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 /*! Translate from XML to CLI commands, internal
* *
* Howto: join strings and pass them down. * Howto: join strings and pass them down.
@ -1352,7 +1480,7 @@ cli_pagination(clicon_handle h,
* @param[in] fn Callback to make print function * @param[in] fn Callback to make print function
*/ */
static int static int
xml2cli1(clicon_handle h, cli2file(clicon_handle h,
FILE *f, FILE *f,
cxobj *xn, cxobj *xn,
char *prepend, char *prepend,
@ -1457,7 +1585,7 @@ xml2cli1(clicon_handle h,
if (match) if (match)
continue; /* Not key itself */ 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; goto done;
} }
ok: ok:
@ -1480,6 +1608,7 @@ xml2cli1(clicon_handle h,
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @see clixon_cli2cbuf
*/ */
int int
clixon_cli2file(clicon_handle h, clixon_cli2file(clicon_handle h,
@ -1497,11 +1626,50 @@ clixon_cli2file(clicon_handle h,
if (skiptop){ if (skiptop){
xc = NULL; xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != 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; goto done;
} }
else { 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; goto done;
} }
retval = 0; retval = 0;

View file

@ -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, int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts); cvec *commands, cvec *helptexts);
int clixon_cli2file(clicon_handle h, FILE *f, cxobj *xn, char *prepend, clicon_output_cb *fn, int skiptop); 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 */ /* 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); 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);

View file

@ -136,7 +136,7 @@ example_client_rpc(clicon_handle h,
fprintf(stdout,"\n"); fprintf(stdout,"\n");
/* pretty-print: /* 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; retval = 0;
done: done:

View file

@ -88,9 +88,9 @@ show("Show a particular state of the system"){
[<ns:string>("Namespace")], show_conf_xpath("candidate"); [<ns:string>("Namespace")], show_conf_xpath("candidate");
version("Show version"), cli_show_version("candidate", "text", "/"); version("Show version"), cli_show_version("candidate", "text", "/");
options("Show clixon options"), cli_show_options(); options("Show clixon options"), cli_show_options();
compare("Compare candidate and running databases"), compare_dbs((int32)0);{ compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{
xml("Show comparison in xml"), compare_dbs((int32)0); xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml");
text("Show comparison in text"), compare_dbs((int32)1); text("Show comparison in text"), compare_dbs("running", "candidate", "text");
} }
pagination("Show list pagination") xpath("Show configuration") <xpath:string>("XPATH expression"){ pagination("Show list pagination") xpath("Show configuration") <xpath:string>("XPATH expression"){
xml, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "xml", "10"); xml, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "xml", "10");

View file

@ -187,5 +187,6 @@
* Seems to be only an optimization since the config is queried from the backend anyway * 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 * The reason this probably should be undef:ed is that the restconf config appears in ps and other in
* cleartext * cleartext
* Plan is to remove this (undef:d) in next release
*/ */
#undef RESTCONF_INLINE #undef RESTCONF_INLINE

View file

@ -39,7 +39,9 @@
/* /*
* Prototypes * 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_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); int clixon_text_syntax_parse_file(FILE *fp, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr);

View file

@ -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, int clixon_xml_parse_va(yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr,
const char *format, ...) __attribute__ ((format (printf, 5, 6))); const char *format, ...) __attribute__ ((format (printf, 5, 6)));
int clixon_xml_attr_copy(cxobj *xin, cxobj *xout, char *name); 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_ */ #endif /* _CLIXON_XML_IO_H_ */

View file

@ -77,6 +77,5 @@ int xml_rpc_isaction(cxobj *xn);
int xml_find_action(cxobj *xn, int top, cxobj **xap); 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 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 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_ */ #endif /* _CLIXON_XML_MAP_H_ */

View file

@ -228,7 +228,7 @@ clicon_option_dump1(clicon_handle h,
goto done; goto done;
break; break;
case FORMAT_TEXT: 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; goto done;
break; break;
default: default:

View file

@ -78,6 +78,7 @@
#define TEXT_TOP_SYMBOL "top" #define TEXT_TOP_SYMBOL "top"
/*! x is element and has eactly one child which in turn has none /*! x is element and has eactly one child which in turn has none
*
* @see child_type in clixon_json.c * @see child_type in clixon_json.c
*/ */
static int static int
@ -100,6 +101,7 @@ tleaf(cxobj *x)
} }
/*! Translate XML to a "pseudo-code" textual format using a callback - internal function /*! Translate XML to a "pseudo-code" textual format using a callback - internal function
*
* @param[in] xn XML object to print * @param[in] xn XML object to print
* @param[in] fn Callback to make print function * @param[in] fn Callback to make print function
* @param[in] f File to print to * @param[in] f File to print to
@ -110,15 +112,16 @@ tleaf(cxobj *x)
* leaflist state: * leaflist state:
* 0: No leaflist * 0: No leaflist
* 1: In leaflist * 1: In leaflist
* @see text2cbuf to buffer (slower)
*/ */
static int static int
xml2txt1(cxobj *xn, text2file(cxobj *xn,
clicon_output_cb *fn, clicon_output_cb *fn,
FILE *f, FILE *f,
int level, int level,
int autocliext, int autocliext,
int *leafl, int *leafl,
char **leaflname) char **leaflname)
{ {
cxobj *xc = NULL; cxobj *xc = NULL;
@ -129,7 +132,7 @@ xml2txt1(cxobj *xn,
char *value; char *value;
cg_var *cvi; cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */ cvec *cvk = NULL; /* vector of index keys */
cbuf *cb = NULL; cbuf *cbb = NULL;
#ifndef TEXT_SYNTAX_NOPREFIX #ifndef TEXT_SYNTAX_NOPREFIX
yang_stmt *yp = NULL; yang_stmt *yp = NULL;
yang_stmt *ymod; yang_stmt *ymod;
@ -184,19 +187,19 @@ xml2txt1(cxobj *xn,
if (children == 0){ /* If no children print line */ if (children == 0){ /* If no children print line */
switch (xml_type(xn)){ switch (xml_type(xn)){
case CX_BODY:{ case CX_BODY:{
if ((cb = cbuf_new()) == NULL){ if ((cbb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
value = xml_value(xn); value = xml_value(xn);
if (index(value, ' ') != NULL) if (index(value, ' ') != NULL)
cprintf(cb, "\"%s\"", value); cprintf(cbb, "\"%s\"", value);
else else
cprintf(cb, "%s", value); cprintf(cbb, "%s", value);
if (*leafl) /* Skip keyword if leaflist */ 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 else
(*fn)(f, "%s;\n", cbuf_get(cb)); (*fn)(f, "%s;\n", cbuf_get(cbb));
break; break;
} }
case CX_ELMNT: case CX_ELMNT:
@ -243,7 +246,7 @@ xml2txt1(cxobj *xn,
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){ if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){
if (yn && yang_key_match(yn, xml_name(xc), NULL)) if (yn && yang_key_match(yn, xml_name(xc), NULL))
continue; /* Skip keys, already printed */ 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; break;
} }
} }
@ -257,8 +260,200 @@ xml2txt1(cxobj *xn,
ok: ok:
retval = 0; retval = 0;
done: done:
if (cb) if (cbb)
cbuf_free(cb); 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; return retval;
} }
@ -274,12 +469,12 @@ xml2txt1(cxobj *xn,
* @retval -1 Error * @retval -1 Error
*/ */
int int
clixon_txt2file(FILE *f, clixon_text2file(FILE *f,
cxobj *xn, cxobj *xn,
int level, int level,
clicon_output_cb *fn, clicon_output_cb *fn,
int skiptop, int skiptop,
int autocliext) int autocliext)
{ {
int retval = 1; int retval = 1;
cxobj *xc; cxobj *xc;
@ -291,11 +486,11 @@ clixon_txt2file(FILE *f,
if (skiptop){ if (skiptop){
xc = NULL; xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != 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; goto done;
} }
else { else {
if (xml2txt1(xn, fn, f, level, autocliext, &leafl, &leaflname) < 0) if (text2file(xn, fn, f, level, autocliext, &leafl, &leaflname) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
@ -303,6 +498,265 @@ clixon_txt2file(FILE *f,
return retval; 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 /*! 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. * 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 /*! Parse a string containing text syntax and return an XML tree
* *
* @param[in] str Input string containing JSON * @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] 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] yb How to bind yang to XML top-level when parsing (if rfc7951)
* @param[in] yspec Yang specification (if rfc 7951) * @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] xt XML top of tree typically w/o children on entry (but created)
* @param[out] xerr Reason for invalid returned as netconf err msg * @param[out] xerr Reason for invalid returned as netconf err msg
* @retval 1 OK and valid * @retval 1 OK and valid
* @retval 0 Invalid (only if yang spec) * @retval 0 Invalid (only if yang spec)
* @retval -1 Error with clicon_err called * @retval -1 Error with clicon_err called
* @see _xml_parse for XML variant * @see _xml_parse for XML variant
* @note Parsing requires YANG, which means yb must be YB_MODULE/_NEXT * @note Parsing requires YANG, which means yb must be YB_MODULE/_NEXT
*/ */

View file

@ -446,11 +446,11 @@ clixon_xml2cbuf1(cbuf *cb,
* @param[in] xn Top-level xml object * @param[in] xn Top-level xml object
* @param[in] level Indentation level for pretty * @param[in] level Indentation level for pretty
* @param[in] pretty Insert \n and spaces to make the xml more readable. * @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] 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, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* Depth is used in NACM * Depth is used in NACM
* @code * @code
* cbuf *cb = cbuf_new(); * cbuf *cb = cbuf_new();
@ -894,3 +894,200 @@ clixon_xml_attr_copy(cxobj *xin,
done: done:
return retval; 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</%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</%s>\n", ((level+1)*PRETTYPRINT_INDENT-1), "<",
xml_name(x0c), b0, xml_name(x0c));
cprintf(cb, "+%*s%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</%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);
}

View file

@ -410,6 +410,7 @@ xml_diff1(cxobj *x0,
* @retval -1 Error * @retval -1 Error
* All xml vectors should be freed after use. * All xml vectors should be freed after use.
* @see xml_tree_equal same algorithm but do not bother with what has changed * @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 int
xml_diff(cxobj *x0, xml_diff(cxobj *x0,
@ -1796,7 +1797,7 @@ purge_tagged_nodes(cxobj *xn,
* @param[in] xc1 XML tree 1 * @param[in] xc1 XML tree 1
* @param[in] xc2 XML tree 2 * @param[in] xc2 XML tree 2
* @param[in] format "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * @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 int
clixon_compare_xmls(cxobj *xc1, clixon_compare_xmls(cxobj *xc1,
@ -1820,7 +1821,7 @@ clixon_compare_xmls(cxobj *xc1,
goto done; goto done;
switch(format){ switch(format){
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_XML: case FORMAT_XML:
@ -1841,7 +1842,7 @@ clixon_compare_xmls(cxobj *xc1,
switch(format){ switch(format){
case FORMAT_TEXT: 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; goto done;
break; break;
case FORMAT_XML: case FORMAT_XML:
@ -1870,127 +1871,3 @@ clixon_compare_xmls(cxobj *xc1,
unlink(filename2); unlink(filename2);
return retval; 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</%s>\n", (level*PRETTYPRINT_INDENT-1), "<",
xml_name(x0c), b0, xml_name(x0c));
cprintf(cb, "+%*s%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;
}

View file

@ -129,9 +129,9 @@ debug("Debugging parts of the system"){
} }
show("Show a particular state of the system"){ show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression") <ns:string>("Namespace"), show_conf_xpath("candidate"); xpath("Show configuration") <xpath:string>("XPATH expression") <ns:string>("Namespace"), show_conf_xpath("candidate");
compare("Compare candidate and running databases"), compare_dbs((int32)0);{ compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{
xml("Show comparison in xml"), compare_dbs((int32)0); xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml");
text("Show comparison in text"), compare_dbs((int32)1); text("Show comparison in text"), compare_dbs("running", "candidate", "text");
} }
configuration("Show configuration"), cli_show_auto_mode("candidate", "text", true, false);{ 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 "); 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 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "^$"
new "cli compare diff" 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" new "cli start shell"
expectpart "$($clixon_cli -1 -f $cfg -l o shell echo foo)" 0 "foo" expectpart "$($clixon_cli -1 -f $cfg -l o shell echo foo)" 0 "foo"

237
test/test_cli_diff.sh Executable file
View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF
cat <<EOF > $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 <<EOF > $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 <<EOF > $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 "^+ <top xmlns=\"urn:example:clixon\">" --not-- "\-" "<data>"
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 "^<top xmlns=\"urn:example:clixon\"><section><name>x</name><table><parameter><name>a</name><value>17</value></parameter><parameter><name>b</name><value>42</value></parameter><parameter><name>d</name><value>98</value></parameter></table></section></top>$"
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 "^<top xmlns=\"urn:example:clixon\"><section><name>x</name><table><parameter><name>b</name><value>42</value></parameter><parameter><name>c</name><value>72</value></parameter><parameter><name>d</name><value>99</value></parameter></table></section></top>$"
new "check compare xml"
expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "<table>" "^\-\ *<parameter>" "^+\ *<parameter>" "^\-\ *<name>a</name>" "^+\ *<name>c</name>" --not-- "^+\ *<name>a</name>" "^\-\ *<name>c</name>"
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 "^\-\ *<first>a1</first>" "^\-\ *<second>a2</second>" "^\-\ *<value>17</value>" "^\-\ *<value>18</value>" "^+\ *<first>c1</first>" "^+\ *<second>c2</second>" "^+\ *<value>72</value>" "^+\ *<value>73</value>" "^+\ *<value>97</value>" "^\-\ *<value>99</value>" --not-- "<value>98</value>"
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

View file

@ -99,9 +99,9 @@ quit("Quit"), cli_quit();
discard("Discard edits (rollback 0)"), discard_changes(); discard("Discard edits (rollback 0)"), discard_changes();
show("Show a particular state of the system"){ show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression") <ns:string>("Namespace"), show_conf_xpath("candidate"); xpath("Show configuration") <xpath:string>("XPATH expression") <ns:string>("Namespace"), show_conf_xpath("candidate");
compare("Compare candidate and running databases"), compare_dbs((int32)0);{ compare("Compare candidate and running databases"), compare_dbs("running", "candidate", "xml");{
xml("Show comparison in xml"), compare_dbs((int32)0); xml("Show comparison in xml"), compare_dbs("running", "candidate", "xml");
text("Show comparison in text"), compare_dbs((int32)1); text("Show comparison in text"), compare_dbs("running", "candidate", "text");
} }
configuration("Show configuration"), cli_show_auto_mode("candidate", "text", true, false);{ 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 "); cli("Show configuration as CLI commands"), cli_show_auto_mode("candidate", "cli", true, false, "report-all", "set ");

View file

@ -334,7 +334,7 @@ main(int argc,
/* 4. Output data (xml/json/text) */ /* 4. Output data (xml/json/text) */
if (output){ if (output){
if (textout){ if (textout){
if (clixon_txt2file(stdout, xt, 0, fprintf, 1, 0) < 0) if (clixon_text2file(stdout, xt, 0, fprintf, 1, 0) < 0)
goto done; goto done;
} }
else if (jsonout){ else if (jsonout){