diff options
Diffstat (limited to 'src/printer_json.c')
-rw-r--r-- | src/printer_json.c | 1077 |
1 files changed, 1077 insertions, 0 deletions
diff --git a/src/printer_json.c b/src/printer_json.c new file mode 100644 index 0000000..233bc02 --- /dev/null +++ b/src/printer_json.c @@ -0,0 +1,1077 @@ +/** + * @file printer_json.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief JSON printer for libyang data structure + * + * Copyright (c) 2015 - 2023 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 <assert.h> +#include <ctype.h> +#include <stdint.h> +#include <stdlib.h> + +#include "context.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_types.h" +#include "printer_data.h" +#include "printer_internal.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_schema.h" + +/** + * @brief JSON printer context. + */ +struct jsonpr_ctx { + struct ly_out *out; /**< output specification */ + const struct lyd_node *root; /**< root node of the subtree being printed */ + const struct lyd_node *parent; /**< parent of the node being printed */ + 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 */ + + uint16_t level_printed; /* level where some data were already printed */ + struct ly_set open; /* currently open array(s) */ + const struct lyd_node *first_leaflist; /**< first printed leaf-list instance, used when printing its metadata/attributes */ +}; + +/** + * @brief Mark that something was already written in the current level, + * used to decide if a comma is expected between the items + */ +#define LEVEL_PRINTED pctx->level_printed = pctx->level + +#define PRINT_COMMA \ + if (pctx->level_printed >= pctx->level) { \ + ly_print_(pctx->out, ",%s", (DO_FORMAT ? "\n" : "")); \ + } + +static LY_ERR json_print_node(struct jsonpr_ctx *pctx, const struct lyd_node *node); + +/** + * @brief Compare 2 nodes, despite it is regular data node or an opaq node, and + * decide if they corresponds to the same schema node. + * + * @return 1 - matching nodes, 0 - non-matching nodes + */ +static int +matching_node(const struct lyd_node *node1, const struct lyd_node *node2) +{ + assert(node1 || node2); + + if (!node1 || !node2) { + return 0; + } else if (node1->schema != node2->schema) { + return 0; + } + if (!node1->schema) { + /* compare node names */ + struct lyd_node_opaq *onode1 = (struct lyd_node_opaq *)node1; + struct lyd_node_opaq *onode2 = (struct lyd_node_opaq *)node2; + + if ((onode1->name.name != onode2->name.name) || (onode1->name.prefix != onode2->name.prefix)) { + return 0; + } + } + + return 1; +} + +/** + * @brief Open the JSON array ('[') for the specified @p node + * + * @param[in] ctx JSON printer context. + * @param[in] node First node of the array. + * @return LY_ERR value. + */ +static LY_ERR +json_print_array_open(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + ly_print_(pctx->out, "[%s", DO_FORMAT ? "\n" : ""); + LY_CHECK_RET(ly_set_add(&pctx->open, (void *)node, 0, NULL)); + LEVEL_INC; + + return LY_SUCCESS; +} + +/** + * @brief Get know if the array for the provided @p node is currently open. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to check. + * @return 1 in case the printer is currently in the array belonging to the provided @p node. + * @return 0 in case the provided @p node is not connected with the currently open array (or there is no open array). + */ +static int +is_open_array(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + if (pctx->open.count && matching_node(node, pctx->open.dnodes[pctx->open.count - 1])) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Close the most inner JSON array. + * + * @param[in] ctx JSON printer context. + */ +static void +json_print_array_close(struct jsonpr_ctx *pctx) +{ + LEVEL_DEC; + ly_set_rm_index(&pctx->open, pctx->open.count - 1, NULL); + ly_print_(pctx->out, "%s%*s]", DO_FORMAT ? "\n" : "", INDENT); +} + +/** + * @brief Get the node's module name to use as the @p node prefix in JSON. + * + * @param[in] node Node to process. + * @return The name of the module where the @p node belongs, it can be NULL in case the module name + * cannot be determined (source format is XML and the refered namespace is unknown/not implemented in the current context). + */ +static const char * +node_prefix(const struct lyd_node *node) +{ + if (node->schema) { + return node->schema->module->name; + } else { + struct lyd_node_opaq *onode = (struct lyd_node_opaq *)node; + const struct lys_module *mod; + + switch (onode->format) { + case LY_VALUE_JSON: + return onode->name.module_name; + case LY_VALUE_XML: + mod = ly_ctx_get_module_implemented_ns(onode->ctx, onode->name.module_ns); + if (!mod) { + return NULL; + } + return mod->name; + default: + /* cannot be created */ + LOGINT(LYD_CTX(node)); + } + } + + return NULL; +} + +/** + * @brief Compare 2 nodes if the belongs to the same module (if they come from the same namespace) + * + * Accepts both regulard a well as opaq nodes. + * + * @param[in] node1 The first node to compare. + * @param[in] node2 The second node to compare. + * @return 0 in case the nodes' modules are the same + * @return 1 in case the nodes belongs to different modules + */ +int +json_nscmp(const struct lyd_node *node1, const struct lyd_node *node2) +{ + assert(node1 || node2); + + if (!node1 || !node2) { + return 1; + } else if (node1->schema && node2->schema) { + if (node1->schema->module == node2->schema->module) { + /* belongs to the same module */ + return 0; + } else { + /* different modules */ + return 1; + } + } else { + const char *pref1 = node_prefix(node1); + const char *pref2 = node_prefix(node2); + + if ((pref1 && pref2) && (pref1 == pref2)) { + return 0; + } else { + return 1; + } + } +} + +/** + * @brief Print the @p text as JSON string - encode special characters and add quotation around the string. + * + * @param[in] out The output handler. + * @param[in] text The string to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_string(struct ly_out *out, const char *text) +{ + uint64_t i; + + if (!text) { + return LY_SUCCESS; + } + + ly_write_(out, "\"", 1); + for (i = 0; text[i]; i++) { + const unsigned char byte = text[i]; + + switch (byte) { + case '"': + ly_print_(out, "\\\""); + break; + case '\\': + ly_print_(out, "\\\\"); + break; + case '\r': + ly_print_(out, "\\r"); + break; + case '\t': + ly_print_(out, "\\t"); + break; + default: + if (iscntrl(byte)) { + /* control character */ + ly_print_(out, "\\u%.4X", byte); + } else { + /* printable character (even non-ASCII UTF8) */ + ly_write_(out, &text[i], 1); + } + break; + } + } + ly_write_(out, "\"", 1); + + return LY_SUCCESS; +} + +/** + * @brief Print JSON object's member name, ending by ':'. It resolves if the prefix is supposed to be printed. + * + * @param[in] ctx JSON printer context. + * @param[in] node The data node being printed. + * @param[in] is_attr Flag if the metadata sign (@) is supposed to be added before the identifier. + * @return LY_ERR value. + */ +static LY_ERR +json_print_member(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_bool is_attr) +{ + PRINT_COMMA; + if ((LEVEL == 1) || json_nscmp(node, pctx->parent)) { + /* print "namespace" */ + ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", + node_prefix(node), node->schema->name, DO_FORMAT ? " " : ""); + } else { + ly_print_(pctx->out, "%*s\"%s%s\":%s", INDENT, is_attr ? "@" : "", node->schema->name, DO_FORMAT ? " " : ""); + } + + return LY_SUCCESS; +} + +/** + * @brief More generic alternative to json_print_member() to print some special cases of the member names. + * + * @param[in] ctx JSON printer context. + * @param[in] parent Parent node to compare modules deciding if the prefix is printed. + * @param[in] format Format to decide how to process the @p prefix. + * @param[in] name Name structure to provide name and prefix to print. If NULL, only "" name is printed. + * @param[in] is_attr Flag if the metadata sign (@) is supposed to be added before the identifier. + * @return LY_ERR value. + */ +static LY_ERR +json_print_member2(struct jsonpr_ctx *pctx, const struct lyd_node *parent, LY_VALUE_FORMAT format, + const struct ly_opaq_name *name, ly_bool is_attr) +{ + const char *module_name = NULL, *name_str; + + PRINT_COMMA; + + /* determine prefix string */ + if (name) { + switch (format) { + case LY_VALUE_JSON: + module_name = name->module_name; + break; + case LY_VALUE_XML: { + const struct lys_module *mod = NULL; + + if (name->module_ns) { + mod = ly_ctx_get_module_implemented_ns(pctx->ctx, name->module_ns); + } + if (mod) { + module_name = mod->name; + } + break; + } + default: + /* cannot be created */ + LOGINT_RET(pctx->ctx); + } + + name_str = name->name; + } else { + name_str = ""; + } + + /* print the member */ + if (module_name && (!parent || (node_prefix(parent) != module_name))) { + ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", module_name, name_str, DO_FORMAT ? " " : ""); + } else { + ly_print_(pctx->out, "%*s\"%s%s\":%s", INDENT, is_attr ? "@" : "", name_str, DO_FORMAT ? " " : ""); + } + + return LY_SUCCESS; +} + +/** + * @brief Print data value. + * + * @param[in] pctx JSON printer context. + * @param[in] ctx Context used to print the value. + * @param[in] val Data value to be printed. + * @param[in] local_mod Module of the current node. + * @return LY_ERR value. + */ +static LY_ERR +json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val, + const struct lys_module *local_mod) +{ + ly_bool dynamic; + LY_DATA_TYPE basetype; + const char *value; + + value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, (void *)local_mod, &dynamic, NULL); + LY_CHECK_RET(!value, LY_EINVAL); + basetype = val->realtype->basetype; + +print_val: + /* leafref is not supported */ + switch (basetype) { + case LY_TYPE_UNION: + /* use the resolved type */ + val = &val->subvalue->value; + basetype = val->realtype->basetype; + goto print_val; + + case LY_TYPE_BINARY: + case LY_TYPE_STRING: + case LY_TYPE_BITS: + case LY_TYPE_ENUM: + case LY_TYPE_INST: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + case LY_TYPE_DEC64: + case LY_TYPE_IDENT: + json_print_string(pctx->out, value); + break; + + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_BOOL: + ly_print_(pctx->out, "%s", value[0] ? value : "null"); + break; + + case LY_TYPE_EMPTY: + ly_print_(pctx->out, "[null]"); + break; + + default: + /* error */ + LOGINT_RET(pctx->ctx); + } + + if (dynamic) { + free((char *)value); + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the attributes of the opaq node. + * + * @param[in] ctx JSON printer context. + * @param[in] node Opaq node where the attributes are placed. + * @return LY_ERR value. + */ +static LY_ERR +json_print_attribute(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + struct lyd_attr *attr; + + for (attr = node->attr; attr; attr = attr->next) { + json_print_member2(pctx, &node->node, attr->format, &attr->name, 0); + + if (attr->hints & (LYD_VALHINT_STRING | LYD_VALHINT_OCTNUM | LYD_VALHINT_HEXNUM | LYD_VALHINT_NUM64)) { + json_print_string(pctx->out, attr->value); + } else if (attr->hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) { + ly_print_(pctx->out, "%s", attr->value[0] ? attr->value : "null"); + } else if (attr->hints & LYD_VALHINT_EMPTY) { + ly_print_(pctx->out, "[null]"); + } else { + /* unknown value format with no hints, use universal string */ + json_print_string(pctx->out, attr->value); + } + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the metadata of the node. + * + * @param[in] ctx JSON printer context. + * @param[in] node Node where the metadata are placed. + * @param[in] wdmod With-defaults module to mark that default attribute is supposed to be printed. + * @return LY_ERR value. + */ +static LY_ERR +json_print_metadata(struct jsonpr_ctx *pctx, const struct lyd_node *node, const struct lys_module *wdmod) +{ + struct lyd_meta *meta; + + if (wdmod) { + ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : ""); + LEVEL_PRINTED; + } + + for (meta = node->meta; meta; meta = meta->next) { + if (!lyd_metadata_should_print(meta)) { + continue; + } + PRINT_COMMA; + ly_print_(pctx->out, "%*s\"%s:%s\":%s", INDENT, meta->annotation->module->name, meta->name, DO_FORMAT ? " " : ""); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value, NULL)); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Check if a value can be printed for at least one metadata. + * + * @param[in] node Node to check. + * @return 1 if node has printable meta otherwise 0. + */ +static ly_bool +node_has_printable_meta(const struct lyd_node *node) +{ + struct lyd_meta *iter; + + if (!node->meta) { + return 0; + } + + LY_LIST_FOR(node->meta, iter) { + if (lyd_metadata_should_print(iter)) { + return 1; + } + } + + return 0; +} + +/** + * @brief Print attributes/metadata of the given @p node. Accepts both regular as well as opaq nodes. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node where the attributes/metadata are placed. + * @param[in] inner Flag if the @p node is an inner node in the tree. + * @return LY_ERR value. + */ +static LY_ERR +json_print_attributes(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_bool inner) +{ + const struct lys_module *wdmod = NULL; + + if (node->schema && (node->schema->nodetype != LYS_CONTAINER) && (((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 */ + /* get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(LYD_CTX(node), "ietf-netconf-with-defaults"); + } + + if (node->schema && (wdmod || node_has_printable_meta(node))) { + if (inner) { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1)); + } else { + LY_CHECK_RET(json_print_member(pctx, node, 1)); + } + ly_print_(pctx->out, "{%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_CHECK_RET(json_print_metadata(pctx, node, wdmod)); + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + } else if (!node->schema && ((struct lyd_node_opaq *)node)->attr) { + if (inner) { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1)); + } else { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), ((struct lyd_node_opaq *)node)->format, + &((struct lyd_node_opaq *)node)->name, 1)); + } + ly_print_(pctx->out, "{%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)node)); + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print leaf data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_leaf(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module)); + LEVEL_PRINTED; + + /* print attributes as sibling */ + json_print_attributes(pctx, node, 0); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata/anyxml content. + * + * @param[in] ctx JSON printer context. + * @param[in] any Anydata node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_any_content(struct jsonpr_ctx *pctx, struct lyd_node_any *any) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *iter; + const struct lyd_node *prev_parent; + uint32_t prev_opts, *prev_lo, temp_lo = 0; + + assert(any->schema->nodetype & LYD_NODE_ANY); + + if ((any->schema->nodetype == LYS_ANYDATA) && (any->value_type != LYD_ANYDATA_DATATREE)) { + LOGINT_RET(pctx->ctx); + } + if (any->value_type == LYD_ANYDATA_LYB) { + uint32_t parser_options = LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_STRICT; + + /* turn logging off */ + prev_lo = ly_temp_log_options(&temp_lo); + + /* try to parse it into a data tree */ + if (lyd_parse_data_mem(pctx->ctx, any->value.mem, LYD_LYB, parser_options, 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(prev_lo); + } + + switch (any->value_type) { + case LYD_ANYDATA_DATATREE: + /* print as an object */ + ly_print_(pctx->out, "{%s", DO_FORMAT ? "\n" : ""); + LEVEL_INC; + + /* close opening tag and print data */ + prev_parent = pctx->parent; + prev_opts = pctx->options; + pctx->parent = &any->node; + pctx->options &= ~LYD_PRINT_WITHSIBLINGS; + LY_LIST_FOR(any->value.tree, iter) { + ret = json_print_node(pctx, iter); + LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); + } + pctx->parent = prev_parent; + pctx->options = prev_opts; + + /* terminate the object */ + LEVEL_DEC; + if (DO_FORMAT) { + ly_print_(pctx->out, "\n%*s}", INDENT); + } else { + ly_print_(pctx->out, "}"); + } + break; + case LYD_ANYDATA_JSON: + if (!any->value.json) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + ly_print_(pctx->out, "null"); + } else { + ly_print_(pctx->out, "{}"); + } + } else { + /* print without escaping special characters */ + ly_print_(pctx->out, "%s", any->value.json); + } + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + if (!any->value.str) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + ly_print_(pctx->out, "null"); + } else { + ly_print_(pctx->out, "{}"); + } + } else { + /* print as a string */ + json_print_string(pctx->out, any->value.str); + } + break; + case LYD_ANYDATA_LYB: + /* LYB format is not supported */ + LOGWRN(pctx->ctx, "Unable to print anydata content (type %d) as JSON.", any->value_type); + break; + } + + return LY_SUCCESS; +} + +/** + * @brief Print content of a single container/list data node including its metadata. + * The envelope specific to nodes are expected to be printed by the caller. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_inner(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + struct lyd_node *child; + const struct lyd_node *prev_parent; + struct lyd_node_opaq *opaq = NULL; + ly_bool has_content = 0; + + LY_LIST_FOR(lyd_child(node), child) { + if (lyd_node_should_print(child, pctx->options)) { + break; + } + } + if (node->meta || child) { + has_content = 1; + } + if (!node->schema) { + opaq = (struct lyd_node_opaq *)node; + } + + if ((node->schema && (node->schema->nodetype == LYS_LIST)) || + (opaq && (opaq->hints != LYD_HINT_DATA) && (opaq->hints & LYD_NODEHINT_LIST))) { + ly_print_(pctx->out, "%s%*s{%s", (is_open_array(pctx, node) && (pctx->level_printed >= pctx->level)) ? + (DO_FORMAT ? ",\n" : ",") : "", INDENT, (DO_FORMAT && has_content) ? "\n" : ""); + } else { + ly_print_(pctx->out, "%s{%s", (is_open_array(pctx, node) && (pctx->level_printed >= pctx->level)) ? "," : "", + (DO_FORMAT && has_content) ? "\n" : ""); + } + LEVEL_INC; + + json_print_attributes(pctx, node, 1); + + /* print children */ + prev_parent = pctx->parent; + pctx->parent = node; + LY_LIST_FOR(lyd_child(node), child) { + LY_CHECK_RET(json_print_node(pctx, child)); + } + pctx->parent = prev_parent; + + LEVEL_DEC; + if (DO_FORMAT && has_content) { + ly_print_(pctx->out, "\n%*s}", INDENT); + } else { + ly_print_(pctx->out, "}"); + } + LEVEL_PRINTED; + + return LY_SUCCESS; +} + +/** + * @brief Print container data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static int +json_print_container(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_inner(pctx, node)); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata/anyxml data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static int +json_print_any(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_any_content(pctx, (struct lyd_node_any *)node)); + LEVEL_PRINTED; + + /* print attributes as sibling */ + json_print_attributes(pctx, node, 0); + + return LY_SUCCESS; +} + +/** + * @brief Check whether a node is the last printed instance of a (leaf-)list. + * + * @param[in] ctx JSON printer context. + * @param[in] node Last printed node. + * @return Whether it is the last printed instance or not. + */ +static ly_bool +json_print_array_is_last_inst(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + if (!is_open_array(pctx, node)) { + /* no array open */ + return 0; + } + + if ((pctx->root == node) && !(pctx->options & LYD_PRINT_WITHSIBLINGS)) { + /* the only printed instance */ + return 1; + } + + if (!node->next || (node->next->schema != node->schema)) { + /* last instance */ + return 1; + } + + return 0; +} + +/** + * @brief Print single leaf-list or list instance. + * + * In case of list, metadata are printed inside the list object. For the leaf-list, + * metadata are marked in the context for later printing after closing the array next to it using + * json_print_metadata_leaflist(). + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_leaf_list(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + const struct lys_module *wdmod = NULL; + + if (!is_open_array(pctx, node)) { + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_array_open(pctx, node)); + if (node->schema->nodetype == LYS_LEAFLIST) { + ly_print_(pctx->out, "%*s", INDENT); + } + } else if (node->schema->nodetype == LYS_LEAFLIST) { + ly_print_(pctx->out, ",%s%*s", DO_FORMAT ? "\n" : "", INDENT); + } + + if (node->schema->nodetype == LYS_LIST) { + /* print list's content */ + LY_CHECK_RET(json_print_inner(pctx, node)); + } else { + assert(node->schema->nodetype == LYS_LEAFLIST); + + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module)); + + if (!pctx->first_leaflist) { + 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, get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(LYD_CTX(node), "ietf-netconf-with-defaults"); + } + if (wdmod || node_has_printable_meta(node)) { + /* we will be printing metadata for these siblings */ + pctx->first_leaflist = node; + } + } + } + + if (json_print_array_is_last_inst(pctx, node)) { + json_print_array_close(pctx); + } + + return LY_SUCCESS; +} + +/** + * @brief Print leaf-list's metadata or opaque nodes attributes. + * This function is supposed to be called when the leaf-list array is closed. + * + * @param[in] ctx JSON printer context. + * @return LY_ERR value. + */ +static LY_ERR +json_print_meta_attr_leaflist(struct jsonpr_ctx *pctx) +{ + const struct lyd_node *prev, *node, *iter; + const struct lys_module *wdmod = NULL, *iter_wdmod; + const struct lyd_node_opaq *opaq = NULL; + + assert(pctx->first_leaflist); + + if (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG)) { + /* get with-defaults module */ + wdmod = ly_ctx_get_module_implemented(pctx->ctx, "ietf-netconf-with-defaults"); + } + + /* node is the first instance of the leaf-list */ + for (node = pctx->first_leaflist, prev = pctx->first_leaflist->prev; + prev->next && matching_node(node, prev); + node = prev, prev = node->prev) {} + + if (node->schema) { + LY_CHECK_RET(json_print_member(pctx, node, 1)); + } else { + opaq = (struct lyd_node_opaq *)node; + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), opaq->format, &opaq->name, 1)); + } + + ly_print_(pctx->out, "[%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_LIST_FOR(node, iter) { + PRINT_COMMA; + if (iter->schema && ((iter->flags & LYD_DEFAULT) || ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(iter)))) { + iter_wdmod = wdmod; + } else { + iter_wdmod = NULL; + } + if ((iter->schema && (node_has_printable_meta(iter) || iter_wdmod)) || (opaq && opaq->attr)) { + ly_print_(pctx->out, "%*s%s", INDENT, DO_FORMAT ? "{\n" : "{"); + LEVEL_INC; + + if (iter->schema) { + LY_CHECK_RET(json_print_metadata(pctx, iter, iter_wdmod)); + } else { + LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)iter)); + } + + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + } else { + ly_print_(pctx->out, "%*snull", INDENT); + } + LEVEL_PRINTED; + if (!matching_node(iter, iter->next)) { + break; + } + } + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s]", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + + return LY_SUCCESS; +} + +/** + * @brief Print opaq data node including its attributes. + * + * @param[in] ctx JSON printer context. + * @param[in] node Opaq node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_opaq(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + ly_bool first = 1, last = 1; + uint32_t hints; + + if (node->hints == LYD_HINT_DATA) { + /* useless and confusing hints */ + hints = 0; + } else { + hints = node->hints; + } + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + if (node->prev->next && matching_node(node->prev, &node->node)) { + first = 0; + } + if (node->next && matching_node(&node->node, node->next)) { + last = 0; + } + } + + if (first) { + LY_CHECK_RET(json_print_member2(pctx, pctx->parent, node->format, &node->name, 0)); + + if (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + LY_CHECK_RET(json_print_array_open(pctx, &node->node)); + } + if (hints & LYD_NODEHINT_LEAFLIST) { + ly_print_(pctx->out, "%*s", INDENT); + } + } else if (hints & LYD_NODEHINT_LEAFLIST) { + ly_print_(pctx->out, ",%s%*s", DO_FORMAT ? "\n" : "", INDENT); + } + if (node->child || (hints & LYD_NODEHINT_LIST) || (hints & LYD_NODEHINT_CONTAINER)) { + LY_CHECK_RET(json_print_inner(pctx, &node->node)); + LEVEL_PRINTED; + } else { + if (hints & LYD_VALHINT_EMPTY) { + ly_print_(pctx->out, "[null]"); + } else if ((hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) && !(hints & LYD_VALHINT_NUM64)) { + ly_print_(pctx->out, "%s", node->value); + } else { + /* string or a large number */ + json_print_string(pctx->out, node->value); + } + LEVEL_PRINTED; + + if (!(hints & LYD_NODEHINT_LEAFLIST)) { + /* attributes */ + json_print_attributes(pctx, (const struct lyd_node *)node, 0); + } else if (!pctx->first_leaflist && node->attr) { + /* attributes printed later */ + pctx->first_leaflist = &node->node; + } + + } + if (last && (hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST))) { + json_print_array_close(pctx); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the types of data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_node(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + if (!lyd_node_should_print(node, pctx->options)) { + if (json_print_array_is_last_inst(pctx, node)) { + json_print_array_close(pctx); + } + return LY_SUCCESS; + } + + if (!node->schema) { + LY_CHECK_RET(json_print_opaq(pctx, (const struct lyd_node_opaq *)node)); + } else { + switch (node->schema->nodetype) { + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + case LYS_CONTAINER: + LY_CHECK_RET(json_print_container(pctx, node)); + break; + case LYS_LEAF: + LY_CHECK_RET(json_print_leaf(pctx, node)); + break; + case LYS_LEAFLIST: + case LYS_LIST: + LY_CHECK_RET(json_print_leaf_list(pctx, node)); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + LY_CHECK_RET(json_print_any(pctx, node)); + break; + default: + LOGINT(pctx->ctx); + return EXIT_FAILURE; + } + } + + pctx->level_printed = pctx->level; + + if (pctx->first_leaflist && !matching_node(node->next, pctx->first_leaflist)) { + json_print_meta_attr_leaflist(pctx); + pctx->first_leaflist = NULL; + } + + return LY_SUCCESS; +} + +LY_ERR +json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + const struct lyd_node *node; + struct jsonpr_ctx pctx = {0}; + const char *delimiter = (options & LYD_PRINT_SHRINK) ? "" : "\n"; + + if (!root) { + ly_print_(out, "{}%s", delimiter); + ly_print_flush(out); + return LY_SUCCESS; + } + + pctx.out = out; + pctx.parent = NULL; + pctx.level = 1; + pctx.level_printed = 0; + pctx.options = options; + pctx.ctx = LYD_CTX(root); + + /* start */ + ly_print_(pctx.out, "{%s", delimiter); + + /* content */ + LY_LIST_FOR(root, node) { + pctx.root = node; + LY_CHECK_RET(json_print_node(&pctx, node)); + if (!(options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + + /* end */ + ly_print_(out, "%s}%s", delimiter, delimiter); + + assert(!pctx.open.count); + ly_set_erase(&pctx.open, NULL); + + ly_print_flush(out); + return LY_SUCCESS; +} |