parser xml BUGFIX full xml context backup

... including opened elements and namespaces.
Fixes cesnet/netopeer2#930
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 570d6b3..a827ab7 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -330,10 +330,7 @@
 lydxml_data_check_opaq(struct lyd_xml_ctx *lydctx, const struct lysc_node **snode)
 {
     LY_ERR ret = LY_SUCCESS;
-    enum LYXML_PARSER_STATUS prev_status;
-    const char *prev_current, *pname, *pprefix;
-    size_t pprefix_len, pname_len;
-    struct lyxml_ctx *xmlctx = lydctx->xmlctx;
+    struct lyxml_ctx *xmlctx = lydctx->xmlctx, pxmlctx;
 
     if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
         /* only checks specific to opaque nodes */
@@ -345,17 +342,10 @@
         return LY_SUCCESS;
     }
 
+    assert(xmlctx->elements.count);
+
     /* backup parser */
-    prev_status = xmlctx->status;
-    pprefix = xmlctx->prefix;
-    pprefix_len = xmlctx->prefix_len;
-    pname = xmlctx->name;
-    pname_len = xmlctx->name_len;
-    prev_current = xmlctx->in->current;
-    if ((xmlctx->status == LYXML_ELEM_CONTENT) && xmlctx->dynamic) {
-        /* it was backed up, do not free */
-        xmlctx->dynamic = 0;
-    }
+    LY_CHECK_RET(lyxml_ctx_backup(xmlctx, &pxmlctx));
 
     /* skip attributes */
     while (xmlctx->status == LYXML_ATTRIBUTE) {
@@ -386,16 +376,7 @@
 
 restore:
     /* restore parser */
-    if (xmlctx->dynamic) {
-        free((char *)xmlctx->value);
-    }
-    xmlctx->status = prev_status;
-    xmlctx->prefix = pprefix;
-    xmlctx->prefix_len = pprefix_len;
-    xmlctx->name = pname;
-    xmlctx->name_len = pname_len;
-    xmlctx->in->current = prev_current;
-
+    lyxml_ctx_restore(xmlctx, &pxmlctx);
     return ret;
 }
 
@@ -537,6 +518,10 @@
             LY_CHECK_GOTO(ret, error);
         }
 
+        /* get NS again, it may have been backed up and restored */
+        ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
+        assert(ns);
+
         /* create node */
         ret = lyd_create_opaq(ctx, name, name_len, prefix, prefix_len, ns->uri, strlen(ns->uri), xmlctx->value,
                 xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, LYD_HINT_DATA, &node);
diff --git a/src/xml.c b/src/xml.c
index 1294b37..91b50bb 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -4,7 +4,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Generic XML parser implementation for libyang
  *
- * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -1039,11 +1039,30 @@
     return ret;
 }
 
+/**
+ * @brief Free all namespaces in XML context.
+ *
+ * @param[in] xmlctx XML context to use.
+ */
+static void
+lyxml_ns_rm_all(struct lyxml_ctx *xmlctx)
+{
+    struct lyxml_ns *ns;
+    uint32_t i;
+
+    for (i = 0; i < xmlctx->ns.count; ++i) {
+        ns = xmlctx->ns.objs[i];
+
+        free(ns->prefix);
+        free(ns->uri);
+        free(ns);
+    }
+    ly_set_erase(&xmlctx->ns, NULL);
+}
+
 void
 lyxml_ctx_free(struct lyxml_ctx *xmlctx)
 {
-    uint32_t u;
-
     if (!xmlctx) {
         return;
     }
@@ -1054,16 +1073,111 @@
         free((char *)xmlctx->value);
     }
     ly_set_erase(&xmlctx->elements, free);
-    for (u = xmlctx->ns.count - 1; u + 1 > 0; --u) {
-        /* remove the ns structure */
-        free(((struct lyxml_ns *)xmlctx->ns.objs[u])->prefix);
-        free(((struct lyxml_ns *)xmlctx->ns.objs[u])->uri);
-        free(xmlctx->ns.objs[u]);
-    }
-    ly_set_erase(&xmlctx->ns, NULL);
+    lyxml_ns_rm_all(xmlctx);
     free(xmlctx);
 }
 
