clixon/lib/src/clixon_xml.c
Olof Hagsand ff3ff0daa9 * There was a problem with ordered-by-user for XML children that appeared in so\
me circumstances and difficult to trigger. Entries entered by the user did not \
appear in the order they were entered. This should now be fixed by adding an enumeration to xml children for sorting ordered by user properly
2019-02-27 18:20:23 +01:00

2367 lines
59 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
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 *****
* XML support functions.
* @see https://www.w3.org/TR/2008/REC-xml-20081126
* https://www.w3.org/TR/2009/REC-xml-names-20091208
*/
#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 <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <assert.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_err.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_options.h" /* xml_spec_populate */
#include "clixon_xml_map.h" /* xml_spec_populate */
#include "clixon_xml_sort.h"
#include "clixon_xml_parse.h"
/*
* Constants
*/
/* Size of xml read buffer */
#define BUFLEN 1024
/* Indentation for xml pretty-print. Consider option? */
#define XML_INDENT 3
/* Name of xml top object created by xml parse functions */
#define XML_TOP_SYMBOL "top"
/*
* Types
*/
/*! xml tree node, with name, type, parent, children, etc
* Note that this is a private type not visible from externally, use
* access functions.
* A word on ordering of x_children:
* If there is no yang specification, xml children are ordered as they are entered.
* If there is a yang specification (and the appropriate functions are called) the
* xml children are ordered as follows:
* 1) After yang specification order.
* 2) list and leaf-list are sorted alphabetically unless ordered-by user.
* Example:
* container c{
* leaf a;
* leaf-list x;
* }
* then regardless in which order the xml is entered, it will be sorted as follows:
* <c>
* <a/>
* <x>a</<x>
* <x>b</<x>
* </c>
* From https://www.w3.org/TR/2009/REC-xml-names-20091208
* Definitions:
* - XML namespace: is identified by a URI reference [RFC3986]; element and
* attribute names may be placed in an XML namespace using the mechanisms
* described in this specification.
* - Expanded name: is a pair consisting of a namespace name and a local name.
* - Namespace name: For a name N in a namespace identified by a URI I, the
* "namespace name" is I.
* For a name N that is not in a namespace, the "namespace name" has no value.
* - Local name: In either case the "local name" is N (also "prefix")
* It is this combination of the universally managed URI namespace with the
* vocabulary's local names that is effective in avoiding name clashes.
*/
struct xml{
char *x_name; /* name of node */
char *x_prefix; /* namespace localname N, called prefix */
struct xml *x_up; /* parent node in hierarchy if any */
struct xml **x_childvec; /* vector of children nodes */
int x_childvec_len;/* length of vector */
enum cxobj_type x_type; /* type of node: element, attribute, body */
char *x_value; /* attribute and body nodes have values */
int x_flags; /* Flags according to XML_FLAG_* */
yang_stmt *x_spec; /* Pointer to specification, eg yang, by
reference, dont free */
cg_var *x_cv; /* Cached value as cligen variable
(eg xml_cmp) */
int _x_vector_i; /* internal use: xml_child_each */
int _x_i; /* internal use for sorting:
see xml_enumerate and xml_cmp */
};
/*
* Variables
*/
/* If set to 1 which is default, strict namespace checking of XML is made.
* If set to 0, "loose" namespace semantics is applied.
* This means: iterate through all yang modules to find matching datanode
* or rpc if no xmlns attribute specifies namespace.
* This is _wrong_, but is the way Clixon originally was written, and some
* code still relies on it.
* This, of course, should change.
* @see CLICON_XML_NS_STRICT clixon configure option
*/
int _CLICON_XML_NS_STRICT = 1;
/* Mapping between xml type <--> string */
static const map_str2int xsmap[] = {
{"error", CX_ERROR},
{"element", CX_ELMNT},
{"attr", CX_ATTR},
{"body", CX_BODY},
{NULL, -1}
};
/*! Translate from xml type in enum form to string keyword
* @param[in] type Xml type
* @retval str String keyword
*/
char *
xml_type2str(enum cxobj_type type)
{
return (char*)clicon_int2str(xsmap, type);
}
/*
* Access functions
*/
/*! Get name of xnode
* @param[in] xn xml node
* @retval name of xml node
*/
char*
xml_name(cxobj *xn)
{
return xn->x_name;
}
/*! Set name of xnode, name is copied
* @param[in] xn xml node
* @param[in] name new name, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
*/
int
xml_name_set(cxobj *xn,
char *name)
{
if (xn->x_name){
free(xn->x_name);
xn->x_name = NULL;
}
if (name){
if ((xn->x_name = strdup(name)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
}
return 0;
}
/*! Get namespace of xnode
* @param[in] xn xml node
* @retval namespace of xml node
*/
char*
xml_prefix(cxobj *xn)
{
return xn->x_prefix;
}
/*! Set name space of xnode, namespace is copied
* @param[in] xn xml node
* @param[in] localname new namespace, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
*/
int
xml_prefix_set(cxobj *xn,
char *localname)
{
if (xn->x_prefix){
free(xn->x_prefix);
xn->x_prefix = NULL;
}
if (localname){
if ((xn->x_prefix = strdup(localname)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
}
return 0;
}
/*! Given an xml tree return URI namespace recursively : default or localname given
*
* Given an XML tree and a prefix (or NULL) return URI namespace.
* @param[in] x XML tree
* @param[in] prefix prefix/ns localname. If NULL then return default.
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
* @retval 0 OK
* @retval -1 Error
* @see xmlns_check XXX can these be merged?
*/
int
xml2ns(cxobj *x,
char *prefix,
char **namespace)
{
int retval = -1;
char *ns;
cxobj *xp;
if (prefix != NULL) /* xmlns:<prefix>="<uri>" */
ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
else /* xmlns="<uri>" */
ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
/* namespace not found, try parent */
if (ns == NULL){
if ((xp = xml_parent(x)) != NULL){
if (xml2ns(xp, prefix, &ns) < 0)
goto done;
}
/* If no parent, return default namespace if defined */
#if defined(DEFAULT_XML_RPC_NAMESPACE)
else
ns = DEFAULT_XML_RPC_NAMESPACE;
#endif
}
if (namespace)
*namespace = ns;
retval = 0;
done:
return retval;
}
/*! Add a namespace attribute to an XML node, either default or specific prefix
* @param[in] x XML tree
* @param[in] prefix prefix/ns localname. If NULL then set default xmlns
* @param[out] namespace URI namespace (or NULL). Will be copied
* @retval 0 OK
* @retval -1 Error
* @see xml2ns
*/
int
xmlns_set(cxobj *x,
char *prefix,
char *namespace)
{
int retval = -1;
cxobj *xa;
if (prefix != NULL){ /* xmlns:<prefix>="<uri>" */
if ((xa = xml_new(prefix, x, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{ /* xmlns="<uri>" */
if ((xa = xml_new("xmlns", x, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
}
if (xml_value_set(xa, namespace) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! See if xmlns:[<localname>=]<uri> exists, if so return <uri>
*
* @param[in] xn XML node
* @param[in] nsn Namespace name
* @retval URI return associated URI if found
* @retval NULL No namespace name binding found for nsn
* @see xml2ns XXX coordinate
*/
static char *
xmlns_check(cxobj *xn,
char *nsn)
{
cxobj *x = NULL;
char *xns;
while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL)
if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 &&
strcmp(xml_name(x), nsn) == 0)
return xml_value(x);
return NULL;
}
/*! Check namespace of xml node by searching recursively among ancestors
* @param[in] xn xml node
* @param[in] namespace check validity of namespace
* @retval 0 Found / validated or no yang spec
* @retval -1 Not found
* @note This function is grossly inefficient
*/
static int
xml_localname_check(cxobj *xn,
void *arg)
{
cxobj *xp = NULL;
char *nsn;
char *n;
yang_stmt *ys = xml_spec(xn);
/* No namespace name - comply */
if ((nsn = xml_prefix(xn)) == NULL)
return 0;
/* Check if NSN defined in same node */
if (xmlns_check(xn, nsn) != NULL)
return 0;
/* Check if NSN defined in some ancestor */
while ((xp = xml_parent(xn)) != NULL) {
if (xmlns_check(xp, nsn) != NULL)
return 0;
xn = xp;
}
#ifdef XMLNS_YANG_ONLY
if (ys == NULL)
return 0; /* If no yang spec */
else
#endif
{
/* Check if my namespace */
if ((n = yang_find_myprefix(ys)) != NULL && strcmp(nsn,n)==0)
return 0;
/* Check if any imported module */
if (yang_find_module_by_prefix(ys, nsn) != NULL)
return 0;
}
/* Not found, error */
clicon_err(OE_XML, ENOENT, "Namespace name %s in %s:%s not found",
nsn, nsn, xml_name(xn));
return -1;
}
/*! Get parent of xnode
* @param[in] xn xml node
* @retval parent xml node
*/
cxobj*
xml_parent(cxobj *xn)
{
return xn->x_up;
}
/*! Set parent of xnode, parent is copied.
* @param[in] xn xml node
* @param[in] parent pointer to new parent xml node
* @retval 0 OK
*/
int
xml_parent_set(cxobj *xn,
cxobj *parent)
{
xn->x_up = parent;
return 0;
}
/*! Get xml node flags, used for internal algorithms
* @param[in] xn xml node
* @retval flag Flags value, see XML_FLAG_*
*/
uint16_t
xml_flag(cxobj *xn,
uint16_t flag)
{
return xn->x_flags&flag;
}
/*! Set xml node flags, used for internal algorithms
* @param[in] xn xml node
* @param[in] flag Flags value to set, see XML_FLAG_*
*/
int
xml_flag_set(cxobj *xn,
uint16_t flag)
{
xn->x_flags |= flag;
return 0;
}
/*! Reset xml node flags, used for internal algorithms
* @param[in] xn xml node
* @param[in] flag Flags value to reset, see XML_FLAG_*
*/
int
xml_flag_reset(cxobj *xn,
uint16_t flag)
{
xn->x_flags &= ~flag;
return 0;
}
/*! Get value of xnode
* @param[in] xn xml node
* @retval value of xml node
*/
char*
xml_value(cxobj *xn)
{
return xn->x_value;
}
/*! Set value of xml node, value is copied
* @param[in] xn xml node
* @param[in] val new value, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
*/
int
xml_value_set(cxobj *xn,
char *val)
{
if (xn->x_value){
free(xn->x_value);
xn->x_value = NULL;
}
if (val){
if ((xn->x_value = strdup(val)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
}
return 0;
}
/*! Append value of xnode, value is copied
* @param[in] xn xml node
* @param[in] val appended value, null-terminated string, copied by function
* @retval NULL on error with clicon-err set, or if value is set to NULL
* @retval new value
*/
char *
xml_value_append(cxobj *xn,
char *val)
{
int len0;
int len;
len0 = xn->x_value?strlen(xn->x_value):0;
if (val){
len = len0 + strlen(val);
if ((xn->x_value = realloc(xn->x_value, len+1)) == NULL){
clicon_err(OE_XML, errno, "realloc");
return NULL;
}
strncpy(xn->x_value + len0, val, len-len0+1);
}
return xn->x_value;
}
/*! Get type of xnode
* @param[in] xn xml node
* @retval type of xml node
*/
enum cxobj_type
xml_type(cxobj *xn)
{
return xn->x_type;
}
/*! Set type of xnode
* @param[in] xn xml node
* @param[in] type new type
* @retval type old type
*/
enum cxobj_type
xml_type_set(cxobj *xn,
enum cxobj_type type)
{
enum cxobj_type old = xn->x_type;
xn->x_type = type;
return old;
}
/*! Get number of children
* @param[in] xn xml node
* @retval number of children in XML tree
* @see xml_child_nr_type
* @see xml_child_nr_notype
*/
int
xml_child_nr(cxobj *xn)
{
return xn->x_childvec_len;
}
/*! Get number of children of EXCEPT specific type
* @param[in] xn xml node
* @param[in] type XML type or -1 for all
* @retval number of typed children in XML tree (except type)
* @see xml_child_nr
* @see xml_child_nr_type
*/
int
xml_child_nr_notype(cxobj *xn,
enum cxobj_type type)
{
cxobj *x = NULL;
int nr = 0;
while ((x = xml_child_each(xn, x, -1)) != NULL) {
if (xml_type(x) != type)
nr++;
}
return nr;
}
/*! Get number of children of specific type
* @param[in] xn xml node
* @param[in] type XML type or -1 for all
* @retval number of typed children in XML tree
* @see xml_child_nr
* @see xml_child_nr_notype
*/
int
xml_child_nr_type(cxobj *xn,
enum cxobj_type type)
{
cxobj *x = NULL;
int len = 0;
while ((x = xml_child_each(xn, x, type)) != NULL)
len++;
return len;
}
/*! Get a specific child
* @param[in] xn xml node
* @param[in] i the number of the child, eg order in children vector
* @retval child in XML tree, or NULL if no such child, or empty child
*/
cxobj *
xml_child_i(cxobj *xn,
int i)
{
if (i < xn->x_childvec_len)
return xn->x_childvec[i];
return NULL;
}
/*! Get a specific child of a specific type
* @param[in] xn xml node
* @param[in] i the number of the child of specific type
* @param[in] type Child type
* @retval child in XML tree, or NULL if no such child, or empty child
* @see xml_child_i
*/
cxobj *
xml_child_i_type(cxobj *xn,
int i,
enum cxobj_type type)
{
cxobj *x = NULL;
int it = 0;
while ((x = xml_child_each(xn, x, type)) != NULL) {
if (x->x_type == type && (i == it++))
return x;
}
return NULL;
}
/*! Set specific child
* @param[in] xn xml node
* @param[in] i the number of the child, eg order in children vector
* @param[in] xc The child to set at position i
* @retval 0 OK
*/
cxobj *
xml_child_i_set(cxobj *xt,
int i,
cxobj *xc)
{
if (i < xt->x_childvec_len)
xt->x_childvec[i] = xc;
return 0;
}
/*! Iterator over xml children objects
*
* @note Never manipulate the child-list during operation or using the
* same object recursively, the function uses an internal field to remember the
* index used. It works as long as the same object is not iterated concurrently.
*
* @param[in] xparent xml tree node whose children should be iterated
* @param[in] xprev previous child, or NULL on init
* @param[in] type matching type or -1 for any
* @code
* cxobj *x = NULL;
* while ((x = xml_child_each(x_top, x, -1)) != NULL) {
* ...
* }
* @endcode
*/
cxobj *
xml_child_each(cxobj *xparent,
cxobj *xprev,
enum cxobj_type type)
{
int i;
cxobj *xn = NULL;
for (i=xprev?xprev->_x_vector_i+1:0; i<xparent->x_childvec_len; i++){
xn = xparent->x_childvec[i];
if (xn == NULL)
continue;
if (type != CX_ERROR && xn->x_type != type)
continue;
break; /* this is next object after previous */
}
if (i < xparent->x_childvec_len) /* found */
xn->_x_vector_i = i;
else
xn = NULL;
return xn;
}
/*! Extend child vector with one and insert xml node there
* Note: does not do anything with child, you may need to set its parent, etc
*/
static int
xml_child_append(cxobj *x,
cxobj *xc)
{
x->x_childvec_len++;
x->x_childvec = realloc(x->x_childvec, x->x_childvec_len*sizeof(cxobj*));
if (x->x_childvec == NULL){
clicon_err(OE_XML, errno, "realloc");
return -1;
}
x->x_childvec[x->x_childvec_len-1] = xc;
return 0;
}
/*! Set a a childvec to a specific size, fill with children after
* @code
* xml_childvec_set(x, 2);
* xml_child_i_set(x, 0, xc0)
* xml_child_i_set(x, 1, xc1);
* @endcode
*/
int
xml_childvec_set(cxobj *x,
int len)
{
x->x_childvec_len = len;
if ((x->x_childvec = calloc(len, sizeof(cxobj*))) == NULL){
clicon_err(OE_XML, errno, "calloc");
return -1;
}
return 0;
}
cxobj **
xml_childvec_get(cxobj *x)
{
return x->x_childvec;
}
/*! Create new xml node given a name and parent. Free with xml_free().
*
* @param[in] name Name of XML node
* @param[in] xp The parent where the new xml node will be appended
* @param[in] spec Yang statement of this XML or NULL.
* @retval xml Created xml object if successful. Free with xml_free()
* @retval NULL Error and clicon_err() called
* @code
* cxobj *x;
* if ((x = xml_new(name, xparent, NULL)) == NULL)
* err;
* ...
* xml_free(x);
* @endcode
* @note yspec may be NULL either because it is not known or it is irrelevant,
* eg for body or attribute
* @see xml_sort_insert
*/
cxobj *
xml_new(char *name,
cxobj *xp,
yang_stmt *yspec)
{
cxobj *x;
if ((x = malloc(sizeof(cxobj))) == NULL){
clicon_err(OE_XML, errno, "malloc");
return NULL;
}
memset(x, 0, sizeof(cxobj));
if ((xml_name_set(x, name)) < 0)
return NULL;
if (xp){
xml_parent_set(x, xp);
if (xml_child_append(xp, x) < 0)
return NULL;
}
x->x_spec = yspec; /* Can be NULL */
return x;
}
/*! Return yang spec of node.
* Not necessarily set. Either has not been set yet (by xml_spec_set( or anyxml.
*/
yang_stmt *
xml_spec(cxobj *x)
{
return x->x_spec;
}
int
xml_spec_set(cxobj *x,
yang_stmt *spec)
{
x->x_spec = spec;
return 0;
}
/*! Return (cached) cligen variable value of xml node
* @param[in] x XML node (body and leaf/leaf-list)
* @retval cv CLIgen variable containing value of x body
* @retval NULL
* @note only applicable if x is body and has yang-spec and is leaf or leaf-list
*/
cg_var *
xml_cv(cxobj *x)
{
return x->x_cv;
}
/*! Return (cached) cligen variable value of xml node
* @param[in] x XML node (body and leaf/leaf-list)
* @param[in] cv CLIgen variable containing value of x body
* @retval 0 OK
* @note only applicable if x is body and has yang-spec and is leaf or leaf-list
*/
int
xml_cv_set(cxobj *x,
cg_var *cv)
{
if (x->x_cv)
cv_free(x->x_cv);
x->x_cv = cv;
return 0;
}
/*! Find an XML node matching name among a parent's children.
*
* Get first XML node directly under x_up in the xml hierarchy with
* name "name".
*
* @param[in] x_up Base XML object
* @param[in] name shell wildcard pattern to match with node name
*
* @retval xmlobj if found.
* @retval NULL if no such node found.
* @see xml_find_type wich is a more generic function
*/
cxobj *
xml_find(cxobj *x_up,
char *name)
{
cxobj *x = NULL;
while ((x = xml_child_each(x_up, x, -1)) != NULL)
if (strcmp(name, xml_name(x)) == 0)
return x;
return NULL;
}
/*! Append xc as child to xp. Remove xc from previous parent.
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] xc Child xml node to insert under xp
* @retval 0 OK
* @retval -1 Error
* @see xml_insert
*/
int
xml_addsub(cxobj *xp,
cxobj *xc)
{
cxobj *oldp;
int i;
if ((oldp = xml_parent(xc)) != NULL){
/* Find child order i in old parent*/
for (i=0; i<xml_child_nr(oldp); i++)
if (xml_child_i(oldp, i) == xc)
break;
/* Remove xc from old parent */
if (i < xml_child_nr(oldp))
xml_child_rm(oldp, i);
}
/* Add xc to new parent */
if (xp){
if (xml_child_append(xp, xc) < 0)
return -1;
/* Set new parent in child */
xml_parent_set(xc, xp);
}
return 0;
}
/*! Insert a new element (xc) under an xml node (xp), move all children to xc.
* Before: xp --> xt
* After: xp --> xc --> xt
* @param[in] xp Parent xml node
* @param[in] tag Name of new xml child
* @retval xc Return the new child (xc)
* @see xml_addsub
* The name of the function is somewhat misleading
*/
cxobj *
xml_insert(cxobj *xp,
char *tag)
{
cxobj *xc; /* new child */
if ((xc = xml_new(tag, NULL, NULL)) == NULL)
goto catch;
while (xp->x_childvec_len)
if (xml_addsub(xc, xml_child_i(xp, 0)) < 0)
goto catch;
if (xml_addsub(xp, xc) < 0)
goto catch;
catch:
return xc;
}
/*! Remove and free an xml node child from xml parent
* @param[in] xc xml child node (to be removed and freed)
* @retval 0 OK
* @retval -1
* @note you cannot remove xchild in the loop (unless yoy keep track of xprev)
*
* @see xml_free Free, dont remove from parent
* @see xml_child_rm Only remove dont free
* Differs from xml_free it is removed from parent.
*/
int
xml_purge(cxobj *xc)
{
int retval = -1;
int i;
cxobj *xp;
if ((xp = xml_parent(xc)) != NULL){
/* Find child order i in parent*/
for (i=0; i<xml_child_nr(xp); i++)
if (xml_child_i(xp, i) == xc)
break;
/* Remove xc from parent */
if (i < xml_child_nr(xp))
if (xml_child_rm(xp, i) < 0)
goto done;
}
xml_free(xc);
retval = 0;
done:
return retval;
}
/*! Remove child xml node from parent xml node. No free and child is root
* @param[in] xp xml parent node
* @param[in] i Number of xml child node (to remove)
* @retval 0 OK
* @retval -1
* @note you should not remove xchild in loop (unless yoy keep track of xprev)
*
* @see xml_rootchild
* @see xml_rm Remove the node itself from parent
*/
int
xml_child_rm(cxobj *xp,
int i)
{
int retval = -1;
cxobj *xc = NULL;
if ((xc = xml_child_i(xp, i)) == NULL){
clicon_err(OE_XML, 0, "Child not found");
goto done;
}
xp->x_childvec[i] = NULL;
xml_parent_set(xc, NULL);
xp->x_childvec_len--;
/* shift up, note same index i used but ok since we break */
for (; i<xp->x_childvec_len; i++)
xp->x_childvec[i] = xp->x_childvec[i+1];
retval = 0;
done:
return retval;
}
/*! Remove this xml node from parent xml node. No freeing and node is new root
* @param[in] xc xml child node to be removed
* @retval 0 OK
* @retval -1
* @note you should not remove xchild in loop (unless yoy keep track of xprev)
*
* @see xml_child_rm Remove a child of a node
*/
int
xml_rm(cxobj *xc)
{
int retval = 0;
cxobj *xp;
cxobj *x;
int i;
if ((xp = xml_parent(xc)) == NULL)
goto done;
retval = -1;
/* Find child in parent */
x = NULL; i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
break;
i++;
}
if (x != NULL)
retval = xml_child_rm(xp, i);
done:
return retval;
}
/*! Return a child sub-tree, while removing parent and all other children
* Given a root xml node, and the i:th child, remove the child from its parent
* and return it, remove the parent and all other children.
* Before: xp-->[..xc..]
* After: xc
* @param[in] xp xml parent node. Will be deleted
* @param[in] i Child nr in parent child vector
* @param[out] xcp xml child node. New root
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt = NULL;
* if (xml_parse_string("<a>2</a>", NULL, &xt) < 0)
* err;
* # Here xt will be: <top><a>2</a></top>
* if (xml_rootchild(xt, 0, &xt) < 0)
* err;
* # Here xt will be: <a>2</a>
* @endcode
* @see xml_child_rm
* @see xml_rootchild_node where xc is explicitly given
*/
int
xml_rootchild(cxobj *xp,
int i,
cxobj **xcp)
{
int retval = -1;
cxobj *xc;
if (xml_parent(xp) != NULL){
clicon_err(OE_XML, 0, "Parent is not root");
goto done;
}
if ((xc = xml_child_i(xp, i)) == NULL){
clicon_err(OE_XML, 0, "Child not found");
goto done;
}
if (xml_child_rm(xp, i) < 0)
goto done;
if (xml_free(xp) < 0)
goto done;
*xcp = xc;
retval = 0;
done:
return retval;
}
/*! Return a child sub-tree, while removing parent and all other children
* Given a root xml node, remove the child from its parent
* , remove the parent and all other children.
* Before: xp-->[..xc..]
* After: xc
* @param[in] xp xml parent node. Must be root. Will be deleted
* @param[in] xc xml child node. Must be a child of xp
* @retval 0 OK
* @retval -1 Error
* @see xml_rootchild where an index is used to find xc
*/
int
xml_rootchild_node(cxobj *xp,
cxobj *xc)
{
int retval = -1;
cxobj *x;
int i;
if (xml_parent(xp) != NULL){
clicon_err(OE_XML, 0, "Parent is not root");
goto done;
}
x = NULL; i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
break;
i++;
}
if (xml_child_rm(xp, i) < 0)
goto done;
if (xml_free(xp) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! help function to sorting: enumerate all children according to present order
* This is so that the child itself know its present order in a list.
* When sorting by "ordered by user", the order should remain in its present
* state.
* A child can always compute its order functionally but it computes
* more cycles,..
* @param[in] xp Enumerate its children
* @retval 0 OK
* @see xml_sort
* @see xml_enumerate_get Call to the child to get the number
*/
int
xml_enumerate_children(cxobj *xp)
{
cxobj *x = NULL;
int i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL)
x->_x_i = i++;
return 0;
}
/*! Get the enumeration of a single child set by enumeration of parent
* @see xml_children_enumerate
* @note that it has to be called right after xml_children_enumerate. If not,
* there are many cases where this info is stale.
* @param[in] x A child whose parent has enumerated its children
* @retval n Enumeration
* @see xml_enumerate_children Call to the parent to compute the nr
*/
int
xml_enumerate_get(cxobj *x)
{
return x->_x_i;
}
/*! Get the first sub-node which is an XML body.
* @param[in] xn xml tree node
* @retval The returned body as a pointer to the name string
* @retval NULL if no such node or no body in found node
* Note, make a copy of the return value to use it properly
* @see xml_find_body
* Explaining picture:
* xt --> xb (x_type=CX_BODY)
* return xb.x_value
*/
char *
xml_body(cxobj *xn)
{
cxobj *xb = NULL;
while ((xb = xml_child_each(xn, xb, CX_BODY)) != NULL)
return xml_value(xb);
return NULL;
}
/*! Get (first) body of xml node, note could be many
* @param[in] xt xml tree node
* Explaining picture:
* xt --> xb (x_type=CX_BODY)
* return xb
*/
cxobj *
xml_body_get(cxobj *xt)
{
cxobj *xb = NULL;
while ((xb = xml_child_each(xt, xb, CX_BODY)) != NULL)
return xb;
return NULL;
}
/*! Find and return the value of an xml child of specific type given prefix and name
*
* The value can be of an attribute only
* @param[in] xt xml tree node
* @param[in] prefix Prefix (namespace local name) or NULL
* @param[in] name name of xml tree node (eg attr name or "body")
* @retval val Pointer to the name string
* @retval NULL No such node or no value in node
* @code
* char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR);
* @endcode
* @note, make a copy of the return value to use it properly
* @see xml_find_type return the xml object
* @see xml_find_value where a body can be found as well
*/
char *
xml_find_type_value(cxobj *xt,
char *prefix,
char *name,
enum cxobj_type type)
{
cxobj *x;
if ((x = xml_find_type(xt, prefix, name, type)) != NULL)
return xml_value(x);
return NULL;
}
/*! Find and return the xml child of specific type given prefix and name
*
* The value can be of an attribute only
* @param[in] xt xml tree node
* @param[in] prefix Prefix (namespace local name) or NULL
* @param[in] name name of xml tree node (eg attr name or "body")
* @retval val Pointer to the name string
* @retval NULL No such node or no value in node
* @code
* cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR);
* @endcode
* @see xml_find which finds any child given name
* @see xml_find_value where a body can be found as well
*/
cxobj *
xml_find_type(cxobj *xt,
char *prefix,
char *name,
enum cxobj_type type)
{
cxobj *x = NULL;
int pmatch; /* prefix match */
char *xprefix; /* xprefix */
while ((x = xml_child_each(xt, x, type)) != NULL) {
xprefix = xml_prefix(x);
if (prefix)
pmatch = xprefix?strcmp(prefix,xprefix)==0:0;
else
pmatch = 1;
if (pmatch && strcmp(name, xml_name(x)) == 0)
return x;
}
return NULL;
}
/*! Find and return the value of a sub xml node
*
* The value can be of an attribute or body.
* @param[in] xt xml tree node
* @param[in] name name of xml tree nod (eg attr name or "body")
* @retval val Pointer to the name string
* @retval NULL No such node or no value in node
*
* Note, make a copy of the return value to use it properly
* @see xml_find_body
* Explaining picture:
* xt --> x
* x_name=name
* return x_value
*/
char *
xml_find_value(cxobj *xt,
char *name)
{
cxobj *x = NULL;
while ((x = xml_child_each(xt, x, -1)) != NULL)
if (strcmp(name, xml_name(x)) == 0)
return xml_value(x);
return NULL;
}
/*! Find and return a body (string) of a sub xml node
* @param[in] xn xml tree node
* @param[in] name name of xml tree node
* @retval The returned body as a pointer to the name string
* @retval NULL if no such node or no body in found node
* @note, make a copy of the return value to use it properly
* @see xml_find_value
* Explaining picture:
* xt --> x --> bx (x_type=CX_BODY)
* x_name=name return x_value
*/
char *
xml_find_body(cxobj *xt,
char *name)
{
cxobj *x=NULL;
while ((x = xml_child_each(xt, x, -1)) != NULL)
if (strcmp(name, xml_name(x)) == 0)
return xml_body(x);
return NULL;
}
/*! Find xml object with matching name and value.
*
* This can be useful if x is a leaf-list with many subs with same name,
* but you need to pick the object with a specific value
* @param[in] xt XML tree
* @param[in] name Name of child (there can be many with same name)
* @param[in] val Value. Must be equal to body of child.
* @retval x Child with matching name and body
*
* Explaining picture:
* xt --> x --> bx (x_type=CX_BODY)
* x_name=name x_value=val
* return x
*/
cxobj *
xml_find_body_obj(cxobj *xt,
char *name,
char *val)
{
cxobj *x = NULL;
char *bstr;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (strcmp(name, xml_name(x)))
continue;
if ((bstr = xml_body(x)) == NULL)
continue;
if (strcmp(bstr, val) == 0)
break; /* x is returned */
}
return x;
}
/*! Free an xl sub-tree recursively, but do not remove it from parent
* @param[in] x the xml tree to be freed.
* @see xml_purge where x is also removed from parent
*/
int
xml_free(cxobj *x)
{
int i;
cxobj *xc;
if (x->x_name)
free(x->x_name);
if (x->x_value)
free(x->x_value);
if (x->x_prefix)
free(x->x_prefix);
for (i=0; i<x->x_childvec_len; i++){
if ((xc = x->x_childvec[i]) != NULL){
xml_free(xc);
x->x_childvec[i] = NULL;
}
}
if (x->x_childvec)
free(x->x_childvec);
if (x->x_cv)
cv_free(x->x_cv);
free(x);
return 0;
}
/*------------------------------------------------------------------------
* XML printing functions. Output a parse tree to file, string cligen buf
*------------------------------------------------------------------------*/
/*! Print an XML tree structure to an output stream and encode chars "<>&"
*
* Uses clicon_xml2cbuf internally
*
* @param[in] f UNIX output stream
* @param[in] xn clicon xml tree
* @param[in] level how many spaces to insert before each line
* @param[in] prettyprint insert \n and spaces tomake the xml more readable.
* @see clicon_xml2cbuf
* One can use clicon_xml2cbuf to get common code, but using fprintf is
* much faster than using cbuf and then printing that,...
*/
int
clicon_xml2file(FILE *f,
cxobj *x,
int level,
int prettyprint)
{
int retval = -1;
char *name;
char *namespace;
cxobj *xc;
int hasbody;
int haselement;
char *val;
char *encstr = NULL; /* xml encoded string */
if (x == NULL)
goto ok;
name = xml_name(x);
namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
fprintf(f, "%s", encstr);
break;
case CX_ATTR:
fprintf(f, " ");
if (namespace)
fprintf(f, "%s:", namespace);
fprintf(f, "%s=\"%s\"", name, xml_value(x));
break;
case CX_ELMNT:
fprintf(f, "%*s<", prettyprint?(level*XML_INDENT):0, "");
if (namespace)
fprintf(f, "%s:", namespace);
fprintf(f, "%s", name);
hasbody = 0;
haselement = 0;
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
switch (xc->x_type){
case CX_ATTR:
if (clicon_xml2file(f, xc, level+1, prettyprint) <0)
goto done;
break;
case CX_BODY:
hasbody=1;
break;
case CX_ELMNT:
haselement=1;
break;
default:
break;
}
}
/* Check for special case <a/> instead of <a></a>:
* Ie, no CX_BODY or CX_ELMNT child.
*/
if (hasbody==0 && haselement==0)
fprintf(f, "/>");
else{
fprintf(f, ">");
if (prettyprint && hasbody == 0)
fprintf(f, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
if (xml_type(xc) != CX_ATTR)
if (clicon_xml2file(f, xc, level+1, prettyprint) <0)
goto done;
}
if (prettyprint && hasbody==0)
fprintf(f, "%*s", level*XML_INDENT, "");
fprintf(f, "</");
if (namespace)
fprintf(f, "%s:", namespace);
fprintf(f, "%s>", name);
}
if (prettyprint)
fprintf(f, "\n");
break;
default:
break;
}/* switch */
ok:
retval = 0;
done:
if (encstr)
free(encstr);
return retval;
}
/*! Print an XML tree structure to an output stream
*
* Uses clicon_xml2file internally
*
* @param[in] f UNIX output stream
* @param[in] xn clicon xml tree
* @see clicon_xml2cbuf
* @see clicon_xml2file
*/
int
xml_print(FILE *f,
cxobj *xn)
{
return clicon_xml2file(f, xn, 0, 1);
}
/*! Print an XML tree structure to a cligen buffer and encode chars "<>&"
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Clicon xml tree
* @param[in] level Indentation level
* @param[in] prettyprint insert \n and spaces tomake the xml more readable.
*
* @code
* cbuf *cb;
* cb = cbuf_new();
* if (clicon_xml2cbuf(cb, xn, 0, 1) < 0)
* goto err;
* fprintf(stderr, "%s", cbuf_get(cb));
* cbuf_free(cb);
* @endcode
* @see clicon_xml2file
*/
int
clicon_xml2cbuf(cbuf *cb,
cxobj *x,
int level,
int prettyprint)
{
int retval = -1;
cxobj *xc;
char *name;
int hasbody;
int haselement;
char *namespace;
char *encstr = NULL; /* xml encoded string */
char *val;
name = xml_name(x);
namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
cprintf(cb, "%s", encstr);
break;
case CX_ATTR:
cprintf(cb, " ");
if (namespace)
cprintf(cb, "%s:", namespace);
cprintf(cb, "%s=\"%s\"", name, xml_value(x));
break;
case CX_ELMNT:
cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, "");
if (namespace)
cprintf(cb, "%s:", namespace);
cprintf(cb, "%s", name);
hasbody = 0;
haselement = 0;
xc = NULL;
/* print attributes only */
while ((xc = xml_child_each(x, xc, -1)) != NULL)
switch (xc->x_type){
case CX_ATTR:
if (clicon_xml2cbuf(cb, xc, level+1, prettyprint) < 0)
goto done;
break;
case CX_BODY:
hasbody=1;
break;
case CX_ELMNT:
haselement=1;
break;
default:
break;
}
/* Check for special case <a/> instead of <a></a> */
if (hasbody==0 && haselement==0)
cprintf(cb, "/>");
else{
cprintf(cb, ">");
if (prettyprint && hasbody == 0)
cprintf(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL)
if (xml_type(xc) != CX_ATTR)
if (clicon_xml2cbuf(cb, xc, level+1, prettyprint) < 0)
goto done;
if (prettyprint && hasbody == 0)
cprintf(cb, "%*s", level*XML_INDENT, "");
cprintf(cb, "</");
if (namespace)
cprintf(cb, "%s:", namespace);
cprintf(cb, "%s>", name);
}
if (prettyprint)
cprintf(cb, "\n");
break;
default:
break;
}/* switch */
retval = 0;
done:
if (encstr)
free(encstr);
return retval;
}
/*! Print actual xml tree datastructures (not xml), mainly for debugging
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Clicon xml tree
* @param[in] level Indentation level
*/
int
xmltree2cbuf(cbuf *cb,
cxobj *x,
int level)
{
cxobj *xc;
int i;
for (i=0; i<level*XML_INDENT; i++)
cprintf(cb, " ");
if (xml_type(x) != CX_BODY)
cprintf(cb, "%s", xml_type2str(xml_type(x)));
if (xml_prefix(x)==NULL)
cprintf(cb, " %s", xml_name(x));
else
cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x));
if (xml_value(x))
cprintf(cb, " value:\"%s\"", xml_value(x));
if (x->x_flags)
cprintf(cb, " flags:0x%x", x->x_flags);
if (xml_child_nr(x))
cprintf(cb, " {");
cprintf(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL)
xmltree2cbuf(cb, xc, level+1);
if (xml_child_nr(x)){
for (i=0; i<level*XML_INDENT; i++)
cprintf(cb, " ");
cprintf(cb, "}\n");
}
return 0;
}
/*--------------------------------------------------------------------
* XML parsing functions. Create XML parse tree from string and file.
*--------------------------------------------------------------------*/
/*! Common internal xml parsing function string to parse-tree
*
* Given a string containing XML, parse into existing XML tree and return
* @param[in] str Pointer to string containing XML definition.
* @param[in] yspec Yang specification or NULL
* @param[in,out] xtop Top of XML parse tree. Assume created. Holds new tree.
* @see xml_parse_file
* @see xml_parse_string
* @see xml_parse_va
* @note special case is empty XML where the parser is not invoked.
*/
static int
_xml_parse(const char *str,
yang_spec *yspec,
cxobj *xt)
{
int retval = -1;
struct xml_parse_yacc_arg ya = {0,};
cxobj *x;
if (strlen(str) == 0)
return 0; /* OK */
if (xt == NULL){
clicon_err(OE_XML, errno, "Unexpected NULL XML");
return -1;
}
if ((ya.ya_parse_string = strdup(str)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
ya.ya_xparent = xt;
ya.ya_skipspace = 1; /* remove all non-terminal bodies (strip pretty-print) */
ya.ya_yspec = yspec;
if (clixon_xml_parsel_init(&ya) < 0)
goto done;
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
goto done;
x = NULL;
while ((x = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
xml_purge(x);
/* Verify namespaces after parsing */
if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0)
goto done;
/* Sort the complete tree after parsing */
if (yspec){
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
goto done;
}
retval = 0;
done:
clixon_xml_parsel_exit(&ya);
if (ya.ya_parse_string != NULL)
free(ya.ya_parse_string);
return retval;
}
/*! FSM to detect substring
*/
static inline int
FSM(char *tag,
char ch,
int state)
{
if (tag[state] == ch)
return state+1;
else
return 0;
}
/*! Read an XML definition from file and parse it into a parse-tree.
*
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
* @param[in] endtag Read until encounter "endtag" in the stream, or NULL
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
* @retval 0 OK
* @retval -1 Error with clicon_err called
*
* @code
* cxobj *xt = NULL;
* xml_parse_file(0, "</config>", yspec, &xt);
* xml_free(xt);
* @endcode
* @see xml_parse_string
* @see xml_parse_va
* @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
*/
int
xml_parse_file(int fd,
char *endtag,
yang_spec *yspec,
cxobj **xt)
{
int retval = -1;
int ret;
int len = 0;
char ch;
char *xmlbuf = NULL;
char *ptr;
int xmlbuflen = BUFLEN; /* start size */
int endtaglen = 0;
int state = 0;
int oldxmlbuflen;
if (endtag != NULL)
endtaglen = strlen(endtag);
if ((xmlbuf = malloc(xmlbuflen)) == NULL){
clicon_err(OE_XML, errno, "malloc");
goto done;
}
memset(xmlbuf, 0, xmlbuflen);
ptr = xmlbuf;
while (1){
if ((ret = read(fd, &ch, 1)) < 0){
clicon_err(OE_XML, errno, "read: [pid:%d]",
(int)getpid());
break;
}
if (ret != 0){
if (endtag)
state = FSM(endtag, ch, state);
xmlbuf[len++] = ch;
}
if (ret == 0 ||
(endtag && (state == endtaglen))){
state = 0;
if (*xt == NULL)
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
goto done;
if (_xml_parse(ptr, yspec, *xt) < 0)
goto done;
break;
}
if (len>=xmlbuflen-1){ /* Space: one for the null character */
oldxmlbuflen = xmlbuflen;
xmlbuflen *= 2;
if ((xmlbuf = realloc(xmlbuf, xmlbuflen)) == NULL){
clicon_err(OE_XML, errno, "realloc");
goto done;
}
memset(xmlbuf+oldxmlbuflen, 0, xmlbuflen-oldxmlbuflen);
ptr = xmlbuf;
}
} /* while */
retval = 0;
done:
if (retval < 0 && *xt){
free(*xt);
*xt = NULL;
}
if (xmlbuf)
free(xmlbuf);
return retval;
}
/*! Read an XML definition from string and parse it into a parse-tree.
*
* @param[in] str String containing XML definition.
* @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty will be created.
* @retval 0 OK
* @retval -1 Error with clicon_err called. Includes parse error
*
* @code
* cxobj *xt = NULL;
* if (xml_parse_string(str, yspec, &xt) < 0)
* err;
* if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
* err;
* @endcode
* @see xml_parse_file
* @see xml_parse_va
* @note You need to free the xml parse tree after use, using xml_free()
* @note If empty on entry, a new TOP xml will be created named "top"
*/
int
xml_parse_string(const char *str,
yang_spec *yspec,
cxobj **xtop)
{
if (*xtop == NULL)
if ((*xtop = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
return -1;
return _xml_parse(str, yspec, *xtop);
}
/*! Read XML from var-arg list and parse it into xml tree
*
* Utility function using stdarg instead of static string.
* @param[in,out] xtop Top of XML parse tree. If it is NULL, top element
called 'top' will be created. Call xml_free() after use
* @param[in] yspec Yang specification, or NULL
* @param[in] format Format string for stdarg according to printf(3)
* @retval 0 OK
* @retval -1 Error with clicon_err called
*
* @code
* cxobj *xt = NULL;
* if (xml_parse_va(&xt, NULL, "<xml>%d</xml>", 22) < 0)
* err;
* xml_free(xt);
* @endcode
* @see xml_parse_string
* @see xml_parse_file
* @note If vararg list is empty, consider using xml_parse_string()
*/
int
xml_parse_va(cxobj **xtop,
yang_spec *yspec,
const char *format, ...)
{
int retval = -1;
va_list args;
char *str = NULL;
int len;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((str = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, len);
va_start(args, format);
len = vsnprintf(str, len, format, args) + 1;
va_end(args);
if (xml_parse_string(str, yspec, xtop) < 0)
goto done;
retval = 0;
done:
if (str)
free(str);
return retval;
}
/*! Copy single xml node from x0 to x1 without copying children
* @param[in] x0 Source XML tree
* @param[in] x1 Destination XML tree (must exist)
* @retval 0 OK
* @retval -1 Error
*/
int
xml_copy_one(cxobj *x0,
cxobj *x1)
{
char *s;
xml_type_set(x1, xml_type(x0));
if ((s = xml_value(x0))){ /* malloced string */
if ((x1->x_value = strdup(s)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
}
if ((s = xml_name(x0))) /* malloced string */
if ((xml_name_set(x1, s)) < 0)
return -1;
if ((s = xml_prefix(x0))) /* malloced string */
if ((xml_prefix_set(x1, s)) < 0)
return -1;
return 0;
}
/*! Copy xml tree x0 to other existing tree x1
*
* x1 should be a created placeholder. If x1 is non-empty,
* the copied tree is appended to the existing tree.
* @param[in] x0 Source XML tree
* @param[in] x1 Destination XML tree (must exist)
* @retval 0 OK
* @retval -1 Error
* @code
* x1 = xml_new("new", xparent, NULL);
* if (xml_copy(x0, x1) < 0)
* err;
* @endcode
*/
int
xml_copy(cxobj *x0,
cxobj *x1)
{
int retval = -1;
cxobj *x;
cxobj *xcopy;
if (xml_copy_one(x0, x1) <0)
goto done;
x = NULL;
while ((x = xml_child_each(x0, x, -1)) != NULL) {
if ((xcopy = xml_new(xml_name(x), x1, xml_spec(x))) == NULL)
goto done;
if (xml_copy(x, xcopy) < 0) /* recursion */
goto done;
}
retval = 0;
done:
return retval;
}
/*! Create and return a copy of xml tree.
*
* @code
* cxobj *x1;
* x1 = xml_dup(x0);
* @endcode
* Note, returned tree should be freed as: xml_free(x1)
*/
cxobj *
xml_dup(cxobj *x0)
{
cxobj *x1;
if ((x1 = xml_new("new", NULL, xml_spec(x0))) == NULL)
return NULL;
if (xml_copy(x0, x1) < 0)
return NULL;
return x1;
}
/*! Copy XML vector from vec0 to vec1
* @param[in] vec0 Source XML tree vector
* @param[in] len0 Length of source XML tree vector
* @param[out] vec1 Destination XML tree vector
* @param[out] len1 Length of destination XML tree vector
*/
int
cxvec_dup(cxobj **vec0,
size_t len0,
cxobj ***vec1,
size_t *len1)
{
int retval = -1;
*len1 = len0;
if ((*vec1 = calloc(len0, sizeof(cxobj*))) == NULL)
goto done;
memcpy(*vec1, vec0, len0*sizeof(cxobj*));
retval = 0;
done:
return retval;
}
/*! Append a new xml tree to an existing xml vector
* @param[in] x XML tree (append this to vector)
* @param[in,out] vec XML tree vector
* @param[in,out] len Length of XML tree vector
*/
int
cxvec_append(cxobj *x,
cxobj ***vec,
size_t *len)
{
int retval = -1;
if ((*vec = realloc(*vec, sizeof(cxobj *) * (*len+1))) == NULL){
clicon_err(OE_XML, errno, "realloc");
goto done;
}
(*vec)[(*len)++] = x;
retval = 0;
done:
return retval;
}
/*! Apply a function call recursively on all xml node children recursively
* Recursively traverse all xml nodes in a parse-tree and apply fn(arg) for
* each object found. The function is called with the xml node and an
* argument as args.
* The tree is traversed depth-first, which at least guarantees that a parent is
* traversed before a child.
* @param[in] xn XML node
* @param[in] type Matching type or -1 for any
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed (subparts may have been skipped)
* @retval 1 OK, aborted on first fn returned 1
*
* @code
* int x_fn(cxobj *x, void *arg)
* {
* return 0;
* }
* xml_apply(xn, CX_ELMNT, x_fn, NULL);
* @endcode
* @note do not delete or move around any children during this function
* @note return value > 0 aborts the traversal
* @see xml_apply0 including top object
* @see xml_apply_ancestor for marking all parents recursively
*/
int
xml_apply(cxobj *xn,
enum cxobj_type type,
xml_applyfn_t fn,
void *arg)
{
int retval = -1;
cxobj *x;
int ret;
x = NULL;
while ((x = xml_child_each(xn, x, type)) != NULL) {
if ((ret = fn(x, arg)) < 0)
goto done;
if (ret == 2)
continue; /* Abort this node, dont recurse */
else if (ret == 1){
retval = 1;
goto done;
}
if ((ret = xml_apply(x, type, fn, arg)) < 0)
goto done;
if (ret == 1){
retval = 1;
goto done;
}
}
retval = 0;
done:
return retval;
}
/*! Apply a function call on top object and all xml node children recursively
* @param[in] xn XML node
* @param[in] type Matching type or -1 for any
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed (subparts may have been skipped)
* @retval 1 OK, aborted on first fn returned 1
* @see xml_apply not including top object
*/
int
xml_apply0(cxobj *xn,
enum cxobj_type type,
xml_applyfn_t fn,
void *arg)
{
int retval = -1;
int ret;
if ((ret = fn(xn, arg)) < 0) /* -1, 0, 1, 2 */
goto done;
if (ret == 1)
retval = 1;
else if (ret > 1)
retval = 0;
else /* 0 */
retval = xml_apply(xn, type, fn, arg);
done:
return retval;
}
/*! Apply a function call recursively on all ancestors
* Recursively traverse upwards to all ancestor nodes in a parse-tree and apply fn(arg) for
* each object found. The function is called with the xml node and an
* argument as args.
* @param[in] xn XML node
* @param[in] fn Callback
* @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter
* @retval 0 OK, all nodes traversed
* @retval n OK, aborted at first encounter of first match
* @code
* int x_fn(cxobj *x, void *arg)
* {
* return 0;
* }
* xml_apply_ancestor(xn, x_fn, NULL);
* @endcode
* @see xml_apply
* @note do not delete or move around any children during this function
* @note It does not apply fn to the root node,..
*/
int
xml_apply_ancestor(cxobj *xn,
xml_applyfn_t fn,
void *arg)
{
int retval = -1;
cxobj *xp = NULL;
int ret;
while ((xp = xml_parent(xn)) != NULL) {
if (fn(xp, arg) < 0)
goto done;
if ((ret = xml_apply_ancestor(xp, fn, arg)) < 0)
goto done;
if (ret > 0){
retval = ret;
goto done;
}
xn = xp;
}
retval = 0;
done:
return retval;
}
/*! Is xpp ancestor of x?
* @param[in] x XML node
* @param[in] xpp Potential ancestor of x in XML tree
* @retval 0 No, xpp is not ancestor of x
* @retval 1 Yes, xpp is ancestor of x
*/
int
xml_isancestor(cxobj *x,
cxobj *xpp)
{
cxobj *xp = NULL;
cxobj *xn = NULL;
xn = x;
while ((xp = xml_parent(xn)) != NULL) {
if (xp == xpp)
return 1;
xn = xp;
}
return 0;
}
/*! Generic parse function for xml values
* @param[in] xb xml tree body node, ie containing a value to be parsed
* @param[in] type Type of value to be parsed in value
* @param[out] cvp CLIgen variable containing the parsed value
* @note free cv with cv_free after use.
* @see xml_body_int32 etc, for type-specific parse functions
* @note range check failure returns 0
*/
int
xml_body_parse(cxobj *xb,
enum cv_type type,
cg_var **cvp)
{
int retval = -1;
cg_var *cv = NULL;
int cvret;
char *bstr;
char *reason = NULL;
if ((bstr = xml_body(xb)) == NULL){
clicon_err(OE_XML, 0, "No body found");
goto done;
}
if ((cv = cv_new(type)) == NULL){
clicon_err(OE_XML, errno, "cv_new");
goto done;
}
if ((cvret = cv_parse1(bstr, cv, &reason)) < 0){
clicon_err(OE_XML, errno, "cv_parse");
goto done;
}
if (cvret == 0){ /* parsing failed */
clicon_err(OE_XML, errno, "Parsing CV: %s", reason);
if (reason)
free(reason);
}
*cvp = cv;
retval = 0;
done:
if (retval < 0 && cv != NULL)
cv_free(cv);
return retval;
}
/*! Parse an xml body as int32
* The real parsing functions are in the cligen code
* @param[in] xb xml tree body node, ie containing a value to be parsed
* @param[out] val Value after parsing
* @retval 0 OK, parsed value in 'val'
* @retval -1 Error, one of: body not found, parse error,
* alloc error.
* @note extend to all other cligen var types and generalize
* @note use yang type info?
* @note range check failure returns 0
*/
int
xml_body_int32(cxobj *xb,
int32_t *val)
{
cg_var *cv = NULL;
if (xml_body_parse(xb, CGV_INT32, &cv) < 0)
return -1;
*val = cv_int32_get(cv);
cv_free(cv);
return 0;
}
/*! Parse an xml body as uint32
* The real parsing functions are in the cligen code
* @param[in] xb xml tree body node, ie containing a value to be parsed
* @param[out] val Value after parsing
* @retval 0 OK, parsed value in 'val'
* @retval -1 Error, one of: body not found, parse error,
* alloc error.
* @note extend to all other cligen var types and generalize
* @note use yang type info?
* @note range check failure returns 0
*/
int
xml_body_uint32(cxobj *xb,
uint32_t *val)
{
cg_var *cv = NULL;
if (xml_body_parse(xb, CGV_UINT32, &cv) < 0)
return -1;
*val = cv_uint32_get(cv);
cv_free(cv);
return 0;
}
/*! Map xml operation from string to enumeration
* @param[in] opstr String, eg "merge"
* @param[out] op Enumeration, eg OP_MERGE
* @code
* enum operation_type op;
* xml_operation("replace", &op)
* @endcode
*/
int
xml_operation(char *opstr,
enum operation_type *op)
{
if (strcmp("merge", opstr) == 0)
*op = OP_MERGE;
else if (strcmp("replace", opstr) == 0)
*op = OP_REPLACE;
else if (strcmp("create", opstr) == 0)
*op = OP_CREATE;
else if (strcmp("delete", opstr) == 0)
*op = OP_DELETE;
else if (strcmp("remove", opstr) == 0)
*op = OP_REMOVE;
else if (strcmp("none", opstr) == 0)
*op = OP_NONE;
else{
clicon_err(OE_XML, 0, "Bad-attribute operation: %s", opstr);
return -1;
}
return 0;
}
/*! Map xml operation from enumeration to string
* @param[in] op enumeration operation, eg OP_MERGE,...
* @retval str String, eg "merge". Static string, no free necessary
* @code
* enum operation_type op;
* xml_operation("replace", &op)
* @endcode
*/
char *
xml_operation2str(enum operation_type op)
{
switch (op){
case OP_MERGE:
return "merge";
break;
case OP_REPLACE:
return "replace";
break;
case OP_CREATE:
return "create";
break;
case OP_DELETE:
return "delete";
break;
case OP_REMOVE:
return "remove";
break;
default:
return "none";
}
}
/*! Specialization of clicon_debug with xml tree
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG.
* @param[in] x XML tree that is logged without prettyprint
* @param[in] format Message to print as argv.
*/
int
clicon_log_xml(int level,
cxobj *x,
char *format, ...)
{
va_list args;
int len;
char *msg = NULL;
cbuf *cb = NULL;
int retval = -1;
/* Print xml as cbuf */
if ((cb = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
goto done;
/* first round: compute length of debug message */
va_start(args, format);
len = vsnprintf(NULL, 0, format, args);
va_end(args);
/* allocate a message string exactly fitting the message length */
if ((msg = malloc(len+1)) == NULL){
fprintf(stderr, "malloc: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
goto done;
}
/* second round: compute write message from format and args */
va_start(args, format);
if (vsnprintf(msg, len+1, format, args) < 0){
va_end(args);
fprintf(stderr, "vsnprintf: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
goto done;
}
va_end(args);
/* Actually log it */
clicon_log(level, "%s: %s", msg, cbuf_get(cb));
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (msg)
free(msg);
return retval;
}
/*
* Turn this on to get a xml parse and pretty print test program
* Usage: xpath
* read xml from input
* Example compile:
gcc -g -o xml -I. -I../clixon ./clixon_xml.c -lclixon -lcligen
* Example run:
echo "<a><b/></a>" | xml
*/
#if 1 /* Test program */
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0);
exit(0);
}
int
main(int argc, char **argv)
{
cxobj *xt = NULL;
cxobj *xc;
cbuf *cb = cbuf_new();
if (argc != 1){
usage(argv[0]);
return 0;
}
if (xml_parse_file(0, "</config>", NULL, &xt) < 0){
fprintf(stderr, "xml parse error %s\n", clicon_err_reason);
return -1;
}
xc = NULL;
while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
xmltree2cbuf(cb, xc, 0); /* dump data structures */
//clicon_xml2cbuf(cb, xc, 0, 1); /* print xml */
}
fprintf(stdout, "%s", cbuf_get(cb));
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return 0;
}
#endif /* Test program */