blob: 0e439d21613f388804123a1e62417af8bc378708 [file] [log] [blame]
Radek Krejcie7b95092019-05-15 11:03:07 +02001/**
2 * @file printer_xml.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @author Radek Krejci <rkrejci@cesnet.cz>
5 * @brief XML printer for libyang data structure
6 *
7 * Copyright (c) 2015 - 2019 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
16#include "common.h"
17
18#include <stdlib.h>
19#include <string.h>
20
21#include "log.h"
22#include "plugins_types.h"
23#include "printer_data.h"
24#include "printer_internal.h"
25#include "tree.h"
26#include "tree_data.h"
27#include "tree_schema.h"
28#include "xml.h"
29
30/**
31 * @brief XML printer context.
32 */
33struct xmlpr_ctx {
34 struct lyout *out; /**< output specification */
35 unsigned int level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */
36 int options; /**< [Data printer flags](@ref dataprinterflags) */
37 int toplevel; /**< top-level flag */
38};
39
40#define LEVEL ctx->level /**< current level */
41#define INDENT ((LEVEL) ? (LEVEL)*2 : 0),"" /**< indentation parameters for printer functions */
42#define LEVEL_INC if (LEVEL) {LEVEL++;} /**< increase indentation level */
43#define LEVEL_DEC if (LEVEL) {LEVEL--;} /**< decrease indentation level */
44
45/**
46 * TODO
47 */
48struct mlist {
49 struct mlist *next;
50 struct lys_module *module;
51} *mlist = NULL, *mlist_new;
52
Radek Krejcie7b95092019-05-15 11:03:07 +020053static LY_ERR
54modlist_add(struct mlist **mlist, const struct lys_module *mod)
55{
56 struct mlist *iter;
57
58 for (iter = *mlist; iter; iter = iter->next) {
59 if (mod == iter->module) {
60 break;
61 }
62 }
63
64 if (!iter) {
65 iter = malloc(sizeof *iter);
66 LY_CHECK_ERR_RET(!iter, LOGMEM(mod->ctx), LY_EMEM);
67 iter->next = *mlist;
68 iter->module = (struct lys_module *)mod;
69 *mlist = iter;
70 }
71
72 return LY_SUCCESS;
73}
Radek Krejcie7b95092019-05-15 11:03:07 +020074
75/**
76 * TODO
77 */
78static void
79xml_print_ns(struct xmlpr_ctx *ctx, const struct lyd_node *node)
80{
81 struct lyd_node *next, *cur, *child;
82 struct lyd_attr *attr;
Radek Krejcie7b95092019-05-15 11:03:07 +020083
Radek Krejci585f1922019-05-17 10:34:15 +020084 struct mlist *mlist = NULL, *miter;
Radek Krejci28681fa2019-09-06 13:08:45 +020085#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +020086 const struct lys_module *wdmod = NULL;
Radek Krejci28681fa2019-09-06 13:08:45 +020087#endif
Radek Krejcie7b95092019-05-15 11:03:07 +020088 /* add node attribute modules */
89 for (attr = node->attr; attr; attr = attr->next) {
90 if (!strcmp(node->schema->name, "filter") &&
91 (!strcmp(node->schema->module->name, "ietf-netconf") ||
92 !strcmp(node->schema->module->name, "notifications"))) {
93 /* exception for NETCONF's filter attributes */
94 continue;
Radek Krejci28681fa2019-09-06 13:08:45 +020095 } else if (modlist_add(&mlist, attr->annotation->module)) {
Radek Krejcie7b95092019-05-15 11:03:07 +020096 goto print;
97 }
98 }
Radek Krejcie7b95092019-05-15 11:03:07 +020099
100 /* add node children nodes and attribute modules */
101 switch (node->schema->nodetype) {
102 case LYS_LEAFLIST:
103 case LYS_LEAF:
104 /* TODO ietf-netconf-with-defaults namespace */
105#if 0
106 if (node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) {
107 /* get with-defaults module and print its namespace */
108 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
109 if (wdmod && modlist_add(&mlist, wdmod)) {
110 goto print;
111 }
112 }
113#endif
114 break;
115 case LYS_CONTAINER:
116 case LYS_LIST:
Radek Krejcie7b95092019-05-15 11:03:07 +0200117 case LYS_ACTION:
118 case LYS_NOTIF:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200119#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200120 if (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG)) {
121 /* get with-defaults module and print its namespace */
122 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
123 if (wdmod && modlist_add(&mlist, wdmod)) {
124 goto print;
125 }
126 }
127#endif
128 LY_LIST_FOR(((struct lyd_node_inner*)node)->child, child) {
129 LYD_TREE_DFS_BEGIN(child, next, cur) {
130 for (attr = cur->attr; attr; attr = attr->next) {
131 if (!strcmp(cur->schema->name, "filter") &&
132 (!strcmp(cur->schema->module->name, "ietf-netconf") ||
133 !strcmp(cur->schema->module->name, "notifications"))) {
134 /* exception for NETCONF's filter attributes */
135 continue;
136 } else {
137 /* TODO annotations r = modlist_add(&mlist, lys_main_module(attr->annotation->module)); */
138 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200139 }
140 LYD_TREE_DFS_END(child, next, cur)}
141 }
142 break;
143 default:
144 break;
145 }
Radek Krejci28681fa2019-09-06 13:08:45 +0200146
Radek Krejcie7b95092019-05-15 11:03:07 +0200147print:
148 /* print used namespaces */
149 while (mlist) {
150 miter = mlist;
151 mlist = mlist->next;
152
153 ly_print(ctx->out, " xmlns:%s=\"%s\"", miter->module->prefix, miter->module->ns);
154 free(miter);
155 }
Radek Krejci28681fa2019-09-06 13:08:45 +0200156}
157
158/**
159 * @brief XML mapping of YANG modules to prefixes in values.
160 *
161 * Implementation of ly_clb_get_prefix
162 */
163static const char *
164xml_print_get_prefix(const struct lys_module *mod, void *private)
165{
166 struct ly_set *ns_list = (struct ly_set*)private;
167
168 ly_set_add(ns_list, (void*)mod, 0);
169 return mod->prefix;
Radek Krejcie7b95092019-05-15 11:03:07 +0200170}
171
172/**
173 * TODO
174 */
175static LY_ERR
176xml_print_attrs(struct xmlpr_ctx *ctx, const struct lyd_node *node)
177{
Radek Krejcie7b95092019-05-15 11:03:07 +0200178 struct lyd_attr *attr;
Radek Krejci28681fa2019-09-06 13:08:45 +0200179#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200180 const char **prefs, **nss;
181 const char *xml_expr = NULL, *mod_name;
182 uint32_t ns_count, i;
183 int rpc_filter = 0;
184 const struct lys_module *wdmod = NULL;
185 char *p;
186 size_t len;
Radek Krejci28681fa2019-09-06 13:08:45 +0200187#endif
188 struct ly_set ns_list = {0};
189 int dynamic;
190 unsigned int u;
Radek Krejcie7b95092019-05-15 11:03:07 +0200191
Radek Krejci28681fa2019-09-06 13:08:45 +0200192#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200193 /* with-defaults */
194 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
195 if ((node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) ||
196 (!node->dflt && (options & LYP_WD_ALL_TAG) && lyd_wd_default((struct lyd_node_leaf_list *)node))) {
197 /* we have implicit OR explicit default node */
198 /* get with-defaults module */
199 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
200 if (wdmod) {
201 /* print attribute only if context include with-defaults schema */
202 ly_print(out, " %s:default=\"true\"", wdmod->prefix);
203 }
204 }
205 }
206 /* technically, check for the extension get-filter-element-attributes from ietf-netconf */
207 if (!strcmp(node->schema->name, "filter")
208 && (!strcmp(node->schema->module->name, "ietf-netconf") || !strcmp(node->schema->module->name, "notifications"))) {
209 rpc_filter = 1;
210 }
Radek Krejci28681fa2019-09-06 13:08:45 +0200211#endif
Radek Krejcie7b95092019-05-15 11:03:07 +0200212 for (attr = node->attr; attr; attr = attr->next) {
Radek Krejci28681fa2019-09-06 13:08:45 +0200213 const char *value = attr->value.realtype->plugin->print(&attr->value, LYD_XML, xml_print_get_prefix, &ns_list, &dynamic);
214
215 /* print namespaces connected with the values's prefixes */
216 for (u = 0; u < ns_list.count; ++u) {
217 const struct lys_module *mod = (const struct lys_module*)ns_list.objs[u];
218 ly_print(ctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns);
219 }
220 ly_set_erase(&ns_list, NULL);
221
222#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200223 if (rpc_filter) {
224 /* exception for NETCONF's filter's attributes */
225 if (!strcmp(attr->name, "select")) {
226 /* xpath content, we have to convert the JSON format into XML first */
227 xml_expr = transform_json2xml(node->schema->module, attr->value_str, 0, &prefs, &nss, &ns_count);
228 if (!xml_expr) {
229 /* error */
230 return EXIT_FAILURE;
231 }
232
233 for (i = 0; i < ns_count; ++i) {
234 ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
235 }
236 free(prefs);
237 free(nss);
238 }
239 ly_print(out, " %s=\"", attr->name);
240 } else {
Radek Krejci28681fa2019-09-06 13:08:45 +0200241#endif
242 ly_print(ctx->out, " %s:%s=\"", attr->annotation->module->prefix, attr->name);
243#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200244 }
Radek Krejci28681fa2019-09-06 13:08:45 +0200245#endif
Radek Krejcie7b95092019-05-15 11:03:07 +0200246
Radek Krejci28681fa2019-09-06 13:08:45 +0200247 if (value && value[0]) {
248 lyxml_dump_text(ctx->out, value, 1);
Radek Krejcie7b95092019-05-15 11:03:07 +0200249 }
Radek Krejci28681fa2019-09-06 13:08:45 +0200250 ly_print(ctx->out, "\"");
251 if (dynamic) {
252 free((void*)value);
Radek Krejcie7b95092019-05-15 11:03:07 +0200253 }
254 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200255
256 return LY_SUCCESS;
257}
258
259/**
260 * @brief Print generic XML element despite of the data node type.
261 *
262 * Prints the element name, attributes and necessary namespaces.
263 *
264 * @param[in] ctx XML printer context.
265 * @param[in] node Data node to be printed.
266 * @return LY_ERR value.
267 */
268static LY_ERR
269xml_print_node_open(struct xmlpr_ctx *ctx, const struct lyd_node *node)
270{
271 if (ctx->toplevel || !node->parent || node->schema->module != node->parent->schema->module) {
272 /* print "namespace" */
273 ly_print(ctx->out, "%*s<%s xmlns=\"%s\"", INDENT, node->schema->name, node->schema->module->ns);
274 } else {
275 ly_print(ctx->out, "%*s<%s", INDENT, node->schema->name);
276 }
277
278 if (ctx->toplevel) {
279 xml_print_ns(ctx, node);
280 ctx->toplevel = 0;
281 }
282
283 LY_CHECK_RET(xml_print_attrs(ctx, node));
284
285 return LY_SUCCESS;
286}
287
288static LY_ERR xml_print_node(struct xmlpr_ctx *ctx, const struct lyd_node *node);
289
290/**
291 * @brief Print XML element representing lyd_node_term.
292 *
293 * @param[in] ctx XML printer context.
294 * @param[in] node Data node to be printed.
295 * @return LY_ERR value.
296 */
297static LY_ERR
298xml_print_term(struct xmlpr_ctx *ctx, const struct lyd_node_term *node)
299{
Radek Krejcia1911222019-07-22 17:24:50 +0200300 struct ly_set ns_list = {0};
301 unsigned int u;
302 int dynamic;
Radek Krejci28681fa2019-09-06 13:08:45 +0200303 const char *value;
304
305 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
306 value = node->value.realtype->plugin->print(&node->value, LYD_XML, xml_print_get_prefix, &ns_list, &dynamic);
Radek Krejcie7b95092019-05-15 11:03:07 +0200307
Radek Krejcia1911222019-07-22 17:24:50 +0200308 /* print namespaces connected with the values's prefixes */
309 for (u = 0; u < ns_list.count; ++u) {
310 const struct lys_module *mod = (const struct lys_module*)ns_list.objs[u];
311 ly_print(ctx->out, "%sxmlns:%s=\"%s\"", u ? " " : "", mod->prefix, mod->ns);
Radek Krejcie7b95092019-05-15 11:03:07 +0200312 }
Radek Krejcia1911222019-07-22 17:24:50 +0200313 ly_set_erase(&ns_list, NULL);
Radek Krejcie7b95092019-05-15 11:03:07 +0200314
Radek Krejcia1911222019-07-22 17:24:50 +0200315 if (!value || !value[0]) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200316 ly_print(ctx->out, "/>%s", LEVEL ? "\n" : "");
317 } else {
318 ly_print(ctx->out, ">");
Radek Krejcia1911222019-07-22 17:24:50 +0200319 lyxml_dump_text(ctx->out, value, 0);
Radek Krejcie7b95092019-05-15 11:03:07 +0200320 ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\n" : "");
321 }
Radek Krejcia1911222019-07-22 17:24:50 +0200322 if (dynamic) {
323 free((void*)value);
324 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200325
326 return LY_SUCCESS;
327}
328
329/**
330 * @brief Print XML element representing lyd_node_inner.
331 *
332 * @param[in] ctx XML printer context.
333 * @param[in] node Data node to be printed.
334 * @return LY_ERR value.
335 */
336static LY_ERR
337xml_print_inner(struct xmlpr_ctx *ctx, const struct lyd_node_inner *node)
338{
339 LY_ERR ret;
340 struct lyd_node *child;
341
342 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
343
344 if (!node->child) {
345 ly_print(ctx->out, "/>%s", ctx->level ? "\n" : "");
346 return LY_SUCCESS;
347 }
348
349 /* children */
350 ly_print(ctx->out, ">%s", ctx->level ? "\n" : "");
351
352 LEVEL_INC;
353 LY_LIST_FOR(node->child, child) {
354 ret = xml_print_node(ctx, child);
355 LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
356 }
357 LEVEL_DEC;
358
359 ly_print(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : "");
360
361 return LY_SUCCESS;
362}
363
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200364static LY_ERR
365xml_print_anydata(struct xmlpr_ctx *ctx, const struct lyd_node_any *node)
Radek Krejcie7b95092019-05-15 11:03:07 +0200366{
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200367 struct lyd_node_any *any = (struct lyd_node_any *)node;
Radek Krejcie7b95092019-05-15 11:03:07 +0200368 struct lyd_node *iter;
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200369 int options_backup;
Radek Krejcie7b95092019-05-15 11:03:07 +0200370
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200371 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
Radek Krejcie7b95092019-05-15 11:03:07 +0200372
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200373 if (!any->value.tree) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200374 /* no content */
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200375no_content:
376 ly_print(ctx->out, "/>%s", LEVEL ? "\n" : "");
377 return LY_SUCCESS;
Radek Krejcie7b95092019-05-15 11:03:07 +0200378 } else {
Radek Krejcie7b95092019-05-15 11:03:07 +0200379 switch (any->value_type) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200380 case LYD_ANYDATA_DATATREE:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200381 /* close opening tag and print data */
382 options_backup = ctx->options;
383 ctx->options &= ~(LYDP_WITHSIBLINGS | LYDP_NETCONF);
384 LEVEL_INC;
385
386 ly_print(ctx->out, ">%s", LEVEL ? "\n" : "");
387 LY_LIST_FOR(any->value.tree, iter) {
388 if (xml_print_node(ctx, iter)) {
389 return EXIT_FAILURE;
Radek Krejcie7b95092019-05-15 11:03:07 +0200390 }
391 }
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200392
393 LEVEL_DEC;
394 ctx->options = options_backup;
Radek Krejcie7b95092019-05-15 11:03:07 +0200395 break;
396 case LYD_ANYDATA_STRING:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200397 /* escape XML-sensitive characters */
398 if (!any->value.str[0]) {
399 goto no_content;
400 }
401 /* close opening tag and print data */
402 ly_print(ctx->out, ">");
403 lyxml_dump_text(ctx->out, any->value.str, 0);
Radek Krejcie7b95092019-05-15 11:03:07 +0200404 break;
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200405 case LYD_ANYDATA_XML:
406 /* print without escaping special characters */
407 if (!any->value.str[0]) {
408 goto no_content;
409 }
410 ly_print(ctx->out, ">%s", any->value.str);
411 break;
412 case LYD_ANYDATA_JSON:
413#if 0 /* TODO LYB format */
414 case LYD_ANYDATA_LYB:
415#endif
416 /* JSON and LYB format is not supported */
417 LOGWRN(node->schema->module->ctx, "Unable to print anydata content (type %d) as XML.", any->value_type);
418 goto no_content;
Radek Krejcie7b95092019-05-15 11:03:07 +0200419 }
420
421 /* closing tag */
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200422 if (any->value_type == LYD_ANYDATA_DATATREE) {
423 ly_print(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : "");
424 } else {
425 ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\n" : "");
426 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200427 }
428
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200429 return LY_SUCCESS;
Radek Krejcie7b95092019-05-15 11:03:07 +0200430}
Radek Krejcie7b95092019-05-15 11:03:07 +0200431
432/**
433 * @brief Print XML element representing lyd_node.
434 *
435 * @param[in] ctx XML printer context.
436 * @param[in] node Data node to be printed.
437 * @return LY_ERR value.
438 */
439static LY_ERR
440xml_print_node(struct xmlpr_ctx *ctx, const struct lyd_node *node)
441{
442 LY_ERR ret = LY_SUCCESS;
443
Michal Vasko9b368d32020-02-14 13:53:31 +0100444 if (!ly_should_print(node, ctx->options)) {
445 /* do not print at all */
Radek Krejcie7b95092019-05-15 11:03:07 +0200446 return EXIT_SUCCESS;
447 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200448
449 switch (node->schema->nodetype) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200450 case LYS_CONTAINER:
451 case LYS_LIST:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200452 case LYS_NOTIF:
453 case LYS_ACTION:
Radek Krejcie7b95092019-05-15 11:03:07 +0200454 ret = xml_print_inner(ctx, (const struct lyd_node_inner*)node);
455 break;
456 case LYS_LEAF:
457 case LYS_LEAFLIST:
458 ret = xml_print_term(ctx, (const struct lyd_node_term*)node);
459 break;
Radek Krejcie7b95092019-05-15 11:03:07 +0200460 case LYS_ANYXML:
461 case LYS_ANYDATA:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200462 ret = xml_print_anydata(ctx, (const struct lyd_node_any*)node);
Radek Krejcie7b95092019-05-15 11:03:07 +0200463 break;
Radek Krejcie7b95092019-05-15 11:03:07 +0200464 default:
465 LOGINT(node->schema->module->ctx);
466 ret = LY_EINT;
467 break;
468 }
469
470 return ret;
471}
472
473LY_ERR
474xml_print_data(struct lyout *out, const struct lyd_node *root, int options)
475{
476 const struct lyd_node *node;
477 struct xmlpr_ctx ctx_ = {.out = out, .level = (options & LYDP_FORMAT ? 1 : 0), .options = options, .toplevel = 1}, *ctx = &ctx_;
478
479 if (!root) {
480 if (out->type == LYOUT_MEMORY || out->type == LYOUT_CALLBACK) {
481 ly_print(out, "");
482 }
483 goto finish;
484 }
485
486 /* content */
487 LY_LIST_FOR(root, node) {
488 if (xml_print_node(ctx, node)) {
489 return EXIT_FAILURE;
490 }
491 if (!(options & LYDP_WITHSIBLINGS)) {
492 break;
493 }
494 }
495
496finish:
497 ly_print_flush(out);
498 return LY_SUCCESS;
499}
500