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);