xml parser NEW opaque parsing of invalid values/lists
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 34e3161..e05f170 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -259,6 +259,91 @@
     return ret;
 }
 
+static LY_ERR
+lydxml_check_list(struct lyxml_context *xmlctx, const struct lysc_node *list, const char **data)
+{
+    LY_ERR ret;
+    struct ly_set key_set = {0};
+    const struct lysc_node *snode;
+    const char *name;
+    char *buffer = NULL, *value;
+    size_t name_len, buffer_size = 0, value_len;
+    int dynamic = 0;
+    uint32_t i, parents_count = xmlctx->elements.count;
+
+    assert(list && (list->nodetype == LYS_LIST));
+
+    /* get all keys into a set (keys do not have if-features or anything) */
+    snode = NULL;
+    while ((snode = lys_getnext(snode, list, NULL, LYS_GETNEXT_NOSTATECHECK)) && (snode->flags & LYS_KEY)) {
+        ly_set_add(&key_set, (void *)snode, LY_SET_OPT_USEASLIST);
+    }
+
+    while ((xmlctx->status == LYXML_ELEMENT) && key_set.count) {
+        /* keys must be from the same module */
+        LY_CHECK_GOTO(ret = lyxml_get_element(xmlctx, data, NULL, NULL, &name, &name_len), cleanup);
+        if (!name) {
+            /* closing previous element */
+            if (xmlctx->elements.count < parents_count) {
+                /* all siblings parsed */
+                break;
+            } else {
+                continue;
+            }
+        }
+
+        /* find key definition */
+        for (i = 0; i < key_set.count; ++i) {
+            snode = (const struct lysc_node *)key_set.objs[i];
+            if (!ly_strncmp(snode->name, name, name_len)) {
+                break;
+            }
+        }
+        if (i == key_set.count) {
+            /* uninteresting, skip it */
+            LY_CHECK_GOTO(ret = lyxml_skip_element(xmlctx, data), cleanup);
+            continue;
+        }
+
+        /* skip attributes */
+        while (xmlctx->status == LYXML_ATTRIBUTE) {
+            LY_CHECK_GOTO(ret = lyxml_get_attribute(xmlctx, data, NULL, NULL, NULL, NULL), cleanup);
+        }
+
+        /* get the value, if any */
+        if (dynamic) {
+            free(value);
+        }
+        value = "";
+        value_len = 0;
+        dynamic = 0;
+        if (xmlctx->status == LYXML_ELEM_CONTENT) {
+            ret = lyxml_get_string(xmlctx, data, &buffer, &buffer_size, &value, &value_len, &dynamic);
+            if (ret && (ret != LY_EINVAL)) {
+                goto cleanup;
+            }
+        }
+
+        /* validate it */
+        LY_CHECK_GOTO(ret = lys_value_validate(NULL, snode, value, value_len, lydxml_resolve_prefix, xmlctx->ctx, LYD_XML), cleanup);
+
+        /* key with a valid value, remove from the set */
+        ly_set_rm_index(&key_set, i, NULL);
+    }
+
+    if (key_set.count) {
+        /* some keys missing */
+        ret = LY_ENOT;
+    } else {
+        /* all keys found and validated */
+        ret = LY_SUCCESS;
+    }
+
+cleanup:
+    ly_set_erase(&key_set, NULL);
+    return ret;
+}
+
 /**
  * @brief Parse XML elements as YANG data node children the specified parent node.
  *
@@ -271,22 +356,21 @@
 static LY_ERR
 lydxml_data_r(struct lyd_xml_ctx *ctx, struct lyd_node_inner *parent, const char **data, struct lyd_node **first)
 {
-    LY_ERR ret = LY_SUCCESS;
-    const char *prefix, *name;
-    size_t prefix_len, name_len;
+    LY_ERR ret = LY_SUCCESS, content_ret;
+    const char *prefix, *name, *backup_data;
+    char *buffer = NULL, *value;
+    size_t prefix_len, name_len, buffer_size = 0, value_len;
     struct ly_set attrs_data = {0};
+    struct lyxml_context backup_ctx;
     const struct lyxml_ns *ns;
     struct lyd_meta *meta = NULL, *meta2, *prev_meta;
     struct ly_attr *attr = NULL;
     const struct lysc_node *snode;
     struct lys_module *mod;
-    unsigned int parents_count = ctx->elements.count;
-    uint32_t prev_opts;
+    uint32_t prev_opts, parents_count = ctx->elements.count;
     struct lyd_node *cur = NULL, *anchor;
     struct ly_prefix *val_prefs;
     int dynamic = 0;
-    char *buffer = NULL, *value;
-    size_t buffer_size = 0, value_len;
 
     while (ctx->status == LYXML_ELEMENT) {
         ret = lyxml_get_element((struct lyxml_context *)ctx, data, &prefix, &prefix_len, &name, &name_len);
@@ -347,6 +431,50 @@
             }
         }
 
+        /* get the value, if any */
+        value = "";
+        value_len = 0;
+        dynamic = 0;
+        content_ret = LY_SUCCESS;
+        if (ctx->status == LYXML_ELEM_CONTENT) {
+            content_ret = lyxml_get_string((struct lyxml_context *)ctx, data, &buffer, &buffer_size, &value, &value_len, &dynamic);
+            if (content_ret && (content_ret != LY_EINVAL)) {
+                LOGINT(ctx->ctx);
+                ret = LY_EINT;
+                goto cleanup;
+            }
+        }
+
+        if (snode && (ctx->options & LYD_OPT_OPAQ)) {
+            if (snode->nodetype & LYD_NODE_TERM) {
+                /* value may not be valid in which case we parse it as an opaque node */
+                if (lys_value_validate(NULL, snode, value, value_len, lydxml_resolve_prefix, ctx, LYD_XML)) {
+                    snode = NULL;
+                }
+            } else if (snode->nodetype == LYS_LIST) {
+                /* use backup context and data pointer */
+                backup_ctx.ctx = ctx->ctx;
+                backup_ctx.line = ctx->line;
+                backup_ctx.status = ctx->status;
+                memset(&backup_ctx.elements, 0, sizeof backup_ctx.elements);
+                for (uint32_t i = 0; i < ctx->elements.count; ++i) {
+                    ly_set_add(&backup_ctx.elements, lyxml_elem_dup(ctx->elements.objs[i]), LY_SET_OPT_USEASLIST);
+                }
+                memset(&backup_ctx.ns, 0, sizeof backup_ctx.ns);
+                for (uint32_t i = 0; i < ctx->ns.count; ++i) {
+                    ly_set_add(&backup_ctx.ns, lyxml_ns_dup(ctx->ns.objs[i]), LY_SET_OPT_USEASLIST);
+                }
+                backup_data = *data;
+
+                /* list may be missing some keys, parse as opaque if it does */
+                if (lydxml_check_list(&backup_ctx, snode, &backup_data)) {
+                    snode = NULL;
+                }
+
+                lyxml_context_clear(&backup_ctx);
+            }
+        }
+
         /* create actual metadata so that prefixes are available in the context */
         if (attrs_data.count) {
             if (snode) {
@@ -369,23 +497,6 @@
 
         if (!snode) {
             if (ctx->options & LYD_OPT_OPAQ) {
-                /* get the value */
-                LY_ERR r = LY_EINVAL;
-                if (ctx->status == LYXML_ELEM_CONTENT) {
-                    r = lyxml_get_string((struct lyxml_context *)ctx, data, &buffer, &buffer_size, &value, &value_len, &dynamic);
-                    if (r && (r != LY_EINVAL)) {
-                        LOGINT(ctx->ctx);
-                        ret = LY_EINT;
-                        goto cleanup;
-                    }
-                }
-                if (r == LY_EINVAL) {
-                    /* empty value */
-                    value = "";
-                    value_len = 0;
-                    dynamic = 0;
-                }
-
                 /* get value prefixes */
                 ret = lyxml_get_prefixes((struct lyxml_context *)ctx, value, value_len, &val_prefs);
                 LY_CHECK_GOTO(ret, cleanup);
@@ -407,22 +518,12 @@
                 break;
             }
         } else if (snode->nodetype & LYD_NODE_TERM) {
-            if (ctx->status == LYXML_ELEM_CONTENT) {
-                /* get the value */
-                ret = lyxml_get_string((struct lyxml_context *)ctx, data, &buffer, &buffer_size, &value, &value_len, &dynamic);
-                if (ret != LY_SUCCESS) {
-                    if (ret == LY_EINVAL) {
-                        /* just indentation of a child element found */
-                        LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_SYNTAX, "Child element inside terminal node \"%s\" found.",
-                               snode->name);
-                    }
-                    goto cleanup;
-                }
-            } else {
-                /* no content - validate empty value */
-                value = "";
-                value_len = 0;
-                dynamic = 0;
+            if (content_ret == LY_EINVAL) {
+                /* just indentation of a child element found */
+                LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_SYNTAX, "Child element inside a terminal node \"%s\" found.",
+                       snode->name);
+                ret = LY_EVALID;
+                goto cleanup;
             }
 
             /* create node */
@@ -452,13 +553,13 @@
                 }
             }
         } else if (snode->nodetype & LYD_NODE_INNER) {
-            if (ctx->status == LYXML_ELEM_CONTENT) {
-                LY_ERR r = lyxml_get_string((struct lyxml_context *)ctx, data, &buffer, &buffer_size, &value, &value_len, &dynamic);
-                if (r != LY_EINVAL && (r != LY_SUCCESS || value_len != 0)) {
-                    LOGINT(ctx->ctx);
-                    ret = LY_EINT;
-                    goto cleanup;
-                }
+            if (value_len) {
+                /* value in inner node */
+                LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_SYNTAX, "Text value inside an inner node \"%s\" found.",
+                       snode->name);
+                ret = LY_EVALID;
+                goto cleanup;
+
             }
 
             /* create node */
@@ -473,8 +574,7 @@
 
             if (snode->nodetype == LYS_LIST) {
                 /* check all keys exist */
-                ret = lyd_parse_check_keys(cur);
-                LY_CHECK_GOTO(ret, cleanup);
+                LY_CHECK_GOTO(ret = lyd_parse_check_keys(cur), cleanup);
             }
 
             if (!(ctx->options & LYD_OPT_PARSE_ONLY)) {
@@ -578,8 +678,8 @@
     ly_free_attr(ctx->ctx, attr, 1);
     lyd_free_tree(cur);
     for (uint32_t u = 0; u < attrs_data.count; ++u) {
-        if (((struct attr_data_s*)attrs_data.objs[u])->dynamic) {
-            free(((struct attr_data_s*)attrs_data.objs[u])->value);
+        if (((struct attr_data_s *)attrs_data.objs[u])->dynamic) {
+            free(((struct attr_data_s *)attrs_data.objs[u])->value);
         }
     }
     ly_set_erase(&attrs_data, free);
diff --git a/src/printer_data.c b/src/printer_data.c
index b74eaee..790b19d 100644
--- a/src/printer_data.c
+++ b/src/printer_data.c
@@ -72,7 +72,7 @@
     out.method.f = f;
 
     if (root) {
-        out.ctx = root->schema->module->ctx;
+        out.ctx = LYD_NODE_CTX(root);
     }
 
     ret = lyd_print_(&out, root, format, options);
@@ -95,7 +95,7 @@
 
     f = fopen(path, "w");
     if (!f) {
-        LOGERR(root ? root->schema->module->ctx : NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", path, strerror(errno));
+        LOGERR(root ? LYD_NODE_CTX(root) : NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", path, strerror(errno));
         return LY_ESYS;
     }
 
@@ -117,7 +117,7 @@
     out.method.fd = fd;
 
     if (root) {
-        out.ctx = root->schema->module->ctx;
+        out.ctx = LYD_NODE_CTX(root);
     }
 
     ret = lyd_print_(&out, root, format, options);
@@ -150,7 +150,7 @@
     out.type = LYOUT_MEMORY;
 
     if (root) {
-        out.ctx = root->schema->module->ctx;
+        out.ctx = LYD_NODE_CTX(root);
     }
 
     ret = lyd_print_(&out, root, format, options);
@@ -180,7 +180,7 @@
     out.method.clb.arg = arg;
 
     if (root) {
-        out.ctx = root->schema->module->ctx;
+        out.ctx = LYD_NODE_CTX(root);
     }
 
     ret = lyd_print_(&out, root, format, options);
diff --git a/src/printer_internal.h b/src/printer_internal.h
index 25b0afb..139a5bc 100644
--- a/src/printer_internal.h
+++ b/src/printer_internal.h
@@ -55,7 +55,7 @@
 
     size_t printed;      /**< Number of printed bytes */
 
-    struct ly_ctx *ctx;  /**< libyang context for error logging */
+    const struct ly_ctx *ctx;   /**< libyang context for error logging */
     LY_ERR status;       /**< current status of the printer */
 };
 
diff --git a/src/tree_data.c b/src/tree_data.c
index 5026d4e..939cfcb 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -148,7 +148,7 @@
 }
 
 API LY_ERR
-lys_value_validate(struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
                    ly_clb_resolve_prefix get_prefix, void *get_prefix_data, LYD_FORMAT format)
 {
     LY_ERR rc = LY_SUCCESS;
@@ -183,7 +183,7 @@
 }
 
 API LY_ERR
-lyd_value_validate(struct ly_ctx *ctx, const struct lyd_node_term *node, const char *value, size_t value_len,
+lyd_value_validate(const struct ly_ctx *ctx, const struct lyd_node_term *node, const char *value, size_t value_len,
                    ly_clb_resolve_prefix get_prefix, void *get_prefix_data, LYD_FORMAT format, const struct lyd_node *tree)
 {
     LY_ERR rc;
diff --git a/src/tree_data.h b/src/tree_data.h
index 80ecca5..c9a0a9d 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -817,7 +817,7 @@
  * @return LY_EINCOMPLETE in case the @p trees is not provided and it was needed to finish the validation (e.g. due to require-instance).
  * @return LY_ERR value if an error occurred.
  */
-LY_ERR lyd_value_validate(struct ly_ctx *ctx, const struct lyd_node_term *node, const char *value, size_t value_len,
+LY_ERR lyd_value_validate(const struct ly_ctx *ctx, const struct lyd_node_term *node, const char *value, size_t value_len,
                           ly_clb_resolve_prefix get_prefix, void *get_prefix_data, LYD_FORMAT format, const struct lyd_node *tree);
 
 /**
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index a942f77..5873306 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -384,7 +384,8 @@
  * @brief Check that a list has all its keys.
  *
  * @param[in] node List to check.
- * @return LY_ERR value.
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOT on a missing key.
  */
 LY_ERR lyd_parse_check_keys(struct lyd_node *node);
 
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 0d23f0c..0efc2e8 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1974,7 +1974,7 @@
  * @return LY_SUCCESS on success
  * @return LY_ERR value if an error occurred.
  */
-LY_ERR lys_value_validate(struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+LY_ERR lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
                           ly_clb_resolve_prefix get_prefix, void *get_prefix_data, LYD_FORMAT format);
 
 /**
diff --git a/src/xml.c b/src/xml.c
index ff76c31..d4b8a9e 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -240,6 +240,31 @@
     }
 }
 
+void *
+lyxml_elem_dup(void *item)
+{
+    struct lyxml_elem *dup;
+
+    dup = malloc(sizeof *dup);
+    memcpy(dup, item, sizeof *dup);
+
+    return dup;
+}
+
+void *
+lyxml_ns_dup(void *item)
+{
+    struct lyxml_ns *dup, *orig;
+
+    orig = (struct lyxml_ns *)item;
+    dup = malloc(sizeof *dup);
+    dup->prefix = orig->prefix ? strdup(orig->prefix) : NULL;
+    dup->uri = strdup(orig->uri);
+    dup->depth = orig->depth;
+
+    return dup;
+}
+
 const struct lyxml_ns *
 lyxml_ns_get(struct lyxml_context *context, const char *prefix, size_t prefix_len)
 {
@@ -383,22 +408,18 @@
 }
 
 LY_ERR
-lyxml_get_element(struct lyxml_context *context, const char **input,
-                  const char **prefix, size_t *prefix_len, const char **name, size_t *name_len)
+lyxml_get_element(struct lyxml_context *context, const char **input, const char **prefix_p, size_t *prefix_len_p,
+                  const char **name_p, size_t *name_len_p)
 {
     struct ly_ctx *ctx = context->ctx; /* shortcut */
-    const char *in = (*input);
-    size_t endtag_len;
+    const char *in = (*input), *prefix = NULL, *name = NULL;
+    size_t endtag_len, prefix_len = 0, name_len = 0;
     bool loop = true;
     int closing = 0;
     unsigned int c;
     LY_ERR rc;
     struct lyxml_elem *e;
 
-    /* initialize output variables */
-    (*prefix) = (*name) = NULL;
-    (*prefix_len) = (*name_len) = 0;
-
     while (loop) {
         rc = lyxml_parse_element_start(context, &in, &closing);
         if (rc) {
@@ -407,20 +428,20 @@
             goto success;
         }
         /* we are at the begining of the element name, remember the identifier start before checking its format */
-        LY_CHECK_RET(rc = lyxml_parse_element_name(context, &in, &endtag_len, &c, prefix, prefix_len, name, name_len));
+        LY_CHECK_RET(rc = lyxml_parse_element_name(context, &in, &endtag_len, &c, &prefix, &prefix_len, &name, &name_len));
 
         if (closing) {
             /* match opening and closing element tags */
             LY_CHECK_ERR_RET(
                     !context->elements.count,
                     LOGVAL(ctx, LY_VLOG_LINE, &context->line, LYVE_SYNTAX,
-                           "Opening and closing elements tag missmatch (\"%.*s\").", *name_len, *name),
+                           "Opening and closing elements tag missmatch (\"%.*s\").", name_len, name),
                     LY_EVALID);
             e = (struct lyxml_elem*)context->elements.objs[context->elements.count - 1];
-            if (e->prefix_len != *prefix_len || e->name_len != *name_len
-                    || (*prefix_len && strncmp(*prefix, e->prefix, e->prefix_len)) || strncmp(*name, e->name, e->name_len)) {
+            if (e->prefix_len != prefix_len || e->name_len != name_len
+                    || (prefix_len && strncmp(prefix, e->prefix, e->prefix_len)) || strncmp(name, e->name, e->name_len)) {
                 LOGVAL(ctx, LY_VLOG_LINE, &context->line, LYVE_SYNTAX,
-                       "Opening and closing elements tag missmatch (\"%.*s\").", *name_len, *name);
+                       "Opening and closing elements tag missmatch (\"%.*s\").", name_len, name);
                 return LY_EVALID;
             }
             /* opening and closing element tags matches, remove record from the opening tags list */
@@ -430,9 +451,9 @@
             /* remove also the namespaces connected with the element */
             lyxml_ns_rm(context);
 
-            /* do not return element information to announce closing element being currently processed */
-            *name = *prefix = NULL;
-            *name_len = *prefix_len = 0;
+            /* clear closing element */
+            name = prefix = NULL;
+            name_len = prefix_len = 0;
 
             if (c == '>') {
                 /* end of closing element */
@@ -461,10 +482,10 @@
                 /* store element opening tag information */
                 e = malloc(sizeof *e);
                 LY_CHECK_ERR_RET(!e, LOGMEM(ctx), LY_EMEM);
-                e->name = *name;
-                e->prefix = *prefix;
-                e->name_len = *name_len;
-                e->prefix_len = *prefix_len;
+                e->name = name;
+                e->prefix = prefix;
+                e->name_len = name_len;
+                e->prefix_len = prefix_len;
                 ly_set_add(&context->elements, e, LY_SET_OPT_USEASLIST);
             }
         }
@@ -483,6 +504,15 @@
     }
     /* move caller's input */
     (*input) = in;
+    /* return values */
+    if (prefix_p) {
+        *prefix_p = prefix;
+        *prefix_len_p = prefix_len;
+    }
+    if (name_p) {
+        *name_p = name;
+        *name_len_p = name_len;
+    }
     return LY_SUCCESS;
 }
 
diff --git a/src/xml.h b/src/xml.h
index f81c49b..8e30478 100644
--- a/src/xml.h
+++ b/src/xml.h
@@ -20,6 +20,7 @@
 
 #include "log.h"
 #include "set.h"
+#include "tree_schema.h"
 
 struct lyout;
 struct ly_prefix;
@@ -94,16 +95,16 @@
  * @param[in] context XML context to track lines or store errors into libyang context.
  * @param[in,out] input Input string to process, updated according to the processed/read data.
  * @param[in] options Currently unused options to modify input processing.
- * @param[out] prefix Pointer to prefix if present in the element name, NULL otherwise.
- * @param[out] prefix_len Length of the prefix if any.
- * @param[out] name Element name. When LY_SUCCESS is returned but name is NULL, check context's status field:
+ * @param[out] prefix_p Pointer to prefix if present in the element name, NULL otherwise.
+ * @param[out] prefix_len_p Length of the prefix if any.
+ * @param[out] name_p Element name. When LY_SUCCESS is returned but name is NULL, check context's status field:
  * - LYXML_END - end of input was reached
  * - LYXML_ELEMENT - closing element found, expecting now a sibling element so call lyxml_get_element() again
- * @param[out] name_len Length of the element name.
+ * @param[out] name_len_p Length of the element name.
  * @return LY_ERR values.
  */
-LY_ERR lyxml_get_element(struct lyxml_context *context, const char **input,
-                         const char **prefix, size_t *prefix_len, const char **name, size_t *name_len);
+LY_ERR lyxml_get_element(struct lyxml_context *context, const char **input, const char **prefix_p, size_t *prefix_len_p,
+                         const char **name_p, size_t *name_len_p);
 
 /**
  * @brief Skip an element after its opening tag was parsed.
@@ -228,4 +229,8 @@
 LY_ERR lyxml_value_compare(const char *value1, const struct ly_prefix *prefs1, const char *value2,
                            const struct ly_prefix *prefs2);
 
+void *lyxml_elem_dup(void *item);
+
+void *lyxml_ns_dup(void *item);
+
 #endif /* LY_XML_H_ */