printer YANG FEATURE initial implementation of YANG printer
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88da234..21d9aec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -180,6 +180,8 @@
     src/tree_schema_compile.c
     src/tree_schema_helpers.c
     src/parser_yang.c
+    src/printer.c
+    src/printer_yang.c
     src/xml.c
     src/xpath.c)
 
@@ -201,6 +203,7 @@
     src/libyang.h
     src/context.h
     src/tree_schema.h
+    src/printer_schema.h
     src/extensions.h
     src/dict.h
     src/log.h
diff --git a/src/libyang.h b/src/libyang.h
index 3c001fc..7b89268 100644
--- a/src/libyang.h
+++ b/src/libyang.h
@@ -26,6 +26,7 @@
 #include "dict.h"
 #include "context.h"
 #include "tree_schema.h"
+#include "printer_schema.h"
 
 /**
  * @mainpage About
diff --git a/src/log.h b/src/log.h
index d8b0a04..0074d83 100644
--- a/src/log.h
+++ b/src/log.h
@@ -150,7 +150,8 @@
     LY_EINT,        /**< Internal error */
     LY_EVALID,      /**< Validation failure */
     LY_EPLUGIN,     /**< Error reported by a plugin */
-    LY_EDENIED      /**< Operation is not allowed */
+    LY_EDENIED,     /**< Operation is not allowed */
+    LY_EOTHER       /**< Unknown error */
 } LY_ERR;
 
 /**
diff --git a/src/parser_yang.c b/src/parser_yang.c
index a45a979..83ad888 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -571,6 +571,7 @@
  * @param[in] ctx yang parser context for logging.
  * @param[in,out] data Data to read from, always moved to currently handled character.
  * @param[in] arg Type of YANG keyword argument expected.
+ * @param[out] flags optional output argument to get flag of the argument's quoting (LYS_*QOUTED - see [schema node flags](@ref snodeflags))
  * @param[out] word_p Pointer to the read string. Can return NULL if \p arg is #Y_MAYBE_STR_ARG.
  * @param[out] word_b Pointer to a dynamically-allocated buffer holding the read string. If not needed,
  * set to NULL. Otherwise equal to \p word_p.
@@ -579,7 +580,8 @@
  * @return LY_ERR values.
  */
 static LY_ERR
-get_argument(struct ly_parser_ctx *ctx, const char **data, enum yang_arg arg, char **word_p, char **word_b, size_t *word_len)
+get_argument(struct ly_parser_ctx *ctx, const char **data, enum yang_arg arg,
+             uint16_t *flags, char **word_p, char **word_b, size_t *word_len)
 {
     size_t buf_len = 0;
 
@@ -600,6 +602,9 @@
                             "unquoted string character, optsep, semicolon or opening brace");
                 return LY_EVALID;
             }
+            if (flags) {
+                (*flags) |= (**data) == '\'' ? LYS_SINGLEQUOTED : LYS_DOUBLEQUOTED;
+            }
             LY_CHECK_RET(read_qstring(ctx, data, arg, word_p, word_b, word_len, &buf_len));
             goto str_end;
         case '/':
@@ -1028,7 +1033,7 @@
     stmt->stmt = lydict_insert(ctx->ctx, word, word_len);
 
     /* get optional argument */
-    LY_CHECK_RET(get_argument(ctx, data, Y_MAYBE_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_MAYBE_STR_ARG, &stmt->flags, &word, &buf, &word_len));
 
     if (word) {
         if (buf) {
@@ -1075,7 +1080,7 @@
     e->insubstmt_index = insubstmt_index;
 
     /* get optional argument */
-    LY_CHECK_RET(get_argument(ctx, data, Y_MAYBE_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_MAYBE_STR_ARG, NULL, &word, &buf, &word_len));
 
     if (word) {
         INSERT_WORD(ctx, buf, e->argument, word, word_len);
@@ -1116,7 +1121,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, arg, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, arg, NULL, &word, &buf, &word_len));
 
     /* store value and spend buf if allocated */
     INSERT_WORD(ctx, buf, *value, word, word_len);
@@ -1158,7 +1163,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 3) && !strncmp(word, "1.0", word_len)) {
         *version = LYS_VERSION_1_0;
@@ -1209,7 +1214,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
 
     INSERT_WORD(ctx, buf, *belongsto, word, word_len);
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret, goto checks) {
@@ -1259,7 +1264,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     /* check value */
     if (lysp_check_date(ctx, word, word_len, "revision-date")) {
@@ -1306,7 +1311,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *includes, inc, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
 
     INSERT_WORD(ctx, buf, inc->name, word, word_len);
 
@@ -1363,7 +1368,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *imports, imp, LY_EVALID);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, imp->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret, goto checks) {
@@ -1420,7 +1425,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *revs, rev, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     /* check value */
     if (lysp_check_date(ctx, word, word_len, "revision")) {
@@ -1475,7 +1480,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *texts, item, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, arg, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, arg, NULL, &word, &buf, &word_len));
 
     INSERT_WORD(ctx, buf, *item, word, word_len);
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -1515,7 +1520,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 4) && !strncmp(word, "true", word_len)) {
         *flags |= LYS_CONFIG_W;
@@ -1565,7 +1570,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 4) && !strncmp(word, "true", word_len)) {
         *flags |= LYS_MAND_TRUE;
@@ -1610,7 +1615,7 @@
     enum yang_keyword kw;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     INSERT_WORD(ctx, buf, restr->arg, word, word_len);
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -1681,7 +1686,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 7) && !strncmp(word, "current", word_len)) {
         *flags |= LYS_STATUS_CURR;
@@ -1737,7 +1742,7 @@
     *when_p = when;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, when->cond, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -1793,7 +1798,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, any->name, word, word_len);
 
     /* parse substatements */
@@ -1865,7 +1870,7 @@
     *flags |= LYS_SET_VALUE;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if (!word_len || (word[0] == '+') || ((word[0] == '0') && (word_len > 1)) || ((val_kw == YANG_VALUE) && !strncmp(word, "-0", 2))) {
         LOGVAL_YANG(ctx, LY_VCODE_INVAL, word_len, word, ly_stmt2str(val_kw));
@@ -1941,7 +1946,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *enums, enm, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, enum_kw == YANG_ENUM ? Y_STR_ARG : Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, enum_kw == YANG_ENUM ? Y_STR_ARG : Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     if (enum_kw == YANG_ENUM) {
         if (!word_len) {
             LOGVAL_YANG(ctx, LYVE_SYNTAX_YANG, "Enum name must not be zero-length.");
@@ -2022,7 +2027,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if (!word_len || (word[0] == '0') || !isdigit(word[0])) {
         LOGVAL_YANG(ctx, LY_VCODE_INVAL, word_len, word, "fraction-digits");
@@ -2086,7 +2091,7 @@
     *flags |= LYS_SET_REQINST;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 4) && !strncmp(word, "true", word_len)) {
         *reqinst = 1;
@@ -2134,7 +2139,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len != 12) || strncmp(word, "invert-match", word_len)) {
         LOGVAL_YANG(ctx, LY_VCODE_INVAL, word_len, word, "modifier");
@@ -2187,7 +2192,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *patterns, restr, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     /* add special meaning first byte */
     if (buf) {
@@ -2255,7 +2260,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_PREF_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_PREF_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, type->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -2360,7 +2365,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, leaf->name, word, word_len);
 
     /* parse substatements */
@@ -2449,7 +2454,7 @@
     *flags |= LYS_SET_MAX;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if (!word_len || (word[0] == '0') || ((word[0] != 'u') && !isdigit(word[0]))) {
         LOGVAL_YANG(ctx, LY_VCODE_INVAL, word_len, word, "max-elements");
@@ -2516,7 +2521,7 @@
     *flags |= LYS_SET_MIN;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if (!word_len || !isdigit(word[0]) || ((word[0] == '0') && (word_len > 1))) {
         LOGVAL_YANG(ctx, LY_VCODE_INVAL, word_len, word, "min-elements");
@@ -2577,7 +2582,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 6) && !strncmp(word, "system", word_len)) {
         *flags |= LYS_ORDBY_SYSTEM;
@@ -2637,7 +2642,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, llist->name, word, word_len);
 
     /* parse substatements */
@@ -2733,7 +2738,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *refines, rf, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, rf->nodeid, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -2801,7 +2806,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *typedefs, tpdf, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, tpdf->name, word, word_len);
 
     /* parse substatements */
@@ -2875,7 +2880,7 @@
     }
 
     /* initiate structure */
-    inout_p->nodetype = LYS_INOUT;
+    inout_p->nodetype = &((struct lysp_action*)parent)->input == inout_p ? LYS_INPUT : LYS_OUTPUT;
     inout_p->parent = parent;
 
     /* parse substatements */
@@ -2955,7 +2960,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *actions, act, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, act->name, word, word_len);
     act->nodetype = LYS_ACTION;
     act->parent = parent;
@@ -3028,7 +3033,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *notifs, notif, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, notif->name, word, word_len);
     notif->nodetype = LYS_NOTIF;
     notif->parent = parent;
@@ -3123,7 +3128,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *groupings, grp, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, grp->name, word, word_len);
     grp->nodetype = LYS_GROUPING;
     grp->parent = parent;
@@ -3217,7 +3222,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *augments, aug, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, aug->nodeid, word, word_len);
     aug->nodetype = LYS_AUGMENT;
     aug->parent = parent;
@@ -3321,7 +3326,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_PREF_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_PREF_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, uses->name, word, word_len);
 
     /* parse substatements */
@@ -3394,7 +3399,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, cas->name, word, word_len);
 
     /* parse substatements */
@@ -3485,7 +3490,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, choice->name, word, word_len);
 
     /* parse substatements */
@@ -3593,7 +3598,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, cont->name, word, word_len);
 
     /* parse substatements */
@@ -3715,7 +3720,7 @@
     }
 
     /* get name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, list->name, word, word_len);
 
     /* parse substatements */
