clixon/lib/src/clixon_xml.c
2025-02-05 11:32:06 +01:00

2794 lines
72 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Clixon XML object (cxobj) support functions.
* @see https://www.w3.org/TR/2008/REC-xml-20081126
* https://www.w3.org/TR/2009/REC-xml-names-20091208
* Canonical XML version (just for info)
* https://www.w3.org/TR/xml-c14n
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_map.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_debug.h"
#include "clixon_options.h" /* xml_bind_yang */
#include "clixon_yang_module.h"
#include "clixon_xml_map.h" /* xml_bind_yang */
#include "clixon_xml_vec.h"
#include "clixon_xml_sort.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_io.h"
#include "clixon_xml_parse.h"
#include "clixon_xml_nsctx.h"
/*
* Constants
*/
/* How many XML children to start with if any. Then add quadratic until threshold when
* add linearly
* Heurestics: if child is body only single child is expected, but element children may
* have siblings
*/
#define XML_CHILDVEC_SIZE_START 1
#define XML_CHILDVEC_SIZE_START_ELMNT 16
#define XML_CHILDVEC_SIZE_THRESHOLD 65536
/* Intention of these macros is to guard against access of type-specific fields
* As debug they can contain an assert.
*/
#define is_element(x) (xml_type(x)==CX_ELMNT)
#define is_bodyattr(x) (xml_type(x)==CX_BODY || xml_type(x)==CX_ATTR)
/*
* Types
*/
#ifdef XML_EXPLICIT_INDEX
static int xml_search_index_free(cxobj *x);
/* A search index pair consisting of a name of an (index) variable and a vector of xml children
* the variable should be a potential child of the XML node
* The vector should have the same elements as the regular XML childvec, but in different order
*
* +-----+-----+-----+
* search index vector i: | b | c | a |
* +-----+-----+-----+
*
* index: "i"
* +-----+-----+-----+
* x_childvec: | a | b | c |
* +-----+-----+-----+
* | | |
* v v v
* +---+ +---+ +---+
* value of "i" | 5 | | 0 | | 2 |
* +---+ +---+ +---+
*/
struct search_index{
qelem_t si_q; /* Queue header */
char *si_name; /* Name of index variable (must be (potential) child of xml node at hand */
clixon_xvec *si_xvec; /* Sorted vector of xml object pointers (should be of YANG type LIST) */
};
#endif
/*! 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.
* @see struct xmlbody For XML body and attributes
*/
struct xml{
enum cxobj_type x_type; /* type of node: element, attribute, body */
char *x_name; /* name of node */
char *x_prefix; /* namespace localname N, called prefix */
uint16_t x_flags; /* Flags according to XML_FLAG_* */
struct xml *x_up; /* parent node in hierarchy if any */
#ifdef XML_PARENT_CANDIDATE
struct xml *x_up_candidate; /* Candidate parent node for special cases (when+xpath) */
#endif
int _x_vector_i; /* internal use: xml_child_each */
int _x_i; /* internal use for stable sorting:
see xml_enumerate_children and xml_cmp */
/*----- next is body/attribute only */
cbuf *x_value_cb; /* attribute and body nodes have values (XXX: this consumes
memory) cv? */
/*----- up to here is common to all next is element only */
struct xml **x_childvec; /* vector of children nodes (XXX: use clixon_vec ) */
int x_childvec_len;/* Number of children */
int x_childvec_max;/* Length of allocated vector */
cvec *x_ns_cache; /* Cached vector of namespaces (set by bind-yang) */
yang_stmt *x_spec; /* Pointer to specification, eg yang,
by reference, dont free */
cg_var *x_cv; /* Cached value as cligen variable (set by xml_cmp) */
#ifdef XML_EXPLICIT_INDEX
struct search_index *x_search_index; /* explicit search index vectors */
#endif
};
/* Variant of struct xml for use by non-elements to save space
* @see struct xml For XML elements
*/
struct xmlbody{
enum cxobj_type xb_type; /* type of node: element, attribute, body */
char *xb_name; /* name of node */
char *xb_prefix; /* namespace localname N, called prefix */
uint16_t xb_flags; /* Flags according to XML_FLAG_* */
struct xml *xb_up; /* parent node in hierarchy if any */
#ifdef XML_PARENT_CANDIDATE
struct xml *xb_up_candidate; /* Candidate parent node for special cases (when+xpath) */
#endif
int _xb_vector_i; /* internal use: xml_child_each */
int _xb_i; /* internal use for sorting:
see xml_enumerate and xml_cmp */
cbuf *xb_value_cb; /* attribute and body nodes have values */
};
/*
* 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);
}
/* Stats (too low-level to hang it on handle) */
static uint64_t _stats_xml_nr = 0;
/*! Get global statistics about XML objects
*
* @param[out] nr Number of existing XML objects (created - freed)
*/
int
xml_stats_global(uint64_t *nr)
{
if (nr)
*nr = _stats_xml_nr;
return 0;
}
/*! Return the alloced memory of a single XML obj
*
* @param[in] x XML object
* @param[out] szp Size of this XML obj
* @retval 0 OK
* (baseline: 96 bytes per object on x86-64)
*/
static int
xml_stats_one(cxobj *x,
size_t *szp)
{
size_t sz = 0;
if (x->x_name)
sz += strlen(x->x_name) + 1;
if (x->x_prefix)
sz += strlen(x->x_prefix) + 1;
switch (xml_type(x)){
case CX_ELMNT:
sz += sizeof(struct xml);
sz += x->x_childvec_max*sizeof(struct xml*);
if (x->x_ns_cache)
sz += cvec_size(x->x_ns_cache);
if (x->x_cv)
sz += cv_size(x->x_cv);
#ifdef XML_EXPLICIT_INDEX
if (x->x_search_index){
/* XXX: only one */
sz += sizeof(struct search_index);
if (x->x_search_index->si_name)
sz += strlen(x->x_search_index->si_name)+1;
if (x->x_search_index->si_xvec)
sz += clixon_xvec_len(x->x_search_index->si_xvec)*sizeof(struct cxobj*);
}
#endif
break;
case CX_BODY:
case CX_ATTR:
sz += sizeof(struct xmlbody);
if (x->x_value_cb)
sz += cbuf_buflen(x->x_value_cb);
break;
default:
break;
}
if (szp)
*szp = sz;
return 0;
}
/*! Return statistics of an XML tree recursively
*
* @param[in] xt XML object
* @param[out] nrp Number of XML obj recursively
* @param[out] szp Size of this XML obj recursively
* @retval 0 OK
* @retval -1 Error
*/
int
xml_stats(cxobj *xt,
uint64_t *nrp,
size_t *szp)
{
int retval = -1;
size_t sz = 0;
cxobj *xc;
if (xt == NULL){
clixon_err(OE_XML, EINVAL, "xml node is NULL");
goto done;
}
*nrp += 1;
xml_stats_one(xt, &sz);
if (szp)
*szp += sz;
xc = NULL;
while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
sz=0;
xml_stats(xc, nrp, &sz);
if (szp)
*szp += sz;
}
retval = 0;
done:
return retval;
}
/*
* Access functions
*/
/*! Get name of xnode
*
* @param[in] xn xml node
* @retval name of xml node
*/
char*
xml_name(cxobj *xn)
{
if (xn == NULL) {
return NULL;
}
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 0 OK
* @retval -1 On error with clicon-err set
*/
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){
clixon_err(OE_XML, errno, "strdup");
return -1;
}
}
return 0;
}
/*! Get prefix of xnode
*
* @param[in] xn xml node
* @retval prefix of xml node
*/
char*
xml_prefix(cxobj *xn)
{
return xn->x_prefix;
}
/*! Set prefix of xnode, prefix is copied
*
* @param[in] xn XML node
* @param[in] prefix New prefix, null-terminated string, copied by function
* @retval 0 OK
* @retval -1 Error with clicon-err set
*/
int
xml_prefix_set(cxobj *xn,
char *prefix)
{
if (xn->x_prefix){
free(xn->x_prefix);
xn->x_prefix = NULL;
}
if (prefix){
if ((xn->x_prefix = strdup(prefix)) == NULL){
clixon_err(OE_XML, errno, "strdup");
return -1;
}
}
return 0;
}
/*! Get cached namespace (given prefix)
*
* @param[in] x XML node
* @param[in] prefix Namespace prefix, or NULL for default
* @retval ns Cached namespace
* @retval NULL No namespace found (not cached or not found)
* @note may want to distinguish between not set cache and no namespace?
*/
char*
nscache_get(cxobj *x,
char *prefix)
{
if (!is_element(x))
return NULL;
if (x->x_ns_cache != NULL)
return xml_nsctx_get(x->x_ns_cache, prefix);
return NULL;
}
/*! Get cached prefix (given namespace)
*
* @param[in] x XML node
* @param[in] namespace
* @param[out] prefix
* @retval 1 Prefix found
* @retval 0 No prefix found
*/
int
nscache_get_prefix(cxobj *x,
char *namespace,
char **prefix)
{
if (!is_element(x))
return 0;
if (x->x_ns_cache != NULL)
return xml_nsctx_get_prefix(x->x_ns_cache, namespace, prefix);
return 0;
}
/*! Dump whole namespace context cache of one xml node
*
* @param[in] x XML node
* @retval nsc Whole namespace context of x
* @retval NULL Empty nsc
* @see nscache_get For a single prefix
*/
cvec *
nscache_get_all(cxobj *x)
{
if (!is_element(x))
return NULL;
return x->x_ns_cache;
}
/*! Set cached namespace for specific namespace. Replace if necessary
*
* @param[in] x XML node
* @param[in] prefix Namespace prefix, or NULL for default
* @param[in] namespace Cached namespace to set (assume non-null?)
* @retval 0 OK
* @retval -1 Error
* @see nscache_replace to replace the whole context
*/
int
nscache_set(cxobj *x,
char *prefix,
char *namespace)
{
int retval = -1;
if (!is_element(x))
return 0;
if (x->x_ns_cache == NULL){
if ((x->x_ns_cache = xml_nsctx_init(prefix, namespace)) == NULL)
goto done;
}
else
return xml_nsctx_add(x->x_ns_cache, prefix, namespace);
retval = 0;
done:
return retval;
}
/*! Set complete cached namespace context
*
* @param[in] x XML node
* @param[in] nsc Namespace context (note consumed, dont free)
* @retval 0 OK
* @retval -1 Error
* @see nscache_set set a single cache line
*/
int
nscache_replace(cxobj *x,
cvec *nsc)
{
int retval = -1;
if (!is_element(x))
return 0;
if (x->x_ns_cache != NULL){
xml_nsctx_free(x->x_ns_cache);
x->x_ns_cache = NULL;
}
x->x_ns_cache = nsc;
retval = 0;
// done:
return retval;
}
/*! Clear cached namespace context
*
* Clear the whole namespace context, not just single cache lines
* @param[in] x XML node
* @retval 0 OK
* @see nscache_set For setting specific namespace cache lines
* @see xml_addsub
*/
int
nscache_clear(cxobj *x)
{
if (!is_element(x))
return 0;
if (x->x_ns_cache != NULL){
xml_nsctx_free(x->x_ns_cache);
x->x_ns_cache = NULL;
}
return 0;
}
/*! Get parent of xnode
*
* @param[in] xn xml node
* @retval parent xml node
*/
cxobj*
xml_parent(cxobj *xn)
{
if (xn == NULL) {
return NULL;
}
return xn->x_up;
}
/*! Set parent of xml node.
*
* @param[in] xn xml node
* @param[in] parent pointer to new parent xml node
* @retval 0 OK
* @see xml_child_rm remove child from parent
*/
int
xml_parent_set(cxobj *xn,
cxobj *parent)
{
xn->x_up = parent;
return 0;
}
#ifdef XML_PARENT_CANDIDATE
/*! Get candidate parent of xnode
*
* @param[in] xn xml node
* @retval parent xml node
*/
cxobj*
xml_parent_candidate(cxobj *xn)
{
if (xn == NULL) {
return NULL;
}
return xn->x_up_candidate;
}
/*! Set candidate parent of xml node
*
* @param[in] xn xml node
* @param[in] parent pointer to candidate parent xml node
* @retval 0 OK
*/
int
xml_parent_candidate_set(cxobj *xn,
cxobj *parent)
{
xn->x_up_candidate = parent;
return 0;
}
#endif /* XML_PARENT_CANDIDATE */
/*! Get xml node flags, used for internal algorithms
*
* @param[in] xn xml node
* @retval flag Flag value(s), see XML_FLAG_MARK et al
*/
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 Flag values to set, see XML_FLAG_MARK et al
*/
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 Flag value(s) 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)
{
if (!is_bodyattr(xn))
return NULL;
return xn->x_value_cb?cbuf_get(xn->x_value_cb):NULL;
}
/*! 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 0 OK
* @retval -1 On error with clicon-err set
*/
int
xml_value_set(cxobj *xn,
char *val)
{
int retval = -1;
size_t sz;
if (!is_bodyattr(xn))
return 0;
if (val == NULL){
clixon_err(OE_XML, EINVAL, "value is NULL");
goto done;
}
sz = strlen(val)+1;
if (xn->x_value_cb == NULL){
if ((xn->x_value_cb = cbuf_new_alloc(sz)) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
}
else
cbuf_reset(xn->x_value_cb);
cbuf_append_str(xn->x_value_cb, val);
retval = 0;
done:
return retval;
}
/*! Append value of xnode, value is copied
*
* @param[in] xn xml node
* @param[in] val appended value, null-terminated string, copied by function
* @retval new value
* @retval NULL on error with clicon-err set, or if value is set to NULL
*/
int
xml_value_append(cxobj *xn,
char *val)
{
int retval = -1;
size_t sz;
if (!is_bodyattr(xn))
return 0;
if (val == NULL){
clixon_err(OE_XML, EINVAL, "value is NULL");
goto done;
}
sz = strlen(val)+1;
if (xn->x_value_cb == NULL){
if ((xn->x_value_cb = cbuf_new_alloc(sz)) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
}
if (cbuf_append_str(xn->x_value_cb, val) < 0){
clixon_err(OE_XML, errno, "cprintf");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Get type of xnode
*
* @param[in] xn xml node
* @retval type of xml node
*/
enum cxobj_type
xml_type(cxobj *xn)
{
if (xn == NULL) {
return CX_ERROR;
}
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)
{
if (xn == NULL) {
return 0;
}
if (!is_element(xn))
return 0;
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;
if (!is_element(xn))
return 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;
if (!is_element(xn))
return 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
* @see xml_child_i_type
* @see xml_child_order
*/
cxobj *
xml_child_i(cxobj *xn,
int i)
{
if (xn == NULL || i < 0) {
return NULL;
}
if (!is_element(xn))
return NULL;
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;
if (!is_element(xn))
return NULL;
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 (!is_element(xt))
return NULL;
if (i < xt->x_childvec_len)
xt->x_childvec[i] = xc;
return 0;
}
/*! Get the order of child
*
* @param[in] xp xml parent node
* @param[in] xc the xml child to look for
* @retval i The order of the child
* @retval -1 if no such child, or empty child
* @see xml_child_i
*/
int
xml_child_order(cxobj *xp,
cxobj *xc)
{
cxobj *x = NULL;
int i = 0;
if (!is_element(xp))
return -1;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
return i;
i++;
}
return -1;
}
/*! Advanced function to decrement _x_vector_i if objects have been removed
*/
int
xml_vector_decrement(cxobj *x,
int nr)
{
x->_x_vector_i -= nr;
return 0;
}
/*! Iterator over xml children objects
*
* @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
* @retval xn Next XML node
* @retval NULL End of list
* @code
* cxobj *x = NULL;
* while ((x = xml_child_each(x_top, x, -1)) != NULL) {
* ...
* }
* @endcode
* @note uses _x_vector_i as a shared resource: you cannot mix loops over same parent
* Further, 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.
* If you need to delete a node you can do something like:
* @code
* cxobj *xprev = NULL;
* cxobj *x = NULL;
* while ((x = xml_child_each(x_top, x, -1)) != NULL) {
* if (something){
* if (xml_purge(x) < 0)
* goto done;
* x = xprev;
* continue;
* }
* xprev = x;
* }
* @endcode
* @see xml_child_index_each
* @see xml_child_each_attr hardcoded for sorted list and attributes
*/
cxobj *
xml_child_each(cxobj *xparent,
cxobj *xprev,
enum cxobj_type type)
{
int i;
cxobj *xn = NULL;
if (xparent == NULL)
return NULL;
if (!is_element(xparent))
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 && xml_type(xn) != 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;
}
/*! Same as xml_child_each but hard-coded for attributes
*
* Assumes attributes are first in list, which they are if they are sorted, but there are
* situations where the children have not (yet) been sorted, in which case you need to use the
* original function.
* @param[in] xparent xml tree node whose children should be iterated
* @param[in] xprev previous child, or NULL on init
* @retval xn Next XML node
* @retval NULL End of list
* @see xml_child_each
*/
cxobj *
xml_child_each_attr(cxobj *xparent,
cxobj *xprev)
{
int i;
cxobj *xn = NULL;
if (xparent == NULL)
return NULL;
if (!is_element(xparent))
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 (xml_type(xn) != CX_ATTR){
return NULL;
}
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
* @see xml_child_insert_pos
* XXX could insert hint if we know this is a yang list and not a leaf to increase start.
*/
static int
xml_child_append(cxobj *xp,
cxobj *xc)
{
size_t start;
if (!is_element(xp))
return 0;
start = XML_CHILDVEC_SIZE_START;
/* Heurestics: if child is body only single child is expected, but element children may
* have siblings
*/
if (xml_type(xc) == CX_ELMNT)
start = XML_CHILDVEC_SIZE_START_ELMNT;
xp->x_childvec_len++;
if (xp->x_childvec_len > xp->x_childvec_max){
if (xp->x_childvec_len < XML_CHILDVEC_SIZE_THRESHOLD)
xp->x_childvec_max = xp->x_childvec_max?2*xp->x_childvec_max:start;
else
xp->x_childvec_max += XML_CHILDVEC_SIZE_THRESHOLD;
xp->x_childvec = realloc(xp->x_childvec, xp->x_childvec_max*sizeof(cxobj*));
if (xp->x_childvec == NULL){
clixon_err(OE_XML, errno, "realloc");
return -1;
}
}
xp->x_childvec[xp->x_childvec_len-1] = xc;
return 0;
}
/*! Insert child XML at specific position under XML parent
*
* @param[in] xp Parent XML node
* @param[in] xc Child XML node
* @param[in] pos Position
* @retval 0 OK
* @retval -1 Error
* @see xml_child_append
* @note does not do anything with child, you may need to set its parent, etc
*/
int
xml_child_insert_pos(cxobj *xp,
cxobj *xc,
int pos)
{
size_t size;
if (!is_element(xp))
return 0;
xp->x_childvec_len++;
if (xp->x_childvec_len > xp->x_childvec_max){
if (xp->x_childvec_len < XML_CHILDVEC_SIZE_THRESHOLD)
xp->x_childvec_max = xp->x_childvec_max?2*xp->x_childvec_max:XML_CHILDVEC_SIZE_START;
else
xp->x_childvec_max += XML_CHILDVEC_SIZE_THRESHOLD;
xp->x_childvec = realloc(xp->x_childvec, xp->x_childvec_max*sizeof(cxobj*));
if (xp->x_childvec == NULL){
clixon_err(OE_XML, errno, "realloc");
return -1;
}
}
size = (xml_child_nr(xp) - pos - 1)*sizeof(cxobj *);
memmove(&xp->x_childvec[pos+1], &xp->x_childvec[pos], size);
xp->x_childvec[pos] = xc;
return 0;
}
/*! Set 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)
{
if (!is_element(x))
return 0;
x->x_childvec_len = len;
x->x_childvec_max = len;
if (x->x_childvec)
free(x->x_childvec);
if ((x->x_childvec = calloc(len, sizeof(cxobj*))) == NULL){
clixon_err(OE_XML, errno, "calloc");
return -1;
}
return 0;
}
/*! Get the children of an XML node as an XML vector
*/
cxobj **
xml_childvec_get(cxobj *x)
{
if (!is_element(x))
return NULL;
return x->x_childvec;
}
/*! Given an XML object and a vector of children xvec, append the children to the object
*
* @param[in] x XML node
* @param[in] xv Clixon xml vector
* @retval 0 OK
* @retval -1 Error
* @note xv is not same type as x_childvec
*/
int
clixon_child_xvec_append(cxobj *xn,
clixon_xvec *xv)
{
int retval = -1;
cxobj *xc;
int i;
for (i=0; i<clixon_xvec_len(xv); i++){
xc = clixon_xvec_i(xv, i);
if (xml_addsub(xn, xc) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Create new xml node given a name and append it to parent if given. 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] type XML type
* @retval xml Created xml object if successful. Free with xml_free()
* @retval NULL Error and clixon_err() called
* @code
* cxobj *x;
* if ((x = xml_new(name, xparent, CX_ELMNT)) == NULL)
* err;
* ...
* xml_free(x);
* @endcode
* @note Differentiates between body/attribute vs element to reduce mem allocation
* @see xml_insert
*/
cxobj *
xml_new(char *name,
cxobj *xp,
enum cxobj_type type)
{
struct xml *x = NULL;
size_t sz;
switch (type){
case CX_ELMNT:
sz = sizeof(struct xml);
break;
case CX_ATTR:
case CX_BODY:
sz = sizeof(struct xmlbody);
break;
default:
clixon_err(OE_XML, EINVAL, "Invalid type: %d", type);
return NULL;
break;
}
if ((x = malloc(sz)) == NULL){
clixon_err(OE_XML, errno, "malloc");
return NULL;
}
memset(x, 0, sz);
xml_type_set(x, type);
if (name && (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_i = xml_child_nr(xp)-1;
}
_stats_xml_nr++;
return x;
}
/*! Create a new XML node and set it's body to a value
*
* @param[in] name The name of the new node
* @param[in] parent The parent to put the new node under
* @param[in] val The value to set in the body
*
* Creates a new node, sets it as a child of the parent, if one was passed in.
* Creates a child of the node, sets the child's type to CX_BODY, and sets
* the value of the body/child.
* Thanks mgsmith@netgate.com
*/
cxobj *
xml_new_body(char *name,
cxobj *parent,
char *val)
{
cxobj *new_node = NULL;
cxobj *body_node;
if (!name || !parent || !val) {
clixon_err(OE_XML, EINVAL, "name, parent or val is NULL");
return NULL;
}
if ((new_node = xml_new(name, parent, CX_ELMNT)) == NULL) {
return NULL;
}
if ((body_node = xml_new("body", new_node, CX_BODY)) == NULL ||
xml_value_set(body_node, val) < 0) {
xml_free(new_node);
new_node = NULL;
body_node = NULL;
} else {
xml_type_set(body_node, CX_BODY);
}
return new_node;
}
/*! 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)
{
if (!is_element(x))
return NULL;
return x->x_spec;
}
int
xml_spec_set(cxobj *x,
yang_stmt *spec)
{
if (!is_element(x))
return 0;
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
* Only applicable if x is body and has yang-spec and is leaf or leaf-list
* Only accessed by xml_cv_cache as part of sorting in xml_cmp
* @see xml_cv_cache
*/
cg_var *
xml_cv(cxobj *x)
{
if (!is_element(x))
return NULL;
return x->x_cv;
}
/*! Set (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
* Only applicable if x is body and has yang-spec and is leaf or leaf-list
* Only accessed by xml_cv_cache as part of sorting in xml_cmp
* @see xml_cv_cache
*/
int
xml_cv_set(cxobj *x,
cg_var *cv)
{
if (!is_element(x))
return 0;
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] xp Base XML object
* @param[in] name Node name
* @retval xmlobj if found.
* @retval NULL if no such node found.
*
* There are several issues with this function:
* @note (1) Ignores prefix which means namespaces are ignored
* @note (2) Does not differentiate between element,attributes and body. You usually want elements.
* @note (3) Linear scalability and relies on strcmp, does not use search/key indexes
* @note (4) Only returns first match, eg a list/leaf-list may have several children with same name
* @see xml_find_type A more generic function fixes (1) and (2) above
*/
cxobj *
xml_find(cxobj *xp,
char *name)
{
cxobj *x = NULL;
if (xp == NULL || name == NULL) {
return NULL;
}
if (!is_element(xp))
return NULL;
while ((x = xml_child_each(xp, x, -1)) != NULL)
if (strcmp(name, xml_name(x)) == 0)
break; /* x is set */
return x;
}
/*! 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_wrap
* @see xml_insert
* @note xc is not sorted correctly, need to call xml_sort on parent
* @see xml_insert which is a higher layer function including yang and sorting
*/
int
xml_addsub(cxobj *xp,
cxobj *xc)
{
int retval = -1;
cxobj *oldp;
int i;
char *pns = NULL; /* parent namespace */
char *cns = NULL; /* child namespace */
cxobj *xa;
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)
goto done;
/* Set new parent in child */
xml_parent_set(xc, xp);
/* Ensure default namespace is not duplicated
* here only remove duplicate default namespace, there may be more */
/* 1. Get parent default namespace */
if (xml2ns(xp, NULL, &pns) < 0)
goto done;
/* 2. Get child default namespace */
if (pns &&
xml_type(xc) == CX_ELMNT &&
(xa = xml_find_type(xc, NULL, "xmlns", CX_ATTR)) != NULL &&
(cns = xml_value(xa)) != NULL){
/* 3. check if same, if so remove child's */
if (strcmp(pns, cns) == 0)
xml_purge(xa);
}
/* clear namespace context cache of child */
nscache_clear(xc);
#ifdef XML_EXPLICIT_INDEX
if (xml_search_index_p(xc))
xml_search_child_insert(xp, xc);
#endif
}
retval = 0;
done:
return retval;
}
/*! Wrap a new node between a parent xml node (xp) and all its children
*
* Before: xp --> xc*
* After: xp --> xw --> xc*
* @param[in] xp Parent xml node
* @param[in] tag Name of new xml child
* @retval xw Return the new child (xw)
* @see xml_addsub
* @see xml_wrap (wrap s single node)
*/
cxobj *
xml_wrap_all(cxobj *xp,
char *tag)
{
cxobj *xw; /* new wrap node */
if (!is_element(xp))
return NULL;
if ((xw = xml_new(tag, NULL, CX_ELMNT)) == NULL)
goto done;
while (xp->x_childvec_len)
if (xml_addsub(xw, xml_child_i(xp, 0)) < 0)
goto done;
if (xml_addsub(xp, xw) < 0)
goto done;
done:
return xw;
}
/*! Wrap a new element above a single xml node (xc) with new tag
*
* Before: xp --> xc # specific child (xp can be NULL)
* After: xp --> xt(tag) --> xc
* @param[in] xp Parent xml node
* @param[in] tag Name of new xml child
* @retval xc Return the new child (xc)
* @retval NULL Error
* @see xml_addsub (give the parent)
* @see xml_wrap_all (wrap all children of a node, not just one)
*/
cxobj *
xml_wrap(cxobj *xc,
char *tag)
{
cxobj *xw; /* new wrap node */
cxobj *xp; /* parent */
xp = xml_parent(xc);
if ((xw = xml_new(tag, xp, CX_ELMNT)) == NULL)
goto done;
if (xml_addsub(xw, xc) < 0)
goto done;
done:
return xw;
}
/*! 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 Error
* @note you cannot remove xchild in the loop (unless you keep track of xprev)
* @note Linear complexity - use xml_child_rm if possible
* @see xml_free Free, dont remove from parent
* @see xml_child_rm Remove if child order is known (does not 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 Error
* @note you should not remove xchild in loop (unless you 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 (!is_element(xp))
return 0;
if ((xc = xml_child_i(xp, i)) == NULL){
clixon_err(OE_XML, 0, "Child not found");
goto done;
}
xml_parent_set(xc, NULL);
xp->x_childvec[i] = NULL;
xp->x_childvec_len--;
if (i<xp->x_childvec_len)
memmove(&xp->x_childvec[i], &xp->x_childvec[i+1], (xp->x_childvec_len-i)*sizeof(cxobj*));
#ifdef XML_EXPLICIT_INDEX
if (xml_type(xc) == CX_ELMNT){
if (xml_search_index_p(xc))
xml_search_child_rm(xp, xc);
}
#endif
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 Error
* @note you should not remove xchild in loop (unless you keep track of xprev)
* @see xml_child_rm Remove a child of a node
*/
int
xml_rm(cxobj *xc)
{
int retval = -1;
cxobj *xp;
cxobj *x;
int i;
if ((xp = xml_parent(xc)) == NULL)
goto ok;
/* Find child in parent XXX: search? */
x = NULL; i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
break;
i++;
}
if (x != NULL)
if (xml_child_rm(xp, i) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Remove all children of specific type
*
* @param[in] x XML node
* @param[in] type Remove all children of xn of this type, or -1 for all
* @retval 0 OK
* @retval -1 Error
*/
int
xml_rm_children(cxobj *xp,
enum cxobj_type type)
{
int retval = -1;
cxobj *xc;
int i;
if (!is_element(xp))
return 0;
for (i=0; i<xml_child_nr(xp);){
xc = xml_child_i(xp, i);
if (type != -1 && xml_type(xc) != type){
i++;
continue;
}
if (xml_child_rm(xp, i) < 0)
goto done;
xml_free(xc);
}
retval = 0;
done:
return retval;
}
/*! Remove top XML object and all children except a single child
*
* 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. (unwrap)
* 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 (clixon_xml_parse_string("<a>2</a>", YB_NONE, NULL, &xt, NULL) < 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 (!is_element(xp))
return 0;
if (xml_parent(xp) != NULL){
clixon_err(OE_XML, 0, "Parent is not root");
goto done;
}
if ((xc = xml_child_i(xp, i)) == NULL){
clixon_err(OE_XML, ENOENT, "Child %d of parent %s not found", i, xml_name(xp));
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;
}
/*! Remove top XML object and all children except a single (given) child
*
* Given a root xml node, remove the child from its parent
* , remove the parent and all other children. (unwrap)
* 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 (!is_element(xp))
return 0;
if (xml_parent(xp) != NULL){
clixon_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;
if (!is_element(xp))
return 0;
while ((x = xml_child_each(xp, x, -1)) != NULL)
x->_x_i = i++;
return 0;
}
/*! Reset enumeration as done by xml_enumerate_children
*/
int
xml_enumerate_reset(cxobj *xp)
{
cxobj *x = NULL;
if (!is_element(xp))
return 0;
while ((x = xml_child_each(xp, x, -1)) != NULL)
x->_x_i = 0;
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 body 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;
if (!is_element(xn))
return 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;
if (!is_element(xt))
return 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,
const char *prefix,
const char *name,
enum cxobj_type type)
{
cxobj *x;
if (!is_element(xt))
return NULL;
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 (any prefix)
* @param[in] name name of xml tree node (eg attr name or "body")
* @param[in] type Matching type or -1 for any
* @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 (and no prefix)
* @see xml_find_value where a body can be found as well
*/
cxobj *
xml_find_type(cxobj *xt,
const char *prefix,
const char *name,
enum cxobj_type type)
{
cxobj *x = NULL;
int pmatch; /* prefix match */
char *xprefix; /* xprefix */
if (!is_element(xt))
return NULL;
while ((x = xml_child_each(xt, x, type)) != NULL) {
if (prefix){
xprefix = xml_prefix(x);
pmatch = xprefix ? strcmp(prefix,xprefix)==0 : 0;
}
else
pmatch = 1;
if (pmatch && (name==NULL || 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,
const char *name)
{
cxobj *x = NULL;
if (!is_element(xt))
return 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 str 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,
const char *name)
{
cxobj *x=NULL;
if (!is_element(xt))
return 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,
const char *name,
char *val)
{
cxobj *x = NULL;
char *bstr;
if (!is_element(xt))
return NULL;
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 xml sub-tree recursively, reset it, but not object itself, do not remove it from parent
*
* @param[in] x the xml tree to be reset
* @see xml_purge where x is also removed from parent
* @see xml_free also free object
*/
int
xml_free0(cxobj *x)
{
int i;
cxobj *xc;
size_t sz = 0;
if (x == NULL)
return 0;
if (x->x_name)
free(x->x_name);
if (x->x_prefix)
free(x->x_prefix);
switch (xml_type(x)){
case CX_ELMNT:
sz = sizeof(struct xml);
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);
if (x->x_ns_cache)
xml_nsctx_free(x->x_ns_cache);
#ifdef XML_EXPLICIT_INDEX
xml_search_index_free(x);
#endif
break;
case CX_BODY:
case CX_ATTR:
sz = sizeof(struct xmlbody);
if (x->x_value_cb)
cbuf_free(x->x_value_cb);
break;
default:
break;
}
if (sz)
memset(x, 0, sz);
return 0;
}
/*! Free an xml 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)
{
if (x == NULL)
return 0;
xml_free0(x);
free(x);
_stats_xml_nr--;
return 0;
}
/*! 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)
{
int retval = -1;
char *s;
if (x0 == NULL || x1 == NULL){
clixon_err(OE_XML, EINVAL, "x0 or x1 is NULL");
goto done;
}
xml_type_set(x1, xml_type(x0));
if ((s = xml_name(x0))) /* malloced string */
if ((xml_name_set(x1, s)) < 0)
goto done;
if ((s = xml_prefix(x0))) /* malloced string */
if ((xml_prefix_set(x1, s)) < 0)
goto done;
switch (xml_type(x0)){
case CX_ELMNT:
xml_spec_set(x1, xml_spec(x0));
break;
case CX_BODY:
case CX_ATTR:
if ((s = xml_value(x0))){ /* malloced string */
if (xml_value_set(x1, s) < 0)
goto done;
}
break;
default:
break;
}
xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT | XML_FLAG_TOP | XML_FLAG_ANYDATA | XML_FLAG_CACHE_DIRTY)); /* Maybe more flags */
retval = 0;
done:
return retval;
}
/*! 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, xml_type(x0));
* if (xml_copy(x0, x1) < 0)
* err;
* @endcode
* @see xml_dup
*/
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_type(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.
*
* @param[in] x0 Old object
* @retval x1 New object, free with xml_free
* @retval NULL Error
* @code
* cxobj *x1;
* if ((x1 = xml_dup(x0)) == NULL)
* err;
* @endcode
* @see xml_cp
*/
cxobj *
xml_dup(cxobj *x0)
{
cxobj *x1;
if ((x1 = xml_new("new", NULL, xml_type(x0))) == NULL)
return NULL;
if (xml_copy(x0, x1) < 0)
return NULL;
return x1;
}
#if 1 /* XXX At some point migrate this code to the clixon_xml_vec.[ch] API */
/*! Append a new xml tree to an existing xml vector last in the list
*
* @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
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj **xvec = NULL;
* int xlen = 0;
* cxobj *x;
*
* if (cxvec_append(x, &xvec, &xlen) < 0)
* err;
* if (xvec)
* free(xvec);
* @endcode
* @see cxvec_prepend
* @see clixon_xvec_append which is its own encapsulated xml vector datatype
*/
int
cxvec_append(cxobj *x,
cxobj ***vec,
int *len)
{
int retval = -1;
if ((*vec = realloc(*vec, sizeof(cxobj *) * (*len+1))) == NULL){
clixon_err(OE_XML, errno, "realloc");
goto done;
}
(*vec)[(*len)++] = x;
retval = 0;
done:
return retval;
}
/*! Prepend a new xml tree to an existing xml vector first in the list
*
* @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
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj **xvec = NULL;
* size_t xlen = 0;
* cxobj *x;
*
* if (cxvec_append(x, &xvec, &xlen) < 0)
* err;
* if (xvec)
* free(xvec);
* @endcode
* @see cxvec_append
* @see clixon_xvec_prepend which is its own encapsulated xml vector datatype
*/
int
cxvec_prepend(cxobj *x,
cxobj ***vec,
int *len)
{
int retval = -1;
if ((*vec = realloc(*vec, sizeof(cxobj *) * (*len+1))) == NULL){
clixon_err(OE_XML, errno, "realloc");
goto done;
}
memmove(&(*vec)[1], &(*vec)[0], sizeof(cxobj *) * (*len));
(*vec)[0] = x;
(*len)++;
retval = 0;
done:
return retval;
}
#endif
/*! 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 OK, aborted on first fn returned 1
* @retval 0 OK, all nodes traversed (subparts may have been skipped)
* @retval -1 Error, aborted at first error encounter
*
* @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;
if (!is_element(xn))
return 0;
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 OK, aborted on first fn returned 1
* @retval 0 OK, all nodes traversed (subparts may have been skipped)
* @retval -1 Error, aborted at first error encounter
* @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 n OK, aborted at first encounter of first match
* @retval 0 OK, all nodes traversed
* @retval -1 Error, aborted at first error encounter
* @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 ((ret = fn(xp, 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 1 Yes, xpp is ancestor of x
* @retval 0 No, xpp is not 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;
}
/*! Get ultimate root ancestor of an xml-node: top-level node without parent
*
* @param[in] xn XML node
* @retval xr XML root node (can be xn)
*/
cxobj *
xml_root(cxobj *xn)
{
cxobj *xp = NULL;
cxobj *x = NULL;
x = xn;
while ((xp = xml_parent(x)) != NULL)
x = xp;
return x;
}
/*! 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{
clixon_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";
}
}
/*! Map xml insert attribute from string to enumeration
*
* @param[in] instr String, eg "first"
* @param[out] ins Enumeration, eg INS_FIRST
* @code
* enum insert_type ins;
* xml_operation("last", &ins)
* @endcode
*/
int
xml_attr_insert2val(char *instr,
enum insert_type *ins)
{
if (strcmp("first", instr) == 0)
*ins = INS_FIRST;
else if (strcmp("last", instr) == 0)
*ins = INS_LAST;
else if (strcmp("before", instr) == 0)
*ins = INS_BEFORE;
else if (strcmp("after", instr) == 0)
*ins = INS_AFTER;
else{
clixon_err(OE_XML, 0, "Bad-attribute operation: %s", instr);
return -1;
}
return 0;
}
/*! Add attribute and value to an xml element as well as namespace mapping
*
* @param[in] xn XML element
* @param[in] name Attribute name
* @param[in] value Attribute value
* @param[in] prefix Attribute prefix, or NULL
* @param[in] namespace Attribute prefix, if NULL do not add namespace mapping
* @retval xa Created attribute object
* @retval NULL Error
*/
cxobj *
xml_add_attr(cxobj *xn,
char *name,
char *value,
char *prefix,
char *namespace)
{
cxobj *xa = NULL;
char *ns = NULL;
if ((xa = xml_new(name, xn, CX_ATTR)) == NULL)
goto done;
if (prefix && xml_prefix_set(xa, prefix) < 0) /* clixon lib */
goto done;
if (xml_value_set(xa, value) < 0)
goto done;
if (namespace){
if (xml2ns(xn, prefix, &ns) < 0)
goto done;
if (ns == NULL && xmlns_set(xn, prefix, namespace) < 0)
goto done;
}
if (xml_sort(xn) < 0)
goto done;
ret:
return xa;
done:
if (xa){
xml_free(xa);
xa = NULL;
}
goto ret;
}
#ifdef XML_EXPLICIT_INDEX
/*! Is this XML object a search index, ie it is registered as a yang clixon cc:search_index
*
* Is this xml node a search index and does it have a parent that is a list and a grandparent
* where a search-vector can be placed
* @param[in] x XML object
* @retval 1 Yes
* @retval 0 No
*/
int
xml_search_index_p(cxobj *x)
{
yang_stmt *y;
cxobj *xp;
/* The index variable has a yang spec */
if ((y = xml_spec(x)) == NULL)
return 0;
/* The index variable is a registered search index */
if (yang_flag_get(y, YANG_FLAG_INDEX) == 0)
return 0;
/* The index variable has a parent which has a LIST yang spec */
if ((xp = xml_parent(x)) == NULL)
return 0;
if ((y = xml_spec(xp)) == NULL)
return 0;
if (yang_keyword_get(y) != Y_LIST)
return 0;
/* The index variable has a grand-parent */
if (xml_parent(xp) == NULL)
return 0;
return 1;
}
/*! Free all search vector pairs of this XML node
*
* @param[in] x XML object
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_search_index_free(cxobj *x)
{
struct search_index *si;
while ((si = x->x_search_index) != NULL) {
DELQ(si, x->x_search_index, struct search_index *);
if (si->si_name)
free(si->si_name);
if (si->si_xvec)
clixon_xvec_free(si->si_xvec);
free(si);
}
return 0;
}
/*! Add single search vector pair to this XML node
*
* @param[in] x XML object
* @param[in] name Name of index variable
* @retval 0 OK
* @retval -1 Error
*/
static struct search_index *
xml_search_index_add(cxobj *x,
char *name)
{
struct search_index *si = NULL;
if ((si = malloc(sizeof(struct search_index))) == NULL){
clixon_err(OE_XML, errno, "malloc");
goto done;
}
memset(si, 0, sizeof(struct search_index));
if ((si->si_name = strdup(name)) == NULL){
clixon_err(OE_XML, errno, "strdup");
free(si);
si = NULL;
goto done;
}
if ((si->si_xvec = clixon_xvec_new()) == NULL){
free(si->si_name);
free(si);
si = NULL;
goto done;
}
ADDQ(si, x->x_search_index);
done:
return si;
}
/*! Add single search vector pair to this XML node
*
* @param[in] x XML object
* @param[in] name Name of index variable
* @retval 0 OK
* @retval -1 Error
*/
static struct search_index *
xml_search_index_get(cxobj *x,
char *name)
{
struct search_index *si = NULL;
if ((si = x->x_search_index) != NULL) {
do {
if (strcmp(si->si_name, name) == 0){
goto done;
break;
}
si = NEXTQ(struct search_index *, si);
} while (si && si != x->x_search_index);
}
done:
return si;
}
/*--------------------------------------------------*/
/*! Get sorted index vector for list for variable "name"
*
* @param[in] xp XML parent object
* @param[in] name Name of index variable
* @param[out] xvec XML object search vector
* @retval 0 OK
*/
int
xml_search_vector_get(cxobj *xp,
char *name,
clixon_xvec **xvec)
{
struct search_index *si;
*xvec = NULL;
if ((si = xp->x_search_index) != NULL) {
do {
if (strcmp(si->si_name, name) == 0){
*xvec = si->si_xvec;
break;
}
si = NEXTQ(struct search_index *, si);
} while (si && si != xp->x_search_index);
}
return 0;
}
/*! Insert a new cxobj into search index vector for list for variable "name"
*
* @param[in] xp XML parent object (the list element)
* @param[in] xi XML index object (that should be added)
* @retval 0 OK
* @retval -1 Error
*/
int
xml_search_child_insert(cxobj *xp,
cxobj *xi)
{
int retval = -1;
char *indexvar;
struct search_index *si;
cxobj *xpp;
int i;
int len;
indexvar = xml_name(xi);
if ((xpp = xml_parent(xp)) == NULL)
goto ok;
/* Find base vector in grandparent */
if ((si = xml_search_index_get(xpp, indexvar)) == NULL){
/* If not found add base vector in grand-parent */
if ((si = xml_search_index_add(xpp, indexvar)) == NULL)
goto done;
}
/* Find element position using binary search and then remove */
len = clixon_xvec_len(si->si_xvec);
if ((i = xml_search_indexvar_binary_pos(xp, indexvar, si->si_xvec, 0, len, len, NULL)) < 0)
goto done;
assert(clixon_xvec_i(si->si_xvec, i) != xp);
if (clixon_xvec_insert_pos(si->si_xvec, xp, i) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Remove a single cxobj from search vector
*
* @param[in] xp XML parent object (the list element)
* @param[in] xi XML index object (that should be added)
* @retval 0 OK
* @retval -1 Error
*/
int
xml_search_child_rm(cxobj *xp,
cxobj *xi)
{
int retval = -1;
cxobj *xpp;
char *indexvar;
int i;
int len;
struct search_index *si;
int eq = 0;
indexvar = xml_name(xi);
if ((xpp = xml_parent(xp)) == NULL)
goto ok;
/* Find base vector in grandparent */
if ((si = xml_search_index_get(xpp, indexvar)) == NULL)
goto ok;
/* Find element using binary search and then remove */
len = clixon_xvec_len(si->si_xvec);
if ((i = xml_search_indexvar_binary_pos(xp, indexvar, si->si_xvec, 0, len, len, &eq)) < 0)
goto done;
// if (clixon_xvec_i(si->si_xvec, i) == xp)
if (eq)
if (clixon_xvec_rm_pos(si->si_xvec, i) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Iterator over xml children objects using (explicit) index variable
*
* @param[in] xparent xml tree node whose children should be iterated
* @param[in] name Name of index variable
* @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_index_each(x_top, "i", x, -1)) != NULL) {
* ...
* }
* @endcode
* @see xml_child_each for looping over structural children.
* @note uses _x_vector_i as a shared resource: you cannot mix loops over same parent
* Further, 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.
* If you need to delete a node you can do somethjing like:
*/
cxobj *
xml_child_index_each(cxobj *xparent,
char *name,
cxobj *xprev,
enum cxobj_type type)
{
cxobj *xn = NULL;
clixon_xvec *xv = NULL;
int i;
if (xparent == NULL)
return NULL;
if (!is_element(xparent))
return NULL;
if (xml_search_vector_get(xparent, name, &xv) < 0)
return NULL;
if (xv == NULL)
return NULL;
for (i=xprev?xprev->_x_vector_i+1:0; i<clixon_xvec_len(xv); i++){
if ((xn = clixon_xvec_i(xv, i)) == NULL)
continue;
if (type != CX_ERROR && xml_type(xn) != type)
continue;
break; /* this is next object after previous */
}
if (i < clixon_xvec_len(xv)) /* found */
xn->_x_vector_i = i;
else
xn = NULL;
return xn;
}
#endif /* XML_EXPLICIT_INDEX */