From cd0d2a1595809b1909bad4d751c498309e244ce3 Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Fri, 3 Jul 2026 11:28:55 -0700 Subject: [PATCH 1/6] RFC9144 NETCONF compare RPC used by cli compare xml --- apps/cli/cli_common.c | 47 ++++++++++++++++++------ lib/clixon/clixon_netconf_lib.h | 4 ++ lib/clixon/clixon_proto_client.h | 1 + lib/clixon/clixon_xml_io.h | 1 + lib/src/clixon_proto_client.c | 63 +++++++++++++++++++++++++++++++- lib/src/clixon_xml_diff.c | 15 ++++++-- lib/src/clixon_xml_io.c | 58 +++++++++++++++++++++++++++++ test/test_cli_diff.sh | 15 +++++--- 8 files changed, 182 insertions(+), 22 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ff120332d..7e1cec192 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -851,6 +851,38 @@ compare_db_names(clixon_handle h, cxobj *xc2 = NULL; cxobj *xerr = NULL; cbuf *cb = NULL; + cxobj *xret = NULL; + cxobj *xe; + cxobj *x; + int i; + + if (format == FORMAT_XML) { /* new implementation for xml format using rpc compare */ + if (clixon_rpc_nmda_compare(h, db1, db2, &xret) < 0) { + clixon_err(OE_PLUGIN, 0, "clixon_rpc_nmda_compare"); + goto done; + } + if (xpath_first(xret, NULL, "//no-matches") != NULL) { + cligen_output(stdout, "===\n"); + retval = 0; + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((xe = xpath_first(xret, NULL, "//differences/yang-patch")) == NULL) { + clixon_err(OE_XML, ENOENT, "Expected yang-patch in rpc compare reply"); + goto done; + } + cligen_output(stdout, "--- %s\n+++ %s\n", db1, db2); + i = 0; + while ((x = xml_child_iter(xe, &i, -1)) != NULL) + clixon_yangpatch2cbuf(cb, x); + cligen_output(stdout, "%s", cbuf_get(cb)); + retval = 0; + goto done; + } + /* xml format handling complete */ if (clicon_rpc_get_config(h, NULL, db1, "/", NULL, NULL, &xc1) < 0) goto done; @@ -866,19 +898,10 @@ compare_db_names(clixon_handle h, goto done; goto done; } - /* Note that XML and TEXT uses a (new) structured in-mem algorithm while + /* Note that 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){ - clixon_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){ clixon_err(OE_UNIX, errno, "cbuf_new"); @@ -893,7 +916,7 @@ compare_db_names(clixon_handle h, if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */ goto done; default: - break; + goto done; } retval = 0; done: @@ -903,6 +926,8 @@ compare_db_names(clixon_handle h, xml_free(xc1); if (xc2) xml_free(xc2); + if (xret) + xml_free(xret); return retval; } diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index b74c82b43..750285f8c 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -80,6 +80,10 @@ */ #define NETCONF_COMPARE_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-nmda-compare" +/* RFC 8342: Network Management Datastore Architecture (NMDA) + */ +#define NETCONF_DATASTORES_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-datastores" + /*! draft-ietf-netconf-privcand NETCONF and RESTCONF Private Candidate Datastores */ #define NETCONF_PRIVCAND_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-netconf-private-candidate" diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 6beb73228..48167c73e 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -86,6 +86,7 @@ int clixon_rpc_api_path2xml(clixon_handle h, const char *api_path, const char *b int clixon_rpc_translate_format(clixon_handle h, enum format_enum format, const char *xpath, cvec *nsc, cxobj *xt, int pretty, int skiptop, int cli_aware, const char *prepend, cbuf *cb); int clicon_rpc_restart_plugin(clixon_handle h, const char *plugin); +int clixon_rpc_nmda_compare(clixon_handle h, const char *db1, const char *db2, cxobj **xt); /*-- Backward compatible 7.6 --*/ static inline int diff --git a/lib/clixon/clixon_xml_io.h b/lib/clixon/clixon_xml_io.h index 90f7ad4c2..6eeb1ac08 100644 --- a/lib/clixon/clixon_xml_io.h +++ b/lib/clixon/clixon_xml_io.h @@ -59,6 +59,7 @@ int clixon_xml_parse_va(yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xe const char *format, ...) __attribute__ ((format (printf, 5, 6))); int clixon_xml_attr_copy(cxobj *xin, cxobj *xout, const char *name); int clixon_xml_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1); +int clixon_yangpatch2cbuf(cbuf *cb, cxobj *xe); static inline int clixon_xml2cbuf(cbuf *cb, diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9a9cdb206..e4c172402 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -740,7 +740,7 @@ clicon_rpc_edit_config(clixon_handle h, cprintf(cb, ""); if (clicon_rpc_msg(h, cb, &xret) < 0) goto done; - if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){ clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Editing configuration"); goto done; } @@ -2495,3 +2495,64 @@ clixon_rpc_nacm_autocli_filter(clixon_handle h, xml_free(xret); return retval; } + +/* Make a clixon compare call from client to backend server + * + * @param[in] h Clixon handle + * @param[in] db1 Source datastore + * @param[in] db2 Target datastore + * @param[out] xt Pointer to roc reply to be freed by caller, or NULL + * @retval 0 rpc reply ok + * @retval -1 Error +*/ + +int +clixon_rpc_nmda_compare(clixon_handle h, + const char *db1, + const char *db2, + cxobj **xt) +{ + int retval = -1; + cbuf *cb = NULL; + cxobj *xret = NULL; + char *username; + uint32_t session_id; + + clixon_debug(CLIXON_DBG_DEFAULT | CLIXON_DBG_DETAIL, "db1: %s, db2: %s", db1, db2); + if (session_id_check(h, &session_id) < 0) + goto done; + if ((cb = cbuf_new()) == NULL){ + clixon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "", netconf_message_id_next(h)); + cprintf(cb, "", + NETCONF_COMPARE_NAMESPACE, NETCONF_DATASTORES_NAMESPACE); + cprintf(cb, "ds:%s", db1); + cprintf(cb, "ds:%s", db2); + cprintf(cb, ""); + if (clicon_rpc_msg(h, cb, &xret) < 0) + goto done; + + if ((xpath_first(xret, NULL, "//rpc-error")) != NULL){ + clixon_err_netconf(h, OE_NETCONF, 0, xret, "compare rpc error"); + goto done; + } + *xt = xret; + xret = NULL; + retval = 0; + done: + clixon_debug(CLIXON_DBG_DEFAULT | CLIXON_DBG_DETAIL, "retval:%d", retval); + if (cb) + cbuf_free(cb); + if (xret) + xml_free(xret); + return retval; +} + diff --git a/lib/src/clixon_xml_diff.c b/lib/src/clixon_xml_diff.c index fa08ec1c2..f940ae623 100644 --- a/lib/src/clixon_xml_diff.c +++ b/lib/src/clixon_xml_diff.c @@ -1783,18 +1783,24 @@ clixon_xml_diff2patch(cxobj *x1, { int retval = -1; int nr = 1; + uint16_t f1 = 0; + uint16_t f2 = 0; - if (x1) + if (x1) { + f1 = xml_flag(x1, XML_FLAG_TOP); xml_flag_set(x1, XML_FLAG_TOP); - if (x2) + } + if (x2) { + f2 = xml_flag(x2, XML_FLAG_TOP); xml_flag_set(x2, XML_FLAG_TOP); + } if (xml_diff2patch(x1, x2, flags, xpatch, &nr) < 0) goto done; retval = 0; done: - if (x1) + if (x1 && !f1) xml_flag_reset(x1, XML_FLAG_TOP); - if (x2) + if (x2 && !f2) xml_flag_reset(x2, XML_FLAG_TOP); return retval; } @@ -1830,3 +1836,4 @@ clixon_xml_diff_nacm_read(clixon_handle h, done: return retval; } + diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c index efa86e822..37db8e2fd 100644 --- a/lib/src/clixon_xml_io.c +++ b/lib/src/clixon_xml_io.c @@ -1736,3 +1736,61 @@ clixon_xml_diff2cbuf(cbuf *cb, { return xml_diff2cbuf(cb, x0, x1, 0, 1); } + +/* Print an XML representation of one edit in a YANG Patch into a cbuf. + * + * @param[in] *cb CLIgen buffer + * @param[in] *xe Edit XML subtree + * @retval 0 OK + * @retval -1 Error + */ +int +clixon_yangpatch2cbuf(cbuf *cb, + cxobj *xe) +{ + int retval = -1; + cxobj *x; + const char *op; + const char *prefix; + + if ((x = xml_find(xe, "target")) == NULL) { + goto done; + } + cprintf(cb, "%s\n", xml_body(x)); + + if ((x = xml_find(xe, "operation")) == NULL) { + goto done; + } + op = xml_body(x); + switch (op[0]) { + case 'c': /* create */ + prefix = "+"; + break; + case 'd': /* delete */ + prefix = "-"; + break; + case 'r': /* replace*/ + if ((x = xml_find(xe, "source-value")) == NULL) + goto done; + if (clixon_xml2cbuf1(cb, x, 1, 1, "-", -1, 1, 0, WITHDEFAULTS_REPORT_ALL) < 0) + goto done; + prefix = "+"; + break; + case 'm': /* move */ + prefix = "~"; + /* not currently supported */ + goto done; + default: + goto done; + } + if ((x = xml_find(xe, "value")) == NULL) + goto done; + if (clixon_xml2cbuf1(cb, x, 1, 1, prefix, -1, 1, 0, WITHDEFAULTS_REPORT_ALL) < 0) + goto done; + + retval = 0; +done: + clixon_debug(CLIXON_DBG_XML | CLIXON_DBG_DETAIL, "retval: %d\n", retval); + return retval; +} + diff --git a/test/test_cli_diff.sh b/test/test_cli_diff.sh index 086543cde..c39bad42b 100755 --- a/test/test_cli_diff.sh +++ b/test/test_cli_diff.sh @@ -73,8 +73,8 @@ module clixon-example { } } } - } - } + } + } } EOF @@ -142,7 +142,7 @@ 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-- "\-" "" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^--- running" "^+++ candidate" "^/clixon-example:top" "^\+\ *" "^\+\ *a" "^\+\ *17" "^\+\ *" "^\+\ *" "^\+\ *b" "^\+\ *42" "^\+\ *" "^\+\ *" "^\+\ *d" "^\+\ *98" "^\+\ *" new "check compare text" expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^+\ *clixon-example:top {" --not-- "^\-" data @@ -150,6 +150,9 @@ expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^+\ *clixon-example: new "commit" expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "^$" +new "check compare xml no-matches" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^===" + new "check running" expectpart "$($clixon_cli -1 -f $cfg show config running)" 0 "^
xa17b42d98
$" @@ -166,8 +169,7 @@ new "check candidate" expectpart "$($clixon_cli -1 -f $cfg show config candidate)" 0 "^
xb42c72d99
$" new "check compare xml b" -echo "$clixon_cli -1 -f $cfg show compare xml" -expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "" "^\-\ *" "^+\ *" "^\-\ *a" "^+\ *c" --not-- "^+\ *a" "^\-\ *c" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^/clixon-example:top/section=x/table/parameter=a" "^\-\ *" "^\-\ *a" "^\-\ *17" "^\-\ *" "^/clixon-example:top/section=x/table/parameter=c" "^\+\ *" "^\+\ *c" "^\+\ *72" "^\+\ *" "^/clixon-example:top/section=x/table/parameter=d/value" "\-\ *98" "^\+\ *99" new "check compare text" expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "^\ *table {" "^\-\ *parameter a {" "^+\ *parameter c {" "^\-\ *value \"98\";" "^+\ *value \"99\";" @@ -213,10 +215,11 @@ 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" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^/clixon-example:top/section=y/multi/parameter=a1,a2" "^\-\ *" "^\-\ *a1" "^\-\ *a2" "^\-\ *17" "^\-\ *18" "^\-\ *" "^\/clixon-example:top/section=y/multi/parameter=c1,c2" "^\+\ *" "^\+\ *c1" "^\+\ *c2" "^\+\ *72" "^\+\ *73" "^\+\ *" "^\/clixon-example:top/section=y/multi/parameter=d1,d2/value=97" "^\+\ *97" "^/clixon-example:top/section=y/multi/parameter=d1,d2/value=99" "^\-\ *99" 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 From 35e8bc3f5fff8979b53930c62dbb9ff10221719b Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Sun, 5 Jul 2026 01:37:14 -0700 Subject: [PATCH 2/6] Do not create a private candidate when comparing --- apps/backend/backend_client.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index d3a938622..208e4e2e6 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1470,7 +1470,6 @@ from_client_compare(clixon_handle h, goto done; goto ok; } - /* Alt: see dsref handling in rpc_datastore_diff */ if ((db1 = xml_find_body(xe, "source")) == NULL){ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) @@ -1482,6 +1481,15 @@ from_client_compare(clixon_handle h, if (netconf_invalid_value(cbret, "protocol", "missing source identifier") < 0) goto done; } + /* Do not create a private candidate when comparing */ + if (strcmp(id1, "candidate") == 0 && clicon_option_bool(h, "CLICON_XMLDB_PRIVATE_CANDIDATE")) { + if (xmldb_candidate_find(h, "candidate", ce->ce_id, &de1, NULL) < 0) + goto done; + if (de1 == NULL) { + free(id1); + id1 = strdup("running"); + } + } if (xmldb_find_create(h, id1, ce->ce_id, &de1, NULL) < 0) goto done; if ((db2 = xml_find_body(xe, "target")) == NULL){ @@ -1494,8 +1502,18 @@ from_client_compare(clixon_handle h, if (netconf_invalid_value(cbret, "protocol", "missing target identifier") < 0) goto done; } + /* Do not create a private candidate when comparing */ + if (strcmp(id2, "candidate") == 0 && clicon_option_bool(h, "CLICON_XMLDB_PRIVATE_CANDIDATE")) { + if (xmldb_candidate_find(h, "candidate", ce->ce_id, &de2, NULL) < 0) + goto done; + if (de2 == NULL) { + free(id2); + id2 = strdup("running"); + } + } if (xmldb_find_create(h, id2, ce->ce_id, &de2, NULL) < 0) goto done; + /* filter: subtree-filter or xpath-filter */ if ((xpath_filter = xml_find(xe, "xpath-filter")) != NULL){ if ((xpath0 = xml_body(xpath_filter)) != NULL){ From 3bc0a15098e6bad6287c9af665fc0731b4ef2d85 Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Sun, 5 Jul 2026 01:42:23 -0700 Subject: [PATCH 3/6] no output when comparing two identical datastores --- apps/cli/cli_common.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 7e1cec192..7a2916ef0 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -858,11 +858,10 @@ compare_db_names(clixon_handle h, if (format == FORMAT_XML) { /* new implementation for xml format using rpc compare */ if (clixon_rpc_nmda_compare(h, db1, db2, &xret) < 0) { - clixon_err(OE_PLUGIN, 0, "clixon_rpc_nmda_compare"); + clixon_err(OE_PLUGIN, 0, "clixon_rpc_nmda_compare %s %s", db1, db2); goto done; } if (xpath_first(xret, NULL, "//no-matches") != NULL) { - cligen_output(stdout, "===\n"); retval = 0; goto done; } From 28bea350987e66228a15aae4558d19f90f5e35b4 Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Sun, 5 Jul 2026 01:44:51 -0700 Subject: [PATCH 4/6] adjust tests to accept new cli output format from compare xml --- test/test_cli_diff.sh | 2 +- test/test_order.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_cli_diff.sh b/test/test_cli_diff.sh index c39bad42b..8653c8bd0 100755 --- a/test/test_cli_diff.sh +++ b/test/test_cli_diff.sh @@ -151,7 +151,7 @@ new "commit" expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "^$" new "check compare xml no-matches" -expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^===" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "^$" new "check running" expectpart "$($clixon_cli -1 -f $cfg show config running)" 0 "^
x
a17b42d98
$" diff --git a/test/test_order.sh b/test/test_order.sh index 80ac8daf9..7fe710ec1 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -462,7 +462,7 @@ new "check ordered-by-user: a b c d" expecteof_netconf "$clixon_netconf -qef $cfg" 0 "$DEFAULTHELLO" "" "" "abcd" new "show compare xml" -expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "\-\ *e" --not-- "a" "b" "c" "d" "+ e" +expectpart "$($clixon_cli -1 -f $cfg show compare xml)" 0 "\+\ *b" "\+\ *c" "\+\ *d" "\-\ *e" "\-\ *b" "\-\ *c" "\-\ *d" new "show compare curly" expectpart "$($clixon_cli -1 -f $cfg show compare text)" 0 "\-\ *order-example:y0" "\- e" --not-- "+ order-example:y0" "\- a" "+ e" From a75c31907ba58a2e3271865aa63921c9bf99b70f Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Sun, 5 Jul 2026 13:49:24 +0200 Subject: [PATCH 5/6] editing error --- lib/src/clixon_proto_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index e4c172402..cbf21c85e 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -740,7 +740,7 @@ clicon_rpc_edit_config(clixon_handle h, cprintf(cb, ""); if (clicon_rpc_msg(h, cb, &xret) < 0) goto done; - if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){ + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Editing configuration"); goto done; } From e9767acff8bdf1bc30e95c81548dc5db9d9a90b0 Mon Sep 17 00:00:00 2001 From: Jan-Olof Carlson Date: Sun, 5 Jul 2026 17:24:00 +0200 Subject: [PATCH 6/6] clean up by removing code not used by cli compare anymore --- lib/clixon/clixon_xml_io.h | 1 - lib/src/clixon_text_syntax.c | 7 +- lib/src/clixon_xml_io.c | 447 ----------------------------------- 3 files changed, 1 insertion(+), 454 deletions(-) diff --git a/lib/clixon/clixon_xml_io.h b/lib/clixon/clixon_xml_io.h index 6eeb1ac08..58550e3b8 100644 --- a/lib/clixon/clixon_xml_io.h +++ b/lib/clixon/clixon_xml_io.h @@ -58,7 +58,6 @@ int clixon_xml_parse_string1(clixon_handle h, const char *str, yang_bind yb, y 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, const char *name); -int clixon_xml_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1); int clixon_yangpatch2cbuf(cbuf *cb, cxobj *xe); static inline int diff --git a/lib/src/clixon_text_syntax.c b/lib/src/clixon_text_syntax.c index 187c6e5b0..0e4a01f19 100644 --- a/lib/src/clixon_text_syntax.c +++ b/lib/src/clixon_text_syntax.c @@ -741,8 +741,6 @@ text_diff_keys(cbuf *cb, * @param[in] skiptop 0: Include top object 1: Skip top-object, only children, * @retval 0 Ok * @retval -1 Error - * @see xml_diff_ordered_by_user - * @see text_diff2cbuf_ordered_by_user */ static int text_diff2cbuf_ordered_by_user(cbuf *cb, @@ -814,7 +812,7 @@ text_diff2cbuf_ordered_by_user(cbuf *cb, * if (clixon_text_diff2cbuf(cb, 0, x0, x1, 0) < 0) * err(); * @endcode - * @see clixon_xml_diff2cbuf + * XXX Leaf-list +/- is not correct * For example, it should be: * value [ @@ -826,9 +824,7 @@ text_diff2cbuf_ordered_by_user(cbuf *cb, * + 97 * - 99 * @see clixon_compare_xmls which uses files and is independent of YANG - * @see xml_diff2cbuf for XML * @see xml_tree_equal Equal or not - * @see xml_diff Diff sets */ static int text_diff2cbuf(cbuf *cb, @@ -1095,7 +1091,6 @@ text_diff2cbuf(cbuf *cb, * if (clixon_text_diff2cbuf(cb, 0, x0, x1) < 0) * err(); * @endcode - * @see clixon_xml_diff2cbuf */ int clixon_text_diff2cbuf(cbuf *cb, diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c index 37db8e2fd..c320278a3 100644 --- a/lib/src/clixon_xml_io.c +++ b/lib/src/clixon_xml_io.c @@ -93,9 +93,6 @@ /* Size of xml read buffer */ #define BUFLEN 1024 -/* Forward */ -static int xml_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1, int level, int skiptop); - /*------------------------------------------------------------------------ * XML printing functions. Output a parse tree to file, string cligen buf *------------------------------------------------------------------------*/ @@ -1293,450 +1290,6 @@ clixon_xml_attr_copy(cxobj *xin, 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 one line of context around diff - */ -static int -xml_diff_context(cbuf *cb, - cxobj *xn, - int level) -{ - int retval = -1; - char *prefix; - char *namespace = NULL; - - prefix = xml_prefix(xn); - if (xml2ns(xn, prefix, &namespace) < 0) - goto done; - cprintf(cb, "%*s<", level, ""); - if (namespace){ - if (prefix) - cprintf(cb, "%s:", prefix); - cprintf(cb, "%s xmlns", xml_name(xn)); - if (prefix) - cprintf(cb, ":%s", prefix); - cprintf(cb, "=\"%s\"", namespace); - cprintf(cb, ">\n"); - } - else - cprintf(cb, "%s>\n", xml_name(xn)); - retval = 0; - done: - return retval; -} - -/*! Handle order-by user(leaf)list for xml_diff2cbuf - * - * @param[out] cb CLIgen buffer - * @param[in] x0 First XML tree - * @param[in] x1 Second XML tree - * @param[in] x0c Start of sublist in first XML tree - * @param[in] x1c Start of sublist in second XML tree - * @param[in] yc Yang of x0c/x1c. If NULL special case of anydata - * @param[in] level How many spaces to insert before each line - * @retval 0 Ok - * @retval -1 Error - * @see xml_diff_ordered_by_user - * @see text_diff2cbuf_ordered_by_user - */ -static int -xml_diff2cbuf_ordered_by_user(cbuf *cb, - cxobj *x0, - cxobj *x1, - cxobj *x0c, - cxobj *x1c, - int ix0c, - int ix1c, - yang_stmt *yc, - int level) -{ - int retval = 1; - cxobj *xi; - cxobj *xj; - int ixi; - int ixj; - - xj = x1c; - ixj = ix1c; - do { /* Mark all x1 as ADD */ - xml_flag_set(xj, XML_FLAG_ADD); - } while ((xj = xml_child_iter(x1, &ixj, CX_ELMNT)) != NULL && - xml_spec(xj) == yc); - /* If in both sets, unmark add/del */ - xi = x0c; - ixi = ix0c; - do { - xml_flag_set(xi, XML_FLAG_DEL); - xj = x1c; - ixj = ix1c; - do { - if (xml_flag(xj, XML_FLAG_ADD) && - xml_cmp(xi, xj, 0, 0, NULL) == 0){ - /* Unmark node in x0 and x1 */ - xml_flag_reset(xi, XML_FLAG_DEL); - xml_flag_reset(xj, XML_FLAG_ADD); - if (xml_diff2cbuf(cb, xi, xj, level+1, 0) < 0) - goto done; - break; - } - } - while ((xj = xml_child_iter(x1, &ixj, CX_ELMNT)) != NULL && - xml_spec(xj) == yc); - } - while ((xi = xml_child_iter(x0, &ixi, CX_ELMNT)) != NULL && - xml_spec(xi) == yc); - - retval = 0; - done: - return retval; -} - -/*! xml_diff2cbuf helper function to compute leaf difference - * - * @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 - */ -static int -xml_diff2cbuf_leaf(cbuf *cb, - cxobj *x0, - cxobj *x1, - int level, - int skiptop, - yang_stmt *y0, - cxobj *x0c, - cxobj *x1c, - char *b0, - char *b1, - int *nr) -{ - int retval = -1; - char *e0 = NULL; - char *e1 = NULL; - - if (*nr==0 && skiptop==0){ - xml_diff_context(cb, x0, level*PRETTYPRINT_INDENT); - xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); - (*nr)++; - } - /* Encode data to XML */ - if (b0){ - if (xml_chardata_encode(&e0, 0, "%s", b0) < 0) - goto done; - } - else - e0 = NULL; - if (b1){ - if (xml_chardata_encode(&e1, 0, "%s", b1) < 0) - goto done; - } - else - e1 = NULL; - cprintf(cb, "-%*s%s>%s\n", ((level+1)*PRETTYPRINT_INDENT-1), "<", - xml_name(x0c), e0, xml_name(x0c)); - cprintf(cb, "+%*s%s>%s\n", ((level+1)*PRETTYPRINT_INDENT-1), "<", - xml_name(x1c), e1, xml_name(x1c)); - if (e0){ - free(e0); - e0 = NULL; - } - if (e1){ - free(e1); - e1 = NULL; - } - retval = 0; - done: - if (e0) - free(e0); - if (e1) - free(e1); - return retval; -} - -/*! Print XML diff of two cxobj trees into a cbuf - * - * YANG dependent, - * Skip objects marked with XML_FLAG_DENY - * 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 - * @code - * 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 - * @see text_diff2cbuf for curly - * @see xml_tree_equal Equal or not - * @see xml_diff Diff sets - */ -static 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 *y0c; - yang_stmt *y1c; - char *b0; - char *b1; - int eq; - int nr=0; - int level1; - cxobj *xi; - cxobj *xj; - int extflag; - int ix0c; - int ix1c; - int ixi; - int ixj; - - level1 = level*PRETTYPRINT_INDENT; - y0 = xml_spec(x0); - /* Traverse x0 and x1 in lock-step */ - ix0c = ix1c = 0; - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - x1c = xml_child_iter(x1, &ix1c, CX_ELMNT); - for (;;){ - if (x0c == NULL && x1c == NULL) - goto ok; - /* Skip if marked as DENY by NACM */ - if (x0c && xml_flag(x0c, XML_FLAG_DENY) != 0){ - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - continue; - } - else if (x1c && xml_flag(x1c, XML_FLAG_DENY) != 0){ - x1c = xml_child_iter(x1, &ix1c, CX_ELMNT); - continue; - } - y0c = NULL; - y1c = NULL; - /* If cl:ignore-compare extension, skip */ - if (x0c && (y0c = xml_spec(x0c)) != NULL){ - if (yang_extension_value(y0c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) - goto done; - if (extflag){ /* skip */ - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - continue; - } - } - if (x1c && (y1c = xml_spec(x1c)) != NULL){ - if (yang_extension_value(y1c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) - goto done; - if (extflag){ /* skip */ - x1c = xml_child_iter(x1, &ix1c, CX_ELMNT); - continue; - } - } - if (x0c == NULL){ - /* Check if one or both subtrees are NULL */ - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x1, level1); - xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, x1c, level+1, 1, "+", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - x1c = xml_child_iter(x1, &ix1c, CX_ELMNT); - continue; - } - else if (x1c == NULL){ - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x0, level1); - xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, x0c, level+1, 1, "-", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - continue; - } - /* Both x0c and x1c exists, check if yang equal */ - eq = xml_cmp(x0c, x1c, 0, 0, NULL); - b0 = xml_body(x0c); - b1 = xml_body(x1c); - if (eq && y0c && y1c && y0c == y1c && yang_find(y0c, Y_ORDERED_BY, "user")){ - if (xml_diff2cbuf_ordered_by_user(cb, x0, x1, x0c, x1c, ix0c, ix1c, y0c, level) < 0) - goto done; - /* Show all marked as DELETE as - entries - */ - xi = x0c; - ixi = ix0c; - do { - if (xml_flag(xi, XML_FLAG_DEL)){ - xml_flag_reset(xi, XML_FLAG_DEL); - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x0, level1); - xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, xi, level+1, 1, "-", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - } - } - while ((xi = xml_child_iter(x0, &ixi, CX_ELMNT)) != NULL && - xml_spec(xi) == y0c); - x0c = xi; /* skip entries in this yang class */ - ix0c = ixi; - /* Show all marked as ADD as + entries - */ - xj = x1c; - ixj = ix1c; - do { - if (xml_flag(xj, XML_FLAG_ADD)){ - xml_flag_reset(xj, XML_FLAG_ADD); - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x1, level1); - xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, xj, level+1, 1, "+", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - } - } - while ((xj = xml_child_iter(x1, &ixj, CX_ELMNT)) != NULL && - xml_spec(xj) == y1c); - x1c = xj; - ix1c = ixj; - continue; - } /* ordered-by user */ - else if (eq < 0){ - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x0, level1); - xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, x0c, level+1, 1, "-", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - continue; - } - else if (eq > 0){ - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x1, level1); - xml_diff_keys(cb, x1, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, x1c, level+1, 1, "+", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - x1c = xml_child_iter(x1, &ix1c, CX_ELMNT); - continue; - } - else{ /* equal */ - /* xml-spec NULL could happen with anydata children for example, - * if so, continute compare children but without yang - */ - if (y0c && y1c && y0c != y1c){ /* choice */ - if (nr==0 && skiptop==0){ - xml_diff_context(cb, x0, level1); - xml_diff_keys(cb, x0, y0, (level+1)*PRETTYPRINT_INDENT); - nr++; - } - if (clixon_xml2cbuf1(cb, x0c, level+1, 1, "-", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - if (clixon_xml2cbuf1(cb, x1c, level+1, 1, "+", -1, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - } - else if (y0c && yang_keyword_get(y0c) == Y_LEAF){ - /* if x0c and x1c are leafs w bodies, then they may be changed */ - if (b0 == NULL && b1 == NULL) - ; - else if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0){ - if (xml_diff2cbuf_leaf(cb, x0, x1, level, skiptop, - y0, x0c, x1c, b0, b1, &nr) < 0) - goto done; - } - } - else if (y0c == NULL && y1c == NULL && (b0 || b1)) { /* Anydata terminals */ - if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0){ - if (xml_diff2cbuf_leaf(cb, x0, x1, level, skiptop, - y0, x0c, x1c, b0, b1, &nr) < 0) - goto done; - } - } - else if (xml_diff2cbuf(cb, x0c, x1c, level+1, 0) < 0) - goto done; - - } - /* Get next */ - x0c = xml_child_iter(x0, &ix0c, CX_ELMNT); - x1c = xml_child_iter(x1, &ix1c, 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 - * @see clixon_text_diff2cbuf - */ -int -clixon_xml_diff2cbuf(cbuf *cb, - cxobj *x0, - cxobj *x1) -{ - return xml_diff2cbuf(cb, x0, x1, 0, 1); -} - /* Print an XML representation of one edit in a YANG Patch into a cbuf. * * @param[in] *cb CLIgen buffer