@@ -3847,7 +3852,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 4) && !strncmp(word, "true", word_len)) {
         *flags |= LYS_YINELEM_TRUE;
@@ -3898,7 +3903,7 @@
     }
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, *argument, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -3938,7 +3943,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *extensions, ex, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, ex->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -3992,7 +3997,7 @@
     uint32_t *d_min, *d_max;
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
 
     if ((word_len == 13) && !strncmp(word, "not-supported", word_len)) {
         dev_mod = LYS_DEV_NOT_SUPPORTED;
@@ -4201,7 +4206,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *deviations, dev, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_STR_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, dev->nodeid, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret, goto checks) {
@@ -4255,7 +4260,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *features, feat, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, feat->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -4304,7 +4309,7 @@
     LY_ARRAY_NEW_RET(ctx->ctx, *identities, ident, LY_EMEM);
 
     /* get value */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, ident->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret,) {
@@ -4386,7 +4391,7 @@
     struct lysp_submodule *dup;
 
     /* (sub)module name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, mod->mod->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret, goto checks) {
@@ -4596,7 +4601,7 @@
     struct lysp_submodule *dup;
 
     /* submodule name */
-    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
+    LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len));
     INSERT_WORD(ctx, buf, submod->name, word, word_len);
 
     YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret, goto checks) {
@@ -4812,7 +4817,7 @@
     LY_CHECK_GOTO(ret, cleanup);
 
     /* read some trailing spaces or new lines */
-    ret = get_argument(context, &data, Y_MAYBE_STR_ARG, &word, &buf, &word_len);
+    ret = get_argument(context, &data, Y_MAYBE_STR_ARG, NULL, &word, &buf, &word_len);
     LY_CHECK_GOTO(ret, cleanup);
 
     if (word) {
@@ -4869,7 +4874,7 @@
     LY_CHECK_GOTO(ret, cleanup);
 
     /* read some trailing spaces or new lines */
-    ret = get_argument(context, &data, Y_MAYBE_STR_ARG, &word, &buf, &word_len);
+    ret = get_argument(context, &data, Y_MAYBE_STR_ARG, NULL, &word, &buf, &word_len);
     LY_CHECK_GOTO(ret, cleanup);
 
     if (word) {
diff --git a/src/printer.c b/src/printer.c
new file mode 100644
index 0000000..653dca8
--- /dev/null
+++ b/src/printer.c
@@ -0,0 +1,395 @@
+/**
+ * @file printer.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Wrapper for all libyang printers.
+ *
+ * Copyright (c) 2015 - 2019 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 "common.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "printer_internal.h"
+
+/**
+ * @brief informational structure shared by printers
+ */
+struct ext_substmt_info_s ext_substmt_info[] = {
+  {NULL, NULL, 0},                              /**< LYEXT_SUBSTMT_SELF */
+  {"argument", "name", SUBST_FLAG_ID},          /**< LYEXT_SUBSTMT_ARGUMENT */
+  {"base", "name", SUBST_FLAG_ID},              /**< LYEXT_SUBSTMT_BASE */
+  {"belongs-to", "module", SUBST_FLAG_ID},      /**< LYEXT_SUBSTMT_BELONGSTO */
+  {"contact", "text", SUBST_FLAG_YIN},          /**< LYEXT_SUBSTMT_CONTACT */
+  {"default", "value", 0},                      /**< LYEXT_SUBSTMT_DEFAULT */
+  {"description", "text", SUBST_FLAG_YIN},      /**< LYEXT_SUBSTMT_DESCRIPTION */
+  {"error-app-tag", "value", 0},                /**< LYEXT_SUBSTMT_ERRTAG */
+  {"error-message", "value", SUBST_FLAG_YIN},   /**< LYEXT_SUBSTMT_ERRMSG */
+  {"key", "value", 0},                          /**< LYEXT_SUBSTMT_KEY */
+  {"namespace", "uri", 0},                      /**< LYEXT_SUBSTMT_NAMESPACE */
+  {"organization", "text", SUBST_FLAG_YIN},     /**< LYEXT_SUBSTMT_ORGANIZATION */
+  {"path", "value", 0},                         /**< LYEXT_SUBSTMT_PATH */
+  {"prefix", "value", SUBST_FLAG_ID},           /**< LYEXT_SUBSTMT_PREFIX */
+  {"presence", "value", 0},                     /**< LYEXT_SUBSTMT_PRESENCE */
+  {"reference", "text", SUBST_FLAG_YIN},        /**< LYEXT_SUBSTMT_REFERENCE */
+  {"revision-date", "date", SUBST_FLAG_ID},     /**< LYEXT_SUBSTMT_REVISIONDATE */
+  {"units", "name", 0},                         /**< LYEXT_SUBSTMT_UNITS */
+  {"value", "value", SUBST_FLAG_ID},            /**< LYEXT_SUBSTMT_VALUE */
+  {"yang-version", "value", SUBST_FLAG_ID},     /**< LYEXT_SUBSTMT_VERSION */
+  {"modifier", "value", SUBST_FLAG_ID},         /**< LYEXT_SUBSTMT_MODIFIER */
+  {"require-instance", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_REQINST */
+  {"yin-element", "value", SUBST_FLAG_ID},      /**< LYEXT_SUBSTMT_YINELEM */
+  {"config", "value", SUBST_FLAG_ID},           /**< LYEXT_SUBSTMT_CONFIG */
+  {"mandatory", "value", SUBST_FLAG_ID},        /**< LYEXT_SUBSTMT_MANDATORY */
+  {"ordered-by", "value", SUBST_FLAG_ID},       /**< LYEXT_SUBSTMT_ORDEREDBY */
+  {"status", "value", SUBST_FLAG_ID},           /**< LYEXT_SUBSTMT_STATUS */
+  {"fraction-digits", "value", SUBST_FLAG_ID},  /**< LYEXT_SUBSTMT_DIGITS */
+  {"max-elements", "value", SUBST_FLAG_ID},     /**< LYEXT_SUBSTMT_MAX */
+  {"min-elements", "value", SUBST_FLAG_ID},     /**< LYEXT_SUBSTMT_MIN */
+  {"position", "value", SUBST_FLAG_ID},         /**< LYEXT_SUBSTMT_POSITION */
+  {"unique", "tag", 0},                         /**< LYEXT_SUBSTMT_UNIQUE */
+};
+
+LY_ERR
+ly_print(struct lyout *out, const char *format, ...)
+{
+    int count = 0;
+    char *msg = NULL, *aux;
+    va_list ap;
+#ifndef HAVE_VDPRINTF
+    FILE *stream;
+#endif
+
+    va_start(ap, format);
+
+    switch (out->type) {
+    case LYOUT_FD:
+#ifdef HAVE_VDPRINTF
+        count = vdprintf(out->method.fd, format, ap);
+#else
+        stream = fdopen(dup(out->method.fd), "a+");
+        if (stream) {
+            count = vfprintf(stream, format, ap);
+            fclose(stream);
+        }
+#endif
+        break;
+    case LYOUT_STREAM:
+        count = vfprintf(out->method.f, format, ap);
+        break;
+    case LYOUT_MEMORY:
+        count = vasprintf(&msg, format, ap);
+        if (out->method.mem.len + count + 1 > out->method.mem.size) {
+            aux = ly_realloc(out->method.mem.buf, out->method.mem.len + count + 1);
+            if (!aux) {
+                out->method.mem.buf = NULL;
+                out->method.mem.len = 0;
+                out->method.mem.size = 0;
+                LOGMEM(NULL);
+                va_end(ap);
+                return -1;
+            }
+            out->method.mem.buf = aux;
+            out->method.mem.size = out->method.mem.len + count + 1;
+        }
+        memcpy(&out->method.mem.buf[out->method.mem.len], msg, count);
+        out->method.mem.len += count;
+        out->method.mem.buf[out->method.mem.len] = '\0';
+        free(msg);
+        break;
+    case LYOUT_CALLBACK:
+        count = vasprintf(&msg, format, ap);
+        count = out->method.clb.f(out->method.clb.arg, msg, count);
+        free(msg);
+        break;
+    }
+
+    va_end(ap);
+
+    if (count < 0) {
+        return LY_EOTHER;
+    } else {
+        return LY_SUCCESS;
+    }
+}
+
+void
+ly_print_flush(struct lyout *out)
+{
+    switch (out->type) {
+    case LYOUT_STREAM:
+        fflush(out->method.f);
+        break;
+    case LYOUT_FD:
+    case LYOUT_MEMORY:
+    case LYOUT_CALLBACK:
+        /* nothing to do */
+        break;
+    }
+}
+
+LY_ERR
+ly_write(struct lyout *out, const char *buf, size_t count)
+{
+    int written = 0;
+
+    if (out->hole_count) {
+        /* we are buffering data after a hole */
+        if (out->buf_len + count > out->buf_size) {
+            out->buffered = ly_realloc(out->buffered, out->buf_len + count);
+            if (!out->buffered) {
+                out->buf_len = 0;
+                out->buf_size = 0;
+                LOGMEM_RET(NULL);
+            }
+            out->buf_size = out->buf_len + count;
+        }
+
+        memcpy(&out->buffered[out->buf_len], buf, count);
+        out->buf_len += count;
+        return LY_SUCCESS;
+    }
+
+    switch (out->type) {
+    case LYOUT_MEMORY:
+        if (out->method.mem.len + count + 1 > out->method.mem.size) {
+            out->method.mem.buf = ly_realloc(out->method.mem.buf, out->method.mem.len + count + 1);
+            if (!out->method.mem.buf) {
+                out->method.mem.len = 0;
+                out->method.mem.size = 0;
+                LOGMEM_RET(NULL);
+            }
+            out->method.mem.size = out->method.mem.len + count + 1;
+        }
+        memcpy(&out->method.mem.buf[out->method.mem.len], buf, count);
+        out->method.mem.len += count;
+        out->method.mem.buf[out->method.mem.len] = '\0';
+        return LY_SUCCESS;
+    case LYOUT_FD:
+        written = write(out->method.fd, buf, count);
+        break;
+    case LYOUT_STREAM:
+        written =  fwrite(buf, sizeof *buf, count, out->method.f);
+        break;
+    case LYOUT_CALLBACK:
+        written = out->method.clb.f(out->method.clb.arg, buf, count);
+        break;
+    }
+
+    if (written < 0) {
+        return LY_EOTHER;
+    } else {
+        return LY_SUCCESS;
+    }
+}
+
+LY_ERR
+ly_write_skip(struct lyout *out, size_t count, size_t *position)
+{
+    switch (out->type) {
+    case LYOUT_MEMORY:
+        if (out->method.mem.len + count > out->method.mem.size) {
+            out->method.mem.buf = ly_realloc(out->method.mem.buf, out->method.mem.len + count);
+            if (!out->method.mem.buf) {
+                out->method.mem.len = 0;
+                out->method.mem.size = 0;
+                LOGMEM_RET(NULL);
+            }
+            out->method.mem.size = out->method.mem.len + count;
+        }
+
+        /* save the current position */
+        *position = out->method.mem.len;
+
+        /* skip the memory */
+        out->method.mem.len += count;
+        break;
+    case LYOUT_FD:
+    case LYOUT_STREAM:
+    case LYOUT_CALLBACK:
+        /* buffer the hole */
+        if (out->buf_len + count > out->buf_size) {
+            out->buffered = ly_realloc(out->buffered, out->buf_len + count);
+            if (!out->buffered) {
+                out->buf_len = 0;
+                out->buf_size = 0;
+                LOGMEM_RET(NULL);
+            }
+            out->buf_size = out->buf_len + count;
+        }
+
+        /* save the current position */
+        *position = out->buf_len;
+
+        /* skip the memory */
+        out->buf_len += count;
+
+        /* increase hole counter */
+        ++out->hole_count;
+    }
+
+    return LY_SUCCESS;
+}
+
+LY_ERR
+ly_write_skipped(struct lyout *out, size_t position, const char *buf, size_t count)
+{
+    LY_ERR ret = LY_SUCCESS;
+
+    switch (out->type) {
+    case LYOUT_MEMORY:
+        /* write */
+        memcpy(&out->method.mem.buf[position], buf, count);
+        break;
+    case LYOUT_FD:
+    case LYOUT_STREAM:
+    case LYOUT_CALLBACK:
+        if (out->buf_len < position + count) {
+            LOGMEM_RET(NULL);
+        }
+
+        /* write into the hole */
+        memcpy(&out->buffered[position], buf, count);
+
+        /* decrease hole counter */
+        --out->hole_count;
+
+        if (!out->hole_count) {
+            /* all holes filled, we can write the buffer */
+            ret = ly_write(out, out->buffered, out->buf_len);
+            out->buf_len = 0;
+        }
+        break;
+    }
+
+    return ret;
+}
+
+static LY_ERR
+lys_print_(struct lyout *out, const struct lys_module *module, LYS_OUTFORMAT format, int UNUSED(line_length), int UNUSED(options))
+{
+    LY_ERR ret;
+
+    switch (format) {
+    case LYS_OUT_YANG:
+        ret = yang_print_parsed(out, module);
+        break;
+    case LYS_OUT_YANG_COMPILED:
+        ret = yang_print_compiled(out, module);
+        break;
+    /* TODO not yet implemented
+    case LYS_OUT_YIN:
+        lys_disable_deviations((struct lys_module *)module);
+        ret = yin_print_model(out, module);
+        lys_enable_deviations((struct lys_module *)module);
+        break;
+    case LYS_OUT_TREE:
+        ret = tree_print_model(out, module, target_node, line_length, options);
+        break;
+    case LYS_OUT_INFO:
+        ret = info_print_model(out, module, target_node);
+        break;
+    case LYS_OUT_JSON:
+        ret = jsons_print_model(out, module, target_node);
+        break;
+    */
+    default:
+        LOGERR(module->ctx, LY_EINVAL, "Unknown output format.");
+        ret = LY_EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+API LY_ERR
+lys_print_file(FILE *f, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options)
+{
+    struct lyout out;
+
+    LY_CHECK_ARG_RET(NULL, f, module, LY_EINVAL);
+
+    memset(&out, 0, sizeof out);
+
+    out.type = LYOUT_STREAM;
+    out.method.f = f;
+
+    return lys_print_(&out, module, format, line_length, options);
+}
+
+API LY_ERR
+lys_print_path(const char *path, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options)
+{
+    FILE *f;
+    LY_ERR ret;
+
+    LY_CHECK_ARG_RET(NULL, path, module, LY_EINVAL);
+
+    f = fopen(path, "w");
+    if (!f) {
+        LOGERR(module->ctx, LY_ESYS, "Failed to open file \"%s\" (%s).", path, strerror(errno));
+        return LY_ESYS;
+    }
+
+    ret = lys_print_file(f, module, format, line_length, options);
+    fclose(f);
+    return ret;
+}
+
+API LY_ERR
+lys_print_fd(int fd, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options)
+{
+    struct lyout out;
+
+    LY_CHECK_ARG_RET(NULL, fd >= 0, module, LY_EINVAL);
+
+    memset(&out, 0, sizeof out);
+
+    out.type = LYOUT_FD;
+    out.method.fd = fd;
+
+    return lys_print_(&out, module, format, line_length, options);
+}
+
+API LY_ERR
+lys_print_mem(char **strp, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options)
+{
+    struct lyout out;
+    LY_ERR r;
+
+    LY_CHECK_ARG_RET(NULL, strp, module, LY_EINVAL);
+
+    memset(&out, 0, sizeof out);
+
+    out.type = LYOUT_MEMORY;
+
+    r = lys_print_(&out, module, format, line_length, options);
+
+    *strp = out.method.mem.buf;
+    return r;
+}
+
+API LY_ERR
+lys_print_clb(ssize_t (*writeclb)(void *arg, const void *buf, size_t count), void *arg, const struct lys_module *module,
+              LYS_OUTFORMAT format, int line_length, int options)
+{
+    struct lyout out;
+
+    LY_CHECK_ARG_RET(NULL, writeclb, module, LY_EINVAL);
+
+    memset(&out, 0, sizeof out);
+
+    out.type = LYOUT_CALLBACK;
+    out.method.clb.f = writeclb;
+    out.method.clb.arg = arg;
+
+    return lys_print_(&out, module, format, line_length, options);
+}
diff --git a/src/printer_internal.h b/src/printer_internal.h
new file mode 100644
index 0000000..4d61285
--- /dev/null
+++ b/src/printer_internal.h
@@ -0,0 +1,80 @@
+/**
+ * @file printer_internal.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Internal structures and functions for libyang
+ *
+ * Copyright (c) 2015-2019 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
+ */
+
+#ifndef LY_PRINTER_INTERNAL_H_
+#define LY_PRINTER_INTERNAL_H_
+
+#include "printer_schema.h"
+
+typedef enum LYOUT_TYPE {
+    LYOUT_FD,          /**< file descriptor */
+    LYOUT_STREAM,      /**< FILE stream */
+    LYOUT_MEMORY,      /**< memory */
+    LYOUT_CALLBACK     /**< print via provided callback */
+} LYOUT_TYPE;
+
+struct lyout {
+    LYOUT_TYPE type;
+    union {
+        int fd;
+        FILE *f;
+        struct {
+            char *buf;
+            size_t len;
+            size_t size;
+        } mem;
+        struct {
+            ssize_t (*f)(void *arg, const void *buf, size_t count);
+            void *arg;
+        } clb;
+    } method;
+
+    /* buffer for holes */
+    char *buffered;
+    size_t buf_len;
+    size_t buf_size;
+
+    /* hole counter */
+    size_t hole_count;
+};
+
+struct ext_substmt_info_s {
+    const char *name;
+    const char *arg;
+    int flags;
+#define SUBST_FLAG_YIN 0x1 /**< has YIN element */
+#define SUBST_FLAG_ID 0x2  /**< the value is identifier -> no quotes */
+};
+
+/* filled in printer.c */
+extern struct ext_substmt_info_s ext_substmt_info[];
+
+
+/**
+ * @brief
+ */
+LY_ERR yang_print_parsed(struct lyout *out, const struct lys_module *module);
+
+/**
+ * @brief
+ */
+LY_ERR yang_print_compiled(struct lyout *out, const struct lys_module *module);
+
+LY_ERR ly_print(struct lyout *out, const char *format, ...);
+
+void ly_print_flush(struct lyout *out);
+
+LY_ERR ly_write(struct lyout *out, const char *buf, size_t count);
+
+#endif /* LY_PRINTER_INTERNAL_H_ */
diff --git a/src/printer_schema.h b/src/printer_schema.h
new file mode 100644
index 0000000..48d71b0
--- /dev/null
+++ b/src/printer_schema.h
@@ -0,0 +1,83 @@
+/**
+ * @file printer_schema.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Schema printers for libyang
+ *
+ * Copyright (c) 2015-2019 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
+ */
+
+#ifndef LY_PRINTER_SCHEMA_H_
+#define LY_PRINTER_SCHEMA_H_
+
+#include <unistd.h>
+
+/**
+ * @brief Print schema tree in the specified format into a memory block.
+ * It is up to caller to free the returned string by free().
+ *
+ * @param[out] strp Pointer to store the resulting dump.
+ * @param[in] module Schema tree to print.
+ * @param[in] format Schema output format.
+ * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer.
+ * @param[in] options Schema output options (see @ref schemaprinterflags).
+ * @return 0 on success, 1 on failure (#ly_errno is set).
+ */
+LY_ERR lys_print_mem(char **strp, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options);
+
+/**
+ * @brief Print schema tree in the specified format into a file descriptor.
+ *
+ * @param[in] module Schema tree to print.
+ * @param[in] fd File descriptor where to print the data.
+ * @param[in] format Schema output format.
+ * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE format.
+ * @param[in] options Schema output options (see @ref schemaprinterflags).
+ * @return 0 on success, 1 on failure (#ly_errno is set).
+ */
+LY_ERR lys_print_fd(int fd, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options);
+
+/**
+ * @brief Print schema tree in the specified format into a file stream.
+ *
+ * @param[in] module Schema tree to print.
+ * @param[in] f File stream where to print the schema.
+ * @param[in] format Schema output format.
+ * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer.
+ * @param[in] options Schema output options (see @ref schemaprinterflags).
+ * @return 0 on success, 1 on failure (#ly_errno is set).
+ */
+LY_ERR lys_print_file(FILE *f, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options);
+
+/**
+ * @brief Print schema tree in the specified format into a file.
+ *
+ * @param[in] path File where to print the schema.
+ * @param[in] module Schema tree to print.
+ * @param[in] format Schema output format.
+ * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer.
+ * @param[in] options Schema output options (see @ref schemaprinterflags).
+ * @return 0 on success, 1 on failure (#ly_errno is set).
+ */
+LY_ERR lys_print_path(const char *path, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options);
+
+/**
+ * @brief Print schema tree in the specified format using a provided callback.
+ *
+ * @param[in] module Schema tree to print.
+ * @param[in] writeclb Callback function to write the data (see write(1)).
+ * @param[in] arg Optional caller-specific argument to be passed to the \p writeclb callback.
+ * @param[in] format Schema output format.
+ * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer.
+ * @param[in] options Schema output options (see @ref schemaprinterflags).
+ * @return LY_ERR value.
+ */
+LY_ERR lys_print_clb(ssize_t (*writeclb)(void *arg, const void *buf, size_t count), void *arg,
+                     const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options);
+
+#endif /* LY_PRINTER_SCHEMA_H_ */
diff --git a/src/printer_yang.c b/src/printer_yang.c
new file mode 100755
index 0000000..51d0960
--- /dev/null
+++ b/src/printer_yang.c
@@ -0,0 +1,1401 @@
+/**
+ * @file printer_yang.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief YANG printer
+ *
+ * Copyright (c) 2015 - 2019 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 "common.h"
+
+#include "printer_internal.h"
+#include "tree_schema.h"
+#include "tree_schema_internal.h"
+
+#define LEVEL ctx->level
+#define INDENT (LEVEL)*2,""
+
+struct ypr_ctx {
+    struct lyout *out;
+    unsigned int level;
+    const struct lys_module *module;
+};
+
+static void
+ypr_encode(struct lyout *out, const char *text, int len)
+{
+    int i, start_len;
+    const char *start;
+    char special = 0;
+
+    if (!len) {
+        return;
+    }
+
+    if (len < 0) {
+        len = strlen(text);
+    }
+
+    start = text;
+    start_len = 0;
+    for (i = 0; i < len; ++i) {
+        switch (text[i]) {
+        case '\n':
+        case '\t':
+        case '\"':
+        case '\\':
+            special = text[i];
+            break;
+        default:
+            ++start_len;
+            break;
+        }
+
+        if (special) {
+            ly_write(out, start, start_len);
+            switch (special) {
+            case '\n':
+                ly_write(out, "\\n", 2);
+                break;
+            case '\t':
+                ly_write(out, "\\t", 2);
+                break;
+            case '\"':
+                ly_write(out, "\\\"", 2);
+                break;
+            case '\\':
+                ly_write(out, "\\\\", 2);
+                break;
+            }
+
+            start += start_len + 1;
+            start_len = 0;
+
+            special = 0;
+        }
+    }
+
+    ly_write(out, start, start_len);
+}
+
+static void
+ypr_open(struct lyout *out, int *flag)
+{
+    if (flag && !*flag) {
+        *flag = 1;
+        ly_print(out, " {\n");
+    }
+}
+
+static void
+ypr_close(struct ypr_ctx *ctx, int flag)
+{
+    if (flag) {
+        ly_print(ctx->out, "%*s}\n", INDENT);
+    } else {
+        ly_print(ctx->out, ";\n");
+    }
+}
+
+static void
+ypr_text(struct ypr_ctx *ctx, const char *name, const char *text, int singleline, int closed)
+{
+    const char *s, *t;
+
+    if (singleline) {
+        ly_print(ctx->out, "%*s%s \"", INDENT, name);
+    } else {
+        ly_print(ctx->out, "%*s%s\n", INDENT, name);
+        LEVEL++;
+
+        ly_print(ctx->out, "%*s\"", INDENT);
+    }
+    t = text;
+    while ((s = strchr(t, '\n'))) {
+        ypr_encode(ctx->out, t, s - t);
+        ly_print(ctx->out, "\n");
+        t = s + 1;
+        if (*t != '\n') {
+            ly_print(ctx->out, "%*s ", INDENT);
+        }
+    }
+
+    ypr_encode(ctx->out, t, strlen(t));
+    if (closed) {
+        ly_print(ctx->out, "\";\n");
+    } else {
+        ly_print(ctx->out, "\"");
+    }
+    if (!singleline) {
+        LEVEL--;
+    }
+}
+
+static void
+ypr_parsed_stmt(struct ypr_ctx *ctx, struct lysp_stmt *stmt)
+{
+    struct lysp_stmt *childstmt;
+    const char *s, *t;
+
+    if (stmt->arg) {
+        if (stmt->flags) {
+            ly_print(ctx->out, "%*s%s\n", INDENT, stmt->stmt);
+            LEVEL++;
+            ly_print(ctx->out, "%*s%c", INDENT, (stmt->flags & LYS_DOUBLEQUOTED) ? '\"' : '\'');
+            t = stmt->arg;
+            while ((s = strchr(t, '\n'))) {
+                ypr_encode(ctx->out, t, s - t);
+                ly_print(ctx->out, "\n");
+                t = s + 1;
+                if (*t != '\n') {
+                    ly_print(ctx->out, "%*s ", INDENT);
+                }
+            }
+            LEVEL--;
+            ypr_encode(ctx->out, t, strlen(t));
+            ly_print(ctx->out, "%c%s", (stmt->flags & LYS_DOUBLEQUOTED) ? '\"' : '\'', stmt->child ? " {\n" : ";\n");
+        } else {
+            ly_print(ctx->out, "%*s%s %s%s", INDENT, stmt->stmt, stmt->arg, stmt->child ? " {\n" : ";\n");
+        }
+    } else {
+        ly_print(ctx->out, "%*s%s%s", INDENT, stmt->stmt, stmt->child ? " {\n" : ";\n");
+    }
+
+    if (stmt->child) {
+        LEVEL++;
+        LY_LIST_FOR(stmt->child, childstmt) {
+            ypr_parsed_stmt(ctx, childstmt);
+        }
+        LEVEL--;
+        ly_print(ctx->out, "%*s}\n", INDENT);
+    }
+}
+
+/**
+ * @param[in] count Number of extensions to print, 0 to print them all.
+ */
+static void
+ypr_parsed_extension_instances(struct ypr_ctx *ctx, LYEXT_SUBSTMT substmt, uint8_t substmt_index,
+                               struct lysp_ext_instance *ext, int *flag, unsigned int count)
+{
+    unsigned int u;
+    struct lysp_stmt *stmt;
+
+    if (!count && ext) {
+        count = LY_ARRAY_SIZE(ext);
+    }
+    LY_ARRAY_FOR(ext, u) {
+        if (!count) {
+            break;
+        }
+        if (ext->insubstmt == substmt && ext->insubstmt_index == substmt_index) {
+            ypr_open(ctx->out, flag);
+            if (ext[u].argument) {
+                ly_print(ctx->out, "%*s%s %s%s", INDENT, ext[u].name, ext[u].argument, ext[u].child ? " {\n" : ";\n");
+            } else {
+                ly_print(ctx->out, "%*s%s%s", INDENT, ext[u].name, ext[u].child ? " {\n" : ";\n");
+            }
+
+            if (ext[u].child) {
+                LEVEL++;
+                LY_LIST_FOR(ext[u].child, stmt) {
+                    ypr_parsed_stmt(ctx, stmt);
+                }
+                LEVEL--;
+                ly_print(ctx->out, "%*s}\n", INDENT);
+            }
+        }
+        count--;
+    }
+}
+
+static void
+ypr_parsed_substmt(struct ypr_ctx *ctx, LYEXT_SUBSTMT substmt, uint8_t substmt_index, const char *text, struct lysp_ext_instance *ext)
+{
+    unsigned int u;
+    int extflag = 0;
+
+    if (!text) {
+        /* nothing to print */
+        return;
+    }
+
+    if (ext_substmt_info[substmt].flags & SUBST_FLAG_ID) {
+        ly_print(ctx->out, "%*s%s %s", INDENT, ext_substmt_info[substmt].name, text);
+    } else {
+        ypr_text(ctx, ext_substmt_info[substmt].name, text,
+                 (ext_substmt_info[substmt].flags & SUBST_FLAG_YIN) ? 0 : 1, 0);
+    }
+
+    LEVEL++;
+    LY_ARRAY_FOR(ext, u) {
+        if (ext[u].insubstmt != substmt || ext[u].insubstmt_index != substmt_index) {
+            continue;
+        }
+        ypr_parsed_extension_instances(ctx, substmt, substmt_index, &ext[u], &extflag, 1);
+    }
+    LEVEL--;
+    ypr_close(ctx, extflag);
+}
+
+static void
+ypr_parsed_unsigned(struct ypr_ctx *ctx, LYEXT_SUBSTMT substmt, uint8_t substmt_index, struct lysp_ext_instance *exts,
+                    unsigned int attr_value, int *flag)
+{
+    char *str;
+
+    if (asprintf(&str, "%u", attr_value) == -1) {
+        LOGMEM(ctx->module->ctx);
+        return;
+    }
+    ypr_open(ctx->out, flag);
+    ypr_parsed_substmt(ctx, substmt, substmt_index, str, exts);
+    free(str);
+}
+
+static void
+ypr_parsed_signed(struct ypr_ctx *ctx, LYEXT_SUBSTMT substmt, uint8_t substmt_index, struct lysp_ext_instance *exts,
+                  signed int attr_value, int *flag)
+{
+    char *str;
+
+    if (asprintf(&str, "%d", attr_value) == -1) {
+        LOGMEM(ctx->module->ctx);
+        return;
+    }
+    ypr_open(ctx->out, flag);
+    ypr_parsed_substmt(ctx, substmt, substmt_index, str, exts);
+    free(str);
+}
+
+static void
+ypr_parsed_revision(struct ypr_ctx *ctx, const struct lysp_revision *rev)
+{
+    if (rev->dsc || rev->ref || rev->exts) {
+        ly_print(ctx->out, "%*srevision %s {\n", INDENT, rev->date);
+        LEVEL++;
+        ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, rev->exts, NULL, 0);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, rev->dsc, rev->exts);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, rev->ref, rev->exts);
+        LEVEL--;
+        ly_print(ctx->out, "%*s}\n", INDENT);
+    } else {
+        ly_print(ctx->out, "%*srevision %s;\n", INDENT, rev->date);
+    }
+}
+
+static void
+ypr_parsed_mandatory(struct ypr_ctx *ctx, uint16_t flags, struct lysp_ext_instance *exts, int *flag)
+{
+    if (flags & LYS_MAND_MASK) {
+        ypr_open(ctx->out, flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MANDATORY, 0, (flags & LYS_MAND_TRUE) ? "true" : "false", exts);
+    }
+}
+
+static void
+ypr_parsed_config(struct ypr_ctx *ctx, uint16_t flags, struct lysp_ext_instance *exts, int *flag)
+{
+    if (flags & LYS_CONFIG_MASK) {
+        ypr_open(ctx->out, flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_CONFIG, 0, (flags & LYS_CONFIG_W) ? "true" : "false", exts);
+    }
+}
+
+static void
+ypr_parsed_status(struct ypr_ctx *ctx, uint16_t flags, struct lysp_ext_instance *exts, int *flag)
+{
+    const char *status = NULL;
+
+    if (flags & LYS_STATUS_CURR) {
+        ypr_open(ctx->out, flag);
+        status = "current";
+    } else if (flags & LYS_STATUS_DEPRC) {
+        ypr_open(ctx->out, flag);
+        status = "deprecated";
+    } else if (flags & LYS_STATUS_OBSLT) {
+        ypr_open(ctx->out, flag);
+        status = "obsolete";
+    }
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_STATUS, 0, status, exts);
+}
+
+static void
+ypr_description(struct ypr_ctx *ctx, const char *dsc, struct lysp_ext_instance *exts, int *flag)
+{
+    if (dsc) {
+        ypr_open(ctx->out, flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, dsc, exts);
+    }
+}
+
+static void
+ypr_reference(struct ypr_ctx *ctx, const char *ref, struct lysp_ext_instance *exts, int *flag)
+{
+    if (ref) {
+        ypr_open(ctx->out, flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, ref, exts);
+    }
+}
+
+static void
+ypr_parsed_iffeatures(struct ypr_ctx *ctx, const char **iff, struct lysp_ext_instance *exts, int *flag)
+{
+    unsigned int u;
+    int extflag;
+
+    LY_ARRAY_FOR(iff, u) {
+        ypr_open(ctx->out, flag);
+        extflag = 0;
+
+        ly_print(ctx->out, "%*sif-feature \"%s\"", INDENT, iff[u]);
+
+        /* extensions */
+        LEVEL++;
+        LY_ARRAY_FOR(exts, u) {
+            if (exts[u].insubstmt != LYEXT_SUBSTMT_IFFEATURE || exts[u].insubstmt_index != u) {
+                continue;
+            }
+            ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_IFFEATURE, u, &exts[u], &extflag, 1);
+        }
+        LEVEL--;
+        ypr_close(ctx, extflag);
+    }
+}
+
+static void
+ypr_parsed_extension(struct ypr_ctx *ctx, const struct lysp_ext *ext)
+{
+    int flag = 0, flag2 = 0;
+    unsigned int i;
+
+    ly_print(ctx->out, "%*sextension %s", INDENT, ext->name);
+    LEVEL++;
+
+    if (ext->exts) {
+        ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, ext->exts, &flag, 0);
+    }
+
+    if (ext->argument) {
+        ypr_open(ctx->out, &flag);
+        ly_print(ctx->out, "%*sargument %s", INDENT, ext->argument);
+        if (ext->exts) {
+            LEVEL++;
+            i = -1;
+            while ((i = lysp_ext_instance_iter(ext->exts, i + 1, LYEXT_SUBSTMT_ARGUMENT)) != LY_ARRAY_SIZE(ext->exts)) {
+                ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_ARGUMENT, 0, &ext->exts[i], &flag2, 1);
+            }
+            LEVEL--;
+        }
+        if ((ext->flags & LYS_YINELEM_MASK) ||
+                (ext->exts && lysp_ext_instance_iter(ext->exts, 0, LYEXT_SUBSTMT_YINELEM) != LY_ARRAY_SIZE(ext->exts))) {
+            ypr_open(ctx->out, &flag2);
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_YINELEM, 0, (ext->flags & LYS_YINELEM_TRUE) ? "true" : "false", ext->exts);
+        }
+        ypr_close(ctx, flag2);
+    }
+
+    ypr_parsed_status(ctx, ext->flags, ext->exts, &flag);
+    ypr_description(ctx, ext->dsc, ext->exts, &flag);
+    ypr_reference(ctx, ext->ref, ext->exts, &flag);
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_feature(struct ypr_ctx *ctx, const struct lysp_feature *feat)
+{
+    int flag = 0;
+
+    ly_print(ctx->out, "\n%*sfeature %s", INDENT, feat->name);
+    LEVEL++;
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, feat->exts, &flag, 0);
+    ypr_parsed_iffeatures(ctx, feat->iffeatures, feat->exts, &flag);
+    ypr_parsed_status(ctx, feat->flags, feat->exts, &flag);
+    ypr_description(ctx, feat->dsc, feat->exts, &flag);
+    ypr_reference(ctx, feat->ref, feat->exts, &flag);
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_identity(struct ypr_ctx *ctx, const struct lysp_ident *ident)
+{
+    int flag = 0;
+    unsigned int u;
+
+    ly_print(ctx->out, "\n%*sidentity %s", INDENT, ident->name);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, ident->exts, &flag, 0);
+    ypr_parsed_iffeatures(ctx, ident->iffeatures, ident->exts, &flag);
+
+    LY_ARRAY_FOR(ident->bases, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_BASE, u, ident->bases[u], ident->exts);
+    }
+
+    ypr_parsed_status(ctx, ident->flags, ident->exts, &flag);
+    ypr_description(ctx, ident->dsc, ident->exts, &flag);
+    ypr_reference(ctx, ident->ref, ident->exts, &flag);
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_restr(struct ypr_ctx *ctx, const struct lysp_restr *restr, const char *name, int *flag)
+{
+    int inner_flag = 0;
+
+    if (!restr) {
+        return;
+    }
+
+    ypr_open(ctx->out, flag);
+    ly_print(ctx->out, "%*s%s \"", INDENT, name);
+    ypr_encode(ctx->out, (restr->arg[0] != 0x15 && restr->arg[0] != 0x06) ? restr->arg : &restr->arg[1], -1);
+    ly_print(ctx->out, "\"");
+
+    LEVEL++;
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, restr->exts, &inner_flag, 0);
+    if (restr->arg[0] == 0x15) {
+        /* special byte value in pattern's expression: 0x15 - invert-match, 0x06 - match */
+        ypr_open(ctx->out, &inner_flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MODIFIER, 0, "invert-match", restr->exts);
+    }
+    if (restr->emsg) {
+        ypr_open(ctx->out, &inner_flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_ERRMSG, 0, restr->emsg, restr->exts);
+    }
+    if (restr->eapptag) {
+        ypr_open(ctx->out, &inner_flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_ERRTAG, 0, restr->eapptag, restr->exts);
+    }
+    if (restr->dsc != NULL) {
+        ypr_open(ctx->out, &inner_flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, restr->dsc,restr->exts);
+    }
+    if (restr->ref != NULL) {
+        ypr_open(ctx->out, &inner_flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, restr->ref, restr->exts);
+    }
+    LEVEL--;
+    ypr_close(ctx, inner_flag);
+}
+
+static void
+ypr_parsed_when(struct ypr_ctx *ctx, struct lysp_when *when, int *flag)
+{
+    int inner_flag = 0;
+
+    if (!when) {
+        return;
+    }
+    ypr_open(ctx->out, flag);
+
+    ly_print(ctx->out, "%*swhen \"", INDENT);
+    ypr_encode(ctx->out, when->cond, -1);
+    ly_print(ctx->out, "\"");
+
+    LEVEL++;
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, when->exts, &inner_flag, 0);
+    ypr_description(ctx, when->dsc, when->exts, &inner_flag);
+    ypr_reference(ctx, when->ref, when->exts, &inner_flag);
+    LEVEL--;
+    ypr_close(ctx, inner_flag);
+}
+
+static void
+ypr_parsed_enum(struct ypr_ctx *ctx, const struct lysp_type_enum *items, LY_DATA_TYPE type, int *flag)
+{
+    unsigned int u;
+    int inner_flag;
+
+    LY_ARRAY_FOR(items, u) {
+        ypr_open(ctx->out, flag);
+        ly_print(ctx->out, "%*s%s \"", INDENT, type == LY_TYPE_BITS ? "bit" : "enum");
+        ypr_encode(ctx->out, items[u].name, -1);
+        ly_print(ctx->out, "\"");
+        inner_flag = 0;
+        LEVEL++;
+        ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, items[u].exts, &inner_flag, 0);
+        ypr_parsed_iffeatures(ctx, items[u].iffeatures, items[u].exts, &inner_flag);
+        if (items[u].flags & LYS_SET_VALUE) {
+            if (type == LY_TYPE_BITS) {
+                ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_POSITION, 0, items[u].exts, items[u].value, &inner_flag);
+            } else { /* LY_TYPE_ENUM */
+                ypr_parsed_signed(ctx, LYEXT_SUBSTMT_VALUE, 0, items[u].exts, items[u].value, &inner_flag);
+            }
+        }
+        ypr_parsed_status(ctx, items[u].flags, items[u].exts, &inner_flag);
+        ypr_description(ctx, items[u].dsc, items[u].exts, &inner_flag);
+        ypr_reference(ctx, items[u].ref, items[u].exts, &inner_flag);
+        LEVEL--;
+        ypr_close(ctx, inner_flag);
+    }
+}
+
+static void
+ypr_parsed_type(struct ypr_ctx *ctx, const struct lysp_type *type)
+{
+    unsigned int u;
+    int flag = 0;
+
+    ly_print(ctx->out, "%*stype %s", INDENT, type->name);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, type->exts, &flag, 0);
+
+    ypr_parsed_restr(ctx, type->range, "range", &flag);
+    ypr_parsed_restr(ctx, type->length, "length", &flag);
+    LY_ARRAY_FOR(type->patterns, u) {
+        ypr_parsed_restr(ctx, &type->patterns[u], "pattern", &flag);
+    }
+    ypr_parsed_enum(ctx, type->bits, LY_TYPE_BITS, &flag);
+    ypr_parsed_enum(ctx, type->enums, LY_TYPE_ENUM, &flag);
+
+    if (type->path) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_PATH, 0, type->path, type->exts);
+    }
+    if (type->flags & LYS_SET_REQINST) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REQINSTANCE, 0, type->require_instance ? "true" : "false", type->exts);
+    }
+    if (type->flags & LYS_SET_FRDIGITS) {
+        ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_FRACDIGITS, 0, type->exts, type->fraction_digits, &flag);
+    }
+    LY_ARRAY_FOR(type->bases, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_BASE, u, type->bases[u], type->exts);
+    }
+    LY_ARRAY_FOR(type->types, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_type(ctx, &type->types[u]);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_typedef(struct ypr_ctx *ctx, const struct lysp_tpdf *tpdf)
+{
+    ly_print(ctx->out, "\n%*stypedef %s {\n", INDENT, tpdf->name);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, tpdf->exts, NULL, 0);
+
+    ypr_parsed_type(ctx, &tpdf->type);
+
+    if (tpdf->units) {
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, tpdf->units, tpdf->exts);
+    }
+    if (tpdf->dflt) {
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, 0, tpdf->dflt, tpdf->exts);
+    }
+
+    ypr_parsed_status(ctx, tpdf->flags, tpdf->exts, NULL);
+    ypr_description(ctx, tpdf->dsc, tpdf->exts, NULL);
+    ypr_reference(ctx, tpdf->ref, tpdf->exts, NULL);
+
+    LEVEL--;
+    ly_print(ctx->out, "%*s}\n", INDENT);
+}
+
+static void ypr_parsed_node(struct ypr_ctx *ctx, const struct lysp_node *node);
+static void ypr_parsed_action(struct ypr_ctx *ctx, const struct lysp_action *action);
+
+static void
+ypr_parsed_grouping(struct ypr_ctx *ctx, const struct lysp_grp *grp)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node *data;
+
+    ly_print(ctx->out, "\n%*sgrouping %s", INDENT, grp->name);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, grp->exts, &flag, 0);
+    ypr_parsed_status(ctx, grp->flags, grp->exts, NULL);
+    ypr_description(ctx, grp->dsc, grp->exts, NULL);
+    ypr_reference(ctx, grp->ref, grp->exts, NULL);
+
+    LY_ARRAY_FOR(grp->typedefs, u) {
+        ypr_parsed_typedef(ctx, &grp->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(grp->groupings, u) {
+        ypr_parsed_grouping(ctx, &grp->groupings[u]);
+    }
+
+    LY_LIST_FOR(grp->data, data) {
+        ypr_parsed_node(ctx, data);
+    }
+
+    LY_ARRAY_FOR(grp->actions, u) {
+        ypr_parsed_action(ctx, &grp->actions[u]);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_inout(struct ypr_ctx *ctx, const struct lysp_action_inout *inout, int *flag)
+{
+    unsigned int u;
+    struct lysp_node *data;
+
+    if (!inout->nodetype) {
+        /* nodetype not set -> input/output is empty */
+        return;
+    }
+    ypr_open(ctx->out, flag);
+
+    ly_print(ctx->out, "\n%*s%s {\n", INDENT, (inout->nodetype == LYS_INPUT ? "input" : "output"));
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, inout->exts, NULL, 0);
+    LY_ARRAY_FOR(inout->musts, u) {
+        ypr_parsed_restr(ctx, &inout->musts[u], "must", NULL);
+    }
+    LY_ARRAY_FOR(inout->typedefs, u) {
+        ypr_parsed_typedef(ctx, &inout->typedefs[u]);
+    }
+    LY_ARRAY_FOR(inout->groupings, u) {
+        ypr_parsed_grouping(ctx, &inout->groupings[u]);
+    }
+
+    LY_LIST_FOR(inout->data, data) {
+        ypr_parsed_node(ctx, data);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, 1);
+}
+
+static void
+ypr_parsed_notification(struct ypr_ctx *ctx, const struct lysp_notif *notif)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node *data;
+
+    ly_print(ctx->out, "%*snotification %s", INDENT, notif->name);
+
+    LEVEL++;
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, notif->exts, &flag, 0);
+    ypr_parsed_iffeatures(ctx, notif->iffeatures, notif->exts, &flag);
+
+    LY_ARRAY_FOR(notif->musts, u) {
+        ypr_parsed_restr(ctx, &notif->musts[u], "must", &flag);
+    }
+    ypr_parsed_status(ctx, notif->flags, notif->exts, &flag);
+    ypr_description(ctx, notif->dsc, notif->exts, &flag);
+    ypr_reference(ctx, notif->ref, notif->exts, &flag);
+
+    LY_ARRAY_FOR(notif->typedefs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_typedef(ctx, &notif->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(notif->groupings, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_grouping(ctx, &notif->groupings[u]);
+    }
+
+    LY_LIST_FOR(notif->data, data) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_node(ctx, data);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_action(struct ypr_ctx *ctx, const struct lysp_action *action)
+{
+    unsigned int u;
+    int flag = 0;
+
+    ly_print(ctx->out, "%*s%s %s", INDENT, action->parent ? "action" : "rpc", action->name);
+
+    LEVEL++;
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, action->exts, &flag, 0);
+    ypr_parsed_iffeatures(ctx, action->iffeatures, action->exts, &flag);
+    ypr_parsed_status(ctx, action->flags, action->exts, &flag);
+    ypr_description(ctx, action->dsc, action->exts, &flag);
+    ypr_reference(ctx, action->ref, action->exts, &flag);
+
+    LY_ARRAY_FOR(action->typedefs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_typedef(ctx, &action->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(action->groupings, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_grouping(ctx, &action->groupings[u]);
+    }
+
+    ypr_parsed_inout(ctx, &action->input, &flag);
+    ypr_parsed_inout(ctx, &action->output, &flag);
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_node_common1(struct ypr_ctx *ctx, const struct lysp_node *node, int *flag)
+{
+    ly_print(ctx->out, "\n%*s%s %s%s", INDENT, lys_nodetype2str(node->nodetype), node->name, flag ? "" : " {\n");
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, node->exts, flag, 0);
+    ypr_parsed_when(ctx, node->when, flag);
+    ypr_parsed_iffeatures(ctx, node->iffeatures, node->exts, flag);
+}
+
+static void
+ypr_parsed_node_common2(struct ypr_ctx *ctx, const struct lysp_node *node, int *flag)
+{
+    ypr_parsed_config(ctx, node->flags, node->exts, flag);
+    if (node->nodetype & (LYS_CHOICE | LYS_LEAF | LYS_ANYDATA)) {
+        ypr_parsed_mandatory(ctx, node->flags, node->exts, flag);
+    }
+    ypr_parsed_status(ctx, node->flags, node->exts, flag);
+    ypr_description(ctx, node->dsc, node->exts, flag);
+    ypr_reference(ctx, node->ref, node->exts, flag);
+
+}
+
+static void
+ypr_parsed_container(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node *child;
+    struct lysp_node_container *cont = (struct lysp_node_container *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+
+    LY_ARRAY_FOR(cont->musts, u) {
+        ypr_parsed_restr(ctx, &cont->musts[u], "must", &flag);
+    }
+    if (cont->presence) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_PRESENCE, 0, cont->presence, cont->exts);
+    }
+
+    ypr_parsed_node_common2(ctx, node, &flag);
+
+    LY_ARRAY_FOR(cont->typedefs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_typedef(ctx, &cont->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(cont->groupings, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_grouping(ctx, &cont->groupings[u]);
+    }
+
+    LY_LIST_FOR(cont->child, child) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_node(ctx, child);
+    }
+
+    LY_ARRAY_FOR(cont->actions, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_action(ctx, &cont->actions[u]);
+    }
+
+    LY_ARRAY_FOR(cont->notifs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_notification(ctx, &cont->notifs[u]);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_choice(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    int flag = 0;
+    struct lysp_node *child;
+    struct lysp_node_choice *choice = (struct lysp_node_choice *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+
+    if (choice->dflt) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, 0, choice->dflt, choice->exts);
+    }
+
+    ypr_parsed_node_common2(ctx, node, &flag);
+
+    LY_LIST_FOR(choice->child, child) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_node(ctx, child);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_leaf(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    struct lysp_node_leaf *leaf = (struct lysp_node_leaf *)node;
+
+    ypr_parsed_node_common1(ctx, node, NULL);
+
+    ypr_parsed_type(ctx, &leaf->type);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, leaf->units, leaf->exts);
+    LY_ARRAY_FOR(leaf->musts, u) {
+        ypr_parsed_restr(ctx, &leaf->musts[u], "must", NULL);
+    }
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, 0, leaf->dflt, leaf->exts);
+
+    ypr_parsed_node_common2(ctx, node, NULL);
+
+    LEVEL--;
+    ly_print(ctx->out, "%*s}\n", INDENT);
+}
+
+static void
+ypr_parsed_leaflist(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    struct lysp_node_leaflist *llist = (struct lysp_node_leaflist *)node;
+
+    ypr_parsed_node_common1(ctx, node, NULL);
+
+    ypr_parsed_type(ctx, &llist->type);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, llist->units, llist->exts);
+    LY_ARRAY_FOR(llist->musts, u) {
+        ypr_parsed_restr(ctx, &llist->musts[u], "must", NULL);
+    }
+    LY_ARRAY_FOR(llist->dflts, u) {
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, u, llist->dflts[u], llist->exts);
+    }
+
+    ypr_parsed_config(ctx, node->flags, node->exts, NULL);
+
+    if (llist->flags & LYS_SET_MIN) {
+        ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MIN, 0, llist->exts, llist->min, NULL);
+    }
+    if (llist->flags & LYS_SET_MAX) {
+        if (llist->max) {
+            ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MAX, 0, llist->exts, llist->max, NULL);
+        } else {
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MAX, 0, "unbounded", llist->exts);
+        }
+    }
+
+    if (llist->flags & LYS_ORDBY_MASK) {
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_ORDEREDBY, 0, (llist->flags & LYS_ORDBY_USER) ? "user" : "system", llist->exts);
+    }
+
+    ypr_parsed_status(ctx, node->flags, node->exts, NULL);
+    ypr_description(ctx, node->dsc, node->exts, NULL);
+    ypr_reference(ctx, node->ref, node->exts, NULL);
+
+    LEVEL--;
+    ly_print(ctx->out, "%*s}\n", INDENT);
+}
+
+static void
+ypr_parsed_list(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node *child;
+    struct lysp_node_list *list = (struct lysp_node_list *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+
+    LY_ARRAY_FOR(list->musts, u) {
+        ypr_parsed_restr(ctx, &list->musts[u], "must", NULL);
+    }
+    if (list->key) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_KEY, 0, list->key, list->exts);
+    }
+    LY_ARRAY_FOR(list->uniques, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNIQUE, u, list->uniques[u], list->exts);
+    }
+
+    ypr_parsed_config(ctx, node->flags, node->exts, NULL);
+
+    if (list->flags & LYS_SET_MIN) {
+        ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MIN, 0, list->exts, list->min, NULL);
+    }
+    if (list->flags & LYS_SET_MAX) {
+        if (list->max) {
+            ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MAX, 0, list->exts, list->max, NULL);
+        } else {
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MAX, 0, "unbounded", list->exts);
+        }
+    }
+
+    if (list->flags & LYS_ORDBY_MASK) {
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_ORDEREDBY, 0, (list->flags & LYS_ORDBY_USER) ? "user" : "system", list->exts);
+    }
+
+    ypr_parsed_status(ctx, node->flags, node->exts, NULL);
+    ypr_description(ctx, node->dsc, node->exts, NULL);
+    ypr_reference(ctx, node->ref, node->exts, NULL);
+
+    LY_ARRAY_FOR(list->typedefs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_typedef(ctx, &list->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(list->groupings, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_grouping(ctx, &list->groupings[u]);
+    }
+
+    LY_LIST_FOR(list->child, child) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_node(ctx, child);
+    }
+
+    LY_ARRAY_FOR(list->actions, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_action(ctx, &list->actions[u]);
+    }
+
+    LY_ARRAY_FOR(list->notifs, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_notification(ctx, &list->notifs[u]);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_refine(struct ypr_ctx *ctx, struct lysp_refine *refine)
+{
+    unsigned int u;
+    int flag = 0;
+
+    ly_print(ctx->out, "%*srefine \"%s\"", INDENT, refine->nodeid);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, refine->exts, &flag, 0);
+    ypr_parsed_iffeatures(ctx, refine->iffeatures, refine->exts, &flag);
+
+    LY_ARRAY_FOR(refine->musts, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_restr(ctx, &refine->musts[u], "must", NULL);
+    }
+
+    if (refine->presence) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_PRESENCE, 0, refine->presence, refine->exts);
+    }
+
+    LY_ARRAY_FOR(refine->dflts, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, u, refine->dflts[u], refine->exts);
+    }
+
+    ypr_parsed_config(ctx, refine->flags, refine->exts, &flag);
+    ypr_parsed_mandatory(ctx, refine->flags, refine->exts, &flag);
+
+    if (refine->flags & LYS_SET_MIN) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MIN, 0, refine->exts, refine->min, NULL);
+    }
+    if (refine->flags & LYS_SET_MAX) {
+        ypr_open(ctx->out, &flag);
+        if (refine->max) {
+            ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MAX, 0, refine->exts, refine->max, NULL);
+        } else {
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MAX, 0, "unbounded", refine->exts);
+        }
+    }
+
+    ypr_description(ctx, refine->dsc, refine->exts, &flag);
+    ypr_reference(ctx, refine->ref, refine->exts, &flag);
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_augment(struct ypr_ctx *ctx, const struct lysp_augment *aug)
+{
+    unsigned int u;
+    struct lysp_node *child;
+
+    ly_print(ctx->out, "%*saugment \"%s\" {\n", INDENT, aug->nodeid);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, aug->exts, NULL, 0);
+    ypr_parsed_when(ctx, aug->when, NULL);
+    ypr_parsed_iffeatures(ctx, aug->iffeatures, aug->exts, NULL);
+    ypr_parsed_status(ctx, aug->flags, aug->exts, NULL);
+    ypr_description(ctx, aug->dsc, aug->exts, NULL);
+    ypr_reference(ctx, aug->ref, aug->exts, NULL);
+
+    LY_LIST_FOR(aug->child, child) {
+        ypr_parsed_node(ctx, child);
+    }
+
+    LY_ARRAY_FOR(aug->actions, u) {
+        ypr_parsed_action(ctx, &aug->actions[u]);
+    }
+
+    LY_ARRAY_FOR(aug->notifs, u) {
+        ypr_parsed_notification(ctx, &aug->notifs[u]);
+    }
+
+    LEVEL--;
+    ly_print(ctx->out, "%*s}\n", INDENT);
+}
+
+
+static void
+ypr_parsed_uses(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node_uses *uses = (struct lysp_node_uses *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+    ypr_parsed_node_common2(ctx, node, &flag);
+
+    LY_ARRAY_FOR(uses->refines, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_refine(ctx, &uses->refines[u]);
+    }
+
+    LY_ARRAY_FOR(uses->augments, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_augment(ctx, &uses->augments[u]);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_anydata(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    unsigned int u;
+    int flag = 0;
+    struct lysp_node_anydata *any = (struct lysp_node_anydata *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+
+    LY_ARRAY_FOR(any->musts, u) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_restr(ctx, &any->musts[u], "must", NULL);
+    }
+
+    ypr_parsed_node_common2(ctx, node, &flag);
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_case(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    int flag = 0;
+    struct lysp_node *child;
+    struct lysp_node_case *cas = (struct lysp_node_case *)node;
+
+    ypr_parsed_node_common1(ctx, node, &flag);
+    ypr_parsed_node_common2(ctx, node, &flag);
+
+    LY_LIST_FOR(cas->child, child) {
+        ypr_open(ctx->out, &flag);
+        ypr_parsed_node(ctx, child);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, flag);
+}
+
+static void
+ypr_parsed_node(struct ypr_ctx *ctx, const struct lysp_node *node)
+{
+    switch (node->nodetype) {
+    case LYS_CONTAINER:
+        ypr_parsed_container(ctx, node);
+        break;
+    case LYS_CHOICE:
+        ypr_parsed_choice(ctx, node);
+        break;
+    case LYS_LEAF:
+        ypr_parsed_leaf(ctx, node);
+        break;
+    case LYS_LEAFLIST:
+        ypr_parsed_leaflist(ctx, node);
+        break;
+    case LYS_LIST:
+        ypr_parsed_list(ctx, node);
+        break;
+    case LYS_USES:
+        ypr_parsed_uses(ctx, node);
+        break;
+    case LYS_ANYXML:
+    case LYS_ANYDATA:
+        ypr_parsed_anydata(ctx, node);
+        break;
+    case LYS_CASE:
+        ypr_parsed_case(ctx, node);
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+ypr_parsed_deviation(struct ypr_ctx *ctx, const struct lysp_deviation *deviation)
+{
+    unsigned int u, v;
+    struct lysp_deviate_add *add;
+    struct lysp_deviate_rpl *rpl;
+    struct lysp_deviate_del *del;
+
+    ly_print(ctx->out, "%*sdeviation \"%s\" {\n", INDENT, deviation->nodeid);
+    LEVEL++;
+
+    ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, deviation->exts, NULL, 0);
+    ypr_description(ctx, deviation->dsc, deviation->exts, NULL);
+    ypr_reference(ctx, deviation->ref, deviation->exts, NULL);
+
+    LY_ARRAY_FOR(deviation->deviates, u) {
+        ly_print(ctx->out, "%*sdeviate ", INDENT);
+        if (deviation->deviates[u].mod == LYS_DEV_NOT_SUPPORTED) {
+            if (deviation->deviates[u].exts) {
+                ly_print(ctx->out, "not-supported {\n");
+                LEVEL++;
+
+                ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, deviation->deviates[u].exts, NULL, 0);
+            } else {
+                ly_print(ctx->out, "not-supported;\n");
+                continue;
+            }
+        } else if (deviation->deviates[u].mod == LYS_DEV_ADD) {
+            add = (struct lysp_deviate_add*)&deviation->deviates[u];
+            ly_print(ctx->out, "add {\n");
+            LEVEL++;
+
+            ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, add->exts, NULL, 0);
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, add->units, add->exts);
+            LY_ARRAY_FOR(add->musts, v) {
+                ypr_parsed_restr(ctx, &add->musts[v], "must", NULL);
+            }
+            LY_ARRAY_FOR(add->uniques, v) {
+                ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNIQUE, v, add->uniques[v], add->exts);
+            }
+            LY_ARRAY_FOR(add->dflts, v) {
+                ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, v, add->dflts[v], add->exts);
+            }
+            ypr_parsed_config(ctx, add->flags, add->exts, NULL);
+            ypr_parsed_mandatory(ctx, add->flags, add->exts, NULL);
+            if (add->flags & LYS_SET_MIN) {
+                ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MIN, 0, add->exts, add->min, NULL);
+            }
+            if (add->flags & LYS_SET_MAX) {
+                if (add->max) {
+                    ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MAX, 0, add->exts, add->max, NULL);
+                } else {
+                    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MAX, 0, "unbounded", add->exts);
+                }
+            }
+        } else if (deviation->deviates[u].mod == LYS_DEV_REPLACE) {
+            rpl = (struct lysp_deviate_rpl*)&deviation->deviates[u];
+            ly_print(ctx->out, "replace {\n");
+            LEVEL++;
+
+            ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, rpl->exts, NULL, 0);
+            if (rpl->type) {
+                ypr_parsed_type(ctx, rpl->type);
+            }
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, rpl->units, rpl->exts);
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, v, rpl->dflt, rpl->exts);
+            ypr_parsed_config(ctx, rpl->flags, rpl->exts, NULL);
+            ypr_parsed_mandatory(ctx, rpl->flags, rpl->exts, NULL);
+            if (rpl->flags & LYS_SET_MIN) {
+                ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MIN, 0, rpl->exts, rpl->min, NULL);
+            }
+            if (rpl->flags & LYS_SET_MAX) {
+                if (rpl->max) {
+                    ypr_parsed_unsigned(ctx, LYEXT_SUBSTMT_MAX, 0, rpl->exts, rpl->max, NULL);
+                } else {
+                    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_MAX, 0, "unbounded", rpl->exts);
+                }
+            }
+        } else if (deviation->deviates[u].mod == LYS_DEV_DELETE) {
+            del = (struct lysp_deviate_del*)&deviation->deviates[u];
+            ly_print(ctx->out, "delete {\n");
+            LEVEL++;
+
+            ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, del->exts, NULL, 0);
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNITS, 0, del->units, del->exts);
+            LY_ARRAY_FOR(del->musts, v) {
+                ypr_parsed_restr(ctx, &del->musts[v], "must", NULL);
+            }
+            LY_ARRAY_FOR(del->uniques, v) {
+                ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_UNIQUE, v, del->uniques[v], del->exts);
+            }
+            LY_ARRAY_FOR(del->dflts, v) {
+                ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DEFAULT, v, del->dflts[v], del->exts);
+            }
+        }
+
+        LEVEL--;
+        ypr_close(ctx, 1);
+    }
+
+    LEVEL--;
+    ypr_close(ctx, 1);
+}
+
+LY_ERR
+yang_print_parsed(struct lyout *out, const struct lys_module *module)
+{
+    unsigned int u;
+    struct lysp_node *data;
+    struct lysp_module *modp = module->parsed;
+    struct ypr_ctx ctx_ = {.out = out, .level = 0, .module = module}, *ctx = &ctx_;
+
+    ly_print(ctx->out, "%*smodule %s {\n", INDENT, module->name);
+    LEVEL++;
+
+    /* module-header-stmts */
+    if (module->version) {
+        if (module->version) {
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_VERSION, 0, module->version == LYS_VERSION_1_1 ? "1.1" : "1", modp->exts);
+        }
+    }
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_NAMESPACE, 0, module->ns, modp->exts);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_PREFIX, 0, module->prefix, modp->exts);
+
+    /* linkage-stmts */
+    LY_ARRAY_FOR(modp->imports, u) {
+        ly_print(out, "\n%*simport %s {\n", INDENT, modp->imports[u].module->name);
+        LEVEL++;
+        ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, modp->imports[u].exts, NULL, 0);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_PREFIX, 0, modp->imports[u].prefix, modp->imports[u].exts);
+        if (modp->imports[u].rev[0]) {
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REVISIONDATE, 0, modp->imports[u].rev, modp->imports[u].exts);
+        }
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, modp->imports[u].dsc, modp->imports[u].exts);
+        ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, modp->imports[u].ref, modp->imports[u].exts);
+        LEVEL--;
+        ly_print(out, "%*s}\n", INDENT);
+    }
+    LY_ARRAY_FOR(modp->includes, u) {
+        if (modp->includes[u].rev[0] || modp->includes[u].dsc || modp->includes[u].ref || modp->includes[u].exts) {
+            ly_print(out, "\n%*sinclude %s {\n", INDENT, modp->includes[u].submodule->name);
+            LEVEL++;
+            ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, modp->includes[u].exts, NULL, 0);
+            if (modp->includes[u].rev[0]) {
+                ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REVISIONDATE, 0, modp->includes[u].rev, modp->includes[u].exts);
+            }
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, modp->includes[u].dsc, modp->includes[u].exts);
+            ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, modp->includes[u].ref, modp->includes[u].exts);
+            LEVEL--;
+            ly_print(out, "%*s}\n", INDENT);
+        } else {
+            ly_print(out, "\n%*sinclude \"%s\";\n", INDENT, modp->includes[u].submodule->name);
+        }
+    }
+
+    /* meta-stmts */
+    if (module->org || module->contact || module->dsc || module->ref) {
+        ly_print(out, "\n");
+    }
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_ORGANIZATION, 0, module->org, modp->exts);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_CONTACT, 0, module->contact, modp->exts);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_DESCRIPTION, 0, module->dsc, modp->exts);
+    ypr_parsed_substmt(ctx, LYEXT_SUBSTMT_REFERENCE, 0, module->ref, modp->exts);
+
+    /* revision-stmts */
+    if (modp->revs) {
+        ly_print(out, "\n");
+    }
+    LY_ARRAY_FOR(modp->revs, u) {
+        ypr_parsed_revision(ctx, &modp->revs[u]);
+    }
+    /* body-stmts */
+    LY_ARRAY_FOR(modp->extensions, u) {
+        ly_print(out, "\n");
+        ypr_parsed_extension(ctx, &modp->extensions[u]);
+    }
+    if (modp->exts) {
+        ly_print(out, "\n");
+        ypr_parsed_extension_instances(ctx, LYEXT_SUBSTMT_SELF, 0, module->parsed->exts, NULL, 0);
+    }
+
+    LY_ARRAY_FOR(modp->features, u) {
+        ypr_parsed_feature(ctx, &modp->features[u]);
+    }
+
+    LY_ARRAY_FOR(modp->identities, u) {
+        ypr_parsed_identity(ctx, &modp->identities[u]);
+    }
+
+    LY_ARRAY_FOR(modp->typedefs, u) {
+        ypr_parsed_typedef(ctx, &modp->typedefs[u]);
+    }
+
+    LY_ARRAY_FOR(modp->groupings, u) {
+        ypr_parsed_grouping(ctx, &modp->groupings[u]);
+    }
+
+    LY_LIST_FOR(modp->data, data) {
+        ypr_parsed_node(ctx, data);
+    }
+
+    LY_ARRAY_FOR(modp->augments, u) {
+        ypr_parsed_augment(ctx, &modp->augments[u]);
+    }
+
+    LY_ARRAY_FOR(modp->rpcs, u) {
+        ypr_parsed_action(ctx, &modp->rpcs[u]);
+    }
+
+    LY_ARRAY_FOR(modp->notifs, u) {
+        ypr_parsed_notification(ctx, &modp->notifs[u]);
+    }
+
+    LY_ARRAY_FOR(modp->deviations, u) {
+        ypr_parsed_deviation(ctx, &modp->deviations[u]);
+    }
+
+    LEVEL--;
+    ly_print(out, "%*s}\n", INDENT);
+    ly_print_flush(out);
+
+    return LY_SUCCESS;
+}
+
+LY_ERR
+yang_print_compiled(struct lyout *out, const struct lys_module *module)
+{
+    (void) out;
+    (void) module;
+
+    return LY_SUCCESS;
+}
diff --git a/src/tree_schema.h b/src/tree_schema.h
index f53499b..5952a0d 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -17,6 +17,7 @@
 
 #include <pcre.h>
 #include <stdint.h>
