| /** |
| * @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 - 2019 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 "common.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "log.h" |
| #include "plugins_types.h" |
| #include "printer_data.h" |
| #include "printer_internal.h" |
| #include "tree.h" |
| #include "tree_data.h" |
| #include "tree_schema.h" |
| #include "xml.h" |
| |
| /** |
| * @brief XML printer context. |
| */ |
| struct xmlpr_ctx { |
| struct lyout *out; /**< output specification */ |
| unsigned int level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ |
| int options; /**< [Data printer flags](@ref dataprinterflags) */ |
| int toplevel; /**< top-level flag */ |
| }; |
| |
| #define LEVEL ctx->level /**< current level */ |
| #define INDENT ((LEVEL) ? (LEVEL)*2 : 0),"" /**< indentation parameters for printer functions */ |
| #define LEVEL_INC if (LEVEL) {LEVEL++;} /**< increase indentation level */ |
| #define LEVEL_DEC if (LEVEL) {LEVEL--;} /**< decrease indentation level */ |
| |
| /** |
| * TODO |
| */ |
| struct mlist { |
| struct mlist *next; |
| struct lys_module *module; |
| } *mlist = NULL, *mlist_new; |
| |
| static LY_ERR |
| modlist_add(struct mlist **mlist, const struct lys_module *mod) |
| { |
| struct mlist *iter; |
| |
| for (iter = *mlist; iter; iter = iter->next) { |
| if (mod == iter->module) { |
| break; |
| } |
| } |
| |
| if (!iter) { |
| iter = malloc(sizeof *iter); |
| LY_CHECK_ERR_RET(!iter, LOGMEM(mod->ctx), LY_EMEM); |
| iter->next = *mlist; |
| iter->module = (struct lys_module *)mod; |
| *mlist = iter; |
| } |
| |
| return LY_SUCCESS; |
| } |
| |
| /** |
| * TODO |
| */ |
| static void |
| xml_print_ns(struct xmlpr_ctx *ctx, const struct lyd_node *node) |
| { |
| struct lyd_node *next, *cur, *child; |
| struct lyd_attr *attr; |
| |
| struct mlist *mlist = NULL, *miter; |
| #if 0 |
| const struct lys_module *wdmod = NULL; |
| #endif |
| /* add node attribute modules */ |
| for (attr = node->attr; attr; attr = attr->next) { |
| if (!strcmp(node->schema->name, "filter") && |
| (!strcmp(node->schema->module->name, "ietf-netconf") || |
| !strcmp(node->schema->module->name, "notifications"))) { |
| /* exception for NETCONF's filter attributes */ |
| continue; |
| } else if (modlist_add(&mlist, attr->annotation->module)) { |
| goto print; |
| } |
| } |
| |
| /* add node children nodes and attribute modules */ |
| switch (node->schema->nodetype) { |
| case LYS_LEAFLIST: |
| case LYS_LEAF: |
| /* TODO ietf-netconf-with-defaults namespace */ |
| #if 0 |
| if (node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) { |
| /* get with-defaults module and print its namespace */ |
| wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1); |
| if (wdmod && modlist_add(&mlist, wdmod)) { |
| goto print; |
| } |
| } |
| #endif |
| break; |
| case LYS_CONTAINER: |
| case LYS_LIST: |
| case LYS_ACTION: |
| case LYS_NOTIF: |
| #if 0 |
| if (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG)) { |
| /* get with-defaults module and print its namespace */ |
| wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1); |
| if (wdmod && modlist_add(&mlist, wdmod)) { |
| goto print; |
| } |
| } |
| #endif |
| LY_LIST_FOR(((struct lyd_node_inner*)node)->child, child) { |
| LYD_TREE_DFS_BEGIN(child, next, cur) { |
| for (attr = cur->attr; attr; attr = attr->next) { |
| if (!strcmp(cur->schema->name, "filter") && |
| (!strcmp(cur->schema->module->name, "ietf-netconf") || |
| !strcmp(cur->schema->module->name, "notifications"))) { |
| /* exception for NETCONF's filter attributes */ |
| continue; |
| } else { |
| /* TODO annotations r = modlist_add(&mlist, lys_main_module(attr->annotation->module)); */ |
| } |
| } |
| LYD_TREE_DFS_END(child, next, cur)} |
| } |
| break; |
| default: |
| break; |
| } |
| |
| print: |
| /* print used namespaces */ |
| while (mlist) { |
| miter = mlist; |
| mlist = mlist->next; |
| |
| ly_print(ctx->out, " xmlns:%s=\"%s\"", miter->module->prefix, miter->module->ns); |
| free(miter); |
| } |
| } |
| |
| /** |
| * @brief XML mapping of YANG modules to prefixes in values. |
| * |
| * Implementation of ly_clb_get_prefix |
| */ |
| static const char * |
| xml_print_get_prefix(const struct lys_module *mod, void *private) |
| { |
| struct ly_set *ns_list = (struct ly_set*)private; |
| |
| ly_set_add(ns_list, (void*)mod, 0); |
| return mod->prefix; |
| } |
| |
| /** |
| * TODO |
| */ |
| static LY_ERR |
| xml_print_attrs(struct xmlpr_ctx *ctx, const struct lyd_node *node) |
| { |
| struct lyd_attr *attr; |
| #if 0 |
| const char **prefs, **nss; |
| const char *xml_expr = NULL, *mod_name; |
| uint32_t ns_count, i; |
| int rpc_filter = 0; |
| const struct lys_module *wdmod = NULL; |
| char *p; |
| size_t len; |
| #endif |
| struct ly_set ns_list = {0}; |
| int dynamic; |
| unsigned int u; |
| |
| #if 0 |
| /* with-defaults */ |
| if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { |
| if ((node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) || |
| (!node->dflt && (options & LYP_WD_ALL_TAG) && lyd_wd_default((struct lyd_node_leaf_list *)node))) { |
| /* we have implicit OR explicit default node */ |
| /* get with-defaults module */ |
| wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1); |
| if (wdmod) { |
| /* print attribute only if context include with-defaults schema */ |
| ly_print(out, " %s:default=\"true\"", wdmod->prefix); |
| } |
| } |
| } |
| /* technically, check for the extension get-filter-element-attributes from ietf-netconf */ |
| if (!strcmp(node->schema->name, "filter") |
| && (!strcmp(node->schema->module->name, "ietf-netconf") || !strcmp(node->schema->module->name, "notifications"))) { |
| rpc_filter = 1; |
| } |
| #endif |
| for (attr = node->attr; attr; attr = attr->next) { |
| const char *value = attr->value.realtype->plugin->print(&attr->value, LYD_XML, xml_print_get_prefix, &ns_list, &dynamic); |
| |
| /* print namespaces connected with the values's prefixes */ |
| for (u = 0; u < ns_list.count; ++u) { |
| const struct lys_module *mod = (const struct lys_module*)ns_list.objs[u]; |
| ly_print(ctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns); |
| } |
| ly_set_erase(&ns_list, NULL); |
| |
| #if 0 |
| if (rpc_filter) { |
| /* exception for NETCONF's filter's attributes */ |
| if (!strcmp(attr->name, "select")) { |
| /* xpath content, we have to convert the JSON format into XML first */ |
| xml_expr = transform_json2xml(node->schema->module, attr->value_str, 0, &prefs, &nss, &ns_count); |
| if (!xml_expr) { |
| /* error */ |
| return EXIT_FAILURE; |
| } |
| |
| for (i = 0; i < ns_count; ++i) { |
| ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]); |
| } |
| free(prefs); |
| free(nss); |
| } |
| ly_print(out, " %s=\"", attr->name); |
| } else { |
| #endif |
| ly_print(ctx->out, " %s:%s=\"", attr->annotation->module->prefix, attr->name); |
| #if 0 |
| } |
| #endif |
| |
| if (value && value[0]) { |
| lyxml_dump_text(ctx->out, value, 1); |
| } |
| ly_print(ctx->out, "\""); |
| if (dynamic) { |
| free((void*)value); |
| } |
| } |
| |
| return LY_SUCCESS; |
| } |
| |
| /** |
| * @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. |
| * @return LY_ERR value. |
| */ |
| static LY_ERR |
| xml_print_node_open(struct xmlpr_ctx *ctx, const struct lyd_node *node) |
| { |
| if (ctx->toplevel || !node->parent || node->schema->module != node->parent->schema->module) { |
| /* print "namespace" */ |
| ly_print(ctx->out, "%*s<%s xmlns=\"%s\"", INDENT, node->schema->name, node->schema->module->ns); |
| } else { |
| ly_print(ctx->out, "%*s<%s", INDENT, node->schema->name); |
| } |
| |
| if (ctx->toplevel) { |
| xml_print_ns(ctx, node); |
| ctx->toplevel = 0; |
| } |
| |
| LY_CHECK_RET(xml_print_attrs(ctx, node)); |
| |
| return LY_SUCCESS; |
| } |
| |
| static LY_ERR xml_print_node(struct xmlpr_ctx *ctx, 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 *ctx, const struct lyd_node_term *node) |
| { |
| struct ly_set ns_list = {0}; |
| unsigned int u; |
| int dynamic; |
| const char *value; |
| |
| LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node)); |
| value = node->value.realtype->plugin->print(&node->value, LYD_XML, xml_print_get_prefix, &ns_list, &dynamic); |
| |
| /* print namespaces connected with the values's prefixes */ |
| for (u = 0; u < ns_list.count; ++u) { |
| const struct lys_module *mod = (const struct lys_module*)ns_list.objs[u]; |
| ly_print(ctx->out, "%sxmlns:%s=\"%s\"", u ? " " : "", mod->prefix, mod->ns); |
| } |
| ly_set_erase(&ns_list, NULL); |
| |
| if (!value || !value[0]) { |
| ly_print(ctx->out, "/>%s", LEVEL ? "\n" : ""); |
| } else { |
| ly_print(ctx->out, ">"); |
| lyxml_dump_text(ctx->out, value, 0); |
| ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\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 *ctx, const struct lyd_node_inner *node) |
| { |
| LY_ERR ret; |
| struct lyd_node *child; |
| |
| LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node)); |
| |
| if (!node->child) { |
| ly_print(ctx->out, "/>%s", ctx->level ? "\n" : ""); |
| return LY_SUCCESS; |
| } |
| |
| /* children */ |
| ly_print(ctx->out, ">%s", ctx->level ? "\n" : ""); |
| |
| LEVEL_INC; |
| LY_LIST_FOR(node->child, child) { |
| ret = xml_print_node(ctx, child); |
| LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); |
| } |
| LEVEL_DEC; |
| |
| ly_print(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : ""); |
| |
| return LY_SUCCESS; |
| } |
| |
| static LY_ERR |
| xml_print_anydata(struct xmlpr_ctx *ctx, const struct lyd_node_any *node) |
| { |
| struct lyd_node_any *any = (struct lyd_node_any *)node; |
| struct lyd_node *iter; |
| int options_backup; |
| |
| LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node)); |
| |
| if (!any->value.tree) { |
| /* no content */ |
| no_content: |
| ly_print(ctx->out, "/>%s", LEVEL ? "\n" : ""); |
| return LY_SUCCESS; |
| } else { |
| switch (any->value_type) { |
| case LYD_ANYDATA_DATATREE: |
| /* close opening tag and print data */ |
| options_backup = ctx->options; |
| ctx->options &= ~(LYDP_WITHSIBLINGS | LYDP_NETCONF); |
| LEVEL_INC; |
| |
| ly_print(ctx->out, ">%s", LEVEL ? "\n" : ""); |
| LY_LIST_FOR(any->value.tree, iter) { |
| if (xml_print_node(ctx, iter)) { |
| return EXIT_FAILURE; |
| } |
| } |
| |
| LEVEL_DEC; |
| ctx->options = options_backup; |
| 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(ctx->out, ">"); |
| lyxml_dump_text(ctx->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(ctx->out, ">%s", any->value.str); |
| break; |
| case LYD_ANYDATA_JSON: |
| #if 0 /* TODO LYB format */ |
| case LYD_ANYDATA_LYB: |
| #endif |
| /* JSON and LYB format is not supported */ |
| LOGWRN(node->schema->module->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(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : ""); |
| } else { |
| ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\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 *ctx, const struct lyd_node *node) |
| { |
| LY_ERR ret = LY_SUCCESS; |
| |
| #if 0 |
| if (!lyd_wd_toprint(node, ctx->options)) { |
| /* wd says do not print */ |
| return EXIT_SUCCESS; |
| } |
| #endif |
| |
| switch (node->schema->nodetype) { |
| case LYS_CONTAINER: |
| case LYS_LIST: |
| case LYS_NOTIF: |
| case LYS_ACTION: |
| ret = xml_print_inner(ctx, (const struct lyd_node_inner*)node); |
| break; |
| case LYS_LEAF: |
| case LYS_LEAFLIST: |
| ret = xml_print_term(ctx, (const struct lyd_node_term*)node); |
| break; |
| case LYS_ANYXML: |
| case LYS_ANYDATA: |
| ret = xml_print_anydata(ctx, (const struct lyd_node_any*)node); |
| break; |
| default: |
| LOGINT(node->schema->module->ctx); |
| ret = LY_EINT; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| LY_ERR |
| xml_print_data(struct lyout *out, const struct lyd_node *root, int options) |
| { |
| const struct lyd_node *node; |
| struct xmlpr_ctx ctx_ = {.out = out, .level = (options & LYDP_FORMAT ? 1 : 0), .options = options, .toplevel = 1}, *ctx = &ctx_; |
| |
| if (!root) { |
| if (out->type == LYOUT_MEMORY || out->type == LYOUT_CALLBACK) { |
| ly_print(out, ""); |
| } |
| goto finish; |
| } |
| |
| /* content */ |
| LY_LIST_FOR(root, node) { |
| if (xml_print_node(ctx, node)) { |
| return EXIT_FAILURE; |
| } |
| if (!(options & LYDP_WITHSIBLINGS)) { |
| break; |
| } |
| } |
| |
| finish: |
| ly_print_flush(out); |
| return LY_SUCCESS; |
| } |
| |