Extended util/clixon_util_xml with yang and validate functionality so it can be used as a stand-alone utility for validating XML/JSON files

This commit is contained in:
Olof hagsand 2019-06-07 10:16:03 +02:00
parent ee863e5dbd
commit 519fac186c
8 changed files with 172 additions and 36 deletions

View file

@ -178,6 +178,7 @@
### Minor changes ### Minor changes
* Extended `util/clixon_util_xml` with yang and validate functionality so it can be used as a stand-alone utility for validating XML/JSON files
* JSON parse and print improvements * JSON parse and print improvements
* Integrated parsing with namespace translation and yang spec lookup * Integrated parsing with namespace translation and yang spec lookup
* Added CLIgen tab-modes in config option CLICON_CLI_TAB_MODE, which means you can control the behaviour of `<tab>` in the CLI. * Added CLIgen tab-modes in config option CLICON_CLI_TAB_MODE, which means you can control the behaviour of `<tab>` in the CLI.

View file

@ -243,6 +243,7 @@ parse_configfile(clicon_handle h,
/*! Add configuration option overriding file setting /*! Add configuration option overriding file setting
* Add to clicon_options hash, and to clicon_conf_xml tree * Add to clicon_options hash, and to clicon_conf_xml tree
* Assumes clicon_conf_xml_set has been called
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] name Name of configuration option (see clixon-config.yang) * @param[in] name Name of configuration option (see clixon-config.yang)
* @param[in] value String value * @param[in] value String value

View file

@ -2,6 +2,9 @@
# Run, eg as: # Run, eg as:
# ./all.sh 2>&1 | tee test.log # break on first test # ./all.sh 2>&1 | tee test.log # break on first test
# Pattern to run tests, default is all, but you may want to narrow it down
: ${pattern:=test_*.sh}
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
echo "usage: $0 # detailed logs and stopon first error" echo "usage: $0 # detailed logs and stopon first error"
exit -1 exit -1
@ -9,10 +12,10 @@ fi
err=0 err=0
testnr=0 testnr=0
for test in test_*.sh; do for test in $pattern; do
if [ $testnr != 0 ]; then echo; fi if [ $testnr != 0 ]; then echo; fi
testfile=$test testfile=$test
. ./$test ret=$(./$test) # . ./$test
errcode=$? errcode=$?
if [ $errcode -ne 0 ]; then if [ $errcode -ne 0 ]; then
err=1 err=1

View file

@ -2,7 +2,7 @@
# Run valgrind leak test for cli, restconf, netconf or background. # Run valgrind leak test for cli, restconf, netconf or background.
# Stop on first error # Stop on first error
# Pattern to run tests # Pattern to run tests, default is all, but you may want to narrow it down
: ${pattern:=test_*.sh} : ${pattern:=test_*.sh}
# Run valgrindtest once, args: # Run valgrindtest once, args:

View file

@ -6,7 +6,7 @@
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
: ${clixon_util_xml:=clixon_util_xml} : ${clixon_util_xml:=clixon_util_xml -o} # -o is output
new "xml parse" new "xml parse"
expecteof "$clixon_util_xml" 0 "<a><b/></a>" "^<a><b/></a>$" expecteof "$clixon_util_xml" 0 "<a><b/></a>" "^<a><b/></a>$"

View file

@ -59,7 +59,7 @@
#include "clixon/clixon.h" #include "clixon/clixon.h"
/* /*
* Turn this on to get a json parse and pretty print test program * JSON parse and pretty print test program
* Usage: xpath * Usage: xpath
* read json from input * read json from input
* Example compile: * Example compile:

View file

@ -34,6 +34,11 @@
* XML support functions. * XML support functions.
* @see https://www.w3.org/TR/2008/REC-xml-20081126 * @see https://www.w3.org/TR/2008/REC-xml-20081126
* https://www.w3.org/TR/2009/REC-xml-names-20091208 * https://www.w3.org/TR/2009/REC-xml-names-20091208
* The function can do yang validation, process xml and json, etc.
* On success, nothing is printed and exitcode 0
* On failure, an error is printed on stderr and exitcode != 0
* Failure error prints are different, it would be nice to make them more
* uniform. (see clicon_rpc_generate_error)
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -49,7 +54,9 @@
#include <stdint.h> #include <stdint.h>
#include <syslog.h> #include <syslog.h>
#include <assert.h> #include <assert.h>
#include <fcntl.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/stat.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -69,12 +76,20 @@
static int static int
usage(char *argv0) usage(char *argv0)
{ {
fprintf(stderr, "usage:%s [options]\n" fprintf(stderr, "usage:%s [options] with xml on stdin\n"
"where options are\n" "where options are\n"
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n" "\t-D <level> \tDebug\n"
"\t-f <file>\tXML input file (overrides stdin)\n"
"\t-J \t\tInput as JSON\n"
"\t-j \t\tOutput as JSON\n" "\t-j \t\tOutput as JSON\n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n", "\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
"\t-o \t\tOutput the file\n"
"\t-v \t\tValidate the result in terms of Yang model (requires -y)\n"
"\t-p \t\tPretty-print output\n"
"\t-y <filename> \tYang filename or dir (load all files)\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
,
argv0); argv0);
exit(0); exit(0);
} }
@ -89,11 +104,33 @@ main(int argc,
cbuf *cb = cbuf_new(); cbuf *cb = cbuf_new();
int c; int c;
int logdst = CLICON_LOG_STDERR; int logdst = CLICON_LOG_STDERR;
int json = 0; int jsonin = 0;
int jsonout = 0;
char *input_filename = NULL;
char *yang_file_dir = NULL;
yang_stmt *yspec = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
int pretty = 0;
int validate = 0;
int output = 0;
clicon_handle h;
struct stat st;
int fd = 0; /* stdin */
cxobj *xcfg = NULL;
cbuf *cberr;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
xcfg = xml_new("clixon-config", NULL, NULL);
clicon_conf_xml_set(h, xcfg);
optind = 1; optind = 1;
opterr = 0; opterr = 0;
while ((c = getopt(argc, argv, "hD:jl:")) != -1) while ((c = getopt(argc, argv, "hD:f:Jjl:pvoy:Y:")) != -1)
switch (c) { switch (c) {
case 'h': case 'h':
usage(argv[0]); usage(argv[0]);
@ -102,35 +139,129 @@ main(int argc,
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &debug) != 1)
usage(argv[0]); usage(argv[0]);
break; break;
case 'f':
input_filename = optarg;
break;
case 'J':
jsonin++;
break;
case 'j': case 'j':
json++; jsonout++;
break; break;
case 'l': /* Log destination: s|e|o|f */ case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(argv[0]); usage(argv[0]);
break; break;
case 'o':
output++;
break;
case 'v':
validate++;
break;
case 'p':
pretty++;
break;
case 'y':
yang_file_dir = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
default: default:
usage(argv[0]); usage(argv[0]);
break; break;
} }
if (validate && !yang_file_dir){
fprintf(stderr, "-v requires -y\n");
usage(argv[0]);
}
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst);
if (xml_parse_file(0, "</config>", NULL, &xt) < 0){ /* 1. Parse yang */
if (yang_file_dir){
if ((yspec = yspec_new()) == NULL)
goto done;
if (stat(yang_file_dir, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", yang_file_dir);
goto done;
}
if (S_ISDIR(st.st_mode)){
if (yang_spec_load_dir(h, yang_file_dir, yspec) < 0)
goto done;
}
else{
if (yang_spec_parse_file(h, yang_file_dir, yspec) < 0)
goto done;
}
}
if (input_filename){
if ((fd = open(input_filename, O_RDONLY)) < 0){
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
goto done;
}
}
/* 2. Parse data (xml/json) */
if (jsonin){
if ((ret = json_parse_file(fd, yspec, &xt, &xerr)) < 0)
goto done;
if (ret == 0){
xml_print(stderr, xerr);
goto done;
}
}
else{
if (xml_parse_file(fd, "</config>", NULL, &xt) < 0){
fprintf(stderr, "xml parse error %s\n", clicon_err_reason); fprintf(stderr, "xml parse error %s\n", clicon_err_reason);
goto done; goto done;
} }
}
/* Dump data structures (for debug) */
if (debug){
cbuf_reset(cb);
xmltree2cbuf(cb, xt, 0);
fprintf(stderr, "%s\n", cbuf_get(cb));
cbuf_reset(cb);
}
/* 3. Validate data (if yspec) */
if (validate){
xc = xml_child_i(xt, 0);
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Populate */
if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Sort */
if (xml_apply0(xc, CX_ELMNT, xml_sort, h) < 0)
goto done;
/* Add default values */
if (xml_apply(xc, CX_ELMNT, xml_default, h) < 0)
goto done;
if (xml_apply0(xc, -1, xml_sort_verify, h) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
if ((ret = xml_yang_validate_all_top(h, xc, cberr)) < 0)
goto done;
if (ret > 0 && (ret = xml_yang_validate_add(h, xc, cberr)) < 0)
goto done;
if (ret == 0){
fprintf(stderr, "%s", cbuf_get(cberr));
goto done;
}
}
/* 4. Output data (xml/json) */
if (output){
xc = NULL; xc = NULL;
while ((xc = xml_child_each(xt, xc, -1)) != NULL) while ((xc = xml_child_each(xt, xc, -1)) != NULL)
if (json) if (jsonout)
xml2json_cbuf(cb, xc, 0); /* print xml */ xml2json_cbuf(cb, xc, pretty); /* print xml */
else else
clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */ clicon_xml2cbuf(cb, xc, 0, pretty); /* print xml */
fprintf(stdout, "%s", cbuf_get(cb)); fprintf(stdout, "%s", cbuf_get(cb));
fflush(stdout); fflush(stdout);
#if 0 }
cbuf_reset(cb);
xmltree2cbuf(cb, xt, 0); /* dump data structures */
fprintf(stderr, "%s\n", cbuf_get(cb));
#endif
retval = 0; retval = 0;
done: done:
if (xt) if (xt)