+#include <stdio.h>
 
 #include "extensions.h"
 
@@ -142,6 +143,8 @@
     LYS_OUT_UNKNOWN = 0, /**< unknown format, used as return value in case of error */
     LYS_OUT_YANG = 1,    /**< YANG schema output format */
     LYS_OUT_YIN = 2,     /**< YIN schema output format */
+    LYS_OUT_YANG_COMPILED, /**< YANG schema output format of the compiled schema tree */
+
     LYS_OUT_TREE,        /**< Tree schema output format, for more information see the [printers](@ref howtoschemasprinters) page */
     LYS_OUT_INFO,        /**< Info schema output format, for more information see the [printers](@ref howtoschemasprinters) page */
     LYS_OUT_JSON,        /**< JSON schema output format, reflecting YIN format with conversion of attributes to object's members */
@@ -160,7 +163,9 @@
 
 #define LYS_CASE 0x0040           /**< case statement node */
 #define LYS_USES 0x0080           /**< uses statement node */
-#define LYS_INOUT 0x200
+#define LYS_INPUT 0x100
+#define LYS_OUTPUT 0x200
+#define LYS_INOUT 0x300
 #define LYS_ACTION 0x400          /**< RPC or action */
 #define LYS_NOTIF 0x800
 #define LYS_GROUPING 0x1000
