* 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:
Olof hagsand 2022-05-19 12:56:44 +02:00
parent 43a57dad79
commit 2ece0b8f51
29 changed files with 1140 additions and 238 deletions

View 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;
}