/** * @file printer_xml.c * @author Michal Vasko * @author Radek Krejci * @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 #include #include #include #include "common.h" #include "context.h" #include "dict.h" #include "log.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: 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; /* 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) { const char *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 (uint32_t i = 0; 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) { struct ly_set ns_list = {0}; ly_bool dynamic; const char *value; xml_print_node_open(pctx, &node->node); value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML, &ns_list, &dynamic, NULL); LY_CHECK_RET(!value, LY_EINVAL); /* print namespaces connected with the values's prefixes */ for (uint32_t u = 0; u < ns_list.count; ++u) { const struct lys_module *mod = (const struct lys_module *)ns_list.objs[u]; ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns); } ly_set_erase(&ns_list, NULL); 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", node->schema->name, DO_FORMAT ? "\n" : ""); } if (dynamic) { free((void *)value); } return LY_SUCCESS; } /** * @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", 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, 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 */ 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(NULL); } 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", INDENT, node->schema->name, DO_FORMAT ? "\n" : ""); } else { ly_print_(pctx->out, "%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", INDENT, node->name.name, DO_FORMAT ? "\n" : ""); } else if (node->value[0]) { ly_print_(pctx->out, "%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; }