@@ -245,6 +250,7 @@
     const char *arg;                 /**< statement's argument */
     struct lysp_stmt *next;          /**< link to the next statement */
     struct lysp_stmt *child;         /**< list of the statement's substatements (linked list) */
+    uint16_t flags;
 };
 
 /**
@@ -494,48 +500,50 @@
  *
  *     1 - container    6 - anydata/anyxml    11 - output       16 - grouping   21 - enum
  *     2 - choice       7 - case              12 - feature      17 - uses       22 - type
- *     3 - leaf         8 - notification      13 - identity     18 - refine
+ *     3 - leaf         8 - notification      13 - identity     18 - refine     23 - stmt
  *     4 - leaflist     9 - rpc               14 - extension    19 - augment
  *     5 - list        10 - input             15 - typedef      20 - deviate
  *
- *                                             1 1 1 1 1 1 1 1 1 1 2 2 2
- *     bit name              1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
- *     ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       1 LYS_CONFIG_W     |x|x|x|x|x|x|x| | | | | | | | | | |x| |x| | |
- *         LYS_SET_BASE     | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       2 LYS_CONFIG_R     |x|x|x|x|x|x|x| | | | | | | | | | |x| |x| | |
- *         LYS_SET_BIT      | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       3 LYS_STATUS_CURR  |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| |
- *         LYS_SET_ENUM     | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       4 LYS_STATUS_DEPRC |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| |
- *         LYS_SET_FRDIGITS | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       5 LYS_STATUS_OBSLT |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| |
- *         LYS_SET_LENGTH   | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       6 LYS_MAND_TRUE    | |x|x| | |x| | | | | | | | | | | |x| |x| | |
- *         LYS_SET_PATH     | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       7 LYS_MAND_FALSE   | |x|x| | |x| | | | | | | | | | | |x| |x| | |
- *         LYS_ORDBY_USER   | | | |x|x| | | | | | | | | | | | | | | | | |
- *         LYS_SET_PATTERN  | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       8 LYS_ORDBY_SYSTEM | | | |x|x| | | | | | | | | | | | | | | | | |
- *         LYS_YINELEM_TRUE | | | | | | | | | | | | | |x| | | | | | | | |
- *         LYS_SET_RANGE    | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *       9 LYS_YINELEM_FALSE| | | | | | | | | | | | | |x| | | | | | | | |
- *         LYS_SET_TYPE     | | | | | | | | | | | | | | | | | | | | | |x|
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *      10 LYS_SET_VALUE    | | | | | | | | | | | | | | | | | | | | |x| |
- *         LYS_SET_REQINST  | | | | | | | | | | | | | | | | | | | | | |x|
- *         LYS_SET_MIN      | | | |x|x| | | | | | | | | | | | |x| |x| | |
- *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *      11 LYS_SET_MAX      | | | |x|x| | | | | | | | | | | | |x| |x| | |
- *     ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *                                             1 1 1 1 1 1 1 1 1 1 2 2 2 2
+ *     bit name              1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+ *     ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       1 LYS_CONFIG_W     |x|x|x|x|x|x|x| | | | | | | | | | |x| |x| | | |
+ *         LYS_SET_BASE     | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       2 LYS_CONFIG_R     |x|x|x|x|x|x|x| | | | | | | | | | |x| |x| | | |
+ *         LYS_SET_BIT      | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       3 LYS_STATUS_CURR  |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | |
+ *         LYS_SET_ENUM     | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       4 LYS_STATUS_DEPRC |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | |
+ *         LYS_SET_FRDIGITS | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       5 LYS_STATUS_OBSLT |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | |
+ *         LYS_SET_LENGTH   | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       6 LYS_MAND_TRUE    | |x|x| | |x| | | | | | | | | | | |x| |x| | | |
+ *         LYS_SET_PATH     | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       7 LYS_MAND_FALSE   | |x|x| | |x| | | | | | | | | | | |x| |x| | | |
+ *         LYS_ORDBY_USER   | | | |x|x| | | | | | | | | | | | | | | | | | |
+ *         LYS_SET_PATTERN  | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       8 LYS_ORDBY_SYSTEM | | | |x|x| | | | | | | | | | | | | | | | | | |
+ *         LYS_YINELEM_TRUE | | | | | | | | | | | | | |x| | | | | | | | | |
+ *         LYS_SET_RANGE    | | | | | | | | | | | | | | | | | | | | | |x| |
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *       9 LYS_YINELEM_FALSE| | | | | | | | | | | | | |x| | | | | | | | | |
+ *         LYS_SET_TYPE     | | | | | | | | | | | | | | | | | | | | | |x| |
+ *         LYS_SINGLEQUOTED | | | | | | | | | | | | | | | | | | | | | | |x|
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *      10 LYS_SET_VALUE    | | | | | | | | | | | | | | | | | | | | |x| | |
+ *         LYS_SET_REQINST  | | | | | | | | | | | | | | | | | | | | | |x| |
+ *         LYS_SET_MIN      | | | |x|x| | | | | | | | | | | | |x| |x| | | |
+ *         LYS_DOUBLEQUOTED | | | | | | | | | | | | | | | | | | | | | | |x|
+ *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *      11 LYS_SET_MAX      | | | |x|x| | | | | | | | | | | | |x| |x| | | |
+ *     ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  *
  */
 
