xml FEATURE namespace manipulation functions
diff --git a/src/xml.c b/src/xml.c
index bd26f16..f0c10a2 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -12,9 +12,12 @@
  *     https://opensource.org/licenses/BSD-3-Clause
  */
 
+#define _POSIX_C_SOURCE 200809L /* strndup() */
+
 #include <ctype.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <string.h>
 
 #include "libyang.h"
 #include "xml.h"
@@ -50,6 +53,13 @@
 /* Ignore whitespaces in the input string p, if EOF log with lyxml_context c */
 #define ign_xmlws(c,p) while (is_xmlws(*(p))) {if (*(p) == '\n') {++c->line;} ++p;}
 
+/**
+ * @brief Ignore any characters until the delim of the size delim_len is read
+ *
+ * Detects number of read new lines.
+ * Returns the pointer to the beginning of the detected delim, or NULL in case the delim not found in
+ * NULL-terminated input string.
+ *  */
 static const char *
 ign_todelim(register const char *input, const char *delim, size_t delim_len, size_t *newlines)
 {
@@ -78,6 +88,14 @@
     return NULL;
 }
 
+/**
+ * @brief Get UTF8 code point of the next character in the input string.
+ *
+ * @param[in,out] input Input string to process, updated according to the processed/read data.
+ * @param[out] utf8_char UTF8 code point of the next character.
+ * @param[out] bytes_read Number of bytes used to encode the read utf8_char.
+ * @return LY_ERR value
+ */
 static LY_ERR
 lyxml_getutf8(const char **input, unsigned int *utf8_char, size_t *bytes_read)
 {
@@ -218,7 +236,22 @@
     return LY_SUCCESS;
 }
 
-LY_ERR
+/**
+ * @brief Check/Get an XML qualified name from the input string.
+ *
+ * The identifier must have at least one valid character complying the name start character constraints.
+ * The identifier is terminated by the first character, which does not comply to the name character constraints.
+ *
+ * See https://www.w3.org/TR/xml-names/#NT-NCName
+ *
+ * @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.
+ * Note that the term_char is also read, so input points after the term_char at the end.
+ * @param[out] term_char The first character in the input string which does not compy to the name constraints.
+ * @param[out] term_char_len Number of bytes used to encode UTF8 term_char. Serves to be able to go back in input string.
+ * @return LY_ERR value.
+ */
+static LY_ERR
 lyxml_check_qname(struct lyxml_context *context, const char **input, unsigned int *term_char, size_t *term_char_len)
 {
     unsigned int c;
@@ -243,27 +276,6 @@
     return LY_SUCCESS;
 }
 
-/**
- * @brief Parse input as XML text (attribute's values and element's content).
- *
- * Mixed content of XML elements is not allowed. Formating whitespaces before child element are ignored,
- * LY_EINVAL is returned in such a case (buffer is not filled, no error is printed) and input is moved
- * to the beginning of a child definition.
- *
- * In the case of attribute's values, the input string is expected to start on a quotation mark to
- * select which delimiter (single or double quote) is used. Otherwise, the element content is being
- * parsed expected to be terminated by '<' character.
- *
- * If function succeeds, the string in output buffer is always NULL-terminated.
- *
- * @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[out] buffer Storage of the output string. If NULL, the buffer is allocated. Otherwise, the buffer
- * is used and enlarged when necessary.
- * @param[out] buffer_size Allocated size of the returned buffer. If a buffer is provided by a caller, it
- * is not being reduced even if the string is shorter. On the other hand, it can be enlarged if needed.
- * @return LY_ERR value.
- */
 LY_ERR
 lyxml_get_string(struct lyxml_context *context, const char **input, char **buffer, size_t *buffer_size)
 {
@@ -464,25 +476,6 @@
 #undef BUFSIZE_CHECK
 }
 
