| /** |
| * @file printer/tree.c |
| * @author Radek Krejci <rkrejci@cesnet.cz> |
| * @brief TREE printer for libyang data model structure |
| * |
| * Copyright (c) 2015 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 <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| |
| #include "common.h" |
| #include "printer.h" |
| #include "tree_schema.h" |
| |
| /* module: <name> |
| * <X>+--rw <node-name> */ |
| #define LY_TREE_MOD_DATA_INDENT 2 |
| |
| /* <^>rpcs: |
| * <X>+---x <rpc-name> */ |
| #define LY_TREE_OP_DATA_INDENT 4 |
| |
| /* +--rw leaf<X>string */ |
| #define LY_TREE_TYPE_INDENT 3 |
| |
| /* +--rw leaf |
| * | <X>string */ |
| #define LY_TREE_WRAP_INDENT 2 |
| |
| /* these options are mostly inherited in recursive print, non-recursive options are parameters */ |
| typedef struct { |
| const struct lys_module *module; /**< (sub)module we are printing from */ |
| uint8_t base_indent; /**< base indent size of all the printed text */ |
| uint64_t indent; /**< bit-field of sibling (1)/ no sibling(0) on corresponding depths */ |
| uint16_t line_length; /**< maximum desired line length */ |
| int spec_config; /**< special config flags - 0 (no special config status), |
| 1 (read-only - rpc output, notification), 2 (write-only - rpc input) */ |
| int options; /**< user-specified tree printer options */ |
| } tp_opts; |
| |
| static void tree_print_snode(struct lyout *out, int level, uint16_t max_name_len, const struct lys_node *node, int mask, |
| const struct lys_node *aug_parent, int subtree, tp_opts *opts); |
| |
| static int |
| tree_print_indent(struct lyout *out, int level, tp_opts *opts) |
| { |
| int i, ret = 0; |
| |
| if (opts->base_indent) { |
| ret += ly_print(out, "%*s", opts->base_indent, " "); |
| } |
| for (i = 0; i < level; ++i) { |
| if (opts->indent & (1 << i)) { |
| ret += ly_print(out, "| "); |
| } else { |
| ret += ly_print(out, " "); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int |
| tree_sibling_is_valid_child(const struct lys_node *node, int including, const struct lys_module *module, |
| const struct lys_node *aug_parent, LYS_NODE nodetype) |
| { |
| struct lys_node *cur, *cur2; |
| |
| assert(!aug_parent || (aug_parent->nodetype == LYS_AUGMENT)); |
| |
| if (!node) { |
| return 0; |
| } else if (!lys_parent(node) && !strcmp(node->name, "config") && !strcmp(node->module->name, "ietf-netconf")) { |
| /* node added by libyang, not actually in the model */ |
| return 0; |
| } |
| |
| /* has a following printed child */ |
| LY_TREE_FOR((struct lys_node *)(including ? node : node->next), cur) { |
| if (aug_parent && (cur->parent != aug_parent)) { |
| /* we are done traversing this augment, the nodes are all direct siblings */ |
| return 0; |
| } |
| |
| if (module->type && (lys_main_module(module) != lys_node_module(cur))) { |
| continue; |
| } |
| |
| if (!lys_is_disabled(cur, 0)) { |
| if ((cur->nodetype == LYS_USES) || ((cur->nodetype == LYS_CASE) && (cur->flags & LYS_IMPLICIT))) { |
| if (tree_sibling_is_valid_child(cur->child, 1, module, NULL, nodetype)) { |
| return 1; |
| } |
| } else { |
| switch (nodetype) { |
| case LYS_GROUPING: |
| /* we are printing groupings, they are printed separately */ |
| if (cur->nodetype == LYS_GROUPING) { |
| return 0; |
| } |
| break; |
| case LYS_RPC: |
| if (cur->nodetype == LYS_RPC) { |
| return 1; |
| } |
| break; |
| case LYS_NOTIF: |
| if (cur->nodetype == LYS_NOTIF) { |
| return 1; |
| } |
| break; |
| default: |
| if (cur->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_CHOICE |
| | LYS_CASE | LYS_ACTION)) { |
| return 1; |
| } |
| if ((cur->nodetype & (LYS_INPUT | LYS_OUTPUT)) && cur->child) { |
| return 1; |
| } |
| /* only nested notifications count here (not top-level) */ |
| if (cur->nodetype == LYS_NOTIF) { |
| for (cur2 = lys_parent(cur); cur2 && (cur2->nodetype == LYS_USES); cur2 = lys_parent(cur2)); |
| if (cur2) { |
| return 1; |
| } |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| /* if in uses, the following printed child can actually be in the parent node :-/ */ |
| if (lys_parent(node) && (lys_parent(node)->nodetype == LYS_USES)) { |
| return tree_sibling_is_valid_child(lys_parent(node), 0, module, NULL, nodetype); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| tree_next_indent(int level, const struct lys_node *node, const struct lys_node *aug_parent, tp_opts *opts) |
| { |
| int next_is_case = 0, has_next = 0; |
| |
| if (level > 64) { |
| LOGINT(node->module->ctx); |
| return; |
| } |
| |
| /* clear level indent (it may have been set for some line wrapping) */ |
| opts->indent &= ~(uint64_t)(1ULL << (level - 1)); |
| |
| /* this is the direct child of a case */ |
| if ((node->nodetype != LYS_CASE) && lys_parent(node) && (lys_parent(node)->nodetype & (LYS_CASE | LYS_CHOICE))) { |
| /* it is not the only child */ |
| if (node->next && lys_parent(node->next) && (lys_parent(node->next)->nodetype == LYS_CHOICE)) { |
| next_is_case = 1; |
| } |
| } |
| |
| /* next is a node that will actually be printed */ |
| has_next = tree_sibling_is_valid_child(node, 0, opts->module, aug_parent, node->nodetype); |
| |
| /* set level indent */ |
| if (has_next && !next_is_case) { |
| opts->indent |= (uint64_t)1ULL << (level - 1); |
| } |
| } |
| |
| static uint16_t |
| tree_get_max_name_len(const struct lys_node *sibling, const struct lys_node *aug_parent, int type_mask, |
| tp_opts *opts) |
| { |
| const struct lys_node *sub; |
| struct lys_module *nodemod; |
| unsigned int max_name_len = 0, name_len; |
| |
| LY_TREE_FOR(sibling, sub) { |
| if (opts->module->type && (sub->module != opts->module)) { |
| /* when printing submodule, we are only concerned with its own data (they are in the module data) */ |
| continue; |
| } |
| if (aug_parent && (sub->parent != aug_parent)) { |
| /* when printing augment children, skip other target children */ |
| continue; |
| } |
| if (!(sub->nodetype & type_mask)) { |
| /* this sibling will not be printed */ |
| continue; |
| } |
| |
| if ((sub->nodetype == LYS_USES) && !(opts->options & LYS_OUTOPT_TREE_USES)) { |
| name_len = tree_get_max_name_len(sub->child, NULL, type_mask, opts); |
| } else { |
| nodemod = lys_node_module(sub); |
| name_len = strlen(sub->name); |
| if (lys_main_module(opts->module) != nodemod) { |
| /* ":" */ |
| ++name_len; |
| if (opts->options & LYS_OUTOPT_TREE_RFC) { |
| name_len += strlen(nodemod->prefix); |
| } else { |
| name_len += strlen(nodemod->name); |
| } |
| } |
| |
| /* add characters for optional opts */ |
| switch (sub->nodetype) { |
| case LYS_LEAF: |
| case LYS_LEAFLIST: |
| case LYS_LIST: |
| case LYS_ANYDATA: |
| case LYS_ANYXML: |
| case LYS_CONTAINER: |
| case LYS_CASE: |
| ++name_len; |
| break; |
| case LYS_CHOICE: |
| /* choice is longer :-/ */ |
| name_len += 2; |
| if (!(sub->flags & LYS_MAND_TRUE)) { |
| ++name_len; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (name_len > max_name_len) { |
| max_name_len = name_len; |
| } |
| } |
| |
| return max_name_len; |
| } |
| |
| static int |
| tree_leaf_is_mandatory(const struct lys_node *node) |
| { |
| const struct lys_node *parent; |
| struct lys_node_list *list; |
| uint16_t i; |
| |
| for (parent = lys_parent(node); parent && parent->nodetype == LYS_USES; parent = lys_parent(parent)); |
| if (parent && parent->nodetype == LYS_LIST) { |
| list = (struct lys_node_list *)parent; |
| for (i = 0; i < list->keys_size; i++) { |
| if (list->keys[i] == (struct lys_node_leaf *)node) { |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| tree_print_wrap(struct lyout *out, int level, int line_printed, uint8_t indent, uint16_t len, tp_opts *opts) |
| { |
| if (opts->line_length && (line_printed + indent + len > opts->line_length)) { |
| ly_print(out, "\n"); |
| line_printed = tree_print_indent(out, level, opts); |
| /* 3 for config + space */ |
| line_printed += ly_print(out, "%*s", 3 + LY_TREE_WRAP_INDENT, ""); |
| } else { |
| line_printed += ly_print(out, "%*s", indent, ""); |
| } |
| |
| return line_printed; |
| } |
| |
| static int |
| tree_print_prefix(struct lyout *out, const struct lys_node *node, tp_opts *opts) |
| { |
| uint16_t ret = 0; |
| const struct lys_module *nodemod; |
| |
| nodemod = lys_node_module(node); |
| if (lys_main_module(opts->module) != nodemod) { |
| if (opts->options & LYS_OUTOPT_TREE_RFC) { |
| ret = ly_print(out, "%s:", nodemod->prefix); |
| } else { |
| ret = ly_print(out, "%s:", nodemod->name); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int |
| tree_print_type(struct lyout *out, const struct lys_type *type, int options, const char **out_str) |
| { |
| struct lys_module *type_mod = ((struct lys_tpdf *)type->parent)->module; |
| const char *str; |
| char *tmp; |
| int printed; |
| |
| if ((type->base == LY_TYPE_LEAFREF) && !type->der->module) { |
| if (options & LYS_OUTOPT_TREE_NO_LEAFREF) { |
| if (out_str) { |
| printed = 7; |
| *out_str = lydict_insert(type_mod->ctx, "leafref", printed); |
| } else { |
| printed = ly_print(out, "leafref"); |
| } |
| } else { |
| if (options & LYS_OUTOPT_TREE_RFC) { |
| str = transform_json2schema(type_mod, type->info.lref.path); |
| if (out_str) { |
| printed = 3 + strlen(str); |
| tmp = malloc(printed + 1); |
| LY_CHECK_ERR_RETURN(!tmp, LOGMEM(type_mod->ctx), 0); |
| sprintf(tmp, "-> %s", str); |
| *out_str = lydict_insert_zc(type_mod->ctx, tmp); |
| } else { |
| printed = ly_print(out, "-> %s", str); |
| } |
| lydict_remove(type_mod->ctx, str); |
| } else { |
| if (out_str) { |
| printed = 3 + strlen(type->info.lref.path); |
| tmp = malloc(printed + 1); |
| LY_CHECK_ERR_RETURN(!tmp, LOGMEM(type_mod->ctx), 0); |
| sprintf(tmp, "-> %s", type->info.lref.path); |
| *out_str = lydict_insert_zc(type_mod->ctx, tmp); |
| } else { |
| printed = ly_print(out, "-> %s", type->info.lref.path); |
| } |
| } |
| } |
| } else if (!lys_type_is_local(type)) { |
| if (options & LYS_OUTOPT_TREE_RFC) { |
| str = transform_module_name2import_prefix(type_mod, type->der->module->name); |
| if (out_str) { |
| printed = strlen(str) + 1 + strlen(type->der->name); |
| tmp = malloc(printed + 1); |
| LY_CHECK_ERR_RETURN(!tmp, LOGMEM(type_mod->ctx), 0); |
| sprintf(tmp, "%s:%s", str, type->der->name); |
| *out_str = lydict_insert_zc(type_mod->ctx, tmp); |
| } else { |
| printed = ly_print(out, "%s:%s", str, type->der->name); |
| } |
| } else { |
| if (out_str) { |
| printed = strlen(type->der->module->name) + 1 + strlen(type->der->name); |
| tmp = malloc(printed + 1); |
| LY_CHECK_ERR_RETURN(!tmp, LOGMEM(type_mod->ctx), 0); |
| sprintf(tmp, "%s:%s", type->der->module->name, type->der->name); |
| *out_str = lydict_insert_zc(type_mod->ctx, tmp); |
| } else { |
| printed = ly_print(out, "%s:%s", type->der->module->name, type->der->name); |
| } |
| } |
| } else { |
| if (out_str) { |
| printed = strlen(type->der->name); |
| *out_str = lydict_insert(type_mod->ctx, type->der->name, printed); |
| } else { |
| printed = ly_print(out, "%s", type->der->name); |
| } |
| } |
| |
| return printed; |
| } |
| |
| static int |
| tree_print_config(struct lyout *out, const struct lys_node *node, int spec_config) |
| { |
| int ret; |
| |
| switch (node->nodetype) { |
| case LYS_RPC: |
| case LYS_ACTION: |
| return ly_print(out, "-x "); |
| case LYS_NOTIF: |
| return ly_print(out, "-n "); |
| case LYS_USES: |
| return ly_print(out, "-u "); |
| case LYS_CASE: |
| return ly_print(out, ":("); |
| default: |
| break; |
| } |
| |
| if (spec_config == 1) { |
| ret = ly_print(out, "-w "); |
| } else if (spec_config == 2) { |
| ret = ly_print(out, "ro "); |
| } else { |
| ret = ly_print(out, "%s ", (node->flags & LYS_CONFIG_W) ? "rw" : (node->flags & LYS_CONFIG_R) ? "ro" : "--"); |
| } |
| |
| if (node->nodetype == LYS_CHOICE) { |
| ret += ly_print(out, "("); |
| } |
| return ret; |
| } |
| |
| static int |
| tree_print_features(struct lyout *out, struct lys_iffeature *iff1, uint8_t iff1_size, struct lys_iffeature *iff2, |
| uint8_t iff2_size, tp_opts *opts, const char **out_str) |
| { |
| int i, printed; |
| struct lyout *o; |
| |
| if (!iff1_size && !iff2_size) { |
| return 0; |
| } |
| |
| if (out_str) { |
| o = malloc(sizeof *o); |
| LY_CHECK_ERR_RETURN(!o, LOGMEM(NULL), 0); |
| o->type = LYOUT_MEMORY; |
| o->method.mem.buf = NULL; |
| o->method.mem.len = 0; |
| o->method.mem.size = 0; |
| } else { |
| o = out; |
| } |
| |
| printed = ly_print(o, "{"); |
| for (i = 0; i < iff1_size; i++) { |
| if (i > 0) { |
| printed += ly_print(o, ","); |
| } |
| printed += ly_print_iffeature(o, opts->module, &iff1[i], opts->options & LYS_OUTOPT_TREE_RFC ? 2 : 1); |
| } |
| for (i = 0; i < iff2_size; i++) { |
| if (i > 0) { |
| printed += ly_print(o, ","); |
| } |
| printed += ly_print_iffeature(o, opts->module, &iff2[i], opts->options & LYS_OUTOPT_TREE_RFC ? 2 : 1); |
| } |
| printed += ly_print(o, "}?"); |
| |
| if (out_str) { |
| *out_str = lydict_insert_zc(opts->module->ctx, o->method.mem.buf); |
| free(o); |
| } |
| |
| return printed; |
| } |
| |
| static int |
| tree_print_keys(struct lyout *out, struct lys_node_leaf **keys, uint8_t keys_size, tp_opts *opts, const char **out_str) |
| { |
| int i, printed; |
| struct lyout *o; |
| |
| if (!keys_size) { |
| return 0; |
| } |
| |
| if (out_str) { |
| o = malloc(sizeof *o); |
| LY_CHECK_ERR_RETURN(!o, LOGMEM(NULL), 0); |
| o->type = LYOUT_MEMORY; |
| o->method.mem.buf = NULL; |
| o->method.mem.len = 0; |
| o->method.mem.size = 0; |
| } else { |
| o = out; |
| } |
| |
| printed = ly_print(o, "["); |
| for (i = 0; i < keys_size; i++) { |
| printed += ly_print(o, "%s%s", keys[i]->name, i + 1 < keys_size ? " " : "]"); |
| } |
| |
| if (out_str) { |
| *out_str = lydict_insert_zc(opts->module->ctx, o->method.mem.buf); |
| free(o); |
| } |
| |
| return printed; |
| } |
| |
| /** |
| * @brief Print schema node in YANG tree diagram formatting. |
| * |
| * @param[in] out libyang output. |
| * @param[in] level Current level of depth. |
| * @param[in] max_name_len Maximal name length of all the siblings (relevant only for nodes with type). |
| * @param[in] node Schema node to print. |
| * @param[in] mask Type mask of children nodes to be printed. |
| * @param[in] aug_parent Augment node parent in case we are printing its direct children. |
| * @param[in] opts Tree printer options structure. |
| */ |
| static void |
| tree_print_snode(struct lyout *out, int level, uint16_t max_name_len, const struct lys_node *node, int mask, |
| const struct lys_node *aug_parent, int subtree, tp_opts *opts) |
| { |
| struct lys_node *sub; |
| int line_len, node_len, child_mask; |
| uint8_t text_len, text_indent; |
| uint16_t max_child_len; |
| const char *text_str; |
| |
| /* disabled/not printed node */ |
| if (lys_is_disabled(node, (node->parent && node->parent->nodetype == LYS_AUGMENT) ? 1 : 0) || !(node->nodetype & mask)) { |
| return; |
| } |
| |
| /* implicit input/output/case */ |
| if (((node->nodetype & mask) & (LYS_INPUT | LYS_OUTPUT | LYS_CASE)) && (node->flags & LYS_IMPLICIT)) { |
| if ((node->nodetype != LYS_CASE) || lys_is_disabled(node->child, 0)) { |
| return; |
| } |
| } |
| |
| /* special uses and grouping handling */ |
| switch (node->nodetype & mask) { |
| case LYS_USES: |
| if (opts->options & LYS_OUTOPT_TREE_USES) { |
| break; |
| } |
| /* fallthrough */ |
| case LYS_GROUPING: |
| goto print_children; |
| case LYS_ANYXML: |
| if (!lys_parent(node) && !strcmp(node->name, "config") && !strcmp(node->module->name, "ietf-netconf")) { |
| /* node added by libyang, not actually in the model */ |
| return; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| /* print indent */ |
| line_len = tree_print_indent(out, level, opts); |
| /* print status */ |
| line_len += ly_print(out, "%s--", (node->flags & LYS_STATUS_DEPRC ? "x" : (node->flags & LYS_STATUS_OBSLT ? "o" : "+"))); |
| /* print config flags (or special opening for case, choice) */ |
| line_len += tree_print_config(out, node, opts->spec_config); |
| /* print optionally prefix */ |
| node_len = tree_print_prefix(out, node, opts); |
| /* print name */ |
| node_len += ly_print(out, node->name); |
| |
| /* print one-character opts */ |
| switch (node->nodetype & mask) { |
| case LYS_LEAF: |
| if (!(node->flags & LYS_MAND_TRUE) && !tree_leaf_is_mandatory(node)) { |
| node_len += ly_print(out, "?"); |
| } |
| break; |
| case LYS_ANYDATA: |
| case LYS_ANYXML: |
| if (!(node->flags & LYS_MAND_TRUE)) { |
| node_len += ly_print(out, "?"); |
| } |
| break; |
| case LYS_CONTAINER: |
| if (((struct lys_node_container *)node)->presence) { |
| node_len += ly_print(out, "!"); |
| } |
| break; |
| case LYS_LIST: |
| case LYS_LEAFLIST: |
| node_len += ly_print(out, "*"); |
| break; |
| case LYS_CASE: |
| /* kinda shady, but consistent in a way */ |
| node_len += ly_print(out, ")"); |
| break; |
| case LYS_CHOICE: |
| node_len += ly_print(out, ")"); |
| if (!(node->flags & LYS_MAND_TRUE)) { |
| node_len += ly_print(out, "?"); |
| } |
| break; |
| default: |
| break; |
| } |
| line_len += node_len; |
| |
| /** |
| * wrapped print |
| */ |
| |
| /* learn next level indent (there is never a sibling for subtree) */ |
| ++level; |
| if (!subtree) { |
| tree_next_indent(level, node, aug_parent, opts); |
| } |
| |
| /* print type/keys */ |
| switch (node->nodetype & mask) { |
| case LYS_LEAF: |
| case LYS_LEAFLIST: |
| assert(max_name_len); |
| text_indent = LY_TREE_TYPE_INDENT + (uint8_t)(max_name_len - node_len); |
| text_len = tree_print_type(out, &((struct lys_node_leaf *)node)->type, opts->options, &text_str); |
| line_len = tree_print_wrap(out, level, line_len, text_indent, text_len, opts); |
| line_len += ly_print(out, text_str); |
| lydict_remove(opts->module->ctx, text_str); |
| break; |
| case LYS_ANYDATA: |
| assert(max_name_len); |
| text_indent = LY_TREE_TYPE_INDENT + (uint8_t)(max_name_len - node_len); |
| line_len = tree_print_wrap(out, level, line_len, text_indent, 7, opts); |
| line_len += ly_print(out, "anydata"); |
| break; |
| case LYS_ANYXML: |
| assert(max_name_len); |
| text_indent = LY_TREE_TYPE_INDENT + (uint8_t)(max_name_len - node_len); |
| line_len = tree_print_wrap(out, level, line_len, text_indent, 6, opts); |
| line_len += ly_print(out, "anyxml"); |
| break; |
| case LYS_LIST: |
| text_len = tree_print_keys(out, ((struct lys_node_list *)node)->keys, ((struct lys_node_list *)node)->keys_size, |
| opts, &text_str); |
| if (text_len) { |
| line_len = tree_print_wrap(out, level, line_len, 1, text_len, opts); |
| line_len += ly_print(out, text_str); |
| lydict_remove(opts->module->ctx, text_str); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| /* print default */ |
| if (!(opts->options & LYS_OUTOPT_TREE_RFC)) { |
| switch (node->nodetype & mask) { |
| case LYS_LEAF: |
| text_str = ((struct lys_node_leaf *)node)->dflt; |
| if (text_str) { |
| line_len = tree_print_wrap(out, level, line_len, 1, 2 + strlen(text_str), opts); |
| line_len += ly_print(out, "<%s>", text_str); |
| } |
| break; |
| case LYS_CHOICE: |
| sub = ((struct lys_node_choice *)node)->dflt; |
| if (sub) { |
| line_len = tree_print_wrap(out, level, line_len, 1, 2 + strlen(sub->name), opts); |
| line_len += ly_print(out, "<%s>", sub->name); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* print if-features */ |
| switch (node->nodetype & mask) { |
| case LYS_CONTAINER: |
| case LYS_LIST: |
| case LYS_CHOICE: |
| case LYS_CASE: |
| case LYS_ANYDATA: |
| case LYS_ANYXML: |
| case LYS_LEAF: |
| case LYS_LEAFLIST: |
| case LYS_RPC: |
| case LYS_ACTION: |
| case LYS_NOTIF: |
| case LYS_USES: |
| if (node->parent && (node->parent->nodetype == LYS_AUGMENT)) { |
| /* if-features from an augment are de facto inherited */ |
| text_len = tree_print_features(out, node->iffeature, node->iffeature_size, |
| node->parent->iffeature, node->parent->iffeature_size, opts, &text_str); |
| } else { |
| text_len = tree_print_features(out, node->iffeature, node->iffeature_size, NULL, 0, opts, &text_str); |
| } |
| if (text_len) { |
| line_len = tree_print_wrap(out, level, line_len, 1, text_len, opts); |
| line_len += ly_print(out, text_str); |
| lydict_remove(opts->module->ctx, text_str); |
| } |
| break; |
| default: |
| /* only grouping */ |
| break; |
| } |
| |
| /* this node is finished printing */ |
| ly_print(out, "\n"); |
| |
| if ((subtree == 1) || ((node->nodetype & mask) == LYS_USES)) { |
| /* we are printing subtree parents, finish here (or uses option) */ |
| return; |
| } |
| |
| /* set special config flag */ |
| switch (node->nodetype & mask) { |
| case LYS_INPUT: |
| opts->spec_config = 1; |
| break; |
| case LYS_OUTPUT: |
| case LYS_NOTIF: |
| opts->spec_config = 2; |
| break; |
| default: |
| break; |
| } |
| |
| print_children: |
| /* set child mask and learn the longest child name (needed only if a child can have type) */ |
| switch (node->nodetype & mask) { |
| case LYS_LEAF: |
| case LYS_LEAFLIST: |
| case LYS_ANYDATA: |
| case LYS_ANYXML: |
| child_mask = 0; |
| max_child_len = 0; |
| break; |
| case LYS_RPC: |
| case LYS_ACTION: |
| child_mask = LYS_INPUT | LYS_OUTPUT; |
| max_child_len = 0; |
| break; |
| case LYS_CHOICE: |
| child_mask = LYS_CASE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA; |
| max_child_len = tree_get_max_name_len(node->child, NULL, child_mask, opts); |
| break; |
| case LYS_CASE: |
| case LYS_NOTIF: |
| child_mask = LYS_CHOICE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES; |
| max_child_len = tree_get_max_name_len(node->child, NULL, child_mask, opts); |
| break; |
| case LYS_INPUT: |
| case LYS_OUTPUT: |
| child_mask = LYS_CHOICE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES; |
| max_child_len = tree_get_max_name_len(node->child, NULL, child_mask, opts); |
| break; |
| case LYS_USES: |
| child_mask = LYS_CHOICE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES | LYS_ACTION | LYS_NOTIF; |
| /* inherit the name length from the parent, it does not change */ |
| max_child_len = max_name_len; |
| break; |
| case LYS_CONTAINER: |
| case LYS_LIST: |
| case LYS_GROUPING: |
| child_mask = LYS_CHOICE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES | LYS_ACTION | LYS_NOTIF; |
| max_child_len = tree_get_max_name_len(node->child, NULL, child_mask, opts); |
| break; |
| default: |
| child_mask = 0; |
| max_child_len = 0; |
| LOGINT(node->module->ctx); |
| break; |
| } |
| |
| /* print descendants (children) */ |
| if (child_mask) { |
| LY_TREE_FOR(node->child, sub) { |
| /* submodule, foreign augments */ |
| if (opts->module->type && (sub->parent != node) && (sub->module != opts->module)) { |
| continue; |
| } |
| tree_print_snode(out, level, max_child_len, sub, child_mask, NULL, 0, opts); |
| } |
| } |
| |
| /* reset special config flag */ |
| switch (node->nodetype & mask) { |
| case LYS_INPUT: |
| case LYS_OUTPUT: |
| case LYS_NOTIF: |
| opts->spec_config = 0; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void |
| tree_print_subtree(struct lyout *out, const struct lys_node *node, tp_opts *opts) |
| { |
| unsigned int depth, i, j; |
| int level = 0; |
| uint16_t max_child_len; |
| const struct lys_node *parent; |
| |
| /* learn the depth of the node */ |
| depth = 0; |
| parent = node; |
| while (lys_parent(parent)) { |
| if (lys_parent(parent)->nodetype != LYS_USES) { |
| ++depth; |
| } |
| parent = lys_parent(parent); |
| } |
| |
| if (parent->nodetype == LYS_RPC) { |
| ly_print(out, "\n%*srpcs:\n", LY_TREE_MOD_DATA_INDENT, ""); |
| opts->base_indent = LY_TREE_OP_DATA_INDENT; |
| } else if (parent->nodetype == LYS_NOTIF) { |
| ly_print(out, "\n%*snotifications:\n", LY_TREE_MOD_DATA_INDENT, ""); |
| opts->base_indent = LY_TREE_OP_DATA_INDENT; |
| } |
| |
| /* print all the parents */ |
| if (depth) { |
| i = depth; |
| do { |
| parent = node; |
| for (j = 0; j < i; ++j) { |
| do { |
| parent = lys_parent(parent); |
| } while (parent->nodetype == LYS_USES); |
| } |
| |
| tree_print_snode(out, level, 0, parent, LYS_CONTAINER | LYS_LIST | LYS_NOTIF | LYS_RPC | LYS_ACTION |
| | LYS_INPUT | LYS_OUTPUT, NULL, 1, opts); |
| |
| ++level; |
| --i; |
| } while (i); |
| } |
| |
| /* print the node and its descendants */ |
| max_child_len = tree_get_max_name_len(node, NULL, LYS_LEAF|LYS_LEAFLIST|LYS_ANYDATA, opts); |
| tree_print_snode(out, level, max_child_len, node, LYS_ANY, NULL, 2, opts); |
| } |
| |
| static int |
| tree_print_aug_target(struct lyout *out, int line_printed, uint8_t indent, const char *path, tp_opts *opts) |
| { |
| int printed, is_last, len; |
| const char *cur, *next; |
| |
| printed = line_printed; |
| cur = path; |
| do { |
| next = strchr(cur + 1, '/'); |
| if (!next) { |
| len = strlen(cur) + 1; |
| is_last = 1; |
| } else { |
| len = next - cur; |
| is_last = 0; |
| } |
| |
| if (opts->line_length && cur != path && (printed + len > opts->line_length)) { |
| /* line_printed is treated as the base indent */ |
| printed = ly_print(out, "\n%*s", line_printed + indent, ""); |
| /* minus the newline */ |
| --printed; |
| } |
| printed += ly_print(out, "%.*s%s", len, cur, is_last ? ":" : ""); |
| |
| cur = next; |
| } while (!is_last); |
| |
| return printed; |
| } |
| |
| int |
| tree_print_model(struct lyout *out, const struct lys_module *module, const char *target_schema_path, |
| int ll, int options) |
| { |
| struct lys_node *node = NULL, *data, *aug; |
| struct ly_set *set; |
| uint16_t max_child_len; |
| int have_rpcs = 0, have_notifs = 0, have_grps = 0, have_augs = 0, printed; |
| const char *str; |
| int i, mask; |
| tp_opts opts; |
| |
| memset(&opts, 0, sizeof opts); |
| opts.module = module; |
| opts.line_length = ll; |
| opts.options = options; |
| |
| /* we are printing only a subtree */ |
| if (target_schema_path) { |
| set = lys_find_path(module, NULL, target_schema_path); |
| if (!set) { |
| return EXIT_FAILURE; |
| } else if (set->number != 1) { |
| LOGVAL(module->ctx, LYE_PATH_INNODE, LY_VLOG_NONE, NULL); |
| if (set->number == 0) { |
| LOGVAL(module->ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Schema path \"%s\" did not match any nodes.", target_schema_path); |
| } else { |
| LOGVAL(module->ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Schema path \"%s\" matched more nodes.", target_schema_path); |
| } |
| ly_set_free(set); |
| return EXIT_FAILURE; |
| } |
| |
| node = set->set.s[0]; |
| ly_set_free(set); |
| } |
| |
| if (module->type) { |
| ly_print(out, "submodule: %s", module->name); |
| data = ((struct lys_submodule *)module)->belongsto->data; |
| if (options & LYS_OUTOPT_TREE_RFC) { |
| ly_print(out, "\n"); |
| } else { |
| ly_print(out, " (belongs-to %s)\n", ((struct lys_submodule *)module)->belongsto->name); |
| } |
| } else { |
| ly_print(out, "module: %s\n", module->name); |
| data = module->data; |
| } |
| |
| /* only subtree */ |
| if (target_schema_path) { |
| opts.base_indent = LY_TREE_MOD_DATA_INDENT; |
| tree_print_subtree(out, node, &opts); |
| return EXIT_SUCCESS; |
| } |
| |
| /* module */ |
| opts.base_indent = LY_TREE_MOD_DATA_INDENT; |
| mask = LYS_CHOICE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES; |
| max_child_len = tree_get_max_name_len(data, NULL, mask, &opts); |
| LY_TREE_FOR(data, node) { |
| if (opts.module->type && (node->module != opts.module)) { |
| /* we're printing the submodule only */ |
| continue; |
| } |
| |
| switch (node->nodetype) { |
| case LYS_RPC: |
| if (!lys_is_disabled(node, 0)) { |
| have_rpcs++; |
| } |
| break; |
| case LYS_NOTIF: |
| if (!lys_is_disabled(node, 0)) { |
| have_notifs++; |
| } |
| break; |
| case LYS_GROUPING: |
| if ((options & LYS_OUTOPT_TREE_GROUPING) && !lys_is_disabled(node, 0)) { |
| have_grps++; |
| } |
| break; |
| default: |
| tree_print_snode(out, 0, max_child_len, node, mask, NULL, 0, &opts); |
| break; |
| } |
| } |
| |
| /* all remaining nodes printed with operation indent */ |
| opts.base_indent = LY_TREE_OP_DATA_INDENT; |
| |
| /* augments */ |
| for (i = 0; i < module->augment_size; i++) { |
| if ((module->type && (module->augment[i].target->module == module)) |
| || (!module->type && (lys_node_module(module->augment[i].target) == module)) |
| || lys_is_disabled((struct lys_node *)&module->augment[i], 0)) { |
| /* submodule, target is our submodule or module, target is in our module or any submodules */ |
| continue; |
| } |
| |
| if (!have_augs) { |
| ly_print(out, "\n"); |
| have_augs = 1; |
| } |
| |
| printed = ly_print(out, "%*saugment ", LY_TREE_MOD_DATA_INDENT, ""); |
| if (options & LYS_OUTOPT_TREE_RFC) { |
| str = transform_json2schema(module, module->augment[i].target_name); |
| tree_print_aug_target(out, printed, LY_TREE_WRAP_INDENT, str, &opts); |
| lydict_remove(module->ctx, str); |
| } else { |
| tree_print_aug_target(out, printed, LY_TREE_WRAP_INDENT, module->augment[i].target_name, &opts); |
| } |
| ly_print(out, "\n"); |
| |
| aug = (struct lys_node *)&module->augment[i]; |
| mask = LYS_CHOICE | LYS_CASE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_USES |
| | LYS_ACTION | LYS_NOTIF; |
| max_child_len = tree_get_max_name_len(aug->child, aug, mask, &opts); |
| LY_TREE_FOR(aug->child, node) { |
| /* submodule, foreign augments */ |
| if (node->parent != aug) { |
| continue; |
| } |
| tree_print_snode(out, 0, max_child_len, node, mask, aug, 0, &opts); |
| } |
| } |
| |
| /* rpcs */ |
| if (have_rpcs) { |
| ly_print(out, "\n%*srpcs:\n", LY_TREE_MOD_DATA_INDENT, ""); |
| |
| LY_TREE_FOR(data, node) { |
| tree_print_snode(out, 0, 0, node, LYS_RPC, NULL, 0, &opts); |
| } |
| } |
| |
| /* notifications */ |
| if (have_notifs) { |
| ly_print(out, "\n%*snotifications:\n", LY_TREE_MOD_DATA_INDENT, ""); |
| |
| LY_TREE_FOR(data, node) { |
| tree_print_snode(out, 0, 0, node, LYS_NOTIF, NULL, 0, &opts); |
| } |
| } |
| |
| /* groupings */ |
| if ((options & LYS_OUTOPT_TREE_GROUPING) && have_grps) { |
| ly_print(out, "\n"); |
| LY_TREE_FOR(data, node) { |
| if (node->nodetype == LYS_GROUPING) { |
| ly_print(out, "%*sgrouping %s:\n", LY_TREE_MOD_DATA_INDENT, "", node->name); |
| |
| tree_print_snode(out, 0, 0, node, LYS_GROUPING, NULL, 0, &opts); |
| } |
| } |
| } |
| |
| ly_print_flush(out); |
| |
| return EXIT_SUCCESS; |
| } |