@@ -640,6 +648,9 @@
 #define LYS_SET_UNITS    0x0400      /**< flag to know if the leaf's/leaflist's units are their own (flag set) or it is taken from the type. */
 #define LYS_SET_CONFIG   0x0800      /**< flag to know if the config property was set explicitly (flag set) or it is inherited. */
 
+#define LYS_SINGLEQUOTED 0x100       /**< flag for single-quoted argument of an extension instance's substatement */
+#define LYS_DOUBLEQUOTED 0x200       /**< flag for double-quoted argument of an extension instance's substatement */
+
 #define LYS_FLAGS_COMPILED_MASK 0xff /**< mask for flags that maps to the compiled structures */
 /** @} */
 
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index f86d249..60162bb 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -638,8 +638,8 @@
 
     COMPILE_CHECK_UNIQUENESS(ctx, idents, name, ident, "identity", ident_p->name);
     DUP_STRING(ctx->ctx, ident_p->name, ident->name);
-    DUP_STRING(ctx->ctx, ident_p->ref, ident->dsc);
-    DUP_STRING(ctx->ctx, ident_p->ref, ident->dsc);
+    DUP_STRING(ctx->ctx, ident_p->dsc, ident->dsc);
+    DUP_STRING(ctx->ctx, ident_p->ref, ident->ref);
     COMPILE_ARRAY_GOTO(ctx, ident_p->iffeatures, ident->iffeatures, options, u, lys_compile_iffeature, ret, done);
     /* backlings (derived) can be added no sooner than when all the identities in the current module are present */
     COMPILE_ARRAY_GOTO(ctx, ident_p->exts, ident->exts, options, u, lys_compile_ext, ret, done);
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index ef83436..6fda323 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -1215,3 +1215,18 @@
     return NULL;
 }
 