-/**
- * @brief Parse input expecting an XML attribute (including XML namespace).
- *
- * Input string is not being modified, so the returned values are not NULL-terminated, instead their length
- * is returned.
- *
- * In case of a namespace definition, prefix just contains xmlns string. In case of the default namespace,
- * prefix is NULL and the attribute name is xmlns.
- *
- * @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 so,
- * when succeeded, it points to the opening quote of the attribute's value.
- * @param[out] prefix Pointer to prefix if present in the attribute name, NULL otherwise.
- * @param[out] prefix_len Length of the prefix if any.
- * @param[out] name Attribute name. LY_SUCCESS can be returned with NULL name only in case the
- * end of the element tag was reached.
- * @param[out] name_len Length of the element name.
- * @return LY_ERR values.
- */
 LY_ERR
 lyxml_get_attribute(struct lyxml_context *context, const char **input,
                     const char **prefix, size_t *prefix_len, const char **name, size_t *name_len)
@@ -553,26 +546,6 @@
     return LY_SUCCESS;
 }
 
-/**
- * @brief Parse input expecting an XML element.
- *
- * Able to silently skip comments, PIs and CData. DOCTYPE is not parsable, so it is reported as LY_EVALID error.
- * If '<' is not found in input, LY_EINVAL is returned (but no error is logged), so it is possible to continue
- * with parsing input as text content.
- *
- * Input string is not being modified, so the returned values are not NULL-terminated, instead their length
- * is returned.
- *
- * @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. LY_SUCCESS can be returned with NULL name only in case the
- * end of the input string was reached (EOF).
- * @param[out] name_len 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)
@@ -671,3 +644,69 @@
     return LY_SUCCESS;
 }
 
+LY_ERR
+lyxml_ns_add(struct lyxml_context *context, const char *element_name, const char *prefix, size_t prefix_len, char *uri)
+{
+    struct lyxml_ns *ns;
+
+    ns = malloc(sizeof *ns);
+    LY_CHECK_ERR_RET(!ns, LOGMEM(context->ctx), LY_EMEM);
+
+    ns->element = element_name;
+    ns->uri = uri;
+    if (prefix) {
+        ns->prefix = strndup(prefix, prefix_len);
+        LY_CHECK_ERR_RET(!ns->prefix, LOGMEM(context->ctx); free(ns), LY_EMEM);
+    } else {
+        ns->prefix = NULL;
+    }
+
+    LY_CHECK_ERR_RET(ly_set_add(&context->ns, ns, LY_SET_OPT_USEASLIST) == -1, free(ns->prefix), LY_EMEM);
+    return LY_SUCCESS;
+}
+
+const struct lyxml_ns *
+lyxml_ns_get(struct lyxml_context *context, const char *prefix, size_t prefix_len)
+{
+    unsigned int u;
+    struct lyxml_ns *ns;
+
+    for (u = context->ns.count - 1; u + 1 > 0; --u) {
+        ns = (struct lyxml_ns *)context->ns.objs[u];
+        if (prefix) {
+            if (!strncmp(prefix, ns->prefix, prefix_len) && ns->prefix[prefix_len] == '\0') {
+                return ns;
+            }
+        } else if (!ns->prefix) {
+            /* default namespace */
+            return ns;
+        }
+    }
+
+    return NULL;
+}
+
+LY_ERR
+lyxml_ns_rm(struct lyxml_context *context, const char *element_name)
+{
+    unsigned int u;
+
+    for (u = context->ns.count - 1; u + 1 > 0; --u) {
+        if (((struct lyxml_ns *)context->ns.objs[u])->element != element_name) {
+            /* we are done, the namespaces from a single element are supposed to be together */
+            break;
+        }
+        /* remove the ns structure */
+        free(((struct lyxml_ns *)context->ns.objs[u])->prefix);
+        free(((struct lyxml_ns *)context->ns.objs[u])->uri);
+        free(context->ns.objs[u]);
+        --context->ns.count;
+    }
+
+    if (!context->ns.count) {
+        /* cleanup the context's namespaces storage */
+        ly_set_erase(&context->ns, NULL);
+    }
+
+    return LY_SUCCESS;
+}
diff --git a/src/xml.h b/src/xml.h
index 759c422..4dbd06d 100644
--- a/src/xml.h
+++ b/src/xml.h
@@ -21,8 +21,9 @@
 #include "set.h"
 
 struct lyxml_ns {
-    char *prefix;
-    char *ns;
+    const char *element;  /* element where the namespace is defined */
+    char *prefix;         /* prefix of the namespace, NULL for the default namespace */
+    char *uri;            /* namespace URI */
 };
 
 enum LYXML_PARSER_STATUS {
@@ -33,7 +34,119 @@
 struct lyxml_context {
     struct ly_ctx *ctx;
     uint64_t line;
-    struct ly_set ns;
+    struct ly_set ns;     /* handled with LY_SET_OPT_USEASLIST */
 };
 
 #endif /* LY_XML_H_ */
+
+/**
+ * @brief Parse input expecting an XML element.
+ *
+ * Able to silently skip comments, PIs and CData. DOCTYPE is not parsable, so it is reported as LY_EVALID error.
+ * If '<' is not found in input, LY_EINVAL is returned (but no error is logged), so it is possible to continue
+ * with parsing input as text content.
+ *
+ * Input string is not being modified, so the returned values are not NULL-terminated, instead their length
+ * is returned.
+ *
+ * @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. LY_SUCCESS can be returned with NULL name only in case the
+ * end of the input string was reached (EOF).
+ * @param[out] name_len 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);
+
+/**
+ * @brief Parse input expecting an XML attribute (including XML namespace).
+ *
+ * Input string is not being modified, so the returned values are not NULL-terminated, instead their length
+ * is returned.
+ *
+ * In case of a namespace definition, prefix just contains xmlns string. In case of the default namespace,
+ * prefix is NULL and the attribute name is xmlns.
+ *
+ * @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 so,
+ * when succeeded, it points to the opening quote of the attribute's value.
+ * @param[out] prefix Pointer to prefix if present in the attribute name, NULL otherwise.
+ * @param[out] prefix_len Length of the prefix if any.
+ * @param[out] name Attribute name. LY_SUCCESS can be returned with NULL name only in case the
+ * end of the element tag was reached.
+ * @param[out] name_len Length of the element name.
+ * @return LY_ERR values.
+ */
+LY_ERR lyxml_get_attribute(struct lyxml_context *context, const char **input,
+                           const char **prefix, size_t *prefix_len, const char **name, size_t *name_len);
+
+/**
+ * @brief Parse input as XML text (attribute's values and element's content).
+ *
+ * Mixed content of XML elements is not allowed. Formating whitespaces before child element are ignored,
+ * LY_EINVAL is returned in such a case (buffer is not filled, no error is printed) and input is moved
+ * to the beginning of a child definition.
+ *
+ * In the case of attribute's values, the input string is expected to start on a quotation mark to
+ * select which delimiter (single or double quote) is used. Otherwise, the element content is being
+ * parsed expected to be terminated by '<' character.
+ *
+ * If function succeeds, the string in output buffer is always NULL-terminated.
+ *
+ * @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[out] buffer Storage of the output string. If NULL, the buffer is allocated. Otherwise, the buffer
+ * is used and enlarged when necessary.
+ * @param[out] buffer_size Allocated size of the returned buffer. If a buffer is provided by a caller, it
+ * is not being reduced even if the string is shorter. On the other hand, it can be enlarged if needed.
+ * @return LY_ERR value.
+ */
+LY_ERR lyxml_get_string(struct lyxml_context *context, const char **input, char **buffer, size_t *buffer_size);
+
+/**
+ * @brief Add namespace definition into XML context.
+ *
+ * Namespaces from a single element are supposed to be added sequentially together (not interleaved by a namespace from other
+ * element). This mimic namespace visibility, since the namespace defined in element E is not visible from its parents or
+ * siblings. On the other hand, namespace from a parent element can be redefined in a child element. This is also reflected
+ * by lyxml_ns_get() which returns the most recent namespace definition for the given prefix.
+ *
+ * When leaving processing of a subtree of some element, caller is supposed to call lyxml_ns_rm() to remove all the namespaces
+ * defined in such an element from the context.
+ *
+ * @param[in] context XML context to work with.
+ * @param[in] element_name Pointer to the element name where the namespace is defined. Serve as an identifier to select
+ * which namespaces are supposed to be removed via lyxml_ns_rm() when leaving the element's subtree.
+ * @param[in] prefix Pointer to the namespace prefix as taken from lyxml_get_attribute(). Can be NULL for default namespace.
+ * @param[in] prefix_len Length of the prefix string (since it is not NULL-terminated when returned from lyxml_get_attribute()).
+ * @param[in] uri Namespace URI (value) to store. Value can be obtained via lyxml_get_string() and caller is not supposed to
+ * work with the pointer when the function succeeds.
+ * @return LY_ERR values.
+ */
+LY_ERR lyxml_ns_add(struct lyxml_context *context, const char *element_name, const char *prefix, size_t prefix_len, char *uri);
+
+/**
+ * @brief Get a namespace record for the given prefix in the current context.
+ *
+ * @param[in] context XML context to work with.
+ * @param[in] prefix Pointer to the namespace prefix as taken from lyxml_get_attribute() or lyxml_get_element().
+ * Can be NULL for default namespace.
+ * @param[in] prefix_len Length of the prefix string (since it is not NULL-terminated when returned from lyxml_get_attribute() or
+ * lyxml_get_element()).
+ * @return The namespace record or NULL if the record for the specified prefix not found.
+ */
+const struct lyxml_ns *lyxml_ns_get(struct lyxml_context *context, const char *prefix, size_t prefix_len);
+
+/**
+ * @brief Remove all the namespaces defined in the given element.
+ *
+ * @param[in] context XML context to work with.
+ * @param[in] element_name Pointer to the element name where the namespaces are defined. Serve as an identifier previously provided
+ * by lyxml_get_element()
+ * @return LY_ERR values.
+ */
+LY_ERR lyxml_ns_rm(struct lyxml_context *context, const char *element_name);
diff --git a/tests/src/xml.c b/tests/src/xml.c
index 4ebdbbd..37a690b 100644
--- a/tests/src/xml.c
+++ b/tests/src/xml.c
@@ -357,6 +357,51 @@
     free(out);
 }
 
