#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_internal.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"
struct xmlpr_ctx {
struct ly_out *out;
uint16_t level;
uint32_t options;
const struct ly_ctx *ctx;
struct ly_set prefix;
struct ly_set ns;
};
#define LYXML_PREFIX_REQUIRED 0x01
#define LYXML_PREFIX_DEFAULT 0x02
static LY_ERR xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node);
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) {
if (!pctx->prefix.objs[i - 1]) {
if (!strcmp(pctx->ns.objs[i - 1], ns)) {
return pctx->prefix.objs[i - 1];
}
break;
}
} else {
if (!strcmp(pctx->ns.objs[i - 1], ns)) {
if (!pctx->prefix.objs[i - 1]) {
continue;
}
if (!strcmp(pctx->prefix.objs[i - 1], new_prefix) || !(prefix_opts & LYXML_PREFIX_REQUIRED)) {
return pctx->prefix.objs[i - 1];
}
}
}
}
ly_print_(pctx->out, " xmlns%s%s=\"%s\"", new_prefix ? ":" : "", new_prefix ? new_prefix : "", ns);
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 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)
{
const struct lys_module *mod;
const char *ns_prefix;
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) {
mod = ly_ctx_get_module_latest(pctx->ctx, name->module_name);
if (mod) {
ns_prefix = (name->prefix && !(prefix_opts & LYXML_PREFIX_DEFAULT)) ? mod->prefix : NULL;
return xml_print_ns(pctx, mod->ns, ns_prefix, prefix_opts);
}
}
break;
default:
LOGINT(pctx->ctx);
}
return NULL;
}
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) {
continue;
}
xml_print_ns(pctx, ns->uri, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : ns->prefix, prefix_opts);
}
break;
default:
LOGINT(pctx->ctx);
}
}
static void
xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node)
{
struct lyd_meta *meta;
const struct lys_module *df_mod = NULL, *mod;
struct ly_set ns_list = {0};
LY_ARRAY_COUNT_TYPE u;
ly_bool dynamic, filter_attrs = 0;
const char *value;
uint32_t i;
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))) {
if (ly_ctx_get_module_latest(LYD_CTX(node), "ietf-netconf-with-defaults")) {
df_mod = ly_ctx_get_module_latest(LYD_CTX(node), "default");
}
if (df_mod) {
ly_print_(pctx->out, " %s:default=\"true\"", xml_print_ns(pctx, df_mod->ns, df_mod->prefix, 0));
}
}
}
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;
}
ly_set_add(&ns_list, NULL, 0, NULL);
value = LYSC_GET_TYPE_PLG(meta->value.realtype->plugin_ref)->print(LYD_CTX(node),
&meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
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"))) {
ly_print_(pctx->out, " %s=\"", meta->name);
} else {
ly_print_(pctx->out, " %s:%s=\"", xml_print_ns(pctx, mod->ns, mod->prefix, 1), meta->name);
}
if (value && value[0]) {
lyxml_dump_text(pctx->out, value, 1);
}
ly_print_(pctx->out, "\"");
if (dynamic) {
free((void *)value);
}
}
}
static void
xml_print_node_open(struct xmlpr_ctx *pctx, const struct lyd_node *node)
{
ly_print_(pctx->out, "%*s<%s", INDENT, node->schema->name);
xml_print_ns(pctx, node->schema->module->ns, NULL, 0);
xml_print_meta(pctx, node);
}
static LY_ERR
xml_print_attr(struct xmlpr_ctx *pctx, const struct lyd_attr *attr)
{
const char *pref;
LY_LIST_FOR(attr, attr) {
pref = NULL;
if (attr->name.prefix) {
pref = xml_print_ns_opaq(pctx, attr->format, &attr->name, 0);
}
if (attr->val_prefix_data) {
xml_print_ns_prefix_data(pctx, attr->format, attr->val_prefix_data, LYXML_PREFIX_REQUIRED);
}
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, "\"");
}
return LY_SUCCESS;
}
static LY_ERR
xml_print_opaq_open(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
{
ly_print_(pctx->out, "%*s<%s", INDENT, node->name.name);
if (node->name.prefix || node->name.module_ns) {
xml_print_ns_opaq(pctx, node->format, &node->name, LYXML_PREFIX_DEFAULT);
}
LY_CHECK_RET(xml_print_attr(pctx, node->attr));
return LY_SUCCESS;
}
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;
if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) {
LOGMEM(pctx->ctx);
goto cleanup;
}
value = LYSC_GET_TYPE_PLG(((struct lysc_node_leaf *)node->schema)->type->plugin_ref)->print(LYD_CTX(node),
&node->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup);
xml_print_node_open(pctx, &node->node);
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;
}
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) {
ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
return LY_SUCCESS;
}
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;
LY_ERR ret;
if ((node->schema->nodetype == LYS_ANYDATA) && node->value) {
LOGINT_RET(pctx->ctx);
}
xml_print_node_open(pctx, &node->node);
if (any->child) {
prev_opts = pctx->options;
pctx->options &= ~LYD_PRINT_SIBLINGS;
LEVEL_INC;
ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
LY_LIST_FOR(any->child, iter) {
ret = xml_print_node(pctx, iter);
LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
}
LEVEL_DEC;
pctx->options = prev_opts;
ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");
} else if (any->value && any->value[0]) {
ly_print_(pctx->out, ">");
lyxml_dump_text(pctx->out, any->value, 0);
ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
} else {
ly_print_(pctx->out, "/>%s", 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]) {
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) {
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 {
ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
}
return LY_SUCCESS;
}
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)) {
return LY_SUCCESS;
}
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;
}
}
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);
LY_LIST_FOR(root, node) {
LY_CHECK_RET(xml_print_node(&pctx, node));
if (!(options & LYD_PRINT_SIBLINGS)) {
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;
}