+unsigned int
+lysp_ext_instance_iter(struct lysp_ext_instance *ext, unsigned int index, LYEXT_SUBSTMT substmt)
+{
+    LY_CHECK_ARG_RET(NULL, ext, LY_EINVAL);
+
+    for (; index < LY_ARRAY_SIZE(ext); index++) {
+        if (ext[index].insubstmt == substmt) {
+            return index;
+        }
+    }
+
+    return LY_ARRAY_SIZE(ext);
+}
+
+
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 0db6a1f..8b7d66b 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -228,6 +228,18 @@
 struct lysc_action **lysc_node_actions_p(struct lysc_node *node);
 
 /**
+ * @brief Iterate over the specified type of the extension instances
+ *
+ * @param[in] ext ([Sized array](@ref sizedarrays)) of extensions to explore
+ * @param[in] index Index in the \p ext array where to start searching (first call with 0, the consequent calls with
+ *            the returned index increased by 1 (until the iteration is not terminated by returning LY_ARRAY_SIZE(ext).
+ * @param[in] substmt Type of the extension (its belongins to the specific substatement) to iterate, use
+ *            #LYEXT_SUBSTMT_ALL to go through all the extensions in the array
+ * @result index in the ext array, LY_ARRAY_SIZE(ext) value if not present.
+ */
+unsigned int lysp_ext_instance_iter(struct lysp_ext_instance *ext, unsigned int index, LYEXT_SUBSTMT substmt);
+
+/**
  * @brief Get the covering schema module structure for the given parsed module structure.
  * @param[in] ctx libyang context to search.
  * @param[in] mod Parsed schema structure.
diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt
index 9657398..f3dc31f 100644
--- a/tests/src/CMakeLists.txt
+++ b/tests/src/CMakeLists.txt
@@ -7,7 +7,8 @@
     src_parser_yang
     src_tree_schema
     src_tree_schema_compile
-    src_tree_schema_helpers)
+    src_tree_schema_helpers
+    src_printer_yang)
 set(local_tests_wraps
     " "
     "-Wl,--wrap=realloc"
@@ -17,6 +18,7 @@
     " "
     " "
     " "
+    " "
     " ")
 set(tests ${tests} ${local_tests} PARENT_SCOPE)
 set(tests_wraps ${tests_wraps} ${local_tests_wraps} PARENT_SCOPE)
diff --git a/tests/src/test_parser_yang.c b/tests/src/test_parser_yang.c
index 2c68caf..8dca6c5 100644
--- a/tests/src/test_parser_yang.c
+++ b/tests/src/test_parser_yang.c
@@ -186,13 +186,13 @@
     ctx.line = 1;
 
     str = " // this is a text of / one * line */ comment\nargument";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_string_equal("argument", word);
     assert_null(buf);
     assert_int_equal(8, len);
 
     str = "/* this is a \n * text // of / block * comment */\"arg\" + \"ume\" \n + \n \"nt\"";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_string_equal("argument", word);
     assert_ptr_equal(buf, word);
     assert_int_equal(8, len);
@@ -223,33 +223,33 @@
 
     /* missing argument */
     str = ";";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_MAYBE_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_MAYBE_STR_ARG, NULL, &word, &buf, &len));
     assert_null(word);
 
     str = "{";
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Invalid character sequence \"{\", expected an argument. Line number 1.");
 
     /* invalid escape sequence */
     str = "\"\\s\"";
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Double-quoted string unknown special character \'\\s\'. Line number 1.");
     str = "\'\\s\'"; /* valid, since it is not an escape sequence in single quoted string */
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_int_equal(2, len);
     assert_string_equal("\\s\'", word);
     assert_int_equal('\0', str[0]); /* input has been eaten */
 
     /* invalid character after the argument */
     str = "hello\"";
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Invalid character sequence \"\"\", expected unquoted string character, optsep, semicolon or opening brace. Line number 1.");
     str = "hello}";
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Invalid character sequence \"}\", expected unquoted string character, optsep, semicolon or opening brace. Line number 1.");
 
     str = "hello/x\t"; /* slash is not an invalid character */
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_int_equal(7, len);
     assert_string_equal("hello/x\t", word);
 
@@ -257,20 +257,20 @@
 
     /* different quoting */
     str = "hello ";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_null(buf);
     assert_int_equal(5, len);
     assert_string_equal("hello ", word);
 
     str = "hello/*comment*/\n";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_null(buf);
     assert_int_equal(5, len);
     assert_false(strncmp("hello", word, len));
 
 
     str = "\"hello\\n\\t\\\"\\\\\";";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_null(buf);
     assert_int_equal(9, len);
     assert_string_equal("hello\\n\\t\\\"\\\\\";", word);
