1215 lines
39 KiB
C
1215 lines
39 KiB
C
/*
|
|
*
|
|
***** 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_queue.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_debug.h"
|
|
#include "clixon_options.h"
|
|
#include "clixon_netconf_lib.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
|
|
* See also DATASTORE_TOP_SYMBOL which is the clixon datastore top symbol. By default also config
|
|
*/
|
|
|
|
#define TEXT_TOP_SYMBOL "top"
|
|
|
|
/* Forward */
|
|
static int text_diff2cbuf(cbuf *cb, cxobj *x0, cxobj *x1, int level, int skiptop);
|
|
|
|
/*! 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 PRETTYPRINT_INDENT spaces per level in front of each line
|
|
* @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 []
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* leaflist state:
|
|
* 0: No leaflist
|
|
* 1: In leaflist
|
|
* @see text2cbuf to buffer (slower)
|
|
*/
|
|
static int
|
|
text2file(cxobj *xn,
|
|
clicon_output_cb *fn,
|
|
FILE *f,
|
|
int level,
|
|
int autocliext,
|
|
int *leafl,
|
|
char **leaflname)
|
|
|
|
{
|
|
int retval = -1;
|
|
cxobj *xc = NULL;
|
|
int children=0;
|
|
int exist = 0;
|
|
yang_stmt *yn;
|
|
char *value;
|
|
cg_var *cvi;
|
|
cvec *cvk = NULL; /* vector of index keys */
|
|
cbuf *cbb = NULL;
|
|
#ifndef TEXT_SYNTAX_NOPREFIX
|
|
yang_stmt *yp = NULL;
|
|
yang_stmt *ymod;
|
|
yang_stmt *ypmod;
|
|
char *prefix = NULL;
|
|
#endif
|
|
if (xn == NULL || fn == NULL){
|
|
clixon_err(OE_XML, EINVAL, "xn or fn is NULL");
|
|
goto done;
|
|
}
|
|
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
|
|
/* 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);
|
|
#endif
|
|
if (yang_keyword_get(yn) == Y_LIST){
|
|
if ((cvk = yang_cvec_get(yn)) == NULL){
|
|
clixon_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;
|
|
(*fn)(f, "%*s]\n", PRETTYPRINT_INDENT*(level), "");
|
|
}
|
|
}
|
|
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){
|
|
clixon_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 */
|
|
(*fn)(f, "%*s%s\n", PRETTYPRINT_INDENT*level, "", cbuf_get(cbb));
|
|
else
|
|
(*fn)(f, "%s;\n", cbuf_get(cbb));
|
|
break;
|
|
}
|
|
case CX_ELMNT:
|
|
(*fn)(f, "%*s%s", PRETTYPRINT_INDENT*level, "", 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)
|
|
(*fn)(f, " %s", xml_body(xc));
|
|
}
|
|
(*fn)(f, ";\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
goto ok;
|
|
}
|
|
if (*leafl == 0){
|
|
(*fn)(f, "%*s", PRETTYPRINT_INDENT*level, "");
|
|
#ifndef TEXT_SYNTAX_NOPREFIX
|
|
if (prefix)
|
|
(*fn)(f, "%s:", prefix);
|
|
#endif
|
|
(*fn)(f, "%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)
|
|
(*fn)(f, " %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);
|
|
(*fn)(f, " [\n");
|
|
}
|
|
else if (!tleaf(xn))
|
|
(*fn)(f, " {\n");
|
|
else
|
|
(*fn)(f, " ");
|
|
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 (text2file(xc, fn, f, level+1, 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;
|
|
(*fn)(f, "%*s]\n", PRETTYPRINT_INDENT*(level+1), "");
|
|
}
|
|
if (!tleaf(xn))
|
|
(*fn)(f, "%*s}\n", PRETTYPRINT_INDENT*level, "");
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (cbb)
|
|
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 []
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* 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)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xc = NULL;
|
|
int children=0;
|
|
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){
|
|
clixon_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){
|
|
clixon_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){
|
|
clixon_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;
|
|
}
|
|
|
|
/*! Translate XML to a "pseudo-code" textual format using a callback
|
|
*
|
|
* @param[in] f File to print to
|
|
* @param[in] xn XML object to print
|
|
* @param[in] level Print PRETTYPRINT_INDENT spaces per level in front of each line
|
|
* @param[in] fn File print function (if NULL, use fprintf)
|
|
* @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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_text2file(FILE *f,
|
|
cxobj *xn,
|
|
int level,
|
|
clicon_output_cb *fn,
|
|
int skiptop,
|
|
int autocliext)
|
|
{
|
|
int retval = 1;
|
|
cxobj *xc;
|
|
int leafl = 0;
|
|
char *leaflname = NULL;
|
|
|
|
if (fn == NULL)
|
|
fn = fprintf;
|
|
if (skiptop){
|
|
xc = NULL;
|
|
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
|
|
if (text2file(xc, fn, f, level, autocliext, &leafl, &leaflname) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
if (text2file(xn, fn, f, level, autocliext, &leafl, &leaflname) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*! Handle order-by user(leaf)list for text_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
|
|
* @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
|
|
* @see xml_diff_ordered_by_user
|
|
* @see text_diff2cbuf_ordered_by_user
|
|
*/
|
|
static int
|
|
text_diff2cbuf_ordered_by_user(cbuf *cb,
|
|
cxobj *x0,
|
|
cxobj *x1,
|
|
cxobj *x0c,
|
|
cxobj *x1c,
|
|
yang_stmt *yc,
|
|
int level,
|
|
int skiptop)
|
|
{
|
|
int retval = 1;
|
|
cxobj *xi;
|
|
cxobj *xj;
|
|
|
|
xj = x1c;
|
|
do {
|
|
xml_flag_set(xj, XML_FLAG_ADD);
|
|
} while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
|
|
xml_spec(xj) == yc);
|
|
/* If in both sets, unmark add/del */
|
|
xi = x0c;
|
|
do {
|
|
xml_flag_set(xi, XML_FLAG_DEL);
|
|
xj = x1c;
|
|
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 (text_diff2cbuf(cb, xi, xj, level+1, 0) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
|
|
xml_spec(xj) == yc);
|
|
}
|
|
while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL &&
|
|
xml_spec(xi) == yc);
|
|
|
|
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
|
|
* @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
|
|
* @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,
|
|
cxobj *x0,
|
|
cxobj *x1,
|
|
int level,
|
|
int skiptop)
|
|
{
|
|
int retval = -1;
|
|
cxobj *x0c = NULL; /* x0 child */
|
|
cxobj *x1c = NULL; /* x1 child */
|
|
yang_stmt *y0c;
|
|
yang_stmt *y1c;
|
|
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
|
|
cxobj *xi;
|
|
cxobj *xj;
|
|
int extflag;
|
|
|
|
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;
|
|
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_each(x0, x0c, 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_each(x1, x1c, CX_ELMNT);
|
|
continue;
|
|
}
|
|
}
|
|
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);
|
|
b0 = xml_body(x0c);
|
|
b1 = xml_body(x1c);
|
|
if (eq && y0c && y1c && y0c == y1c && yang_find(y0c, Y_ORDERED_BY, "user")){
|
|
if (text_diff2cbuf_ordered_by_user(cb, x0, x1, x0c, x1c, y0c,
|
|
level, skiptop) < 0)
|
|
goto done;
|
|
/* Add all in x0 marked as DELETE in x0vec
|
|
* Flags can remain: XXX should apply to all
|
|
*/
|
|
xi = x0c;
|
|
do {
|
|
if (xml_flag(xi, XML_FLAG_DEL)){
|
|
xml_flag_reset(xi, XML_FLAG_DEL);
|
|
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, xi, level+1, "-", 0, &leafl, &leaflname) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL &&
|
|
xml_spec(xi) == y0c);
|
|
x0c = xi;
|
|
|
|
/* Add all in x1 marked as ADD in x1vec */
|
|
xj = x1c;
|
|
do {
|
|
if (xml_flag(xj, XML_FLAG_ADD)){
|
|
xml_flag_reset(xj, XML_FLAG_ADD);
|
|
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, xj, level+1, "+", 0, &leafl, &leaflname) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL &&
|
|
xml_spec(xj) == y1c);
|
|
x1c = xj;
|
|
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(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 (y0c && y1c && y0c != y1c){ /* 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 (y0c && yang_keyword_get(y0c) == Y_LEAF){
|
|
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 (y0c == NULL && y1c == NULL && (b0 || b1)) {
|
|
/* Special case for anydata terminals */
|
|
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
|
|
*
|
|
* This is a compromise between making the text parser (1) YANG aware or (2) not.
|
|
* (1) The reason for is that some constructs such as "list <keyval1> {" does not
|
|
* contain enough info (eg name of XML tag for <keyval1>)
|
|
* (2) The reason against is of principal of making the parser design simpler in a bottom-up mode
|
|
* The compromise between (1) and (2) is to first parse without YANG (2) and then call a special
|
|
* function after YANG binding to populate key tags properly.
|
|
* @param[in] xn XML node
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see text_mark_bodies where marking of bodies made transformed here
|
|
*/
|
|
static int
|
|
text_populate_list(cxobj *xn)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yn;
|
|
yang_stmt *yc;
|
|
cxobj *xc;
|
|
cxobj *xb;
|
|
cvec *cvk; /* vector of index keys */
|
|
cg_var *cvi = NULL;
|
|
char *namei;
|
|
|
|
if ((yn = xml_spec(xn)) == NULL)
|
|
goto ok;
|
|
if (yang_keyword_get(yn) == Y_LIST){
|
|
cvk = yang_cvec_get(yn);
|
|
/* Loop over bodies and keys and create key leafs
|
|
*/
|
|
cvi = NULL;
|
|
xb = NULL;
|
|
while ((xb = xml_find_type(xn, NULL, NULL, CX_BODY)) != NULL) {
|
|
if (!xml_flag(xb, XML_FLAG_BODYKEY))
|
|
continue;
|
|
xml_flag_reset(xb, XML_FLAG_BODYKEY);
|
|
if ((cvi = cvec_next(cvk, cvi)) == NULL){
|
|
clixon_err(OE_XML, 0, "text parser, key and body mismatch");
|
|
goto done;
|
|
}
|
|
namei = cv_string_get(cvi);
|
|
if ((xc = xml_new(namei, xn, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
yc = yang_find(yn, Y_LEAF, namei);
|
|
xml_spec_set(xc, yc);
|
|
if ((xml_addsub(xc, xb)) < 0)
|
|
goto done;
|
|
|
|
}
|
|
if (xml_sort(xn) < 0)
|
|
goto done;
|
|
}
|
|
xc = NULL;
|
|
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) {
|
|
if (text_populate_list(xc) < 0)
|
|
goto done;
|
|
}
|
|
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
|
|
* @retval 1 OK and valid
|
|
* @retval 0 Invalid (only if yang spec)
|
|
* @retval -1 Error
|
|
* @see _xml_parse for XML variant
|
|
* @note Parsing requires YANG, which means yb must be YB_MODULE/_NEXT
|
|
*/
|
|
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 */
|
|
cxobj *xc;
|
|
|
|
clixon_debug(CLIXON_DBG_PARSE, "%s", str);
|
|
if (yb != YB_MODULE && yb != YB_MODULE_NEXT){
|
|
clixon_err(OE_YANG, EINVAL, "yb must be YB_MODULE or YB_MODULE_NEXT");
|
|
return -1;
|
|
}
|
|
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 */
|
|
clixon_log(NULL, LOG_NOTICE, "TEXT SYNTAX error: line %d", ts.ts_linenum);
|
|
if (clixon_err_category() == 0)
|
|
clixon_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_MODULE_NEXT:
|
|
if ((ret = xml_bind_yang(NULL, 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(NULL, x, YB_MODULE, yspec, xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
failed++;
|
|
break;
|
|
default: /* shouldnt happen */
|
|
break;
|
|
} /* switch */
|
|
/* Look for YANG lists nodes and convert bodies to keys */
|
|
xc = NULL;
|
|
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
|
|
if (text_populate_list(xc) < 0)
|
|
goto done;
|
|
}
|
|
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:
|
|
clixon_debug(CLIXON_DBG_PARSE, "retval:%d", 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
|
|
* @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)
|
|
{
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "");
|
|
if (xt==NULL){
|
|
clixon_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
|
|
* @retval 1 OK and valid
|
|
* @retval 0 Invalid (only if yang spec) w xerr set
|
|
* @retval -1 Error
|
|
*
|
|
* @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
|
|
* @note Parsing requires YANG, which means yb must be YB_MODULE/_NEXT
|
|
*
|
|
* @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){
|
|
clixon_err(OE_XML, EINVAL, "xt is NULL");
|
|
return -1;
|
|
}
|
|
if ((textbuf = malloc(textbuflen)) == NULL){
|
|
clixon_err(OE_XML, errno, "malloc");
|
|
goto done;
|
|
}
|
|
memset(textbuf, 0, textbuflen);
|
|
ptr = textbuf;
|
|
while (1){
|
|
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
|
clixon_err(OE_XML, errno, "read");
|
|
break;
|
|
}
|
|
if (ret != 0)
|
|
textbuf[len++] = ch;
|
|
if (ret == 0){
|
|
if (*xt == NULL)
|
|
if ((*xt = xml_new(TEXT_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){
|
|
clixon_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;
|
|
}
|