* Experimental text syntax parser/loader
* Added new text syntax parsing and loading from CLI
* Unified text output functions to `xml2txt` and moved to clixon_text_syntax.[ch]
* The following are removed: `cli_xml2txt` and `xml2txt_cb`
* Text output format changed:
* Namespace/modulename added to top-level
* See [Support performant load_config_file(...) for TEXT format](https://github.com/clicon/clixon/issues/324)
This commit is contained in:
parent
43a57dad79
commit
2ece0b8f51
29 changed files with 1140 additions and 238 deletions
421
lib/src/clixon_text_syntax.c
Normal file
421
lib/src/clixon_text_syntax.c
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* TEXT / curly-brace syntax parsing and translations
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <syslog.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include "clixon_err.h"
|
||||
#include "clixon_log.h"
|
||||
#include "clixon_queue.h"
|
||||
#include "clixon_string.h"
|
||||
#include "clixon_hash.h"
|
||||
#include "clixon_handle.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_xml_io.h"
|
||||
#include "clixon_xml_sort.h"
|
||||
#include "clixon_xml_nsctx.h"
|
||||
#include "clixon_xml_bind.h"
|
||||
#include "clixon_text_syntax.h"
|
||||
#include "clixon_text_syntax_parse.h"
|
||||
|
||||
/* Size of json read buffer when reading from file*/
|
||||
#define BUFLEN 1024
|
||||
|
||||
/* Name of xml top object created by parse functions */
|
||||
#define TOP_SYMBOL "top"
|
||||
|
||||
/*! x is element and has eactly one child which in turn has none
|
||||
* @see child_type in clixon_json.c
|
||||
*/
|
||||
static int
|
||||
tleaf(cxobj *x)
|
||||
{
|
||||
cxobj *xc;
|
||||
|
||||
if (xml_type(x) != CX_ELMNT)
|
||||
return 0;
|
||||
if (xml_child_nr_notype(x, CX_ATTR) != 1)
|
||||
return 0;
|
||||
/* From here exactly one noattr child, get it */
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(x, xc, -1)) != NULL)
|
||||
if (xml_type(xc) != CX_ATTR)
|
||||
break;
|
||||
if (xc == NULL)
|
||||
return -1; /* n/a */
|
||||
return (xml_child_nr_notype(xc, CX_ATTR) == 0);
|
||||
}
|
||||
|
||||
/*! Translate XML to a "pseudo-code" textual format using a callback - internal function
|
||||
* @param[in] xn XML object to print
|
||||
* @param[in] fn Callback to make print function
|
||||
* @param[in] f File to print to
|
||||
* @param[in] level print 4 spaces per level in front of each line
|
||||
* @see xml2txt XXX why are these different?
|
||||
*/
|
||||
int
|
||||
xml2txt(cxobj *xn,
|
||||
clicon_output_cb *fn,
|
||||
FILE *f,
|
||||
int level)
|
||||
{
|
||||
cxobj *xc = NULL;
|
||||
int children=0;
|
||||
int retval = -1;
|
||||
int exist = 0;
|
||||
yang_stmt *yn;
|
||||
yang_stmt *yp;
|
||||
yang_stmt *ymod;
|
||||
yang_stmt *ypmod;
|
||||
char *prefix = NULL;
|
||||
|
||||
if (xn == NULL || fn == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xn or fn is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((yn = xml_spec(xn)) != NULL){
|
||||
if (yang_extension_value(yn, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
|
||||
goto done;
|
||||
if (exist)
|
||||
goto ok;
|
||||
/* Find out prefix if needed: topmost or new module a la API-PATH */
|
||||
if (ys_real_module(yn, &ymod) < 0)
|
||||
goto done;
|
||||
if ((yp = yang_parent_get(yn)) != NULL &&
|
||||
yp != ymod){
|
||||
if (ys_real_module(yp, &ypmod) < 0)
|
||||
goto done;
|
||||
if (ypmod != ymod)
|
||||
prefix = yang_argument_get(ymod);
|
||||
}
|
||||
else
|
||||
prefix = yang_argument_get(ymod);
|
||||
}
|
||||
xc = NULL; /* count children (elements and bodies, not attributes) */
|
||||
while ((xc = xml_child_each(xn, xc, -1)) != NULL)
|
||||
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY)
|
||||
children++;
|
||||
if (!children){ /* If no children print line */
|
||||
switch (xml_type(xn)){
|
||||
case CX_BODY:
|
||||
(*fn)(f, "%s;\n", xml_value(xn));
|
||||
break;
|
||||
case CX_ELMNT:
|
||||
(*fn)(f, "%*s%s;\n", 4*level, "", xml_name(xn));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
goto ok;
|
||||
}
|
||||
(*fn)(f, "%*s", 4*level, "");
|
||||
if (prefix)
|
||||
(*fn)(f, "%s:", prefix);
|
||||
(*fn)(f, "%s ", xml_name(xn));
|
||||
if (!tleaf(xn))
|
||||
(*fn)(f, "{\n");
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(xn, xc, -1)) != NULL){
|
||||
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY)
|
||||
if (xml2txt(xc, fn, f, level+1) < 0)
|
||||
break;
|
||||
}
|
||||
if (!tleaf(xn))
|
||||
(*fn)(f, "%*s}\n", 4*level, "");
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Parse a string containing text syntax and return an XML tree
|
||||
*
|
||||
|
||||
* @param[in] str Input string containing JSON
|
||||
* @param[in] rfc7951 Do sanity checks according to RFC 7951 JSON Encoding of Data Modeled with YANG
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing (if rfc7951)
|
||||
* @param[in] yspec Yang specification (if rfc 7951)
|
||||
* @param[out] xt XML top of tree typically w/o children on entry (but created)
|
||||
* @param[out] xerr Reason for invalid returned as netconf err msg
|
||||
*
|
||||
* @see _xml_parse for XML variant
|
||||
* @retval 1 OK and valid
|
||||
* @retval 0 Invalid (only if yang spec)
|
||||
* @retval -1 Error with clicon_err called
|
||||
*/
|
||||
static int
|
||||
_text_syntax_parse(char *str,
|
||||
yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
clixon_text_syntax_yacc ts = {0,};
|
||||
int ret;
|
||||
cxobj *x;
|
||||
cbuf *cberr = NULL;
|
||||
int failed = 0; /* yang assignment */
|
||||
|
||||
clicon_debug(1, "%s %d %s", __FUNCTION__, yb, str);
|
||||
ts.ts_parse_string = str;
|
||||
ts.ts_linenum = 1;
|
||||
ts.ts_xtop = xt;
|
||||
ts.ts_yspec = yspec;
|
||||
if (clixon_text_syntax_parsel_init(&ts) < 0)
|
||||
goto done;
|
||||
if (clixon_text_syntax_parseparse(&ts) != 0) { /* yacc returns 1 on error */
|
||||
clicon_log(LOG_NOTICE, "TEXT SYNTAX error: line %d", ts.ts_linenum);
|
||||
if (clicon_errno == 0)
|
||||
clicon_err(OE_JSON, 0, "TEXT SYNTAX parser error with no error code (should not happen)");
|
||||
goto done;
|
||||
}
|
||||
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(ts.ts_xtop, x, CX_ELMNT)) != NULL) {
|
||||
/* Populate, ie associate xml nodes with yang specs
|
||||
*/
|
||||
switch (yb){
|
||||
case YB_NONE:
|
||||
break;
|
||||
case YB_PARENT:
|
||||
/* xt:n Has spec
|
||||
* x: <a> <-- populate from parent
|
||||
*/
|
||||
if ((ret = xml_bind_yang0(x, YB_PARENT, NULL, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
case YB_MODULE_NEXT:
|
||||
if ((ret = xml_bind_yang(x, YB_MODULE, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
case YB_MODULE:
|
||||
/* xt:<top> nospec
|
||||
* x: <a> <-- populate from modules
|
||||
*/
|
||||
if ((ret = xml_bind_yang0(x, YB_MODULE, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
case YB_RPC:
|
||||
if ((ret = xml_bind_yang_rpc(x, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Add message-id */
|
||||
if (*xerr && clixon_xml_attr_copy(x, *xerr, "message-id") < 0)
|
||||
goto done;
|
||||
failed++;
|
||||
}
|
||||
break;
|
||||
} /* switch */
|
||||
}
|
||||
if (failed)
|
||||
goto fail;
|
||||
/* Sort the complete tree after parsing. Sorting is not really meaningful if Yang
|
||||
not bound */
|
||||
if (yb != YB_NONE)
|
||||
if (xml_sort_recurse(xt) < 0)
|
||||
goto done;
|
||||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (cberr)
|
||||
cbuf_free(cberr);
|
||||
clixon_text_syntax_parsel_exit(&ts);
|
||||
return retval;
|
||||
fail: /* invalid */
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Parse string containing TEXT syntax and return an XML tree
|
||||
*
|
||||
* @param[in] str String containing TEXT syntax
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Yang specification, mandatory to make module->xmlns translation
|
||||
* @param[in,out] xt Top object, if not exists, on success it is created with name 'top'
|
||||
* @param[out] xerr Reason for invalid returned as netconf err msg
|
||||
* @retval 1 OK and valid
|
||||
* @retval 0 Invalid (only if yang spec) w xerr set
|
||||
* @retval -1 Error with clicon_err called
|
||||
*
|
||||
* @code
|
||||
* cxobj *x = NULL;
|
||||
* if (clixon_text_syntax_parse_string(str, YB_MODULE, yspec, &x, &xerr) < 0)
|
||||
* err;
|
||||
* xml_free(x);
|
||||
* @endcode
|
||||
* @note you need to free the xml parse tree after use, using xml_free()
|
||||
* @see clixon_text_syntax_parse_file From a file
|
||||
*/
|
||||
int
|
||||
clixon_text_syntax_parse_string(char *str,
|
||||
yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (*xt == NULL){
|
||||
if ((*xt = xml_new("top", NULL, CX_ELMNT)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
return _text_syntax_parse(str, yb, yspec, *xt, xerr);
|
||||
}
|
||||
|
||||
/*! Read a TEXT syntax definition from file and parse it into a parse-tree.
|
||||
*
|
||||
* File will be parsed as follows:
|
||||
* (1) parsed according to TEXT syntax; # Only this check if yspec is NULL
|
||||
* (2) sanity checked wrt yang
|
||||
* (4) an xml parse tree will be returned
|
||||
*
|
||||
* @param[in] fp File descriptor to the TEXT syntax file
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt Pointer to (XML) parse tree. If empty, create.
|
||||
* @param[out] xerr Reason for invalid returned as netconf err msg
|
||||
*
|
||||
* @code
|
||||
* cxobj *xt = NULL;
|
||||
* if (clixon_text_syntax_parse_file(stdin, YB_MODULE, yspec, &xt) < 0)
|
||||
* err;
|
||||
* xml_free(xt);
|
||||
* @endcode
|
||||
* @note you need to free the xml parse tree after use, using xml_free()
|
||||
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
|
||||
* @note May block on file I/O
|
||||
*
|
||||
* @retval 1 OK and valid
|
||||
* @retval 0 Invalid (only if yang spec) w xerr set
|
||||
* @retval -1 Error with clicon_err called
|
||||
*
|
||||
* @see clixon_text_syntax_parse_string
|
||||
*/
|
||||
int
|
||||
clixon_text_syntax_parse_file(FILE *fp,
|
||||
yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
char *textbuf = NULL;
|
||||
int textbuflen = BUFLEN; /* start size */
|
||||
int oldtextbuflen;
|
||||
char *ptr;
|
||||
char ch;
|
||||
int len = 0;
|
||||
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if ((textbuf = malloc(textbuflen)) == NULL){
|
||||
clicon_err(OE_XML, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(textbuf, 0, textbuflen);
|
||||
ptr = textbuf;
|
||||
while (1){
|
||||
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
||||
clicon_err(OE_XML, errno, "read");
|
||||
break;
|
||||
}
|
||||
if (ret != 0)
|
||||
textbuf[len++] = ch;
|
||||
if (ret == 0){
|
||||
if (*xt == NULL)
|
||||
if ((*xt = xml_new(TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
if (len){
|
||||
if ((ret = _text_syntax_parse(ptr, yb, yspec, *xt, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (len >= textbuflen-1){ /* Space: one for the null character */
|
||||
oldtextbuflen = textbuflen;
|
||||
textbuflen *= 2;
|
||||
if ((textbuf = realloc(textbuf, textbuflen)) == NULL){
|
||||
clicon_err(OE_XML, errno, "realloc");
|
||||
goto done;
|
||||
}
|
||||
memset(textbuf+oldtextbuflen, 0, textbuflen-oldtextbuflen);
|
||||
ptr = textbuf;
|
||||
}
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
if (retval < 0 && *xt){
|
||||
free(*xt);
|
||||
*xt = NULL;
|
||||
}
|
||||
if (textbuf)
|
||||
free(textbuf);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue