blob: ecc6e75057aeaab45558239e7427c629bfa25257 [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
53#if 0
54static LY_ERR
55modlist_add(struct mlist **mlist, const struct lys_module *mod)
56{
57 struct mlist *iter;
58
59 for (iter = *mlist; iter; iter = iter->next) {
60 if (mod == iter->module) {
61 break;
62 }
63 }
64
65 if (!iter) {
66 iter = malloc(sizeof *iter);
67 LY_CHECK_ERR_RET(!iter, LOGMEM(mod->ctx), LY_EMEM);
68 iter->next = *mlist;
69 iter->module = (struct lys_module *)mod;
70 *mlist = iter;
71 }
72
73 return LY_SUCCESS;
74}
75#endif
76
77/**
78 * TODO
79 */
80static void
81xml_print_ns(struct xmlpr_ctx *ctx, const struct lyd_node *node)
82{
83 struct lyd_node *next, *cur, *child;
84 struct lyd_attr *attr;
Radek Krejcie7b95092019-05-15 11:03:07 +020085
86#if 0
Radek Krejci585f1922019-05-17 10:34:15 +020087 struct mlist *mlist = NULL, *miter;
88
Radek Krejcie7b95092019-05-15 11:03:07 +020089 const struct lys_module *wdmod = NULL;
90
91 /* add node attribute modules */
92 for (attr = node->attr; attr; attr = attr->next) {
93 if (!strcmp(node->schema->name, "filter") &&
94 (!strcmp(node->schema->module->name, "ietf-netconf") ||
95 !strcmp(node->schema->module->name, "notifications"))) {
96 /* exception for NETCONF's filter attributes */
97 continue;
98 } else {
99 r = modlist_add(&mlist, lys_main_module(attr->annotation->module));
100 }
101 if (r) {
102 goto print;
103 }
104 }
105#endif
106
107 /* add node children nodes and attribute modules */
108 switch (node->schema->nodetype) {
109 case LYS_LEAFLIST:
110 case LYS_LEAF:
111 /* TODO ietf-netconf-with-defaults namespace */
112#if 0
113 if (node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) {
114 /* get with-defaults module and print its namespace */
115 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
116 if (wdmod && modlist_add(&mlist, wdmod)) {
117 goto print;
118 }
119 }
120#endif
121 break;
122 case LYS_CONTAINER:
123 case LYS_LIST:
Radek Krejcie7b95092019-05-15 11:03:07 +0200124 case LYS_ACTION:
125 case LYS_NOTIF:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200126#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200127 if (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG)) {
128 /* get with-defaults module and print its namespace */
129 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
130 if (wdmod && modlist_add(&mlist, wdmod)) {
131 goto print;
132 }
133 }
134#endif
135 LY_LIST_FOR(((struct lyd_node_inner*)node)->child, child) {
136 LYD_TREE_DFS_BEGIN(child, next, cur) {
137 for (attr = cur->attr; attr; attr = attr->next) {
138 if (!strcmp(cur->schema->name, "filter") &&
139 (!strcmp(cur->schema->module->name, "ietf-netconf") ||
140 !strcmp(cur->schema->module->name, "notifications"))) {
141 /* exception for NETCONF's filter attributes */
142 continue;
143 } else {
144 /* TODO annotations r = modlist_add(&mlist, lys_main_module(attr->annotation->module)); */
145 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200146 }
147 LYD_TREE_DFS_END(child, next, cur)}
148 }
149 break;
150 default:
151 break;
152 }
Radek Krejci585f1922019-05-17 10:34:15 +0200153#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200154print:
155 /* print used namespaces */
156 while (mlist) {
157 miter = mlist;
158 mlist = mlist->next;
159
160 ly_print(ctx->out, " xmlns:%s=\"%s\"", miter->module->prefix, miter->module->ns);
161 free(miter);
162 }
Radek Krejci585f1922019-05-17 10:34:15 +0200163#endif
Radek Krejcie7b95092019-05-15 11:03:07 +0200164}
165
166/**
167 * TODO
168 */
169static LY_ERR
170xml_print_attrs(struct xmlpr_ctx *ctx, const struct lyd_node *node)
171{
172 (void) ctx;
173 (void) node;
174
175#if 0
176 struct lyd_attr *attr;
177 const char **prefs, **nss;
178 const char *xml_expr = NULL, *mod_name;
179 uint32_t ns_count, i;
180 int rpc_filter = 0;
181 const struct lys_module *wdmod = NULL;
182 char *p;
183 size_t len;
184
185 LY_PRINT_SET;
186
187 /* with-defaults */
188 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
189 if ((node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) ||
190 (!node->dflt && (options & LYP_WD_ALL_TAG) && lyd_wd_default((struct lyd_node_leaf_list *)node))) {
191 /* we have implicit OR explicit default node */
192 /* get with-defaults module */
193 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
194 if (wdmod) {
195 /* print attribute only if context include with-defaults schema */
196 ly_print(out, " %s:default=\"true\"", wdmod->prefix);
197 }
198 }
199 }
200 /* technically, check for the extension get-filter-element-attributes from ietf-netconf */
201 if (!strcmp(node->schema->name, "filter")
202 && (!strcmp(node->schema->module->name, "ietf-netconf") || !strcmp(node->schema->module->name, "notifications"))) {
203 rpc_filter = 1;
204 }
205
206 for (attr = node->attr; attr; attr = attr->next) {
207 if (rpc_filter) {
208 /* exception for NETCONF's filter's attributes */
209 if (!strcmp(attr->name, "select")) {
210 /* xpath content, we have to convert the JSON format into XML first */
211 xml_expr = transform_json2xml(node->schema->module, attr->value_str, 0, &prefs, &nss, &ns_count);
212 if (!xml_expr) {
213 /* error */
214 return EXIT_FAILURE;
215 }
216
217 for (i = 0; i < ns_count; ++i) {
218 ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
219 }
220 free(prefs);
221 free(nss);
222 }
223 ly_print(out, " %s=\"", attr->name);
224 } else {
225 ly_print(out, " %s:%s=\"", attr->annotation->module->prefix, attr->name);
226 }
227
228 switch (attr->value_type) {
229 case LY_TYPE_BINARY:
230 case LY_TYPE_STRING:
231 case LY_TYPE_BITS:
232 case LY_TYPE_ENUM:
233 case LY_TYPE_BOOL:
234 case LY_TYPE_DEC64:
235 case LY_TYPE_INT8:
236 case LY_TYPE_INT16:
237 case LY_TYPE_INT32:
238 case LY_TYPE_INT64:
239 case LY_TYPE_UINT8:
240 case LY_TYPE_UINT16:
241 case LY_TYPE_UINT32:
242 case LY_TYPE_UINT64:
243 if (attr->value_str) {
244 /* xml_expr can contain transformed xpath */
245 lyxml_dump_text(out, xml_expr ? xml_expr : attr->value_str, LYXML_DATA_ATTR);
246 }
247 break;
248
249 case LY_TYPE_IDENT:
250 if (!attr->value_str) {
251 break;
252 }
253 p = strchr(attr->value_str, ':');
254 assert(p);
255 len = p - attr->value_str;
256 mod_name = attr->annotation->module->name;
257 if (!strncmp(attr->value_str, mod_name, len) && !mod_name[len]) {
258 lyxml_dump_text(out, ++p, LYXML_DATA_ATTR);
259 } else {
260 /* avoid code duplication - use instance-identifier printer which gets necessary namespaces to print */
261 goto printinst;
262 }
263 break;
264 case LY_TYPE_INST:
265printinst:
266 xml_expr = transform_json2xml(node->schema->module, ((struct lyd_node_leaf_list *)node)->value_str, 1,
267 &prefs, &nss, &ns_count);
268 if (!xml_expr) {
269 /* error */
270 return EXIT_FAILURE;
271 }
272
273 for (i = 0; i < ns_count; ++i) {
274 ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
275 }
276 free(prefs);
277 free(nss);
278
279 lyxml_dump_text(out, xml_expr, LYXML_DATA_ATTR);
280 lydict_remove(node->schema->module->ctx, xml_expr);
281 break;
282
283 /* LY_TYPE_LEAFREF not allowed */
284 case LY_TYPE_EMPTY:
285 break;
286
287 default:
288 /* error */
289 LOGINT(node->schema->module->ctx);
290 return EXIT_FAILURE;
291 }
292
293 ly_print(out, "\"");
294
295 if (xml_expr) {
296 lydict_remove(node->schema->module->ctx, xml_expr);
297 }
298 }
299#endif
300
301 return LY_SUCCESS;
302}
303
304/**
305 * @brief Print generic XML element despite of the data node type.
306 *
307 * Prints the element name, attributes and necessary namespaces.
308 *
309 * @param[in] ctx XML printer context.
310 * @param[in] node Data node to be printed.
311 * @return LY_ERR value.
312 */
313static LY_ERR
314xml_print_node_open(struct xmlpr_ctx *ctx, const struct lyd_node *node)
315{
316 if (ctx->toplevel || !node->parent || node->schema->module != node->parent->schema->module) {
317 /* print "namespace" */
318 ly_print(ctx->out, "%*s<%s xmlns=\"%s\"", INDENT, node->schema->name, node->schema->module->ns);
319 } else {
320 ly_print(ctx->out, "%*s<%s", INDENT, node->schema->name);
321 }
322
323 if (ctx->toplevel) {
324 xml_print_ns(ctx, node);
325 ctx->toplevel = 0;
326 }
327
328 LY_CHECK_RET(xml_print_attrs(ctx, node));
329
330 return LY_SUCCESS;
331}
332
333static LY_ERR xml_print_node(struct xmlpr_ctx *ctx, const struct lyd_node *node);
334
335/**
Radek Krejcia1911222019-07-22 17:24:50 +0200336 * @brief XML mapping of YANG modules to prefixes in values.
337 *
338 * Implementation of ly_clb_get_prefix
339 */
340static const char *
341xml_print_get_prefix(const struct lys_module *mod, void *private)
342{
343 struct ly_set *ns_list = (struct ly_set*)private;
344
345 ly_set_add(ns_list, (void*)mod, 0);
346 return mod->prefix;
347}
348
349/**
Radek Krejcie7b95092019-05-15 11:03:07 +0200350 * @brief Print XML element representing lyd_node_term.
351 *
352 * @param[in] ctx XML printer context.
353 * @param[in] node Data node to be printed.
354 * @return LY_ERR value.
355 */
356static LY_ERR
357xml_print_term(struct xmlpr_ctx *ctx, const struct lyd_node_term *node)
358{
359 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
Radek Krejcia1911222019-07-22 17:24:50 +0200360 struct ly_set ns_list = {0};
361 unsigned int u;
362 int dynamic;
363 const char *value = node->value.realtype->plugin->print(&node->value, LYD_XML, xml_print_get_prefix, &ns_list, &dynamic);
Radek Krejcie7b95092019-05-15 11:03:07 +0200364
Radek Krejcia1911222019-07-22 17:24:50 +0200365 /* print namespaces connected with the values's prefixes */
366 for (u = 0; u < ns_list.count; ++u) {
367 const struct lys_module *mod = (const struct lys_module*)ns_list.objs[u];
368 ly_print(ctx->out, "%sxmlns:%s=\"%s\"", u ? " " : "", mod->prefix, mod->ns);
Radek Krejcie7b95092019-05-15 11:03:07 +0200369 }
Radek Krejcia1911222019-07-22 17:24:50 +0200370 ly_set_erase(&ns_list, NULL);
Radek Krejcie7b95092019-05-15 11:03:07 +0200371
Radek Krejcia1911222019-07-22 17:24:50 +0200372 if (!value || !value[0]) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200373 ly_print(ctx->out, "/>%s", LEVEL ? "\n" : "");
374 } else {
375 ly_print(ctx->out, ">");
Radek Krejcia1911222019-07-22 17:24:50 +0200376 lyxml_dump_text(ctx->out, value, 0);
Radek Krejcie7b95092019-05-15 11:03:07 +0200377 ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\n" : "");
378 }
Radek Krejcia1911222019-07-22 17:24:50 +0200379 if (dynamic) {
380 free((void*)value);
381 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200382
383 return LY_SUCCESS;
384}
385
386/**
387 * @brief Print XML element representing lyd_node_inner.
388 *
389 * @param[in] ctx XML printer context.
390 * @param[in] node Data node to be printed.
391 * @return LY_ERR value.
392 */
393static LY_ERR
394xml_print_inner(struct xmlpr_ctx *ctx, const struct lyd_node_inner *node)
395{
396 LY_ERR ret;
397 struct lyd_node *child;
398
399 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
400
401 if (!node->child) {
402 ly_print(ctx->out, "/>%s", ctx->level ? "\n" : "");
403 return LY_SUCCESS;
404 }
405
406 /* children */
407 ly_print(ctx->out, ">%s", ctx->level ? "\n" : "");
408
409 LEVEL_INC;
410 LY_LIST_FOR(node->child, child) {
411 ret = xml_print_node(ctx, child);
412 LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
413 }
414 LEVEL_DEC;
415
416 ly_print(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : "");
417
418 return LY_SUCCESS;
419}
420
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200421static LY_ERR
422xml_print_anydata(struct xmlpr_ctx *ctx, const struct lyd_node_any *node)
Radek Krejcie7b95092019-05-15 11:03:07 +0200423{
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200424 struct lyd_node_any *any = (struct lyd_node_any *)node;
Radek Krejcie7b95092019-05-15 11:03:07 +0200425 struct lyd_node *iter;
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200426 int options_backup;
Radek Krejcie7b95092019-05-15 11:03:07 +0200427
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200428 LY_CHECK_RET(xml_print_node_open(ctx, (struct lyd_node *)node));
Radek Krejcie7b95092019-05-15 11:03:07 +0200429
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200430 if (!any->value.tree) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200431 /* no content */
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200432no_content:
433 ly_print(ctx->out, "/>%s", LEVEL ? "\n" : "");
434 return LY_SUCCESS;
Radek Krejcie7b95092019-05-15 11:03:07 +0200435 } else {
Radek Krejcie7b95092019-05-15 11:03:07 +0200436 switch (any->value_type) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200437 case LYD_ANYDATA_DATATREE:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200438 /* close opening tag and print data */
439 options_backup = ctx->options;
440 ctx->options &= ~(LYDP_WITHSIBLINGS | LYDP_NETCONF);
441 LEVEL_INC;
442
443 ly_print(ctx->out, ">%s", LEVEL ? "\n" : "");
444 LY_LIST_FOR(any->value.tree, iter) {
445 if (xml_print_node(ctx, iter)) {
446 return EXIT_FAILURE;
Radek Krejcie7b95092019-05-15 11:03:07 +0200447 }
448 }
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200449
450 LEVEL_DEC;
451 ctx->options = options_backup;
Radek Krejcie7b95092019-05-15 11:03:07 +0200452 break;
453 case LYD_ANYDATA_STRING:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200454 /* escape XML-sensitive characters */
455 if (!any->value.str[0]) {
456 goto no_content;
457 }
458 /* close opening tag and print data */
459 ly_print(ctx->out, ">");
460 lyxml_dump_text(ctx->out, any->value.str, 0);
Radek Krejcie7b95092019-05-15 11:03:07 +0200461 break;
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200462 case LYD_ANYDATA_XML:
463 /* print without escaping special characters */
464 if (!any->value.str[0]) {
465 goto no_content;
466 }
467 ly_print(ctx->out, ">%s", any->value.str);
468 break;
469 case LYD_ANYDATA_JSON:
470#if 0 /* TODO LYB format */
471 case LYD_ANYDATA_LYB:
472#endif
473 /* JSON and LYB format is not supported */
474 LOGWRN(node->schema->module->ctx, "Unable to print anydata content (type %d) as XML.", any->value_type);
475 goto no_content;
Radek Krejcie7b95092019-05-15 11:03:07 +0200476 }
477
478 /* closing tag */
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200479 if (any->value_type == LYD_ANYDATA_DATATREE) {
480 ly_print(ctx->out, "%*s</%s>%s", INDENT, node->schema->name, LEVEL ? "\n" : "");
481 } else {
482 ly_print(ctx->out, "</%s>%s", node->schema->name, LEVEL ? "\n" : "");
483 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200484 }
485
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200486 return LY_SUCCESS;
Radek Krejcie7b95092019-05-15 11:03:07 +0200487}
Radek Krejcie7b95092019-05-15 11:03:07 +0200488
489/**
490 * @brief Print XML element representing lyd_node.
491 *
492 * @param[in] ctx XML printer context.
493 * @param[in] node Data node to be printed.
494 * @return LY_ERR value.
495 */
496static LY_ERR
497xml_print_node(struct xmlpr_ctx *ctx, const struct lyd_node *node)
498{
499 LY_ERR ret = LY_SUCCESS;
500
501#if 0
502 if (!lyd_wd_toprint(node, ctx->options)) {
503 /* wd says do not print */
504 return EXIT_SUCCESS;
505 }
506#endif
507
508 switch (node->schema->nodetype) {
Radek Krejcie7b95092019-05-15 11:03:07 +0200509 case LYS_CONTAINER:
510 case LYS_LIST:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200511 case LYS_NOTIF:
512 case LYS_ACTION:
Radek Krejcie7b95092019-05-15 11:03:07 +0200513 ret = xml_print_inner(ctx, (const struct lyd_node_inner*)node);
514 break;
515 case LYS_LEAF:
516 case LYS_LEAFLIST:
517 ret = xml_print_term(ctx, (const struct lyd_node_term*)node);
518 break;
Radek Krejcie7b95092019-05-15 11:03:07 +0200519 case LYS_ANYXML:
520 case LYS_ANYDATA:
Radek Krejci26a5dfb2019-07-26 14:51:06 +0200521 ret = xml_print_anydata(ctx, (const struct lyd_node_any*)node);
Radek Krejcie7b95092019-05-15 11:03:07 +0200522 break;
Radek Krejcie7b95092019-05-15 11:03:07 +0200523 default:
524 LOGINT(node->schema->module->ctx);
525 ret = LY_EINT;
526 break;
527 }
528
529 return ret;
530}
531
532LY_ERR
533xml_print_data(struct lyout *out, const struct lyd_node *root, int options)
534{
535 const struct lyd_node *node;
536 struct xmlpr_ctx ctx_ = {.out = out, .level = (options & LYDP_FORMAT ? 1 : 0), .options = options, .toplevel = 1}, *ctx = &ctx_;
537
538 if (!root) {
539 if (out->type == LYOUT_MEMORY || out->type == LYOUT_CALLBACK) {
540 ly_print(out, "");
541 }
542 goto finish;
543 }
544
545 /* content */
546 LY_LIST_FOR(root, node) {
547 if (xml_print_node(ctx, node)) {
548 return EXIT_FAILURE;
549 }
550 if (!(options & LYDP_WITHSIBLINGS)) {
551 break;
552 }
553 }
554
555finish:
556 ly_print_flush(out);
557 return LY_SUCCESS;
558}
559