summaryrefslogtreecommitdiffstats
path: root/src/printer_xml.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/printer_xml.c630
1 files changed, 630 insertions, 0 deletions
diff --git a/src/printer_xml.c b/src/printer_xml.c
new file mode 100644
index 0000000..31c5ad4
--- /dev/null
+++ b/src/printer_xml.c
@@ -0,0 +1,630 @@
+/**
+ * @file printer_xml.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief XML printer for libyang data structure
+ *
+ * Copyright (c) 2015 - 2022 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.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "context.h"
+#include "dict.h"
+#include "log.h"
+#include "ly_common.h"
+#include "out.h"
+#include "out_internal.h"
+#include "parser_data.h"
+#include "plugins_exts/metadata.h"
+#include "plugins_types.h"
+#include "printer_data.h"
+#include "printer_internal.h"
+#include "set.h"
+#include "tree.h"
+#include "tree_data.h"
+#include "tree_schema.h"
+#include "xml.h"
+
+/**
+ * @brief XML printer context.
+ */
+struct xmlpr_ctx {
+ struct ly_out *out; /**< output specification */
+ uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */
+ uint32_t options; /**< [Data printer flags](@ref dataprinterflags) */
+ const struct ly_ctx *ctx; /**< libyang context */
+ struct ly_set prefix; /**< printed namespace prefixes */
+ struct ly_set ns; /**< printed namespaces */
+};
+
+#define LYXML_PREFIX_REQUIRED 0x01 /**< The prefix is not just a suggestion but a requirement. */
+#define LYXML_PREFIX_DEFAULT 0x02 /**< The namespace is required to be a default (without prefix) */
+
+/**
+ * @brief Print a namespace if not already printed.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] ns Namespace to print, expected to be in dictionary.
+ * @param[in] new_prefix Suggested new prefix, NULL for a default namespace without prefix. Stored in the dictionary.
+ * @param[in] prefix_opts Prefix options changing the meaning of parameters.
+ * @return Printed prefix of the namespace to use.
+ */
+static const char *
+xml_print_ns(struct xmlpr_ctx *pctx, const char *ns, const char *new_prefix, uint32_t prefix_opts)
+{
+ uint32_t i;
+
+ for (i = pctx->ns.count; i > 0; --i) {
+ if (!new_prefix) {
+ /* find default namespace */
+ if (!pctx->prefix.objs[i - 1]) {
+ if (!strcmp(pctx->ns.objs[i - 1], ns)) {
+ /* matching default namespace */
+ return pctx->prefix.objs[i - 1];
+ }
+ /* not matching default namespace */
+ break;
+ }
+ } else {
+ /* find prefixed namespace */
+ if (!strcmp(pctx->ns.objs[i - 1], ns)) {
+ if (!pctx->prefix.objs[i - 1]) {
+ /* default namespace is not interesting */
+ continue;
+ }
+
+ if (!strcmp(pctx->prefix.objs[i - 1], new_prefix) || !(prefix_opts & LYXML_PREFIX_REQUIRED)) {
+ /* the same prefix or can be any */
+ return pctx->prefix.objs[i - 1];
+ }
+ }
+ }
+ }
+
+ /* suitable namespace not found, must be printed */
+ ly_print_(pctx->out, " xmlns%s%s=\"%s\"", new_prefix ? ":" : "", new_prefix ? new_prefix : "", ns);
+
+ /* and added into namespaces */
+ if (new_prefix) {
+ LY_CHECK_RET(lydict_insert(pctx->ctx, new_prefix, 0, &new_prefix), NULL);
+ }
+ LY_CHECK_RET(ly_set_add(&pctx->prefix, (void *)new_prefix, 1, NULL), NULL);
+ LY_CHECK_RET(ly_set_add(&pctx->ns, (void *)ns, 1, &i), NULL);
+
+ /* return it */
+ return pctx->prefix.objs[i];
+}
+
+static const char *
+xml_print_ns_opaq(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, const struct ly_opaq_name *name, uint32_t prefix_opts)
+{
+ switch (format) {
+ case LY_VALUE_XML:
+ if (name->module_ns) {
+ return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts);
+ }
+ break;
+ case LY_VALUE_JSON:
+ if (name->module_name) {
+ const struct lys_module *mod = ly_ctx_get_module_latest(pctx->ctx, name->module_name);
+
+ if (mod) {
+ return xml_print_ns(pctx, mod->ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts);
+ }
+ }
+ break;
+ default:
+ /* cannot be created */
+ LOGINT(pctx->ctx);
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Print prefix data.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] format Value prefix format, only ::LY_VALUE_XML supported.
+ * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
+ * @param[in] prefix_opts Prefix options changing the meaning of parameters.
+ * @return LY_ERR value.
+ */
+static void
+xml_print_ns_prefix_data(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, void *prefix_data, uint32_t prefix_opts)
+{
+ const struct ly_set *set;
+ const struct lyxml_ns *ns;
+ uint32_t i;
+
+ switch (format) {
+ case LY_VALUE_XML:
+ set = prefix_data;
+ for (i = 0; i < set->count; ++i) {
+ ns = set->objs[i];
+ if (!ns->prefix) {
+ /* default namespace is not for the element */
+ continue;
+ }
+
+ xml_print_ns(pctx, ns->uri, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : ns->prefix, prefix_opts);
+ }
+ break;
+ default:
+ /* cannot be created */
+ LOGINT(pctx->ctx);
+ }
+}
+
+/**
+ * @brief Print metadata of a node.
+ *
+ * @param[in] pctx XML printer context.
+ * @param[in] node Data node with metadata.
+ */
+static void
+xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node)
+{
+ struct lyd_meta *meta;
+ const struct lys_module *mod;
+ struct ly_set ns_list = {0};
+ LY_ARRAY_COUNT_TYPE u;
+ ly_bool dynamic, filter_attrs = 0;
+ const char *value;
+ uint32_t i;
+
+ /* with-defaults */
+ if (node->schema->nodetype & LYD_NODE_TERM) {
+ if (((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) ||
+ ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(node))) {
+ /* we have implicit OR explicit default node, print attribute only if context include with-defaults schema */
+ mod = ly_ctx_get_module_latest(LYD_CTX(node), "ietf-netconf-with-defaults");
+ if (mod) {
+ ly_print_(pctx->out, " %s:default=\"true\"", xml_print_ns(pctx, mod->ns, mod->prefix, 0));
+ }
+ }
+ }
+
+ /* check for NETCONF filter unqualified attributes */
+ if (!strcmp(node->schema->module->name, "notifications")) {
+ filter_attrs = 1;
+ } else {
+ LY_ARRAY_FOR(node->schema->exts, u) {
+ if (!strcmp(node->schema->exts[u].def->name, "get-filter-element-attributes") &&
+ !strcmp(node->schema->exts[u].def->module->name, "ietf-netconf")) {
+ filter_attrs = 1;
+ break;
+ }
+ }
+ }
+
+ for (meta = node->meta; meta; meta = meta->next) {
+ if (!lyd_metadata_should_print(meta)) {
+ continue;
+ }
+
+ /* store the module of the default namespace, NULL because there is none */
+ ly_set_add(&ns_list, NULL, 0, NULL);
+
+ /* print the value */
+ value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
+
+ /* print namespaces connected with the value's prefixes */
+ for (i = 1; i < ns_list.count; ++i) {
+ mod = ns_list.objs[i];
+ xml_print_ns(pctx, mod->ns, mod->prefix, 1);
+ }
+ ly_set_erase(&ns_list, NULL);
+
+ mod = meta->annotation->module;
+ if (filter_attrs && !strcmp(mod->name, "ietf-netconf") && (!strcmp(meta->name, "type") ||
+ !strcmp(meta->name, "select"))) {
+ /* print special NETCONF filter unqualified attributes */
+ ly_print_(pctx->out, " %s=\"", meta->name);
+ } else {
+ /* print the metadata with its namespace */
+ ly_print_(pctx->out, " %s:%s=\"", xml_print_ns(pctx, mod->ns, mod->prefix, 1), meta->name);
+ }
+
+ /* print metadata value */
+ if (value && value[0]) {
+ lyxml_dump_text(pctx->out, value, 1);
+ }
+ ly_print_(pctx->out, "\"");
+ if (dynamic) {
+ free((void *)value);
+ }
+ }
+}
+
+/**
+ * @brief Print generic XML element despite of the data node type.
+ *
+ * Prints the element name, attributes and necessary namespaces.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] node Data node to be printed.
+ */
+static void
+xml_print_node_open(struct xmlpr_ctx *pctx, const struct lyd_node *node)
+{
+ /* print node name */
+ ly_print_(pctx->out, "%*s<%s", INDENT, node->schema->name);
+
+ /* print default namespace */
+ xml_print_ns(pctx, node->schema->module->ns, NULL, 0);
+
+ /* print metadata */
+ xml_print_meta(pctx, node);
+}
+
+static LY_ERR
+xml_print_attr(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
+{
+ const struct lyd_attr *attr;
+ const char *pref;
+
+ LY_LIST_FOR(node->attr, attr) {
+ pref = NULL;
+ if (attr->name.prefix) {
+ /* print attribute namespace */
+ pref = xml_print_ns_opaq(pctx, attr->format, &attr->name, 0);
+ }
+
+ /* print namespaces connected with the value's prefixes */
+ if (attr->val_prefix_data) {
+ xml_print_ns_prefix_data(pctx, attr->format, attr->val_prefix_data, LYXML_PREFIX_REQUIRED);
+ }
+
+ /* print the attribute with its prefix and value */
+ ly_print_(pctx->out, " %s%s%s=\"", pref ? pref : "", pref ? ":" : "", attr->name.name);
+ lyxml_dump_text(pctx->out, attr->value, 1);
+ ly_print_(pctx->out, "\""); /* print attribute value terminator */
+
+ }
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+xml_print_opaq_open(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
+{
+ /* print node name */
+ ly_print_(pctx->out, "%*s<%s", INDENT, node->name.name);
+
+ if (node->name.prefix || node->name.module_ns) {
+ /* print default namespace */
+ xml_print_ns_opaq(pctx, node->format, &node->name, LYXML_PREFIX_DEFAULT);
+ }
+
+ /* print attributes */
+ LY_CHECK_RET(xml_print_attr(pctx, node));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node);
+
+/**
+ * @brief Print XML element representing lyd_node_term.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] node Data node to be printed.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node)
+{
+ LY_ERR rc = LY_SUCCESS;
+ struct ly_set ns_list = {0};
+ ly_bool dynamic = 0;
+ const char *value = NULL;
+ const struct lys_module *mod;
+ uint32_t i;
+
+ /* store the module of the default namespace */
+ if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) {
+ LOGMEM(pctx->ctx);
+ goto cleanup;
+ }
+
+ /* print the value */
+ value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML,
+ &ns_list, &dynamic, NULL);
+ LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup);
+
+ /* print node opening */
+ xml_print_node_open(pctx, &node->node);
+
+ /* print namespaces connected with the values's prefixes */
+ for (i = 1; i < ns_list.count; ++i) {
+ mod = ns_list.objs[i];
+ ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns);
+ }
+
+ if (!value[0]) {
+ ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
+ } else {
+ ly_print_(pctx->out, ">");
+ lyxml_dump_text(pctx->out, value, 0);
+ ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
+ }
+
+cleanup:
+ ly_set_erase(&ns_list, NULL);
+ if (dynamic) {
+ free((void *)value);
+ }
+ return rc;
+}
+
+/**
+ * @brief Print XML element representing lyd_node_inner.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] node Data node to be printed.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+xml_print_inner(struct xmlpr_ctx *pctx, const struct lyd_node_inner *node)
+{
+ LY_ERR ret;
+ struct lyd_node *child;
+
+ xml_print_node_open(pctx, &node->node);
+
+ LY_LIST_FOR(node->child, child) {
+ if (lyd_node_should_print(child, pctx->options)) {
+ break;
+ }
+ }
+ if (!child) {
+ /* there are no children that will be printed */
+ ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
+ return LY_SUCCESS;
+ }
+
+ /* children */
+ ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
+
+ LEVEL_INC;
+ LY_LIST_FOR(node->child, child) {
+ ret = xml_print_node(pctx, child);
+ LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
+ }
+ LEVEL_DEC;
+
+ ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+xml_print_anydata(struct xmlpr_ctx *pctx, const struct lyd_node_any *node)
+{
+ struct lyd_node_any *any = (struct lyd_node_any *)node;
+ struct lyd_node *iter;
+ uint32_t prev_opts, *prev_lo, temp_lo = 0;
+ LY_ERR ret;
+
+ if ((node->schema->nodetype == LYS_ANYDATA) && (node->value_type != LYD_ANYDATA_DATATREE)) {
+ LOGINT_RET(pctx->ctx);
+ }
+
+ xml_print_node_open(pctx, &node->node);
+
+ if (!any->value.tree) {
+ /* no content */
+no_content:
+ ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
+ return LY_SUCCESS;
+ } else {
+ if (any->value_type == LYD_ANYDATA_LYB) {
+ /* turn logging off */
+ prev_lo = ly_temp_log_options(&temp_lo);
+
+ /* try to parse it into a data tree */
+ if (lyd_parse_data_mem((struct ly_ctx *)LYD_CTX(node), any->value.mem, LYD_LYB,
+ LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_STRICT, 0, &iter) == LY_SUCCESS) {
+ /* successfully parsed */
+ free(any->value.mem);
+ any->value.tree = iter;
+ any->value_type = LYD_ANYDATA_DATATREE;
+ }
+
+ /* turn logging on again */
+ ly_temp_log_options(prev_lo);
+ }
+
+ switch (any->value_type) {
+ case LYD_ANYDATA_DATATREE:
+ /* close opening tag and print data */
+ prev_opts = pctx->options;
+ pctx->options &= ~LYD_PRINT_WITHSIBLINGS;
+ LEVEL_INC;
+
+ ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
+ LY_LIST_FOR(any->value.tree, iter) {
+ ret = xml_print_node(pctx, iter);
+ LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
+ }
+
+ LEVEL_DEC;
+ pctx->options = prev_opts;
+ break;
+ case LYD_ANYDATA_STRING:
+ /* escape XML-sensitive characters */
+ if (!any->value.str[0]) {
+ goto no_content;
+ }
+ /* close opening tag and print data */
+ ly_print_(pctx->out, ">");
+ lyxml_dump_text(pctx->out, any->value.str, 0);
+ break;
+ case LYD_ANYDATA_XML:
+ /* print without escaping special characters */
+ if (!any->value.str[0]) {
+ goto no_content;
+ }
+ ly_print_(pctx->out, ">%s", any->value.str);
+ break;
+ case LYD_ANYDATA_JSON:
+ case LYD_ANYDATA_LYB:
+ /* JSON and LYB format is not supported */
+ LOGWRN(pctx->ctx, "Unable to print anydata content (type %d) as XML.", any->value_type);
+ goto no_content;
+ }
+
+ /* closing tag */
+ if (any->value_type == LYD_ANYDATA_DATATREE) {
+ ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");
+ } else {
+ ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
+ }
+ }
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+xml_print_opaq(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
+{
+ LY_ERR ret;
+ struct lyd_node *child;
+
+ LY_CHECK_RET(xml_print_opaq_open(pctx, node));
+
+ if (node->value[0]) {
+ /* print namespaces connected with the value's prefixes */
+ if (node->val_prefix_data) {
+ xml_print_ns_prefix_data(pctx, node->format, node->val_prefix_data, LYXML_PREFIX_REQUIRED);
+ }
+
+ ly_print_(pctx->out, ">");
+ lyxml_dump_text(pctx->out, node->value, 0);
+ }
+
+ if (node->child) {
+ /* children */
+ if (!node->value[0]) {
+ ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
+ }
+
+ LEVEL_INC;
+ LY_LIST_FOR(node->child, child) {
+ ret = xml_print_node(pctx, child);
+ LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
+ }
+ LEVEL_DEC;
+
+ ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->name.name, DO_FORMAT ? "\n" : "");
+ } else if (node->value[0]) {
+ ly_print_(pctx->out, "</%s>%s", node->name.name, DO_FORMAT ? "\n" : "");
+ } else {
+ /* no value or children */
+ ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
+ }
+
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Print XML element representing lyd_node.
+ *
+ * @param[in] ctx XML printer context.
+ * @param[in] node Data node to be printed.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node)
+{
+ LY_ERR ret = LY_SUCCESS;
+ uint32_t ns_count;
+
+ if (!lyd_node_should_print(node, pctx->options)) {
+ /* do not print at all */
+ return LY_SUCCESS;
+ }
+
+ /* remember namespace definition count on this level */
+ ns_count = pctx->ns.count;
+
+ if (!node->schema) {
+ ret = xml_print_opaq(pctx, (const struct lyd_node_opaq *)node);
+ } else {
+ switch (node->schema->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ case LYS_ACTION:
+ ret = xml_print_inner(pctx, (const struct lyd_node_inner *)node);
+ break;
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ ret = xml_print_term(pctx, (const struct lyd_node_term *)node);
+ break;
+ case LYS_ANYXML:
+ case LYS_ANYDATA:
+ ret = xml_print_anydata(pctx, (const struct lyd_node_any *)node);
+ break;
+ default:
+ LOGINT(pctx->ctx);
+ ret = LY_EINT;
+ break;
+ }
+ }
+
+ /* remove all added namespaces */
+ while (ns_count < pctx->ns.count) {
+ lydict_remove(pctx->ctx, pctx->prefix.objs[pctx->prefix.count - 1]);
+ ly_set_rm_index(&pctx->prefix, pctx->prefix.count - 1, NULL);
+ ly_set_rm_index(&pctx->ns, pctx->ns.count - 1, NULL);
+ }
+
+ return ret;
+}
+
+LY_ERR
+xml_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options)
+{
+ const struct lyd_node *node;
+ struct xmlpr_ctx pctx = {0};
+
+ if (!root) {
+ if ((out->type == LY_OUT_MEMORY) || (out->type == LY_OUT_CALLBACK)) {
+ ly_print_(out, "");
+ }
+ goto finish;
+ }
+
+ pctx.out = out;
+ pctx.level = 0;
+ pctx.options = options;
+ pctx.ctx = LYD_CTX(root);
+
+ /* content */
+ LY_LIST_FOR(root, node) {
+ LY_CHECK_RET(xml_print_node(&pctx, node));
+ if (!(options & LYD_PRINT_WITHSIBLINGS)) {
+ break;
+ }
+ }
+
+finish:
+ assert(!pctx.prefix.count && !pctx.ns.count);
+ ly_set_erase(&pctx.prefix, NULL);
+ ly_set_erase(&pctx.ns, NULL);
+ ly_print_flush(out);
+ return LY_SUCCESS;
+}