- Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2`. - Added special case for api-path:s beginning with //
2700 lines
67 KiB
C
2700 lines
67 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
|
Copyright (C) 2017-2019 Olof Hagsand
|
|
Copyright (C) 2020-2021 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_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_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_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 lineraly
|
|
* 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 sorting:
|
|
see xml_enumerate 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 */
|
|
uint64_t _stats_nr = 0;
|
|
|
|
/*! Get global statistics about XML objects
|
|
*/
|
|
int
|
|
xml_stats_global(uint64_t *nr)
|
|
{
|
|
if (nr)
|
|
*nr = _stats_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;
|
|
clicon_debug(1, "%s %zu", __FUNCTION__, sz);
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
/*! Print memory stats of a single object
|
|
*/
|
|
static int
|
|
xml_print_stats_one(FILE *f,
|
|
cxobj *x)
|
|
{
|
|
size_t sz = 0;
|
|
|
|
xml_stats_one(x, &sz);
|
|
fprintf(f, "%s:\n", xml_name(x));
|
|
fprintf(f, " sum: \t\t%u\n", (unsigned int)sz);
|
|
if (xml_type(x) == CX_ELMNT)
|
|
fprintf(f, " base struct: \t%u\n", (unsigned int)sizeof(struct xml));
|
|
else
|
|
fprintf(f, " base struct: \t%u\n", (unsigned int)sizeof(struct xmlbody));
|
|
if (x->x_name)
|
|
fprintf(f, " name: \t%u\n", (unsigned int)strlen(x->x_name) + 1);
|
|
if (x->x_prefix)
|
|
fprintf(f, " prefix: \t%u\n", (unsigned int)strlen(x->x_prefix) + 1);
|
|
if (xml_type(x) == CX_ELMNT){
|
|
if (x->x_childvec_max)
|
|
fprintf(f, " childvec: \t%u\n", (unsigned int)(x->x_childvec_max*sizeof(struct xml*)));
|
|
if (x->x_ns_cache)
|
|
fprintf(f, " ns-cache: \t%u\n", (unsigned int)cvec_size(x->x_ns_cache));
|
|
if (x->x_cv)
|
|
fprintf(f, " value-cv: \t%u\n", (unsigned int)cv_size(x->x_cv));
|
|
if (x->x_search_index)
|
|
fprintf(f, " search-index: \t%u\n",
|
|
(unsigned int)(strlen(x->x_search_index->si_name) + 1 + clixon_xvec_len(x->x_search_index->si_xvec)*sizeof(struct cxobj*)));
|
|
}
|
|
else{
|
|
if (x->x_value_cb)
|
|
fprintf(f, " value-cb: \t%u\n", cbuf_buflen(x->x_value_cb));
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*! Return statistics of an XML tree recursively
|
|
* @param[in] xt XML object
|
|
* @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){
|
|
clicon_err(OE_XML, EINVAL, "xml node is NULL");
|
|
goto done;
|
|
}
|
|
// xml_print_stats_one(stderr, xt);
|
|
*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;
|
|
}
|
|
clicon_debug(1, "%s %zu", __FUNCTION__, *szp);
|
|
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 -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 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 -1 Error with clicon-err set
|
|
* @retval 0 OK
|
|
*/
|
|
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){
|
|
clicon_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 0 No prefix found
|
|
* @retval 1 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 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;
|
|
}
|
|
|
|
#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 -1 on error with clicon-err set
|
|
* @retval 0 OK
|
|
*/
|
|
int
|
|
xml_value_set(cxobj *xn,
|
|
char *val)
|
|
{
|
|
int retval = -1;
|
|
size_t sz;
|
|
|
|
if (!is_bodyattr(xn))
|
|
return 0;
|
|
if (val == NULL){
|
|
clicon_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){
|
|
clicon_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 NULL on error with clicon-err set, or if value is set to NULL
|
|
* @retval new value
|
|
*/
|
|
int
|
|
xml_value_append(cxobj *xn,
|
|
char *val)
|
|
{
|
|
int retval = -1;
|
|
size_t sz;
|
|
|
|
if (!is_bodyattr(xn))
|
|
return 0;
|
|
if (val == NULL){
|
|
clicon_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){
|
|
clicon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
}
|
|
if (cbuf_append_str(xn->x_value_cb, val) < 0){
|
|
clicon_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
|
|
*/
|
|
static 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 xml The child xml node
|
|
* @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;
|
|
}
|
|
|
|
/*! 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
|
|
* @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 somethhing 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
|
|
#ifdef XML_EXPLICIT_INDEX
|
|
* @see xml_child_index_each
|
|
#endif XML_EXPLICIT_INDEX
|
|
*/
|
|
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;
|
|
}
|
|
|
|
|
|
/*! 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){
|
|
clicon_err(OE_XML, errno, "realloc");
|
|
return -1;
|
|
}
|
|
}
|
|
xp->x_childvec[xp->x_childvec_len-1] = xc;
|
|
return 0;
|
|
}
|
|
|
|
/*! Insert child xc at position i under parent xp
|
|
*
|
|
* @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 i)
|
|
{
|
|
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){
|
|
clicon_err(OE_XML, errno, "realloc");
|
|
return -1;
|
|
}
|
|
}
|
|
size = (xml_child_nr(xp) - i - 1)*sizeof(cxobj *);
|
|
memmove(&xp->x_childvec[i+1], &xp->x_childvec[i], size);
|
|
xp->x_childvec[i] = 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){
|
|
clicon_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;
|
|
}
|
|
|
|
/*! 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] type XML type
|
|
* @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, CX_ELMNT)) == NULL)
|
|
* err;
|
|
* ...
|
|
* xml_free(x);
|
|
* @endcode
|
|
* @note Differentiates between body/attribute vs element to reduce mem allocation
|
|
* @see xml_sort_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:
|
|
clicon_err(OE_XML, EINVAL, "Invalid type: %d", type);
|
|
return NULL;
|
|
break;
|
|
}
|
|
if ((x = malloc(sz)) == NULL){
|
|
clicon_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_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) {
|
|
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 NULL Error
|
|
* @retval xc Return the new child (xc)
|
|
* @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
|
|
* @note you cannot remove xchild in the loop (unless yoy 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
|
|
* @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 (!is_element(xp))
|
|
return 0;
|
|
if ((xc = xml_child_i(xp, i)) == NULL){
|
|
clicon_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
|
|
* @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 (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){
|
|
clicon_err(OE_XML, 0, "Parent is not root");
|
|
goto done;
|
|
}
|
|
if ((xc = xml_child_i(xp, i)) == NULL){
|
|
clicon_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){
|
|
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;
|
|
|
|
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 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
|
|
* @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 && 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 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 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 == 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:
|
|
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:
|
|
if (x->x_value_cb)
|
|
cbuf_free(x->x_value_cb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(x);
|
|
_stats_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){
|
|
clicon_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)); /* 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.
|
|
*
|
|
* @code
|
|
* cxobj *x1;
|
|
* x1 = xml_dup(x0);
|
|
* @endcode
|
|
* Note, returned tree should be freed as: xml_free(x1)
|
|
* @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 */
|
|
/*! 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,
|
|
int len0,
|
|
cxobj ***vec1,
|
|
int *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 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_cxvec_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){
|
|
clicon_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_prepend
|
|
* @see clixon_cxvec_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){
|
|
clicon_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 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;
|
|
|
|
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 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 ((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 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;
|
|
}
|
|
|
|
/*! 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{
|
|
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";
|
|
}
|
|
}
|
|
/*! 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{
|
|
clicon_err(OE_XML, 0, "Bad-attribute operation: %s", instr);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! 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,
|
|
const 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){
|
|
clicon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (clicon_xml2cbuf(cb, x, 0, 0, -1) < 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;
|
|
}
|
|
|
|
#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){
|
|
clicon_err(OE_XML, errno, "malloc");
|
|
goto done;
|
|
}
|
|
memset(si, 0, sizeof(struct search_index));
|
|
if ((si->si_name = strdup(name)) == NULL){
|
|
clicon_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)
|
|
*/
|
|
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)
|
|
*/
|
|
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 */
|