/*
 *
  ***** 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 parse and print 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" 
#include "clixon_yang_module.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_vec.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_parse.h"
#include "clixon_xml_io.h"

/*
 * Constants
 */
/* Size of xml read buffer */
#define BUFLEN 1024  
/* Indentation for xml pretty-print. Consider option? */
#define XML_INDENT 3

/*------------------------------------------------------------------------
 * XML printing functions. Output a parse tree to file, string cligen buf
 *------------------------------------------------------------------------*/

/*! Print an XML tree structure to an output stream and encode chars "<>&"
 *
 * @param[in]   f          UNIX output stream
 * @param[in]   xn         Clicon xml tree
 * @param[in]   level      How many spaces to insert before each line
 * @param[in]   pretty     Insert \n and spaces to make the xml more readable.
 * @param[in]   fn         Callback to make print function
 * @param[in]   autocliext How to handle autocli extensions: 0: ignore 1: follow
 * @retval      0          OK
 * @retval     -1          Error
 * @see clixon_xml2cbuf
 * One can use clixon_xml2cbuf to get common code, but using fprintf is
 * much faster than using cbuf and then printing that,...
 *
 */
static int
xml2file_recurse(FILE             *f, 
		 cxobj            *x, 
		 int               level, 
		 int               pretty,
		 clicon_output_cb *fn,
		 int               autocliext)
{
    int    retval = -1;
    char  *name;
    char  *namespace;
    cxobj *xc;
    int    hasbody;
    int    haselement;
    char  *val;
    char  *encstr = NULL; /* xml encoded string */
    int    exist = 0;
    yang_stmt *y;
	
    if (x == NULL)
	goto ok;
    if (autocliext &&
	(y = xml_spec(x)) != NULL){
	if (yang_extension_value(y, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
	    goto done;
	if (exist)
	    goto ok;
    }
    name = xml_name(x);
    namespace = xml_prefix(x);
    switch(xml_type(x)){
    case CX_BODY:
	if ((val = xml_value(x)) == NULL) /* incomplete tree */
	    break;
	if (xml_chardata_encode(&encstr, "%s", val) < 0)
	    goto done;
	(*fn)(f, "%s", encstr);
	break;
    case CX_ATTR:
	(*fn)(f, " ");
	if (namespace)
	    (*fn)(f, "%s:", namespace);
	(*fn)(f, "%s=\"%s\"", name, xml_value(x));
	break;
    case CX_ELMNT:
	(*fn)(f, "%*s<", pretty?(level*XML_INDENT):0, "");
	if (namespace)
	    (*fn)(f, "%s:", namespace);
	(*fn)(f, "%s", name);
	hasbody = 0;
	haselement = 0;
	xc = NULL;
	/* print attributes only */
	while ((xc = xml_child_each(x, xc, -1)) != NULL) {
	    switch (xml_type(xc)){
	    case CX_ATTR:
		if (xml2file_recurse(f, xc, level+1, pretty, fn, autocliext) <0)
		    goto done;
		break;
	    case CX_BODY:
		hasbody=1;
		break;
	    case CX_ELMNT:
		haselement=1;
		break;
	    default:
		break;
	    }
	}
	/* Check for special case <a/> instead of <a></a>:
	 * Ie, no CX_BODY or CX_ELMNT child.
	 */
	if (hasbody==0 && haselement==0) 
	    (*fn)(f, "/>");
	else{
	    (*fn)(f, ">");
	    if (pretty && hasbody == 0)
		    (*fn)(f, "\n");
	    xc = NULL;
	    while ((xc = xml_child_each(x, xc, -1)) != NULL) {
		if (xml_type(xc) != CX_ATTR)
		    if (xml2file_recurse(f, xc, level+1, pretty, fn, autocliext) <0)
			goto done;
	    }
	    if (pretty && hasbody==0)
		(*fn)(f, "%*s", level*XML_INDENT, "");
	    (*fn)(f, "</");
	    if (namespace)
		(*fn)(f, "%s:", namespace);
	    (*fn)(f, "%s>", name);
	}
	if (pretty)
	    (*fn)(f, "\n");
	break;
    default:
	break;
    }/* switch */
 ok:
    retval = 0;
 done:
    if (encstr)
	free(encstr);
    return retval;
}

/*! Print an XML tree structure to an output stream and encode chars "<>&"
 *
 * @param[in]  f       Output file
 * @param[in]  xn      XML tree
 * @param[in]  level   How many spaces to insert before each line
 * @param[in]  pretty  Insert \n and spaces to make the xml more readable.
 * @param[in]  fn       File print function (if NULL, use fprintf)
 * @param[in]  skiptop 0: Include top object 1: Skip top-object, only children, 
 * @param[in]  autocliext How to handle autocli extensions: 0: ignore 1: follow
 * @retval     0       OK
 * @retval    -1       Error
 * @see clixon_xml2cbuf print to a cbuf string
 * @note There is a slight "layer violation" with the autocli parameter: it should normally be set
 *       for CLI calls, but not for others.
 */
int
clixon_xml2file(FILE             *f, 
		cxobj            *xn, 
		int               level, 
		int               pretty,
		clicon_output_cb *fn,
		int               skiptop,
		int               autocliext)
{
    int   retval = 1;
    cxobj *xc;

    if (fn == NULL)
	fn = fprintf;
    if (skiptop){
	xc = NULL;
	while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
	    if (xml2file_recurse(f, xc, level, pretty, fn, autocliext) < 0)
		goto done;
    }
    else {
	if (xml2file_recurse(f, xn, level, pretty, fn, autocliext) < 0)
	    goto done;
    }
    retval = 0;
 done:
    return retval;

}

/*! Print an XML tree structure to an output stream
 *
 * Utility function eg in gdb. For code, use clixon_xml2file
 * @param[in]   f           UNIX output stream
 * @param[in]   xn          clicon xml tree
 * @see clixon_xml2cbuf
 * @see clixon_xml2cbuf_cb print using a callback
 */
int
xml_print(FILE  *f, 
	  cxobj *x)
{
    return xml2file_recurse(f, x, 0, 1, fprintf, 0);
}

/*! Dump cxobj structure with pointers and flags for debugging, internal function
 */
static int
xml_dump1(FILE  *f, 
	  cxobj *x,
	  int    indent)
{
    cxobj *xc;
    
    if (xml_type(x) != CX_ELMNT)
	return 0;
    fprintf(stderr, "%*s %s(%s)",
	    indent*3, "",
	    //	    x,
	    xml_name(x),
	    xml_type2str(xml_type(x)));
    if (xml_flag(x, XML_FLAG_ADD))
	fprintf(stderr, " add");
    if (xml_flag(x, XML_FLAG_DEL))
	fprintf(stderr, " delete");
    if (xml_flag(x, XML_FLAG_CHANGE))
	fprintf(stderr, " change");
    if (xml_flag(x, XML_FLAG_MARK))
	fprintf(stderr, " mark");
    fprintf(stderr, "\n");
    xc = NULL;
    while ((xc = xml_child_each(x, xc, -1)) != NULL) {
	xml_dump1(f, xc, indent+1);
    }
    return 0;
}
	 
/*! Dump cxobj structure with pointers and flags for debugging
 *
 * @param[in]   f    UNIX output stream
 * @param[in]   x    Clixon xml tree
 * @see xml_print
 */
int
xml_dump(FILE  *f, 
	 cxobj *x)
{
    return xml_dump1(f, x, 0);
}

/*! Internal: print  XML tree structure to a cligen buffer and encode chars "<>&"
 *
 * @param[in,out] cb       Cligen buffer to write to
 * @param[in]     xn       Clixon xml tree
 * @param[in]     level    Indentation level for prettyprint
 * @param[in]     pretty   Insert \n and spaces to make the xml more readable.
 * @param[in]     depth    Limit levels of child resources: -1 is all, 0 is none, 1 is node itself
 */
static int
clixon_xml2cbuf1(cbuf   *cb, 
		 cxobj  *x, 
		 int     level,
		 int     pretty,
		 int32_t depth)
{
    int    retval = -1;
    cxobj *xc;
    char  *name;
    int    hasbody;
    int    haselement;
    char  *namespace;
    char  *val;
    
    if (depth == 0)
	goto ok;
    name = xml_name(x);
    namespace = xml_prefix(x);
    switch(xml_type(x)){
    case CX_BODY:
	if ((val = xml_value(x)) == NULL) /* incomplete tree */
	    break;
	if (xml_chardata_cbuf_append(cb, val) < 0)
	    goto done;
	break;
    case CX_ATTR:
	cbuf_append_str(cb, " ");
	if (namespace){
	    cbuf_append_str(cb, namespace);
	    cbuf_append_str(cb, ":");
	}
	cprintf(cb, "%s=\"%s\"", name, xml_value(x));
	break;
    case CX_ELMNT:
	if (pretty)
	    cprintf(cb, "%*s<", level*XML_INDENT, "");
	else
	    cbuf_append_str(cb, "<");
	if (namespace){
	    cbuf_append_str(cb, namespace);
	    cbuf_append_str(cb, ":");
	}
	cbuf_append_str(cb, name);
	hasbody = 0;
	haselement = 0;
	xc = NULL;
	/* print attributes only */
	while ((xc = xml_child_each(x, xc, -1)) != NULL) 
	    switch (xml_type(xc)){
	    case CX_ATTR:
		if (clixon_xml2cbuf1(cb, xc, level+1, pretty, -1) < 0)
		    goto done;
		break;
	    case CX_BODY:
		hasbody=1;
		break;
	    case CX_ELMNT:
		haselement=1;
		break;
	    default:
		break;
	    }
	/* Check for special case <a/> instead of <a></a> */
	if (hasbody==0 && haselement==0) 
	    cbuf_append_str(cb, "/>");
	else{
	    cbuf_append_str(cb, ">");
	    if (pretty && hasbody == 0)
		cbuf_append_str(cb, "\n");
	    xc = NULL;
	    while ((xc = xml_child_each(x, xc, -1)) != NULL) 
		if (xml_type(xc) != CX_ATTR)
		    if (clixon_xml2cbuf1(cb, xc, level+1, pretty, depth-1) < 0)
			goto done;
	    if (pretty && hasbody == 0)
		cprintf(cb, "%*s", level*XML_INDENT, "");
	    cbuf_append_str(cb, "</");
	    if (namespace){
		cbuf_append_str(cb, namespace);
		cbuf_append_str(cb, ":");
	    }
	    cbuf_append_str(cb, name);
	    cbuf_append_str(cb, ">");
	}
	if (pretty)
	    cbuf_append_str(cb, "\n");
	break;
    default:
	break;
    }/* switch */
 ok:
    retval = 0;
 done:
    return retval;
}

/*! Print an XML tree structure to a cligen buffer and encode chars "<>&"
 *
 * @param[in,out] cb      Cligen buffer to write to
 * @param[in]     xn      Top-level xml object
 * @param[in]     level   Indentation level for pretty
 * @param[in]     pretty  Insert \n and spaces to make the xml more readable.
 * @param[in]     depth   Limit levels of child resources: -1: all, 0: none, 1: node itself
 * @param[in]     skiptop 0: Include top object 1: Skip top-object, only children, 
 * @retval        0       OK
 * @retval        -1      Error
 * Depth is used in NACM
 * @code
 *   cbuf *cb = cbuf_new();
 *   if (clixon_xml2cbuf(cb, xn, 0, 1, -1, 0) < 0)
 *     goto err;
 *   fprintf(stderr, "%s", cbuf_get(cb));
 *   cbuf_free(cb);
 * @endcode
 * @see  clixon_xml2file
 */
int
clixon_xml2cbuf(cbuf   *cb, 
		cxobj  *xn,
		int     level,
		int     pretty,
		int32_t depth,
		int     skiptop)
{
    int    retval = -1;
    cxobj *xc;
    
    if (skiptop){
	xc = NULL;
	while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
	    if (clixon_xml2cbuf1(cb, xc, level, pretty, depth) < 0)
		goto done;
    }
    else {
	if (clixon_xml2cbuf1(cb, xn, level, pretty, depth) < 0)
	    goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Print actual xml tree datastructures (not xml), mainly for debugging
 * @param[in,out] cb          Cligen buffer to write to
 * @param[in]     xn          Clicon xml tree
 * @param[in]     level       Indentation level
 */
int
xmltree2cbuf(cbuf  *cb, 
	     cxobj *x,
	     int    level)
{
    cxobj *xc;
    int    i;

    for (i=0; i<level*XML_INDENT; i++)
	cprintf(cb, " ");
    if (xml_type(x) != CX_BODY)
	cprintf(cb, "%s", xml_type2str(xml_type(x)));
    if (xml_prefix(x)==NULL)
	cprintf(cb, " %s", xml_name(x));
    else
	cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x));
    if (xml_value(x))
	cprintf(cb, " value:\"%s\"", xml_value(x));
    if (xml_flag(x, 0xff))
	cprintf(cb, " flags:0x%x", xml_flag(x, 0xff));
    if (xml_child_nr(x))
	cprintf(cb, " {");
    cprintf(cb, "\n");
    xc = NULL;
    while ((xc = xml_child_each(x, xc, -1)) != NULL) 
	xmltree2cbuf(cb, xc, level+1);
    if (xml_child_nr(x)){
	for (i=0; i<level*XML_INDENT; i++)
	    cprintf(cb, " ");
	cprintf(cb, "}\n");
    }
    return 0;
}

/*--------------------------------------------------------------------
 * XML parsing functions. Create XML parse tree from string and file.
 *--------------------------------------------------------------------*/
/*! Common internal xml parsing function string to parse-tree
 *
 * Given a string containing XML, parse into existing XML tree and return
 * @param[in]     str   Pointer to string containing XML definition. 
 * @param[in]     yb    How to bind yang to XML top-level when parsing
 * @param[in]     yspec Yang specification (only if bind is TOP or CONFIG)
 * @param[in,out] xtop  Top of XML parse tree. Assume created. Holds new tree.
 * @param[out]    xerr  Reason for failure (yang assignment not made)
 * @retval        1     Parse OK and all yang assignment made
 * @retval        0     Parse OK but yang assigment not made (or only partial) and xerr set
 * @retval       -1     Error with clicon_err called. Includes parse error
 * @see clixon_xml_parse_file
 * @see clixon_xml_parse_string
 * @see _json_parse
 * @note special case is empty XML where the parser is not invoked.
 * It is questionable empty XML is legal. From https://www.w3.org/TR/2008/REC-xml-20081126 Sec 2.1:
 *    A well-formed document ... contains one or more elements.
 * But in clixon one can invoke a parser on a sub-part of a document where it makes sense to accept
 * an empty XML. For example where an empty config: <config></config> is parsed.
 * In other cases, such as receiving netconf ]]>]]> it should represent a complete document and 
 * therefore not well-formed.
 * Therefore checking for empty XML must be done by a calling function which knows wether the 
 * the XML represents a full document or not.
 */
static int 
_xml_parse(const char *str, 
	   yang_bind   yb,
	   yang_stmt  *yspec,
	   cxobj      *xt,
	   cxobj     **xerr)
{
    int             retval = -1;
    clixon_xml_yacc xy = {0,};
    cxobj          *x;
    int             ret;
    int             failed = 0; /* yang assignment */
    int             i;

    clicon_debug(2, "%s", __FUNCTION__);
    if (strlen(str) == 0){
	return 1; /* OK */
    }
    if (xt == NULL){
	clicon_err(OE_XML, errno, "Unexpected NULL XML");
	return -1;	
    }
    if ((xy.xy_parse_string = strdup(str)) == NULL){
	clicon_err(OE_XML, errno, "strdup");
	return -1;
    }
    xy.xy_xtop = xt;
    xy.xy_xparent = xt;
    xy.xy_yspec = yspec;
    if (clixon_xml_parsel_init(&xy) < 0)
	goto done;    
    if (clixon_xml_parseparse(&xy) != 0)  /* yacc returns 1 on error */
	goto done;
    /* Purge all top-level body objects */
    x = NULL;
    while ((x = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
	xml_purge(x);
    /* Traverse new objects */
    for (i = 0; i < xy.xy_xlen; i++) {
	x = xy.xy_xvec[i];
	/* Verify namespaces after parsing */
	if (xml2ns_recurse(x) < 0)
	    goto done;
	/* Populate, ie associate xml nodes with yang specs 
	 */
	switch (yb){
	case YB_NONE:
	    break;
	case YB_PARENT:
	    /* xt:n         Has spec
	     * x:   <a> <-- populate from parent
	     */
	    if ((ret = xml_bind_yang0(x, YB_PARENT, NULL, xerr)) < 0)
		goto done;
	    if (ret == 0)
		failed++;
	    break;

	case YB_MODULE_NEXT:
	    if ((ret = xml_bind_yang(x, YB_MODULE, yspec, xerr)) < 0)
		goto done;
	    if (ret == 0)
		failed++;
	    break;
	case YB_MODULE:
	    /* xt:<top>     nospec
	     * x:   <a> <-- populate from modules
	     */
	    if ((ret = xml_bind_yang0(x, YB_MODULE, yspec, xerr)) < 0)
		goto done;
	    if (ret == 0)
		failed++;
	    break;
	case YB_RPC:
	    if ((ret = xml_bind_yang_rpc(x, yspec, xerr)) < 0)
		goto done;
	    if (ret == 0){ /* Add message-id */
		if (*xerr && clixon_xml_attr_copy(x, *xerr, "message-id") < 0)
		    goto done;
		failed++;
	    }
	    break;
	} /* switch */
    }
    if (failed)
	goto fail;
    /* Sort the complete tree after parsing. Sorting is not really meaningful if Yang
       not bound */
    if (yb != YB_NONE)
	if (xml_sort_recurse(xt) < 0)
	    goto done;
    retval = 1;
  done:
    clixon_xml_parsel_exit(&xy);
    if (xy.xy_parse_string != NULL)
	free(xy.xy_parse_string);
    if (xy.xy_xvec)
	free(xy.xy_xvec);
    return retval; 
 fail: /* invalid */
    retval = 0;
    goto done;
}

/*! Read an XML definition from file and parse it into a parse-tree, advanced API
 *
 * @param[in]     fd    A file descriptor containing the XML file (as ASCII characters)
 * @param[in]     yb    How to bind yang to XML top-level when parsing
 * @param[in]     yspec Yang specification (only if bind is TOP or CONFIG)
 * @param[in,out] xt    Pointer to XML parse tree. If empty, create.
 * @param[out]    xerr  Pointer to XML error tree, if retval is 0
 * @retval        1     Parse OK and all yang assignment made
 * @retval        0     Parse OK but yang assigment not made (or only partial) and xerr set
 * @retval       -1     Error with clicon_err called. Includes parse error
 *
 * @code
 *  cxobj *xt = NULL;
 *  cxobj *xerr = NULL;
 *  FILE  *f;
 *  if ((f = fopen(filename, "r")) == NULL)
 *    err;
 *  if ((ret = clixon_xml_parse_file(f, YB_MODULE, yspec, &xt, &xerr)) < 0)
 *    err;
 *  xml_free(xt);
 * @endcode
 * @see clixon_xml_parse_string
 * @see clixon_json_parse_file
 * @note, If xt empty, a top-level symbol will be added so that <tree../> will be:  <top><tree.../></tree></top>
 * @note May block on file I/O
 */
int 
clixon_xml_parse_file(FILE      *fp, 
		      yang_bind  yb,
		      yang_stmt *yspec,
		      cxobj    **xt,
		      cxobj    **xerr)
{
    int   retval = -1;
    int   ret;
    int   len = 0;
    char  ch;
    char *xmlbuf = NULL;
    char *ptr;
    int   xmlbuflen = BUFLEN; /* start size */
    int   oldxmlbuflen;
    int   failed = 0;

    if (xt==NULL || fp == NULL){
	clicon_err(OE_XML, EINVAL, "arg is NULL");
	return -1;
    }
    if (yb == YB_MODULE && yspec == NULL){
	clicon_err(OE_XML, EINVAL, "yspec is required if yb == YB_MODULE");
	return -1;
    }
    if ((xmlbuf = malloc(xmlbuflen)) == NULL){
	clicon_err(OE_XML, errno, "malloc");
	goto done;
    }
    memset(xmlbuf, 0, xmlbuflen);
    ptr = xmlbuf;
    while (1){
	if ((ret = fread(&ch, 1, 1, fp)) < 0){
	    clicon_err(OE_XML, errno, "read");
	    break;
	}
	if (ret != 0){
	    xmlbuf[len++] = ch;
	}
	if (ret == 0) {
	    if (*xt == NULL)
		if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
		    goto done;
	    if ((ret = _xml_parse(ptr, yb, yspec, *xt, xerr)) < 0)
		goto done;
	    if (ret == 0)
		failed++;
	    break;
	}
	if (len >= xmlbuflen-1){ /* Space: one for the null character */
	    oldxmlbuflen = xmlbuflen;
	    xmlbuflen *= 2;
	    if ((xmlbuf = realloc(xmlbuf, xmlbuflen)) == NULL){
		clicon_err(OE_XML, errno, "realloc");
		goto done;
	    }
	    memset(xmlbuf+oldxmlbuflen, 0, xmlbuflen-oldxmlbuflen);
	    ptr = xmlbuf;
	}
    } /* while */
    retval = (failed==0) ? 1 : 0;
 done:
    if (retval < 0 && *xt){
	free(*xt);
	*xt = NULL;
    }
    if (xmlbuf)
	free(xmlbuf);
    return retval;
}

/*! Read an XML definition from string and parse it into a parse-tree, advanced API
 *
 * @param[in]     str   String containing XML definition. 
 * @param[in]     yb    How to bind yang to XML top-level when parsing
 * @param[in]     yspec Yang specification, or NULL
 * @param[in,out] xt    Pointer to XML parse tree. If empty will be created.
 * @param[out]    xerr  Reason for failure (yang assignment not made) if retval = 0
 * @retval        1     Parse OK and all yang assignment made
 * @retval        0     Parse OK but yang assigment not made (or only partial), xerr is set
 * @retval       -1     Error with clicon_err called. Includes parse error
 *
 * @code
 *  cxobj *xt = NULL;
 *  cxobj *xerr = NULL;
 *  if (clixon_xml_parse_string(str, YB_MODULE, yspec, &xt, &xerr) < 0)
 *    err;
 *  if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
 *    err;
 * @endcode
 * @see clixon_xml_parse_file
 * @see clixon_xml_parse_va
 * @note You need to free the xml parse tree after use, using xml_free()
 * @note If empty on entry, a new TOP xml will be created named "top"
 */
int 
clixon_xml_parse_string(const char *str, 
			yang_bind   yb,
			yang_stmt  *yspec,
			cxobj     **xt,
			cxobj     **xerr)
{
    if (xt==NULL){
	clicon_err(OE_XML, EINVAL, "xt is NULL");
	return -1;
    }
    if (yb == YB_MODULE && yspec == NULL){
	clicon_err(OE_XML, EINVAL, "yspec is required if yb == YB_MODULE");
	return -1;
    }
    if (*xt == NULL){
	if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
	    return -1;
    }
    return _xml_parse(str, yb, yspec, *xt, xerr);
}

/*! Read XML from var-arg list and parse it into xml tree
 *
 * Utility function using stdarg instead of static string.

 * @param[in]     yb     How to bind yang to XML top-level when parsing
 * @param[in]     yspec  Yang specification, or NULL
 * @param[in,out] xtop   Top of XML parse tree. If it is NULL, top element 
 *                       called 'top' will be created. Call xml_free() after use
 * @param[out]    xerr   Reason for failure (yang assignment not made)
 * @param[in]     format Format string for stdarg according to printf(3)
 * @retval        1      Parse OK and all yang assignment made
 * @retval        0      Parse OK but yang assigment not made (or only partial)
 * @retval       -1      Error with clicon_err called. Includes parse error
 *
 * @code
 *  cxobj *xt = NULL;
 *  if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL, "<xml>%d</xml>", 22) < 0)
 *    err;
 *  xml_free(xt);
 * @endcode
 * @see clixon_xml_parse_string
 * @see clixon_xml_parse_file
 * @note If vararg list is empty, consider using clixon_xml_parse_string()
 */
int 
clixon_xml_parse_va(yang_bind   yb,
		    yang_stmt  *yspec,		 
		    cxobj     **xtop,
		    cxobj     **xerr,
		    const char *format, ...)
{
    int     retval = -1;
    va_list args;
    char   *str = NULL;
    int     len;

    va_start(args, format);
    len = vsnprintf(NULL, 0, format, args) + 1;
    va_end(args);
    if ((str = malloc(len)) == NULL){
	clicon_err(OE_UNIX, errno, "malloc");
	goto done;
    }
    memset(str, 0, len);
    va_start(args, format);
    len = vsnprintf(str, len, format, args) + 1;
    va_end(args);
    retval = clixon_xml_parse_string(str, yb, yspec, xtop, xerr); 
 done:
    if (str)
	free(str);
    return retval;
}

/*! Copy an attribute value(eg message-id) from one xml (eg rpc input) to another xml (eg rpc outgoing)
 * @param[in]  xin   Get attr value from this XML
 * @param[in]  xout  Set attr value to this XML
 * @param[in]  name  Attribute name
 * @retval     0     OK
 * @retval    -1     Error
 * Alternative is to use: char *val = xml_find_value(x, name);
 * @code
 *  if (clixon_xml_attr_copy(xin, xout, "message-id") < 0)
 *    err;
 * @endcode
 */
int
clixon_xml_attr_copy(cxobj *xin,
		     cxobj *xout,
		     char  *name)
{
    int    retval = -1;
    char  *msgid;
    cxobj *xa;

    if (xin == NULL || xout == NULL){
	clicon_err(OE_XML, EINVAL, "xin or xout NULL");
	goto done;
    }
    if ((msgid = xml_find_value(xin, "message-id")) != NULL){
	if ((xa = xml_new("message-id", xout, CX_ATTR)) == NULL)
	    goto done;
	if (xml_value_set(xa, msgid) < 0)
	    goto done;
    }
    retval = 0;
 done:
    return retval;
}