+/**
+ * @brief Duplicate an XML element.
+ *
+ * @param[in] elem Element to duplicate.
+ * @return Element duplicate.
+ * @return NULL on error.
+ */
+static struct lyxml_elem *
+lyxml_elem_dup(const struct lyxml_elem *elem)
+{
+    struct lyxml_elem *dup;
+
+    dup = malloc(sizeof *dup);
+    LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL);
+
+    memcpy(dup, elem, sizeof *dup);
+
+    return dup;
+}
+
+/**
+ * @brief Duplicate an XML namespace.
+ *
+ * @param[in] ns Namespace to duplicate.
+ * @return Namespace duplicate.
+ * @return NULL on error.
+ */
+static struct lyxml_ns *
+lyxml_ns_dup(const struct lyxml_ns *ns)
+{
+    struct lyxml_ns *dup;
+
+    dup = malloc(sizeof *dup);
+    LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL);
+
+    if (ns->prefix) {
+        dup->prefix = strdup(ns->prefix);
+        LY_CHECK_ERR_RET(!dup->prefix, LOGMEM(NULL); free(dup), NULL);
+    } else {
+        dup->prefix = NULL;
+    }
+    dup->uri = strdup(ns->uri);
+    LY_CHECK_ERR_RET(!dup->uri, LOGMEM(NULL); free(dup->prefix); free(dup), NULL);
+    dup->depth = ns->depth;
+
+    return dup;
+}
+
+LY_ERR
+lyxml_ctx_backup(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup)
+{
+    uint32_t i;
+
+    /* first make shallow copy */
+    memcpy(backup, xmlctx, sizeof *backup);
+
+    if ((xmlctx->status == LYXML_ELEM_CONTENT) && xmlctx->dynamic) {
+        /* it was backed up, do not free */
+        xmlctx->dynamic = 0;
+    }
+
+    /* backup in current pointer only */
+    backup->in = (void *)xmlctx->in->current;
+
+    /* duplicate elements */
+    backup->elements.objs = malloc(xmlctx->elements.size * sizeof(struct lyxml_elem));
+    for (i = 0; i < xmlctx->elements.count; ++i) {
+        backup->elements.objs[i] = lyxml_elem_dup(xmlctx->elements.objs[i]);
+    }
+
+    /* duplicate ns */
+    backup->ns.objs = malloc(xmlctx->ns.size * sizeof(struct lyxml_ns));
+    for (i = 0; i < xmlctx->ns.count; ++i) {
+        backup->ns.objs[i] = lyxml_ns_dup(xmlctx->ns.objs[i]);
+    }
+
+    return LY_SUCCESS;
+}
+
+void
+lyxml_ctx_restore(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup)
+{
+    if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) {
+        /* free dynamic value */
+        free((char *)xmlctx->value);
+    }
+
+    /* free elements */
+    ly_set_erase(&xmlctx->elements, free);
+
+    /* free ns */
+    lyxml_ns_rm_all(xmlctx);
+
+    /* restore in current pointer */
+    xmlctx->in->current = (void *)backup->in;
+    backup->in = xmlctx->in;
+
+    /* restore backup */
+    memcpy(xmlctx, backup, sizeof *xmlctx);
+}
+
 LY_ERR
 lyxml_dump_text(struct ly_out *out, const char *text, ly_bool attribute)
 {
diff --git a/src/xml.h b/src/xml.h
index 518c445..1261e31 100644
--- a/src/xml.h
+++ b/src/xml.h
@@ -57,8 +57,8 @@
 
 /* element tag identifier for matching opening and closing tags */
 struct lyxml_elem {
-    const char *prefix;
-    const char *name;
+    const char *prefix; /**< only pointer, not in dictionary */
+    const char *name;   /**< only pointer, not in dictionary */
     size_t prefix_len;
     size_t name_len;
 };
@@ -162,6 +162,23 @@
 void lyxml_ctx_free(struct lyxml_ctx *xmlctx);
 
 /**
+ * @brief Create a backup of XML context.
+ *
+ * @param[in] xmlctx XML context to back up.
+ * @param[out] backup Backup XML context.
+ * @return LY_ERR value.
+ */
+LY_ERR lyxml_ctx_backup(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup);
+
+/**
+ * @brief Restore previous backup of XML context.
+ *
+ * @param[in,out] xmlctx XML context to restore.
+ * @param[in] backup Backup XML context to restore, is unasble afterwards.
+ */
+void lyxml_ctx_restore(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup);
+
+/**
  * @brief Compare values and their prefix mappings.
  *
  * @param[in] ctx1 Libyang context for resolving prefixes in @p value1.
diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c
index b83432c..1e74bb9 100644
--- a/tests/utests/data/test_parser_xml.c
+++ b/tests/utests/data/test_parser_xml.c
@@ -251,6 +251,13 @@
     CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<foo3 xmlns=\"urn:tests:a\"/>\n");
     lyd_free_all(tree);
 
+    /* list, opaq flag */
+    data = "<l1 xmlns=\"urn:tests:a\"/>";
+    CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_XML, "l1", 0, 0, NULL,  0,  "");
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<l1 xmlns=\"urn:tests:a\"/>\n");
+    lyd_free_all(tree);
+
     /* missing key, no flags */
     data = "<l1 xmlns=\"urn:tests:a\">\n"
             "  <a>val_a</a>\n"