@@ -280,7 +280,7 @@
     /* - space and tabs before newline are stripped out
      * - space and tabs after newline (indentation) are stripped out
      */
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_non_null(buf);
     assert_ptr_equal(word, buf);
     assert_int_equal(14, len);
@@ -289,7 +289,7 @@
 
     ctx.indent = 14;
     str = "\"hello\n \tworld!\"";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_non_null(buf);
     assert_ptr_equal(word, buf);
     assert_int_equal(12, len);
@@ -297,30 +297,30 @@
     free(buf);
 
     str = "\'hello\'";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_null(buf);
     assert_int_equal(5, len);
     assert_false(strncmp("hello", word, 5));
 
     str = "\"hel\"  +\t\n\"lo\"";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_ptr_equal(word, buf);
     assert_int_equal(5, len);
     assert_string_equal("hello", word);
     free(buf);
     str = "\"hel\"  +\t\nlo"; /* unquoted the second part */
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Both string parts divided by '+' must be quoted. Line number 5.");
 
     str = "\'he\'\t\n+ \"llo\"";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_ptr_equal(word, buf);
     assert_int_equal(5, len);
     assert_string_equal("hello", word);
     free(buf);
 
     str = " \t\n\"he\"+\'llo\'";
-    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_SUCCESS, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     assert_ptr_equal(word, buf);
     assert_int_equal(5, len);
     assert_string_equal("hello", word);
@@ -328,7 +328,7 @@
 
     /* missing argument */
     str = ";";
-    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, &word, &buf, &len));
+    assert_int_equal(LY_EVALID, get_argument(&ctx, &str, Y_STR_ARG, NULL, &word, &buf, &len));
     logbuf_assert("Invalid character sequence \";\", expected an argument. Line number 7.");
 }
 
@@ -1914,14 +1914,14 @@
     assert_non_null(rpcs->typedefs);
     assert_int_equal(LYS_STATUS_CURR, rpcs->flags);
     /* input */
-    assert_int_equal(rpcs->input.nodetype, LYS_INOUT);
+    assert_int_equal(rpcs->input.nodetype, LYS_INPUT);
     assert_non_null(rpcs->input.groupings);
     assert_non_null(rpcs->input.exts);
     assert_non_null(rpcs->input.musts);
     assert_non_null(rpcs->input.typedefs);
     assert_non_null(rpcs->input.data);
     /* output */
-    assert_int_equal(rpcs->output.nodetype, LYS_INOUT);
+    assert_int_equal(rpcs->output.nodetype, LYS_OUTPUT);
     assert_non_null(rpcs->output.groupings);
     assert_non_null(rpcs->output.exts);
     assert_non_null(rpcs->output.musts);
diff --git a/tests/src/test_printer_yang.c b/tests/src/test_printer_yang.c
new file mode 100644
index 0000000..922b170
--- /dev/null
+++ b/tests/src/test_printer_yang.c
@@ -0,0 +1,202 @@
+/*
+ * @file test_printer_yang.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for functions from printer_yang.c
+ *
+ * Copyright (c) 2019 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 "../../src/common.c"
+#include "../../src/set.c"
+#include "../../src/log.c"
+#include "../../src/hash_table.c"
+#include "../../src/xpath.c"
+#include "../../src/parser_yang.c"
+#include "../../src/context.c"
+#include "../../src/tree_schema_helpers.c"
+#include "../../src/tree_schema_free.c"
+#include "../../src/tree_schema_compile.c"
+#include "../../src/tree_schema.c"
+#include "../../src/printer_yang.c"
+#include "../../src/printer.c"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+int store = -1; /* negative for infinite logging, positive for limited logging */
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+#if ENABLE_LOGGER_CHECKING
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+    (void) level; /* unused */
+    if (store) {
+        if (path && path[0]) {
+            snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+        } else {
+            strncpy(logbuf, msg, BUFSIZE - 1);
+        }
+        if (store > 0) {
+            --store;
+        }
+    }
+}
+#endif
+
+static int
+logger_setup(void **state)
+{
+    (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+    ly_set_log_clb(logger, 1);
+#endif
+    return 0;
+}
+
+static int
+logger_teardown(void **state)
+{
+    (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+    if (*state) {
+        fprintf(stderr, "%s\n", logbuf);
+    }
+#endif
+    return 0;
+}
+
+void
+logbuf_clean(void)
+{
+    logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+#   define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+#   define logbuf_assert(str)
+#endif
+
+
+static void
+test_module(void **state)
+{
+    *state = test_module;
+
+    struct ly_ctx *ctx = {0};
+    const struct lys_module *mod;
+    const char *orig = "module a {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"urn:test:a\";\n"
+            "  prefix a;\n\n"
+            "  import ietf-yang-types {\n"
+            "    prefix yt;\n"
+            "    revision-date 2013-07-15;\n"
+            "    description\n"
+            "      \"YANG types\";\n"
+            "    reference\n"
+            "      \"RFC reference\";\n"
+            "  }\n\n"
+            "  organization\n"
+            "    \"ORG\";\n"
+            "  contact\n"
+            "    \"Radek Krejci.\";\n"
+            "  description\n"
+            "    \"Long multiline\n"
+            "      description.\";\n"
+            "  reference\n"
+            "    \"some reference\";\n"
+            "}\n";
+    char *printed;
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &ctx));
+
+    assert_non_null(mod = lys_parse_mem(ctx, orig, LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG, 0, 0));
+    assert_string_equal(printed, orig);
+    free(printed);
+
+    orig = "module b {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"urn:test:b\";\n"
+            "  prefix b;\n\n"
+            "  revision 2019-04-16 {\n"
+            "    description\n"
+            "      \"text\";\n"
+            "    reference\n"
+            "      \"text\";\n"
+            "  }\n"
+            "  revision 2019-04-15 {\n"
+            "    description\n"
+            "      \"initial revision\";\n"
+            "  }\n\n"
+            "  feature f1 {\n"
+            "    status current;\n"
+            "    description\n"
+            "      \"text\";\n"
+            "    reference\n"
+            "      \"text\";\n"
+            "  }\n\n"
+            "  feature f2 {\n"
+            "    if-feature \"not f1\";\n"
+            "  }\n"
+            "}\n";
+    assert_non_null(mod = lys_parse_mem(ctx, orig, LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG, 0, 0));
+    assert_string_equal(printed, orig);
+    free(printed);
+
+    orig = "module c {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"urn:test:c\";\n"
+            "  prefix c;\n\n"
+            "  feature f1;\n\n"
+            "  identity i1 {\n"
+            "    if-feature \"f1\";\n"
+            "    description\n"
+            "      \"text\";\n"
+            "    reference\n"
+            "      \"text32\";\n"
+            "  }\n\n"
+            "  identity i2 {\n"
+            "    base i1;\n"
+            "    status obsolete;\n"
+            "  }\n"
+            "}\n";
+    assert_non_null(mod = lys_parse_mem(ctx, orig, LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG, 0, 0));
+    assert_string_equal(printed, orig);
+    free(printed);
+
+    *state = NULL;
+    ly_ctx_destroy(ctx, NULL);
+}
+
+/* TODO: include */
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_module, logger_setup, logger_teardown),
+    };
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}