text: /*
   *
    ***** BEGIN LICENSE BLOCK *****
   
    Copyright (C) 2009-2019 Olof Hagsand
    Copyright (C) 2020 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 *****
  
   * This file has code for several variants of paths in cxobj trees:
   * - api-path as defined by RESTCONF
   * - instance-identifier as defined by YANG
   * - clixon-path is an internal format which both ^ use as internal representation
   *
   * 1. Instance-identifier
   * "Instance-identifier" is a subset of XML Xpaths and defined in Yang, used in NACM for example.
   *  and defined in RF7950 Sections 9.13 and 14. 
   *  To note: prefixes depend on the XML context in which the value occurs,
   *  In RFC8341 node-instance-identifiers are defined as:
   *   All the same rules as an instance-identifier apply, except that predicates for keys are optional.
   *   If a key predicate is missing, then the node-instance-identifier represents all possible server
   *   instances for that key.
   *
   * 2. Api-path
   * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   * BNF:
   *  <api-path>       := <root> ("/" (<api-identifier> | <list-instance>))*
   *  <root>           := <string> # See note 1 below
   *  <api-identifier> := [<module-name> ":"] <identifier>
   *  <module-name>    := <identifier>
   *  <list-instance>  := <api-identifier> "=" key-value *("," key-value)
   *  <key-value>      := <string>
   *  <string>         := <an unquoted string>
   *  <identifier>     := (<ALPHA> | "_") (<ALPHA> | <DIGIT> | "_" | "-" | ".")
   *
   * @note 1. <root> is the RESTCONF root resource (Sec 3.3) omitted in all calls below, it is
   *          assumed to be stripped from api-path before calling these functions.
   * @note 2. characters in a key value string are constrained, and some characters need to be 
   *          percent-encoded, 
   */
  #ifdef HAVE_CONFIG_H
  #include "clixon_config.h" /* generated by config & autoconf */
  #endif
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <errno.h>
  #include <ctype.h>
  #include <string.h>
  #include <syslog.h>
  #include <fcntl.h>
  #include <assert.h>
  #include <arpa/inet.h>
  #include <sys/param.h>
  #include <netinet/in.h>
  
  /* cligen */
  #include <cligen/cligen.h>
  
  /* clixon */
  #include "clixon_string.h"
  #include "clixon_queue.h"
  #include "clixon_hash.h"
  #include "clixon_handle.h"
  #include "clixon_string.h"
  #include "clixon_err.h"
  #include "clixon_log.h"
  #include "clixon_options.h"
  #include "clixon_yang.h" 
  #include "clixon_xml.h"
  #include "clixon_xml_nsctx.h"
  #include "clixon_xml_vec.h"
  #include "clixon_xml_sort.h"
  #include "clixon_netconf_lib.h"
  #include "clixon_xml_map.h"
  #include "clixon_yang_module.h" 
  #include "clixon_path.h"
  #include "clixon_api_path_parse.h"
  #include "clixon_instance_id_parse.h"
  
  /*! Given api-path, parse it, and return a clixon-path struct
   *
   * @param[in]  api_path  String with api-path syntax according to RESTCONF RFC8040
   * @param[out] cplist    Structured internal clixon-path
   * @retval     0         OK
   * @retval    -1         Error
   * @code
   *   clixon_path  *cplist = NULL;
   *   if (api_path_parse(api_path, &cplist) < 0)
   *     err;
   *   if (api_path_resolve(cplist, yt) < 0)
   *     err;
   *   ...
   *   if (cplist)
   *	clixon_path_free(cplist);
   * @endcode
   * @see clixon_path_free 
   */
  static int
  api_path_parse(char         *api_path,
  	       clixon_path **cplist)
  {
      int                  retval = -1;
      clixon_api_path_yacc ay = {0,};
  
      clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
      ay.ay_parse_string = api_path;
      ay.ay_name = "api-path parser";
      ay.ay_linenum = 1;
      if (api_path_scan_init(&ay) < 0)
  	goto done;
      if (api_path_parse_init(&ay) < 0)
  	goto done;
      if (clixon_api_path_parseparse(&ay) != 0) { /* yacc returns 1 on error */
  	clicon_log(LOG_NOTICE, "API-PATH error: on line %d", ay.ay_linenum);
  	if (clicon_errno == 0)
  	    clicon_err(OE_XML, 0, "API-PATH parser error with no error code (should not happen)");
  	goto done;
      }
      api_path_parse_exit(&ay);
      api_path_scan_exit(&ay);
      *cplist = ay.ay_top;
      retval = 0;
   done:
      return retval;
  }
  
  /*! Given instance-id path, parse it, and return an clixon-path struct
   *
   * @param[in]  api_path  String with syntax according to YANG RFC7980
   * @param[out] cplist    Structured internal clixon-path
   * @retval     0         OK
   * @retval    -1         Error
   * @code
   *   clixon_path  *cplist = NULL;
   *   if (instance_id_parse(api_path, &cplist) < 0)
   *     err;
   *   ...
   *   if (cplist)
   *	clixon_path_free(cplist);
   * @endcode
   * @see clixon_path_free 
   */
  static int
  instance_id_parse(char         *path,
  		  clixon_path **cplist)
  {
      int                     retval = -1;
      clixon_instance_id_yacc iy = {0,};
  
      clicon_debug(1, "%s path:%s", __FUNCTION__, path);
      iy.iy_parse_string = path;
      iy.iy_name = "instance-id parser";
      iy.iy_linenum = 1;
      if (instance_id_scan_init(&iy) < 0)
  	goto done;
      if (instance_id_parse_init(&iy) < 0)
  	goto done;
      if (clixon_instance_id_parseparse(&iy) != 0) { /* yacc returns 1 on error */
  	clicon_log(LOG_NOTICE, "Instance-id error: on line %d", iy.iy_linenum);
  	if (clicon_errno == 0)
  	    clicon_err(OE_XML, 0, "Instance-id parser error with no error code (should not happen)");
  	goto done;
      }
      instance_id_parse_exit(&iy);
      instance_id_scan_exit(&iy);
      *cplist = iy.iy_top;
      retval = 0;
   done:
      return retval;
  }
  
  static int
  clixon_path_free(clixon_path *cplist)
  {
      clixon_path *cp;
      
      while ((cp = cplist) != NULL){
  	DELQ(cp, cplist, clixon_path *);
  	if (cp->cp_prefix)
  	    free(cp->cp_prefix);
  	if (cp->cp_id)
  	    free(cp->cp_id);
  	if (cp->cp_cvk)
  	    cvec_free(cp->cp_cvk);
  	free(cp);
      }
      return 0;
  }
  
  /*! Print path on instance-id/xpath form
   */
  static int
  clixon_path_print(FILE        *f,
  		  clixon_path *cplist)
  {
      clixon_path *cp;
      cg_var      *cv;
      
      if ((cp = cplist) != NULL){
  	do {
  	    fprintf(f, "/");
  	    if (cp->cp_prefix)
  		fprintf(f, "%s:", cp->cp_prefix);
  	    fprintf(f, "%s", cp->cp_id);
  	    if (cp->cp_cvk){
  		fprintf(f, "=");
  		cv = NULL;
  		while ((cv = cvec_each(cp->cp_cvk, cv)) != NULL){
  		    fprintf(f, "[");
  		    /* If cvk has one integer argument, interpret as position, eg x/y[42] */
  		    if (cvec_len(cp->cp_cvk) == 1 && (cv_type_get(cv) == CGV_UINT32))
  			fprintf(f, "%u", cv_uint32_get(cv));
  		    else
  			fprintf(f, "%s=\"%s\"", cv_name_get(cv), cv_string_get(cv));
  		    fprintf(f, "]");
  		}
  	    }
  	    cp = NEXTQ(clixon_path *, cp); 
  	} while (cp && cp != cplist);
      }
      fprintf(f, "\n");
      return 0;
  }
  
  /*! Given an XML node, return root node
   * A root node is an ancestor xr of x with one or both of the following properties
   * - its XML parent is NULL parent, 
   * - its associated yang specification's parent is a yang module.
   * @param[in]   x    XML node
   * @param[out]  xr   XML root
   */
  int
  xml_yang_root(cxobj  *x,
  	      cxobj **xr)
  {
      int        retval = -1;
      cxobj     *xp;
      yang_stmt *y;
      yang_stmt *yp;
      
      while ((xp = xml_parent(x)) != NULL){
  	if ((y = xml_spec(x)) != NULL &&
  	    (yp = yang_parent_get(y)) != NULL)
  	    /* Actually, maybe only the Y_MODULE clause is relevant */
  	    if (yp==NULL ||
  		yang_keyword_get(yp) == Y_MODULE ||
  		yang_keyword_get(yp) == Y_SUBMODULE)
  		break; /* x is the root */
  	x = xp;
      }
      *xr = x;
      retval = 0;
      return retval;
  }
  
  /*! Construct an xml key format from yang statement using wildcards for keys
   * Recursively construct it to the top.
   * Example: 
   *   yang:  container a -> list b -> key c -> leaf d
   *   xpath: /modname:a/b/%s/d
   * @param[in]  ys      Yang statement
   * @param[in]  inclkey If set include key leaf (eg last leaf d in ex)
   * @param[out] cb      api_path_fmt,
   * @retval     0    OK
   * @retval    -1    Error
   * @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression"
   */ 
  static int
  yang2api_path_fmt_1(yang_stmt *ys, 
  		    int        inclkey,
  		    cbuf      *cb)
  {
      yang_stmt *yp; /* parent */
      yang_stmt *ymod;
      int        i;
      cvec      *cvk = NULL; /* vector of index keys */
      int        retval = -1;
  
      if ((yp = yang_parent_get(ys)) == NULL){
  	clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
  	goto done;
      }
      if (yp != NULL && /* XXX rm */
  	yang_keyword_get(yp) != Y_MODULE && 
  	yang_keyword_get(yp) != Y_SUBMODULE){
  
  	if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
  	    goto done;
  	if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
  #if 0
  	    /* In some cases, such as cli_show_auto, a trailing '/' should
  	     * NOT be present if ys is a key in a list.
  	     * But in other cases (I think most), the / should be there,
  	     * so a patch is added in cli_show_auto instead.
  	     */
  		if (yang_keyword_get(ys) == Y_LEAF && yp && 
  		    yang_keyword_get(yp) == Y_LIST &&
  		yang_key_match(yp, ys->ys_argument) == 1)
  		;
  	    else
  #endif
  		cprintf(cb, "/");
  	}
  	/* If parent namespace/module is different from child -> add child prefix */
  	if (ys_real_module(yp) != (ymod = ys_real_module(ys)))
  	    cprintf(cb, "%s:", yang_argument_get(ymod));
      }
      else /* top symbol - mark with name prefix */
  	cprintf(cb, "/%s:", yang_argument_get(yp));
  
      if (inclkey){
  	if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
  	    cprintf(cb, "%s", yang_argument_get(ys));
      }
      else{
  	if (yang_keyword_get(ys) == Y_LEAF && yp && 
  	    yang_keyword_get(yp) == Y_LIST){
  	    if (yang_key_match(yp, yang_argument_get(ys)) == 0)
  		cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
  	}
  	else  
  	    if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
  		cprintf(cb, "%s", yang_argument_get(ys));
      }
  
      switch (yang_keyword_get(ys)){
      case Y_LIST:
  	cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
  	if (cvec_len(cvk))
  	    cprintf(cb, "=");
  	/* Iterate over individual keys  */
  	for (i=0; i<cvec_len(cvk); i++){
  	    if (i)
  		cprintf(cb, ",");
  	    cprintf(cb, "%%s");
  	}
  	break;
      case Y_LEAF_LIST:
  	cprintf(cb, "=%%s");
  	break;
      default:
  	break;
      } /* switch */
      retval = 0;
   done:
      return retval;
  }
  
  /*! Construct an api_path_format from yang statement using wildcards for keys
   * Recursively construct it to the top.
   * Example: 
   *   yang:  container a -> list b -> key c -> leaf d
   *   api_path: /a/b=%s/d
   * @param[in]  ys           Yang statement
   * @param[in]  inclkey      If set include key leaf (eg last leaf d in ex)
   * @param[out] api_path_fmt XML api path. Needs to be freed after use.
   * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   */ 
  int
  yang2api_path_fmt(yang_stmt *ys, 
  		  int        inclkey,
  		  char     **api_path_fmt)
  {
      int   retval = -1;
      cbuf *cb = NULL;
  
      if ((cb = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
      if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
  	goto done;
      if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
  	clicon_err(OE_UNIX, errno, "strdup");
  	goto done;
      }
      retval = 0;
   done:
      if (cb)
  	cbuf_free(cb);
      return retval;
  }
  
  /*! Transform an xml key format and a vector of values to an XML key
   * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
   * Example: 
   *   xmlkeyfmt:  /interfaces/interface=%s/ipv4/address=%s
   *   cvv:     0 : set interfaces interface e ipv4 address 1.2.3.4
   *            1 : name = "e"
   *            2 : ip = "1.2.3.4"
   *   api_path:  /interfaces/interface=e/ipv4/address=1.2.3.4
   * @param[in]  api_path_fmt  XML key format, eg /aaa/%s/name
   * @param[in]  cvv           cligen variable vector, one for every wildchar in api_path_fmt
   * @param[out] api_path      api_path, eg /aaa/17. Free after use
   * @note first and last elements of cvv are not used,..
   * @see api_path_fmt2xpath
   * @example
   *  api_path_fmt: /interfaces/interface=%s/name
   *  cvv:          -
   *  api_path:     /interfaces/interface/name
   * @example
   *  api_path_fmt: /interfaces/interface=%s/name
   *  cvv:          e0
   *  api_path:     /interfaces/interface=e0/name
   * @example
   *  api_path_fmt: /subif-entry=%s,%s/subid
   *  cvv:          foo
   *  api_path:     /subif-entry=foo/subid
   *
   * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   */
  int
  api_path_fmt2api_path(char  *api_path_fmt, 
  		      cvec  *cvv, 
  		      char **api_path)
  {
      int   retval = -1;
      char  c;
      int   esc=0;
      cbuf *cb = NULL;
      int   i;
      int   j;
      char *str;
      char *strenc=NULL;
      cg_var *cv;
      
      if ((cb = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
      j = 1; /* j==0 is cli string */
      for (i=0; i<strlen(api_path_fmt); i++){
  	c = api_path_fmt[i];
  	if (esc){
  	    esc = 0;
  	    if (c!='s')
  		continue;
  	    if (j == cvec_len(cvv)) /* last element */
  		;
  	    else{
  		cv = cvec_i(cvv, j++);
  		if ((str = cv2str_dup(cv)) == NULL){
  		    clicon_err(OE_UNIX, errno, "cv2str_dup");
  		    goto done;
  		}
  		if (uri_percent_encode(&strenc, "%s", str) < 0)
  		    goto done;
  		cprintf(cb, "%s", strenc); 
  		free(strenc); strenc = NULL;
  		free(str); str = NULL;
  	    }
  	}
  	else
  	    if (c == '%')
  		esc++;
  	    else{
  		if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%' && j == cvec_len(cvv))
  		    ; /* skip */
  		else
  		    cprintf(cb, "%c", c);
  	    }
      }
      if ((*api_path = strdup(cbuf_get(cb))) == NULL){
  	clicon_err(OE_UNIX, errno, "strdup");
  	goto done;
      }
      retval = 0;
   done:
      if (cb)
  	cbuf_free(cb);
      return retval;
  }
  
  /*! Transform an xml key format and a vector of values to an XML path
   * Used to input xmldb_get()
   * @param[in]  api_path_fmt  XML key format
   * @param[in]  cvv    cligen variable vector, one for every wildchar in api_path_fmt
   * @param[out] xpath     XPATH
   * Add .* in last %s position.
   * @example
   *   api_path_fmt:  /interface/%s/address/%s 
   *   cvv:           name=eth0
   *   xpath:         /interface/[name='eth0']/address
   * @example
   *   api_path_fmt:  /ip/me/%s (if key)
   *   cvv:           -
   *   xpath:        /ipv4/me/a
   * @example 
   *   api_path_fmt: /subif-entry=%s,%s/subid
   *   cvv:          foo
   *   xpath:        /subif-entry[if-name=foo]/subid"
   * @example 
   *   api_path_fmt: /a:b/c
   *   xpath       : /b/c prefix:a
   * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   */ 
  int
  api_path_fmt2xpath(char  *api_path_fmt, 
  		   cvec  *cvv, 
  		   char **xpath)
  {
      int     retval = -1;
      char    c;
      int     esc=0;
      cbuf   *cb = NULL;
      int     i;
      int     j;
      char   *str;
      cg_var *cv;
  
      if ((cb = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
      j = 1; /* j==0 is cli string */
      for (i=0; i<strlen(api_path_fmt); i++){
  	c = api_path_fmt[i];
  	if (esc){
  	    esc = 0;
  	    if (c!='s')
  		continue;
  	    if (j == cvec_len(cvv)) /* last element */
  		;
  	    else{
  		cv = cvec_i(cvv, j++);
  		if ((str = cv2str_dup(cv)) == NULL){
  		    clicon_err(OE_UNIX, errno, "cv2str_dup");
  		    goto done;
  		}
  		cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
  		free(str);
  	    }
  	}
  	else /* regular char */
  	    if (c == '%')
  		esc++;
  	    else{
  		if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
  		    ; /* skip */
  		else
  		    cprintf(cb, "%c", c);
  	    }
      }
      if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
  	clicon_err(OE_UNIX, errno, "strdup");
  	goto done;
      }
      retval = 0;
   done:
      if (cb)
  	cbuf_free(cb);
      return retval;
  }
  
  /*! Translate from restconf api-path(cvv) to xml xpath(cbuf) and namespace context
   * 
   * @param[in]     api_path URI-encoded path expression" (RFC8040 3.5.3) as cvec
   * @param[in]     offset   Offset of cvec, where api-path starts
   * @param[in]     yspec    Yang spec
   * @param[in,out] xpath    The xpath as cbuf (must be created and may have content)
   * @param[out]    nsc      Namespace context of xpath (free w xml_nsctx_free)
   * @param[out]    xerr     Netconf error message
   * @retval        1        OK
   * @retval        0        Invalid api_path or associated XML, netconf error xml set
   * @retval       -1        Fatal error, clicon_err called
   *
   * @code
   *   cbuf *xpath = cbuf_new();
   *   cvec *cvv = NULL; 
   *   cvec *nsc = NULL;
   *   if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0)
   *      err;
   *   if ((ret = api_path2xpath_cvv(yspec, cvv, 0, cxpath, &nsc, NULL)) < 0)
   *      err;
   *   if (ret == 1)
   *     ... access xpath as cbuf_get(xpath) 
   *   cbuf_free(xpath);
   *   cvec_free(nsc);
   * @endcode
   * It works like this:
   * Assume origin incoming path is 
   * "www.foo.com/restconf/a/b=c",  pi is 2 and pcvec is:
   *   ["www.foo.com" "restconf" "a" "b=c"]
   * which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
   * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   * @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
   * @note Not proper namespace translation from api-path 2 xpath!!!
   * @see api_path2xml  For api-path to xml tree
   * @see api_path2xpath  Using strings as parameters
   */
  static int
  api_path2xpath_cvv(cvec       *api_path,
  		   int         offset,
  		   yang_stmt  *yspec,
  		   cbuf       *xpath,
  		   cvec      **nscp,
  		   cxobj     **xerr)
  {
      int        retval = -1;
      int        i;
      cg_var    *cv;
      char      *nodeid;
      char      *prefix = NULL;  /* api-path (module) prefix */
      char      *xprefix = NULL; /* xml xpath prefix */
      char      *name = NULL;
      cvec      *cvk = NULL; /* vector of index keys */
      yang_stmt *y = NULL;
      yang_stmt *ymod = NULL;
      char      *val;
      cg_var    *cvi;
      char     **valvec = NULL;
      int        vi;
      int        nvalvec;
      cbuf      *cberr = NULL;
      char      *namespace = NULL;
      cvec      *nsc = NULL;
  			
      cprintf(xpath, "/");
      /* Initialize namespace context */
      if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
  	goto done;
      if ((cberr = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
      for (i=offset; i<cvec_len(api_path); i++){
          cv = cvec_i(api_path, i);
  	nodeid = cv_name_get(cv);
  	/* api-path: prefix points to module */
  	if (nodeid_split(nodeid, &prefix, &name) < 0)
  	    goto done;
  	clicon_debug(1, "%s [%d] cvname: %s:%s",
  		     __FUNCTION__, i, prefix?prefix:"", name);
  	/* top-node must have prefix */
  	if (i == offset && prefix == NULL){
  	    cprintf(cberr, "'%s': Expected prefix:name", nodeid);
  	    if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  		goto done;
  	    goto fail;
  	}
  	ymod = NULL;
  	if (prefix){ /* if prefix -> get module + change namespace */
  	    if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
  		cprintf(cberr, "No such yang module: %s", prefix);
  		if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  		    goto done;
  		goto fail;
  	    }
  	    namespace = yang_find_mynamespace(ymod); /* change namespace */
  	}
  	if (i == offset && ymod) /* root */
  	    y = yang_find_datanode(ymod, name);
  	else
  	    y = yang_find_datanode(y, name);
  	if (y == NULL){
  	    if (xerr && netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
  		goto done;
  	    goto fail;
  	}
  
  	/* Get XML/xpath prefix given namespace.
  	 * note different from api-path prefix
  	 */
  	if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
  	    xprefix = yang_find_myprefix(y);
  	    clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
  	    /* not found, add it to nsc 
  	     * XXX: do we always have to add it? It could be default?
  	     */
  	    //	    if (xml2prefix(x1, namespace, &pexisting));
  	    if (xml_nsctx_add(nsc, xprefix, namespace) < 0)
  		goto done;
  	}
  	/* Check if has value, means '=' */
          if (cv2str(cv, NULL, 0) > 0){
              if ((val = cv2str_dup(cv)) == NULL)
                  goto done;
  	    switch (yang_keyword_get(y)){
  	    case Y_LIST:
  		/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
  		 * Note that vnr can be < length of cvk, due to empty or unset values
  		 */
  		if (valvec){ /* loop, valvec may have been used before */
  		    free(valvec);
  		    valvec = NULL;
  		}
  		if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
  		    goto done;
  		cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
  		cvi = NULL;
  		/* Iterate over individual yang keys  */
  		cprintf(xpath, "/");
  		if (xprefix)
  		    cprintf(xpath, "%s:", xprefix);
  		cprintf(xpath, "%s", name);
  		vi = 0;
  		while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
  		    cprintf(xpath, "[");
  		    if (xprefix)
  			cprintf(xpath, "%s:", xprefix);
  		    cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
  		}
  		break;
  	    case Y_LEAF_LIST: /* XXX: LOOP? */
  		cprintf(xpath, "/");
  		if (xprefix)
  		    cprintf(xpath, "%s:", xprefix);
  		cprintf(xpath, "%s", name);
  		if (val)
  		    cprintf(xpath, "[.='%s']", val);
  		else
  		    cprintf(xpath, "[.='']");
  		break;
  	    default:
  		if (i != offset)
  		    cprintf(xpath, "/");
  		if (xprefix)
  		    cprintf(xpath, "%s:", xprefix);
  		cprintf(xpath, "%s", name);
  		break;
  	    }
  	    if (val)
  		free(val);
          }
          else{
  	    if (i != offset)
  		cprintf(xpath, "/");
  	    if (xprefix)
  		cprintf(xpath, "%s:", xprefix);
  	    cprintf(xpath, "%s", name);
  	}
  	if (prefix){
  	    free(prefix);
  	    prefix = NULL;
  	}
  	if (name){
  	    free(name);
  	    name = NULL;
  	}
      } /* for */
      retval = 1; /* OK */
      if (nscp){
  	*nscp = nsc;
  	nsc = NULL;
      }
   done:
      clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
      if (cberr)
  	cbuf_free(cberr);
      if (valvec)
  	free(valvec);
      if (prefix)
  	free(prefix);
      if (nsc)
  	cvec_free(nsc);
      if (name)
  	free(name);
      return retval;
   fail:
      retval = 0; /* Validation failed */
      goto done;
  }
  
  /*! Translate from restconf api-path to xml xpath and namespace
   * @param[in]  api_path  URI-encoded path expression" (RFC8040 3.5.3)
   * @param[in]  yspec     Yang spec
   * @param[out] xpath     xpath (use free() to deallocate)
   * @param[out] nsc       Namespace context of xpath (free w xml_nsctx_free)
   * @param[out] xerr      Netconf error message
   * @retval     1         OK
   * @retval     0         Invalid api_path or associated XML, netconf called
   * @retval    -1         Fatal error, clicon_err called
   * @code
   *   char *xpath = NULL;
   *   cvec *nsc = NULL;
   *   if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc)) < 0)
   *      err;
   *   if (ret == 1)
   *      ... access xpath as cbuf_get(xpath) 
   *   free(xpath)
   *   cvec_free(nsc);
   * @endcode
   * @see api_path2xml  For api-path to xml translation (maybe could be combined?)
   */
  int
  api_path2xpath(char       *api_path,
  	       yang_stmt  *yspec,
  	       char      **xpathp,
  	       cvec      **nsc,
  	       cxobj     **xerr)
  {
      int    retval = -1;
      cvec  *cvv = NULL; /* api-path vector */
      cbuf  *xpath = NULL; /* xpath as cbuf (sub-function uses that) */
      int    ret;
  
      if (api_path == NULL){
  	clicon_err(OE_XML, EINVAL, "api_path is NULL");
  	goto done;
      }
      /* Split api-path into cligen variable vector */
      if (str2cvec(api_path, '/', '=', &cvv) < 0)
  	goto done;
      if ((xpath = cbuf_new()) == NULL)
          goto done;
      if ((ret = api_path2xpath_cvv(cvv, 0, yspec, xpath, nsc, xerr)) < 0)
  	goto done;
      if (ret == 0)
  	goto fail;
      /* prepare output xpath parameter */
      if (xpathp)
  	if ((*xpathp = strdup(cbuf_get(xpath))) == NULL){
  	    clicon_err(OE_UNIX, errno, "strdup");
  	    goto done;
  	}
      retval = 1;
   done:
      if (cvv)
  	cvec_free(cvv);
      if (xpath)
  	cbuf_free(xpath);
      return retval;
   fail:
      retval = 0; /* Validation failed */
      goto done;
  }
  
  /*! Create xml tree from api-path as vector
   * @param[in]   vec       APIpath as char* vector
   * @param[in]   nvec      Length of vec
   * @param[in]   x0        Xpath tree so far
   * @param[in]   y0        Yang spec for x0
   * @param[in]   nodeclass Set to schema nodes, data nodes, etc
   * @param[out]  xbotp     Resulting xml tree 
   * @param[out]  ybotp    Yang spec matching xpathp
   * @param[out]  xerr      Netconf error message (if retval=0)
   * @retval      1         OK
   * @retval      0         Invalid api_path or associated XML, netconf error
   * @retval     -1         Fatal error, clicon_err called
   *
   * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
   * @see api_path2xpath For api-path to xml xpath translation
   * @see api_path2xml
   */
  static int
  api_path2xml_vec(char      **vec,
  		 int         nvec,
  		 cxobj      *x0,
  		 yang_stmt  *y0,
  		 yang_class  nodeclass,
  		 int         strict,
  		 cxobj     **xbotp,
  		 yang_stmt **ybotp,
  		 cxobj     **xerr)
  {
      int        retval = -1;
      char      *nodeid;
      char      *name = NULL;
      char      *prefix = NULL;
      char      *restval = NULL;
      char      *restval_enc;
      cxobj     *xn = NULL; /* new */
      cxobj     *xb;        /* body */
      cvec      *cvk = NULL; /* vector of index keys */
      cg_var    *cvi;
      char      *keyname;
      char     **valvec = NULL;
      int        nvalvec = 0;
      int        vi;
      cxobj     *x = NULL;
      yang_stmt *y = NULL;
      yang_stmt *ymod;
      yang_stmt *ykey;
      char      *namespace = NULL;
      cbuf      *cberr = NULL;
  
      if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
  	if (xbotp)
  	    *xbotp = x0;
  	if (ybotp)
  	    *ybotp = y0;
  	goto ok;
      } /* E.g "x=1,2" -> nodeid:x restval=1,2 */
      if ((cberr = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
  
      /* restval is RFC 3896 encoded */
      if ((restval_enc = index(nodeid, '=')) != NULL){
  	*restval_enc = '\0';
  	restval_enc++;
  	if (uri_percent_decode(restval_enc, &restval) < 0)
  	    goto done;
      }
      /* Split into prefix and localname */
      if (nodeid_split(nodeid, &prefix, &name) < 0)
  	goto done;
      if (yang_keyword_get(y0) == Y_SPEC){ /* top-node */
  	if (prefix == NULL){
  	    cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid);
  	    if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  		goto done;
  	    goto fail;
  	}
  	if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
  	    cprintf(cberr, "No such yang module prefix");
  	    if (netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0)
  		goto done;
  	    goto fail;
  	}
  	namespace = yang_find_mynamespace(ymod);
  	y0 = ymod;
      }
      y = (nodeclass==YC_SCHEMANODE)?
  	yang_find_schemanode(y0, name):
  	yang_find_datanode(y0, name);
      if (y == NULL){
  	if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
  	    goto done;
  	goto fail;
      }
      if (prefix && namespace == NULL){
  	if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
  	    cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix);
  	    if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  		goto done;
  	    goto fail;
  	}
  	namespace = yang_find_mynamespace(ymod);
      }
      switch (yang_keyword_get(y)){
      case Y_LEAF_LIST:
  	if (0 && restval==NULL){
  	    clicon_err(OE_XML, 0, "malformed key, expected '=restval'");
  	    goto done;
  	}
  	if ((x = xml_new(yang_argument_get(y), x0, CX_ELMNT)) == NULL)
  	    goto done;
  	xml_spec_set(x, y);
  
  	if ((xb = xml_new("body", x, CX_BODY)) == NULL)
  	    goto done; 
  	if (restval && xml_value_set(xb, restval) < 0)
  	    goto done;
  	break;
      case Y_LIST:
  	cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
  	if (valvec){ /* loop, valvec may have been used before */
  	    free(valvec);
  	    valvec = NULL;
  	}
  	if (restval==NULL){
  	    if (strict){
  		cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
  		if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
  		    goto done;
  	        goto fail;
  	    }
  	}
  	else{
  	    /* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3) 
  	     * Note that vnr can be < length of cvk, due to empty or unset values
  	     */
  	    if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
  		goto done;
  	    if ((nvalvec != cvec_len(cvk)) && strict){ 	    
  		cprintf(cberr, "List key %s length mismatch", name);
  		if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
  		    goto done;
  		goto fail;
  	    }
  	}
  	cvi = NULL;
  	/* create list object */
  	if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
  	    goto done;
  	xml_spec_set(x, y);
  
  	vi = 0;
  	/* Create keys */
  	while ((cvi = cvec_each(cvk, cvi)) != NULL){
  	    keyname = cv_string_get(cvi);
  	    if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
  		cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"",
  			yang_argument_get(y), keyname);
  		if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  		    goto done;
  		goto fail;
  	    }
  	    if ((xn = xml_new(keyname, x, CX_ELMNT)) == NULL)
  		goto done; 
  	    xml_spec_set(xn, ykey);
  
  	    if ((xb = xml_new("body", xn, CX_BODY)) == NULL)
  		goto done;
  	    if (vi++ < nvalvec){
  		if (xml_value_set(xb, valvec[vi-1]) < 0)
  		    goto done;
  	    }
  	}
  	break;
      default: /* eg Y_CONTAINER, Y_LEAF */
  	if ((x = xml_find_type(x0, NULL, name, CX_ELMNT)) == NULL){ /* eg key of list */
  	    if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
  		goto done;
  	    xml_spec_set(x, y);
  	}
  	break;
      }
      if (x && namespace){
  	if (xmlns_set(x, NULL, namespace) < 0)
  	    goto done;
      }
      if ((retval = api_path2xml_vec(vec+1, nvec-1, 
  				   x, y, 
  				   nodeclass, strict,
  				   xbotp, ybotp, xerr)) < 1)
  	goto done;
   ok:
      retval = 1; /* OK */
   done:
      clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
      if (cberr)
  	cbuf_free(cberr);
      if (prefix)
  	free(prefix);
      if (name)
  	free(name);
      if (restval)
  	free(restval);
      if (valvec)
  	free(valvec);
      return retval;
   fail:
      retval = 0; /* invalid api-path or XML */
      goto done;
  }
  
  /*! Create xml tree from api-path
   * @param[in]     api_path   (Absolute) API-path as defined in RFC 8040
   * @param[in]     yspec      Yang spec
   * @param[in,out] xtop       Incoming XML tree
   * @param[in]     nodeclass  Set to schema nodes, data nodes, etc
   * @param[out]    xbotp      Resulting xml tree (end of xpath)
   * @param[out]    ybotp      Yang spec matching xbotp
   * @param[out]    xerr       Netconf error message (if retval=0)
   * @retval        1          OK
   * @retval        0          Invalid api_path or associated XML, netconf error
   * @retval       -1          Fatal error, clicon_err called
   * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
   * @example
   *   api_path: /subif-entry=foo/subid
   *   xtop[in]  <config/> 
   *   xtop[out]:<config/> <subif-entry>
   *                            <if-name>foo<if-name><subid/>>
   *                       </subif-entry></config>
   *   xbotp:    <subid/>
   *   ybotp:    Y_LEAF subid
   * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
   * @see api_path2xpath   For api-path to xpath translation (maybe could be combined?)
   */
  int
  api_path2xml(char       *api_path,
  	     yang_stmt  *yspec,
  	     cxobj      *xtop,
  	     yang_class  nodeclass,
  	     int         strict,
  	     cxobj     **xbotp,
  	     yang_stmt **ybotp,
  	     cxobj     **xerr)
  {
      int    retval = -1;
      char **vec = NULL;
      int    nvec;
      cxobj *xroot;
      cbuf  *cberr = NULL;
  
      clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
      if (xtop == NULL){
  	clicon_err(OE_XML, EINVAL, "xtop is NULL");
  	goto done;
      }
      if ((cberr = cbuf_new()) == NULL){
  	clicon_err(OE_UNIX, errno, "cbuf_new");
  	goto done;
      }
      if (*api_path != '/'){
  	cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path);
  	if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  	    goto done;
  	goto fail;
      }
      if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
  	goto done;
      /* Remove trailing '/'. Like in /a/ -> /a */
      if (nvec > 1 && !strlen(vec[nvec-1]))
  	nvec--;
      if (nvec < 1){
  	cprintf(cberr, "Malformed api-path: %s: too short)", api_path);
  	if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
  	    goto done;
  	goto fail;
      }
      nvec--; /* NULL-terminated */
      if ((retval = api_path2xml_vec(vec+1, nvec,
  				   xtop, yspec, nodeclass, strict, 
  				   xbotp, ybotp, xerr)) < 1)
  	goto done;
      xml_yang_root(*xbotp, &xroot);
      if (xmlns_assign(xroot) < 0)
  	goto done;
      // ok:
      retval = 1;
   done:
      if (cberr)
  	cbuf_free(cberr);
      if (vec)
  	free(vec);
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Construct an api_path from an XML node (single level not recursive)
   * @param[in]  x     XML node (need to be yang populated)
   * @param[out] cb    api_path, must be initialized
   * @retval     0     OK
   * @retval    -1     Error
   * @see yang2api_path_fmt
   * @see xml2xpath
   */
  int
  xml2api_path_1(cxobj *x,
  
  	       cbuf  *cb)
  {
      int           retval = -1;
      yang_stmt    *y = NULL;
      cvec         *cvk = NULL; /* vector of index keys */
      cg_var       *cvi;
      enum rfc_6020 keyword;
      int           i;
      char         *keyname;
      cxobj        *xkey;
      cxobj        *xb;
      char         *b;
      char         *enc;
      yang_stmt    *ymod;
      cxobj        *xp;
      
      if ((y = xml_spec(x)) == NULL){
  	cprintf(cb, "/%s", xml_name(x));
  	goto ok;
      }
      ymod = ys_module(y);
      xp = xml_parent(x);
      if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */
  	cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x));
      else
  	cprintf(cb, "/%s", xml_name(x));
      keyword = yang_keyword_get(y);
      switch (keyword){
      case Y_LEAF_LIST:
  	b = xml_body(x);
  	enc = NULL;
  	if (uri_percent_encode(&enc, "%s", b) < 0)
  	    goto done;
  	cprintf(cb, "=%s", enc?enc:"");
  	if (enc)
  	    free(enc);
  	break;
      case Y_LIST:
  	cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
  	if (cvec_len(cvk))
  	    cprintf(cb, "=");
  	/* Iterate over individual keys  */
  	cvi = NULL;
  	i = 0;
  	while ((cvi = cvec_each(cvk, cvi)) != NULL) {
  	    keyname = cv_string_get(cvi);
  	    if ((xkey = xml_find(x, keyname)) == NULL)
  		goto done; /* No key in xml */
  	    if ((xb = xml_find(x, keyname)) == NULL)
  		goto done;
  	    if (i++)
  		cprintf(cb, ",");
  	    b = xml_body(xb);
  	    enc = NULL;
  	    if (uri_percent_encode(&enc, "%s", b) < 0)
  		goto done;
  	    cprintf(cb, "%s", enc?enc:"");
  	    if (enc)
  		free(enc);
  	}
  	break;
      default:
  	break;
      }
  #if 0
      { /* Just for testing */
  	cxobj *xc;
  	if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
  	    if (xml2api_path_1(xc, cb) < 0)
  		goto done;
      }
  #endif
   ok:
      retval = 0;
   done:
      return retval;
  }
  
  /*! Resolve api-path module:names to yang statements
   * @param[in]  cplist   Lisp of clixon-path
   * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
   * @retval    -1        Error
   * @retval     0        Fail
   * @retval     1        OK
   * Reasons for fail (retval = 0) are: XXX
   * - Modulename in api-path does not correspond to existing module
   * - Modulename not defined for top-level id. 
   * - Corresponding yang node for id not found
   * - Number of keys in key-value list does not match Yang list
   * - Key-values only defined for list or leaf-list
   * @see instance_id_resolve
   */
  static int
  api_path_resolve(clixon_path *cplist,
  		 yang_stmt   *yt)
  {
      int          retval = -1;
      clixon_path *cp;
      yang_stmt   *yc;
      int          i;
      cg_var      *cva;
      cg_var      *cvy;
      
      if ((cp = cplist) != NULL){
  	do {
  	    if (yang_keyword_get(yt) == Y_SPEC){
  		if (cp->cp_prefix == NULL){
  		    clicon_err(OE_YANG, ENOENT, "Modulename not defined for top-level id.");
  		    goto fail;
  		}
  		if ((yt = yang_find_module_by_name(yt, cp->cp_prefix)) == NULL){
  		    clicon_err(OE_YANG, ENOENT, "Modulename in api-path does not correspond to existing module");
  		    goto fail;
  		}
  	    }
  	    if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
  		clicon_err(OE_YANG, ENOENT, "Corresponding yang node for id not found");
  		goto fail;
  	    }
  	    cp->cp_yang = yc;
  	    if (cp->cp_cvk){
  		/* Iterate over yang list keys and assign as names (or "." for leaf-list) in cvk */
  		if (yang_keyword_get(yc) == Y_LEAF_LIST){
  		    cva = NULL;
  		    while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
  			if (cv_name_get(cva) == NULL)
  			    cv_name_set(cva, ".");
  		    }
  		}
  		else if (yang_keyword_get(yc) == Y_LIST){
  		    if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
  			clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list");
  			goto fail;
  		    }
  		    i = 0;
  		    cva = NULL;
  		    while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
  			if (cv_name_get(cva) != NULL){
  			    clicon_err(OE_YANG, ENOENT, "Unexpected named key %s (shouldnt happen)",
  				       cv_name_get(cva));
  			    goto fail;
  			}
  			cvy = cvec_i(yang_cvec_get(yc), i++);
  			cv_name_set(cva, cv_string_get(cvy));
  		    }
  		}
  		else{
  		    clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
  		    goto fail;
  		}
  	    }
  	    yt = yc;
      	    cp = NEXTQ(clixon_path *, cp);
  	} while (cp && cp != cplist);
      }
      retval = 1;
   done:
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Resolve instance-id prefix:names to yang statements
   * @param[in]  cplist   Lisp of clixon-path
   * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
   * @param[in]  xt       XML statement  for prefix context
   * @retval    -1        Error
   * @retval     0        Fail
   * @retval     1        OK
   * @note: The spec says: prefixes depend on the XML context in which the value occurs. 
   *  However, canonical prefixes/namespaces are used based on loaded yang modules.
   *  This means that prefix=NULL is not allowed.
   * Reasons for fail (retval = 0) are:
   * - No prefix of identifier (keynames may omit prefix)
   * - Prefix does not correspond to existing module.
   * - Corresponding yang node for id not found
   * - Number of keys in key-value list does not match Yang list
   * - Key-values only defined for list or leaf-list
   * @note key-value presence in lists is not enforced due to use-cases where instance-id need not have
   *       them (eg RFC8341) alternative could be to have a "strict" parameter, but gets complicated
   * @see api_path_resolve
   */
  static int
  instance_id_resolve(clixon_path *cplist,
  		    yang_stmt   *yt)
  {
      int          retval = -1;
      clixon_path *cp;
      yang_stmt   *yc;
      cg_var      *cva;
      yang_stmt   *yspec;
      char        *kname;
      
      yspec = ys_spec(yt);
      if ((cp = cplist) != NULL){
  	do {
  	    if (cp->cp_prefix == NULL){
  		clicon_err(OE_YANG, ENOENT, "No prefix of identifier (keynames may omit prefix)");
  		goto fail;
  	    }
  	    if (yang_keyword_get(yt) == Y_SPEC){
  		if ((yt = yang_find_module_by_prefix_yspec(yspec, cp->cp_prefix)) == NULL){
  		    clicon_err(OE_YANG, ENOENT, "Prefix does not correspond to existing module.");
  		    goto fail;
  		}
  	    }
  	    if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
  		clicon_err(OE_YANG, ENOENT, "Node %s used in path has no corresponding yang node",
  			   cp->cp_id);
  		goto fail;
  	    }
  	    cp->cp_yang = yc;
  	    switch (yang_keyword_get(yc)){
  	    case Y_LIST:
  		if (cp->cp_cvk == NULL){
  #if 0 /* see key-value presence in lists note above */
  		    clicon_err(OE_YANG, ENOENT, "key-values mandatory for lists");
  		    goto fail;		    
  #endif
  		    break;
  		}
  #if 0 /* see key-value presence in lists note above */
  		if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
  		    clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list ");
  		    goto fail;
  		}
  #endif
  		cva = NULL;
  		while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
  		    if ((kname = cv_name_get(cva)) != NULL){ /* Check var exists */
  			if (yang_find(yc, 0, kname) == NULL){
  			    clicon_err(OE_YANG, ENOENT, "Index variable %s used in path is not child of Yang node %s",
  				       kname, yang_argument_get(yc));
  			    goto fail;
  			}
  		    }
  		    else{ /* Assume index */
  			
  		    }
  		}
  		break;
  	    case Y_LEAF_LIST:
  		break;
  	    default:
  		if (cp->cp_cvk != NULL){
  		    clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
  		    goto fail;
  		}
  		break;
  	    }
  	    yt = yc;
      	    cp = NEXTQ(clixon_path *, cp);
  	} while (cp && cp != cplist);
      }
      retval = 1;
   done:
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Search XML tree using (internal) Clixon path struct 
   *
   * @param[in]  xt       Top xml-tree where to search
   * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
   * @param[in]  cplist   Lisp of clixon-path
   * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
   * @retval    -1        Error
   * @retval     0        Fail  fail: eg no yang 
   * @retval     1        OK with found xml nodes in xvec (if any)
   */
  static int
  clixon_path_search(cxobj        *xt,
  		   yang_stmt    *yt,
  		   clixon_path  *cplist,
  		   clixon_xvec **xvec0)
  {
      int          retval = -1;
      char        *modns; /* Module namespace of api-path */
      clixon_path *cp;
      clixon_xvec *xvecp = NULL;
      clixon_xvec *xvecc = NULL;
      yang_stmt   *yc;
      cxobj       *xp;	
      int          i;
      cg_var      *cv;
  
      if ((xvecp = clixon_xvec_new()) == NULL)
  	goto done;    
      modns = NULL;
      if ((cp = cplist) != NULL){
  	if (clixon_xvec_append(xvecp, xt) < 0)
  	    goto done;
  	do {
  	    yc = cp->cp_yang;
  	    if ((modns = yang_find_mynamespace(yc)) == NULL)
  		goto fail;
  	    if (xvecp){
  		if ((xvecc = clixon_xvec_new()) == NULL)
  		    goto done;
  		for (i=0; i<clixon_xvec_len(xvecp); i++){
  		    xp = clixon_xvec_i(xvecp, i); /* Iterate over parent set */
  		    if (cp->cp_cvk && /* cornercase for instance-id [<pos>] special case */
  			(yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) &&
  			cvec_len(cp->cp_cvk) == 1 && (cv = cvec_i(cp->cp_cvk,0)) &&
  			(cv_type_get(cv) == CGV_UINT32)){
  			if (clixon_xml_find_pos(xp, yc, cv_uint32_get(cv), xvecc) < 0)
  			    goto done;
  		    }
  		    else if (clixon_xml_find_index(xp, yang_parent_get(yc),
  						   modns, yang_argument_get(yc),
  						   cp->cp_cvk, xvecc) < 0)
  			goto done;
  		    /* XXX: xvecc append! either here or in the functions */
  		} /* for */
  	    }
  	    if (xvecp)
  		clixon_xvec_free(xvecp);
  	    xvecp = xvecc;
  	    xvecc = NULL;
  	    cp = NEXTQ(clixon_path *, cp);
  	} while (cp && cp != cplist);
  	*xvec0 = xvecp;
  	xvecp = NULL;
      } /* if */
      retval = 1;
   done:
      if (xvecp)
  	clixon_xvec_free(xvecp);
      if (xvecc)
  	clixon_xvec_free(xvecc);
      return retval;
   fail: /* eg no yang for api-path, no match */
      *xvec0 = NULL;
      retval = 0;
      goto done;
  }
  
  /*! Given RESTCONF api-path and an XML tree, return matching xml node vector using stdarg
   *
   * @param[in]  xt       Top xml-tree where to search
   * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
   * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
   * @param[in]  format   Format string for api-path syntax
   * @retval    -1        Error
   * @retval     0        Non-fatal failure, yang bind failures, etc, 
   * @retval     1        OK with found xml nodes in xvec (if any) (xvec contains matches)
   * Reasons for nomatch (retval = 0) are:
   * - Modulename in api-path does not correspond to existing module
   * - Modulename not defined for top-level id. 
   * - Number of keys in key-value list does not match Yang list
   * @code
   *    cxobj **vec = NULL;
   *    size_t  len = 0;
   *    if (clixon_xml_find_api_path(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0) 
   *       err;
   *    for (i=0; i<len; i++){
   *       x = vec[i];
   *       ...
   *    }
   *    free(xvec);
   * @endcode
   * @see clixon_xml_find_instance_id
   */
  int
  clixon_xml_find_api_path(cxobj        *xt, 
  			 yang_stmt    *yt,
  			 cxobj      ***xvec,
  			 int          *xlen,
  			 const char   *format,
  			 ...)
  {
      int              retval = -1;
      va_list          ap;
      size_t           len;
      char            *api_path = NULL;
      clixon_path     *cplist = NULL;
      int              ret;
      clixon_xvec     *xv = NULL;
      
      va_start(ap, format);
      len = vsnprintf(NULL, 0, format, ap);
      va_end(ap);
      /* allocate an api-path string exactly fitting the length */
      if ((api_path = malloc(len+1)) == NULL){
  	clicon_err(OE_UNIX, errno, "malloc");
  	goto done;
      }
      /* second round: actually compute api-path string content */
      va_start(ap, format);
      if (vsnprintf(api_path, len+1, format, ap) < 0){
  	clicon_err(OE_UNIX, errno, "vsnprintf");
  	va_end(ap);
  	goto done;
      }
      va_end(ap);
      /* Parse api-path string to structured clixon-path data */
      if (api_path_parse(api_path, &cplist) < 0)
  	goto done;
      if (clicon_debug_get())
  	clixon_path_print(stderr, cplist);
      /* Resolve module:name to yang-stmt, fail if not successful */
      if ((ret = api_path_resolve(cplist, yt)) < 0)
  	goto done;
      if (ret == 0)
  	goto fail;
      if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
  	goto done;
      if (ret == 0)
  	goto fail;
      /* Convert to api xvec format */
      if (clixon_xvec_extract(xv, xvec, xlen) < 0)
  	goto done;
      retval = 1;
   done:
      if (xv)
  	clixon_xvec_free(xv);
      if (cplist)
  	clixon_path_free(cplist);
      if (api_path)
  	free(api_path);
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
   *
   * Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for 
   * example.
   * @param[in]  xt       Top xml-tree where to search
   * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
   * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
   * @param[out] xlen     Returns length of vector in return value
   * @param[in]  format   Format string for api-path syntax
   * @retval    -1        Error
   * @retval     0        Non-fatal failure, yang bind failures, etc, 
   * @retval     1        OK with found xml nodes in xvec (if any)
   * Reasons for nomatch (retval = 0) are:
   * - Modulename in api-path does not correspond to existing module
   * - Modulename not defined for top-level id. 
   * - Number of keys in key-value list does not match Yang list
   * @code
   *    cxobj **vec = NULL;
   *    int     len = 0;
   *    if (clixon_xml_find_instance_id(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0) 
   *       goto err;
   *    for (i=0; i<len; i++){
   *       x = vec[i];
   *       ...
   *    }
   *    clixon_xvec_free(xvec);
   * @endcode
   * @note canonical namespace contexts are used, see xpath2canonical
   * @see clixon_xml_find_api_path    for RESTCONF api-paths
   * @see RFC7950 Sec 9.13 
   */
  int
  clixon_xml_find_instance_id(cxobj     *xt, 
  			    yang_stmt *yt,
  			    cxobj   ***xvec,
  			    int       *xlen,
  			    const char *format,
  			    ...)
  {
      int          retval = -1;
      va_list      ap;
      size_t       len;
      char        *path = NULL;
      clixon_path *cplist = NULL;
      int          ret;
      clixon_xvec *xv = NULL;
      
      va_start(ap, format);
      len = vsnprintf(NULL, 0, format, ap);
      va_end(ap);
      /* allocate a path string exactly fitting the length */
      if ((path = malloc(len+1)) == NULL){
  	clicon_err(OE_UNIX, errno, "malloc");
  	goto done;
      }
      /* second round: actually compute api-path string content */
      va_start(ap, format);
      if (vsnprintf(path, len+1, format, ap) < 0){
  	clicon_err(OE_UNIX, errno, "vsnprintf");
  	va_end(ap);
  	goto done;
      }
      va_end(ap);
      if (instance_id_parse(path, &cplist) < 0)
  	goto done;
      if (clicon_debug_get())
  	clixon_path_print(stderr, cplist);
      /* Resolve module:name to pointer to yang-stmt, fail if not successful */
      if ((ret = instance_id_resolve(cplist, yt)) < 0)
  	goto done;
      if (ret == 0)
  	goto fail;
      if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
  	goto done;
      if (ret == 0)
  	goto fail;
      /* Convert to api xvec format */
      if (xv && clixon_xvec_extract(xv, xvec, xlen) < 0)
  	goto done;
      retval = 1;
   done:
      if (xv)
  	clixon_xvec_free(xv);
      if (cplist)
  	clixon_path_free(cplist);
      if (path)
  	free(path);
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  