2351 lines
59 KiB
C
2351 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
|
|
*/
|
|
|
|
/* 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;
|
|
}
|
|
/* 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 xml The child xml node
|
|
* @retval 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;
|
|
|
|
if (xparent == NULL)
|
|
return 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, should be called "wrap"
|
|
*/
|
|
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;
|
|
}
|
|
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 */
|
|
|