+static void
+test_ns(void **state)
+{
+    (void) state; /* unused */
+
+    const char *e1, *e2;
+    const struct lyxml_ns *ns;
+
+    struct lyxml_context ctx;
+    memset(&ctx, 0, sizeof ctx);
+    ctx.line = 1;
+
+    e1 = "element1";
+    e2 = "element2";
+    assert_int_equal(LY_SUCCESS, lyxml_ns_add(&ctx, e1, NULL, 0, strdup("urn:default")));
+    assert_int_equal(LY_SUCCESS, lyxml_ns_add(&ctx, e1, "nc", 2, strdup("urn:nc1")));
+    assert_int_equal(LY_SUCCESS, lyxml_ns_add(&ctx, e2, "nc", 2, strdup("urn:nc2")));
+    assert_int_equal(3, (&ctx)->ns.count);
+    assert_int_not_equal(0, (&ctx)->ns.size);
+
+    ns = lyxml_ns_get(&ctx, NULL, 0);
+    assert_non_null(ns);
+    assert_null(ns->prefix);
+    assert_string_equal("urn:default", ns->uri);
+
+    ns = lyxml_ns_get(&ctx, "nc", 2);
+    assert_non_null(ns);
+    assert_string_equal("nc", ns->prefix);
+    assert_string_equal("urn:nc2", ns->uri);
+
+    assert_int_equal(LY_SUCCESS, lyxml_ns_rm(&ctx, e2));
+    assert_int_equal(2, (&ctx)->ns.count);
+
+    ns = lyxml_ns_get(&ctx, "nc", 2);
+    assert_non_null(ns);
+    assert_string_equal("nc", ns->prefix);
+    assert_string_equal("urn:nc1", ns->uri);
+
+    assert_int_equal(LY_SUCCESS, lyxml_ns_rm(&ctx, e1));
+    assert_int_equal(0, (&ctx)->ns.count);
+
+    assert_null(lyxml_ns_get(&ctx, "nc", 2));
+    assert_null(lyxml_ns_get(&ctx, NULL, 0));
+}
+
 int main(void)
 {
     const struct CMUnitTest tests[] = {
@@ -364,6 +409,7 @@
         cmocka_unit_test_setup(test_element, logger_setup),
         cmocka_unit_test_setup(test_attribute, logger_setup),
         cmocka_unit_test_setup(test_text, logger_setup),
+        cmocka_unit_test_setup(test_ns, logger_setup),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);