data tree FEATURE support for NETCONF messages
diff --git a/doc/transition.dox b/doc/transition.dox
index 966e26e..310ca46 100644
--- a/doc/transition.dox
+++ b/doc/transition.dox
@@ -275,9 +275,7 @@
  * lyd_parse_fd()               | ::lyd_parse_data_fd()           | Renamed and limited to data trees only.
  * lyd_parse_mem()              | ::lyd_parse_data_mem()          | ^
  * lyd_parse_path()             | ::lyd_parse_data_path()         | ^
- * -                            | ::lyd_parse_notif()             | Separated function to parse Notifications.
- * -                            | ::lyd_parse_rpc()               | Separated function to parse RPCs.
- * -                            | ::lyd_parse_reply()             | Separated function to parse replies to RPCs
+ * -                            | ::lyd_parse_op()                | Separated function for parsing RPCs, actions, replies, and notifications.
  * -                            | ::lyd_print_all()               | New API accepting ::ly_out.
  * -                            | ::lyd_print_tree()              | ^
  *
diff --git a/src/common.h b/src/common.h
index a9e100e..f03535d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -46,6 +46,7 @@
 #define GETMACRO3(_1, _2, _3, NAME, ...) NAME
 #define GETMACRO4(_1, _2, _3, _4, NAME, ...) NAME
 #define GETMACRO5(_1, _2, _3, _4, _5, NAME, ...) NAME
+#define GETMACRO6(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
 
 /*
  * If the compiler supports attribute to mark objects as hidden, mark all
@@ -196,8 +197,10 @@
 #define LY_CHECK_ARG_RET3(CTX, ARG1, ARG2, ARG3, RETVAL) LY_CHECK_ARG_RET2(CTX, ARG1, ARG2, RETVAL);LY_CHECK_ARG_RET1(CTX, ARG3, RETVAL)
 #define LY_CHECK_ARG_RET4(CTX, ARG1, ARG2, ARG3, ARG4, RETVAL) LY_CHECK_ARG_RET3(CTX, ARG1, ARG2, ARG3, RETVAL);\
     LY_CHECK_ARG_RET1(CTX, ARG4, RETVAL)
-#define LY_CHECK_ARG_RET(CTX, ...) GETMACRO5(__VA_ARGS__, LY_CHECK_ARG_RET4, LY_CHECK_ARG_RET3, LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1)\
-    (CTX, __VA_ARGS__)
+#define LY_CHECK_ARG_RET5(CTX, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL) LY_CHECK_ARG_RET4(CTX, ARG1, ARG2, ARG3, ARG4, RETVAL);\
+    LY_CHECK_ARG_RET1(CTX, ARG5, RETVAL)
+#define LY_CHECK_ARG_RET(CTX, ...) GETMACRO6(__VA_ARGS__, LY_CHECK_ARG_RET5, LY_CHECK_ARG_RET4, LY_CHECK_ARG_RET3, \
+    LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1) (CTX, __VA_ARGS__)
 
 /* count sequence size for LY_VCODE_INCHILDSTMT validation error code */
 size_t LY_VCODE_INSTREXP_len(const char *str);
diff --git a/src/diff.c b/src/diff.c
index 67039af..ad72ed9 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1001,7 +1001,7 @@
 
     /* apply diff recursively */
     LY_LIST_FOR(lyd_child_no_keys(diff_node), diff_child) {
-        LY_CHECK_RET(lyd_diff_apply_r(lyd_node_children_p(match), match, diff_child, diff_cb, cb_data));
+        LY_CHECK_RET(lyd_diff_apply_r(lyd_node_child_p(match), match, diff_child, diff_cb, cb_data));
     }
 
     return LY_SUCCESS;
diff --git a/src/in.c b/src/in.c
index 8912b32..f4c8fd6 100644
--- a/src/in.c
+++ b/src/in.c
@@ -381,50 +381,108 @@
 }
 
 LY_ERR
+lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op)
+{
+    const struct lyd_node *iter;
+
+    *op = NULL;
+
+    if (!parent) {
+        /* no parent, nothing to look for */
+        return LY_SUCCESS;
+    }
+
+    /* we need to find the operation node if it already exists */
+    for (iter = parent; iter; iter = lyd_parent(iter)) {
+        if (iter->schema && (iter->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF))) {
+            break;
+        }
+    }
+
+    if (!iter) {
+        /* no operation found */
+        return LY_SUCCESS;
+    }
+
+    if (!(int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY))) {
+        LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent %s \"%s\" node when not parsing any operation.",
+                lys_nodetype2str(iter->schema->nodetype), iter->schema->name);
+        return LY_EINVAL;
+    } else if ((iter->schema->nodetype == LYS_RPC) && !(int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY))) {
+        LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent RPC \"%s\" node when not parsing RPC nor rpc-reply.",
+                iter->schema->name);
+        return LY_EINVAL;
+    } else if ((iter->schema->nodetype == LYS_ACTION) && !(int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY))) {
+        LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent action \"%s\" node when not parsing action nor rpc-reply.",
+                iter->schema->name);
+        return LY_EINVAL;
+    } else if ((iter->schema->nodetype == LYS_NOTIF) && !(int_opts & LYD_INTOPT_NOTIF)) {
+        LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent notification \"%s\" node when not parsing a notification.",
+                iter->schema->name);
+        return LY_EINVAL;
+    }
+
+    *op = (struct lyd_node *)iter;
+    return LY_SUCCESS;
+}
+
+LY_ERR
 lyd_parser_check_schema(struct lyd_ctx *lydctx, const struct lysc_node *snode)
 {
-    LY_ERR ret = LY_EVALID;
+    LY_ERR rc = LY_SUCCESS;
 
     LOG_LOCSET(snode, NULL, NULL, NULL);
 
-    if ((lydctx->parse_options & LYD_PARSE_NO_STATE) && (snode->flags & LYS_CONFIG_R)) {
+    if ((lydctx->parse_opts & LYD_PARSE_NO_STATE) && (snode->flags & LYS_CONFIG_R)) {
         LOGVAL(lydctx->data_ctx->ctx, LY_VCODE_INNODE, "state", snode->name);
+        rc = LY_EVALID;
         goto cleanup;
     }
 
-    if (snode->nodetype & (LYS_RPC | LYS_ACTION)) {
+    if (snode->nodetype == LYS_RPC) {
         if (lydctx->int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) {
             if (lydctx->op_node) {
-                LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\", %s \"%s\" already parsed.",
-                        lys_nodetype2str(snode->nodetype), snode->name,
-                        lys_nodetype2str(lydctx->op_node->schema->nodetype), lydctx->op_node->schema->name);
-                goto cleanup;
+                goto error_node_dup;
             }
         } else {
-            LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\".",
-                    lys_nodetype2str(snode->nodetype), snode->name);
-            goto cleanup;
+            goto error_node_inval;
+        }
+    } else if (snode->nodetype == LYS_ACTION) {
+        if (lydctx->int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) {
+            if (lydctx->op_node) {
+                goto error_node_dup;
+            }
+        } else {
+            goto error_node_inval;
         }
     } else if (snode->nodetype == LYS_NOTIF) {
         if (lydctx->int_opts & LYD_INTOPT_NOTIF) {
             if (lydctx->op_node) {
-                LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\", %s \"%s\" already parsed.",
-                        lys_nodetype2str(snode->nodetype), snode->name,
-                        lys_nodetype2str(lydctx->op_node->schema->nodetype), lydctx->op_node->schema->name);
-                goto cleanup;
+                goto error_node_dup;
             }
         } else {
-            LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\".",
-                    lys_nodetype2str(snode->nodetype), snode->name);
-            goto cleanup;
+            goto error_node_inval;
         }
     }
 
-    ret = LY_SUCCESS;
+    /* success */
+    goto cleanup;
+
+error_node_dup:
+    LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\", %s \"%s\" already parsed.",
+            lys_nodetype2str(snode->nodetype), snode->name, lys_nodetype2str(lydctx->op_node->schema->nodetype),
+            lydctx->op_node->schema->name);
+    rc = LY_EVALID;
+    goto cleanup;
+
+error_node_inval:
+    LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\".", lys_nodetype2str(snode->nodetype),
+            snode->name);
+    rc = LY_EVALID;
 
 cleanup:
     LOG_LOCBACK(1, 0, 0, 0);
-    return ret;
+    return rc;
 }
 
 LY_ERR
@@ -435,7 +493,7 @@
 
     LY_CHECK_RET(lyd_create_term(schema, value, value_len, dynamic, format, prefix_data, hints, &incomplete, node));
 
-    if (incomplete && !(lydctx->parse_options & LYD_PARSE_ONLY)) {
+    if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) {
         LY_CHECK_RET(ly_set_add(&lydctx->node_types, *node, 1, NULL));
     }
     return LY_SUCCESS;
@@ -457,7 +515,7 @@
     LY_CHECK_RET(lyd_create_meta(parent, meta, mod, name, name_len, value, value_len, dynamic, format, prefix_data,
             hints, 0, &incomplete));
 
-    if (incomplete && !(lydctx->parse_options & LYD_PARSE_ONLY)) {
+    if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) {
         LY_CHECK_RET(ly_set_add(&lydctx->meta_types, *meta, 1, NULL));
     }
 
diff --git a/src/lyb.h b/src/lyb.h
index bb72f0b..85ad901 100644
--- a/src/lyb.h
+++ b/src/lyb.h
@@ -56,17 +56,17 @@
 struct lyd_lyb_ctx {
     union {
         struct {
-            uint32_t parse_options;        /**< various @ref dataparseroptions. */
-            uint32_t validate_options;     /**< various @ref datavalidationoptions. */
+            uint32_t parse_opts;   /**< various @ref dataparseroptions. */
+            uint32_t val_opts;     /**< various @ref datavalidationoptions. */
         };
         uint32_t print_options;
     };
     uint32_t int_opts;             /**< internal data parser options */
     uint32_t path_len;             /**< used bytes in the path buffer */
     char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */
-    struct ly_set unres_node_type; /**< set of nodes validated with LY_EINCOMPLETE result */
-    struct ly_set unres_meta_type; /**< set of metadata validated with LY_EINCOMPLETE result */
-    struct ly_set when_check;      /**< set of nodes with "when" conditions */
+    struct ly_set node_when;       /**< set of nodes with "when" conditions */
+    struct ly_set node_types;      /**< set of nodes validated with LY_EINCOMPLETE result */
+    struct ly_set meta_types;      /**< set of metadata validated with LY_EINCOMPLETE result */
     struct lyd_node *op_node;      /**< if an RPC/action/notification is being parsed, store the pointer to it */
 
     /* callbacks */
diff --git a/src/parser_data.h b/src/parser_data.h
index f6327ea..d32aaff 100644
--- a/src/parser_data.h
+++ b/src/parser_data.h
@@ -58,10 +58,8 @@
  *     example the *:operational* datastore is not necessarily valid and results of the NETCONF's \<get\> or \<get-config\>
  *     oprations used with filters will be incomplete (and thus invalid). This can be allowed using ::LYD_PARSE_ONLY,
  *     the ::LYD_PARSE_NO_STATE should be used for the data returned by \<get-config\> operation.
- * - ::lyd_parse_rpc() is used for parsing RPCs/Actions, optionally including the RPC envelopes known from the NETCONF
- *   protocol.
- * - ::lyd_parse_reply() processes reply to a previous RPC/Action, which must be provided.
- * - ::lyd_parse_notif() is able to process complete Notification message.
+ * - ::lyd_parse_op() is used for parsing RPCs/actions, replies, and notifications. Even NETCONF rpc, rpc-reply, and
+ *     notification messages are supported.
  *
  * Further information regarding the processing input instance data can be found on the following pages.
  * - @subpage howtoDataValidation
@@ -73,9 +71,7 @@
  * - ::lyd_parse_data_mem()
  * - ::lyd_parse_data_fd()
  * - ::lyd_parse_data_path()
- * - ::lyd_parse_rpc()
- * - ::lyd_parse_reply()
- * - ::lyd_parse_notif()
+ * - ::lyd_parse_op()
  */
 
 /**
@@ -188,52 +184,32 @@
 /** @} datavalidationoptions */
 
 /**
- * @ingroup datatree
- * @defgroup datavalidateop Operation to validate
- *
- * Operation provided to ::lyd_validate_op() to validate.
- *
- * The operation cannot be determined automatically since RPC/action and a reply to it share the common top level node
- * referencing the RPC/action schema node and may not have any input/output children to use for distinction.
- *
- * @{
- */
-typedef enum {
-    LYD_VALIDATE_OP_RPC = 1,   /**< Validate RPC/action request (input parameters). */
-    LYD_VALIDATE_OP_REPLY,     /**< Validate RPC/action reply (output parameters). */
-    LYD_VALIDATE_OP_NOTIF      /**< Validate Notification operation. */
-} LYD_VALIDATE_OP;
-
-/** @} datavalidateop */
-
-/**
  * @brief Parse (and validate) data from the input handler as a YANG data tree.
  *
  * @param[in] ctx Context to connect with the tree being built here.
+ * @param[in] parent Optional parent to connect the parsed nodes to.
  * @param[in] in The input handle to provide the dumped data in the specified @p format to parse (and validate).
  * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler.
  * @param[in] parse_options Options for parser, see @ref dataparseroptions.
  * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree Resulting data tree built from the input data. Note that NULL can be a valid result as a representation of an empty YANG data tree.
- * The returned data are expected to be freed using ::lyd_free_all().
+ * @param[out] tree Full parsed data tree, note that NULL can be a valid tree. If @p parent is set, set to NULL.
  * @return LY_SUCCESS in case of successful parsing (and validation).
  * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
  */
-LY_ERR lyd_parse_data(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, uint32_t parse_options,
-        uint32_t validate_options, struct lyd_node **tree);
+LY_ERR lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format,
+        uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree);
 
 /**
  * @brief Parse (and validate) input data as a YANG data tree.
  *
- * Wrapper around ::lyd_parse_data() hiding work with the input handler.
+ * Wrapper around ::lyd_parse_data() hiding work with the input handler and some obscure options.
  *
  * @param[in] ctx Context to connect with the tree being built here.
  * @param[in] data The input data in the specified @p format to parse (and validate).
  * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler.
  * @param[in] parse_options Options for parser, see @ref dataparseroptions.
  * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree Resulting data tree built from the input data. Note that NULL can be a valid result as a representation of an empty YANG data tree.
- * The returned data are expected to be freed using ::lyd_free_all().
+ * @param[out] tree Full parsed data tree, note that NULL can be a valid tree
  * @return LY_SUCCESS in case of successful parsing (and validation).
  * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
  */
@@ -243,33 +219,32 @@
 /**
  * @brief Parse (and validate) input data as a YANG data tree.
  *
- * Wrapper around ::lyd_parse_data() hiding work with the input handler.
+ * Wrapper around ::lyd_parse_data() hiding work with the input handler and some obscure options.
  *
  * @param[in] ctx Context to connect with the tree being built here.
- * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the input data in the specified @p format to parse (and validate).
+ * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the input data in the
+ * specified @p format to parse.
  * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler.
  * @param[in] parse_options Options for parser, see @ref dataparseroptions.
  * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree Resulting data tree built from the input data. Note that NULL can be a valid result as a representation of an empty YANG data tree.
- * The returned data are expected to be freed using ::lyd_free_all().
+ * @param[out] tree Full parsed data tree, note that NULL can be a valid tree
  * @return LY_SUCCESS in case of successful parsing (and validation).
  * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
  */
-LY_ERR lyd_parse_data_fd(const struct ly_ctx *ctx, int fd, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree);
+LY_ERR lyd_parse_data_fd(const struct ly_ctx *ctx, int fd, LYD_FORMAT format, uint32_t parse_options,
+        uint32_t validate_options, struct lyd_node **tree);
 
 /**
  * @brief Parse (and validate) input data as a YANG data tree.
  *
- * Wrapper around ::lyd_parse_data() hiding work with the input handler.
+ * Wrapper around ::lyd_parse_data() hiding work with the input handler and some obscure options.
  *
  * @param[in] ctx Context to connect with the tree being built here.
  * @param[in] path Path to the file with the input data in the specified @p format to parse (and validate).
  * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler.
  * @param[in] parse_options Options for parser, see @ref dataparseroptions.
  * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree Resulting data tree built from the input data. Note that NULL can be a valid result as a representation of an empty YANG data tree.
- * The returned data are expected to be freed using ::lyd_free_all().
+ * @param[out] tree Full parsed data tree, note that NULL can be a valid tree
  * @return LY_SUCCESS in case of successful parsing (and validation).
  * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
  */
@@ -277,51 +252,66 @@
         uint32_t validate_options, struct lyd_node **tree);
 
 /**
- * @brief Parse (no validation) data from the input handler as a YANG RPC/action request.
+ * @ingroup datatree
+ * @defgroup datatype Data operation type
  *
- * @param[in] ctx Context to connect with the tree being parsed.
- * @param[in] in Input handle to provide the dumped data in the specified @p format to parse.
- * @param[in] format Format of the input data to be parsed.
- * @param[out] tree Resulting full RPC/action request tree built from the input data. The returned data are expected to be freed using ::lyd_free_all().
- * In contrast to YANG data tree, result of parsing RPC/action cannot be NULL until an error occurs.
- * @param[out] op Optional pointer to the actual operation node inside the full action @p tree, useful only for action.
- * @return LY_SUCCESS in case of successful parsing.
- * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
+ * Operation provided to ::lyd_validate_op() to validate.
+ *
+ * The operation cannot be determined automatically since RPC/action and a reply to it share the common top level node
+ * referencing the RPC/action schema node and may not have any input/output children to use for distinction.
+ *
+ * @{
  */
-LY_ERR lyd_parse_rpc(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree,
-        struct lyd_node **op);
+enum lyd_type {
+    LYD_TYPE_YANG_DATA = 0,         /* generic YANG instance data */
+    LYD_TYPE_YANG_RPC,              /* instance of a YANG RPC/action request with only "input" data children,
+                                       including all parents in case of an action */
+    LYD_TYPE_YANG_NOTIF,            /* instance of a YANG notification , including all parents in case of a nested one */
+    LYD_TYPE_YANG_REPLY,            /* instance of a YANG RPC/action reply with only "output" data children,
+                                       including all parents in case of an action */
+    LYD_TYPE_NETCONF_RPC,           /* complete NETCONF RPC invocation as defined for
+                                       [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and
+                                       [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */
+    LYD_TYPE_NETCONF_REPLY_OR_NOTIF /* complete NETCONF RPC reply as defined for
+                                       [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and
+                                       [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) or
+                                       [notification](https://tools.ietf.org/html/rfc7950#section-7.16.2), which
+                                       of these is parsed is decided based on the first element name */
+};
+/** @} datatype */
 
 /**
- * @brief Parse (no validation) data from the input handler as a YANG RPC/action reply.
+ * @brief Parse YANG data into an operation data tree.
  *
- * @param[in] ctx Context to connect with the tree being parsed.
- * @param[in] in Input handle to provide the dumped data in the specified @p format to parse (and validate).
- * @param[in] format Format of the input data to be parsed.
- * @param[out] tree Resulting full RPC/action reply tree built from the input data. The returned data are expected to be freed using ::lyd_free_all().
- * In contrast to YANG data tree, result of parsing RPC/action cannot be NULL until an error occurs.
- * At least one of the @p tree and @p op output variables must be provided.
- * @param[out] op Pointer to the actual operation node inside the full action reply @p tree, useful only for action. At least one of the @p op
- * and @p tree output variables must be provided.
- * @return LY_SUCCESS in case of successful parsing.
- * @return LY_ERR value in case of error. Additional error information can be obtained from the request's context using ly_err* functions.
- */
-LY_ERR lyd_parse_reply(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree,
-        struct lyd_node **op);
-
-/**
- * @brief Parse (no validation) data from the input handler as a YANG notification.
+ * At least one of @p parent, @p tree, or @p op must always be set.
  *
- * @param[in] ctx Context to connect with the tree being parsed.
- * @param[in] in Input handle to provide the dumped data in the specified @p format to parse (and validate).
- * @param[in] format Format of the input data to be parsed.
- * @param[out] tree Resulting full notification tree built from the input data. The returned data are expected to be freed using ::lyd_free_all().
- * In contrast to YANG data tree, result of parsing notification cannot be NULL until an error occurs.
- * @param[out] ntf Optional pointer to the actual notification node inside the full notification @p tree, useful for nested notifications.
- * @return LY_SUCCESS in case of successful parsing.
- * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions.
+ * Specific @p data_type values have different parameter meaning as follows:
+ * - ::LYD_TYPE_NETCONF_RPC:
+ *   - @p parent - must be NULL, the whole RPC is expected;
+ *   - @p format - must be ::LYD_XML, NETCONF supports only this format;
+ *   - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as
+ *               a separate opaque data tree;
+ *   - @p op - must be provided, the RPC/action data tree itself will be returned here, pointing to the operation;
+ *
+ * - ::LYD_TYPE_NETCONF_REPLY_OR_NOTIF:
+ *   - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node;
+ *   - @p format - must be ::LYD_XML, NETCONF supports only this format;
+ *   - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as
+ *               a separate opaque data tree;
+ *   - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation,
+ *             it will be set to NULL if no data nodes were parsed in the reply (ok or rpc-error);
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] parent Optional parent to connect the parsed nodes to.
+ * @param[in] in Input handle to read the input from.
+ * @param[in] format Expected format of the data in @p in.
+ * @param[in] data_type Expected operation to parse (@ref datatype).
+ * @param[out] tree Optional full parsed data tree. If @p parent is set, set to NULL.
+ * @param[out] op Optional parsed operation node.
+ * @return LY_ERR value.
  */
-LY_ERR lyd_parse_notif(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree,
-        struct lyd_node **ntf);
+LY_ERR lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format,
+        enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op);
 
 /**
  * @brief Fully validate a data tree.
@@ -348,20 +338,18 @@
 LY_ERR lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, struct lyd_node **diff);
 
 /**
- * @brief Validate an RPC/action, notification, or RPC/action reply.
+ * @brief Validate an RPC/action request, reply, or notification.
  *
  * @param[in,out] op_tree Operation tree with any parents. It can point to the operation itself or any of
  * its parents, only the operation subtree is actually validated.
- * @param[in] tree Tree to be used for validating references from the operation subtree.
- * @param[in] op Operation to validate (@ref datavalidateop), the given @p op_tree must correspond to this value. Note that
- * it isn't possible to detect the operation simply from the @p op_tree since RPC/action and their reply share the same
- * RPC/action data node and in case one of the input and output do not define any data node children, it is not possible
- * to get know what is here given for validation and if it is really valid.
+ * @param[in] dep_tree Tree to be used for validating references from the operation subtree.
+ * @param[in] data_type Operation type to validate (only YANG operations are accepted, @ref datatype).
  * @param[out] diff Optional diff with any changes made by the validation.
  * @return LY_SUCCESS on success.
  * @return LY_ERR error on error.
  */
-LY_ERR lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *tree, LYD_VALIDATE_OP op, struct lyd_node **diff);
+LY_ERR lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *dep_tree, enum lyd_type data_type,
+        struct lyd_node **diff);
 
 /** @} datatree */
 
diff --git a/src/parser_internal.h b/src/parser_internal.h
index 408433d..be99884 100644
--- a/src/parser_internal.h
+++ b/src/parser_internal.h
@@ -15,6 +15,7 @@
 #ifndef LY_PARSER_INTERNAL_H_
 #define LY_PARSER_INTERNAL_H_
 
+#include "parser_data.h"
 #include "plugins_types.h"
 #include "tree_schema_internal.h"
 
@@ -29,12 +30,22 @@
 typedef void (*lyd_ctx_free_clb)(struct lyd_ctx *ctx);
 
 /**
+ * @brief Internal data parser flags.
+ */
+#define LYD_INTOPT_RPC              0x01    /**< RPC request is being parsed. */
+#define LYD_INTOPT_ACTION           0x02    /**< Action request is being parsed. */
+#define LYD_INTOPT_REPLY            0x04    /**< RPC/action reply is being parsed. */
+#define LYD_INTOPT_NOTIF            0x08    /**< Notification is being parsed. */
+#define LYD_INTOPT_WITH_SIBLINGS    0x10    /**< Parse the whole input with any siblings. */
+#define LYD_INTOPT_NO_SIBLINGS      0x20    /**< If there are any siblings, return an error. */
+
+/**
  * @brief Internal (common) context for YANG data parsers.
  */
 struct lyd_ctx {
-    uint32_t parse_options;        /**< various @ref dataparseroptions. */
-    uint32_t validate_options;     /**< various @ref datavalidationoptions. */
-    uint32_t int_opts;             /**< internal data parser options */
+    uint32_t parse_opts;           /**< various @ref dataparseroptions. */
+    uint32_t val_opts;             /**< various @ref datavalidationoptions. */
+    uint32_t int_opts;             /**< internal parser options */
     uint32_t path_len;             /**< used bytes in the path buffer */
 #define LYD_PARSER_BUFSIZE 4078
     char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */
@@ -109,145 +120,67 @@
         struct ly_in *in, struct lysp_submodule **submod);
 
 /**
- * @brief Parse XML string as YANG data tree.
+ * @brief Parse XML string as a YANG data tree.
  *
- * @param[in] ctx libyang context
+ * @param[in] ctx libyang context.
+ * @param[in] parent Parent to connect the parsed nodes to, if any.
+ * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL.
  * @param[in] in Input structure.
- * @param[in] parse_options Options for parser, see @ref dataparseroptions.
- * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree_p Parsed data tree. Note that NULL can be a valid result.
+ * @param[in] parse_opts Options for parser, see @ref dataparseroptions.
+ * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions.
+ * @param[in] data_type Expected data type of the data.
+ * @param[out] envp Individual parsed envelopes tree, returned only by specific @p data_type.
+ * @param[out] parsed Set to add all the parsed siblings into.
  * @param[out] lydctx_p Data parser context to finish validation.
  * @return LY_ERR value.
  */
-LY_ERR lyd_parse_xml_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p);
+LY_ERR lyd_parse_xml(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct lyd_node **envp, struct ly_set *parsed,
+        struct lyd_ctx **lydctx_p);
 
 /**
- * @brief Parse XML string as YANG RPC/action invocation.
+ * @brief Parse JSON string as a YANG data tree.
  *
  * @param[in] ctx libyang context.
+ * @param[in] parent Parent to connect the parsed nodes to, if any.
+ * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL.
  * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action tree.
- * @param[out] op_p Optional pointer to the actual operation. Useful mainly for action.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_xml_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
-
-/**
- * @brief Parse XML string as YANG notification.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full notification tree.
- * @param[out] op_p Optional pointer to the actual notification. Useful mainly for nested notifications.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_xml_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p);
-
-/**
- * @brief Parse XML string as YANG RPC/action reply.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action reply tree.
- * @param[out] op_p Optional pointer to the reply operation. Useful mainly for action.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_xml_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
-
-/**
- * @brief Parse JSON string as YANG data tree.
- *
- * @param[in] ctx libyang context
- * @param[in] in Input structure.
- * @param[in] parse_options Options for parser, see @ref dataparseroptions.
- * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree_p Parsed data tree. Note that NULL can be a valid result.
+ * @param[in] parse_opts Options for parser, see @ref dataparseroptions.
+ * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions.
+ * @param[in] data_type Expected data type of the data.
+ * @param[out] parsed Set to add all the parsed siblings into.
  * @param[out] lydctx_p Data parser context to finish validation.
  * @return LY_ERR value.
  */
-LY_ERR lyd_parse_json_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p);
+LY_ERR lyd_parse_json(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct ly_set *parsed, struct lyd_ctx **lydctx_p);
 
 /**
- * @brief Parse JSON string as YANG notification.
+ * @brief Parse binary LYB data as a YANG data tree.
  *
  * @param[in] ctx libyang context.
+ * @param[in] parent Parent to connect the parsed nodes to, if any.
+ * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL.
  * @param[in] in Input structure.
- * @param[out] tree_p Parsed full notification tree.
- * @param[out] ntf_p Optional pointer to the actual notification. Useful mainly for nested notifications.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_json_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p);
-
-/**
- * @brief Parse JSON string as YANG RPC/action invocation.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action tree.
- * @param[out] op_p Optional pointer to the actual operation. Useful mainly for action.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_json_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
-
-/**
- * @brief Parse JSON string as YANG RPC/action reply.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action reply tree.
- * @param[out] op_p Optional pointer to the reply operation. Useful mainly for action.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_json_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
-
-/**
- * @brief Parse binary data as YANG data tree.
- *
- * @param[in] ctx libyang context
- * @param[in] in Input structure.
- * @param[in] parse_options Options for parser, see @ref dataparseroptions.
- * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[out] tree_p Parsed data tree. Note that NULL can be a valid result.
+ * @param[in] parse_opts Options for parser, see @ref dataparseroptions.
+ * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions.
+ * @param[in] data_type Expected data type of the data.
+ * @param[out] parsed Set to add all the parsed siblings into.
  * @param[out] lydctx_p Data parser context to finish validation.
  * @return LY_ERR value.
  */
-LY_ERR lyd_parse_lyb_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p);
+LY_ERR lyd_parse_lyb(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct ly_set *parsed, struct lyd_ctx **lydctx_p);
 
 /**
- * @brief Parse binary data as YANG RPC/action invocation.
+ * @brief Search all the parents for an operation node, check validity based on internal parser flags.
  *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action tree.
- * @param[out] op_p Optional pointer to the actual operation. Useful mainly for action.
+ * @param[in] parent Parent to connect the parsed nodes to.
+ * @param[in] int_opt Internal parser options.
+ * @param[out] op Found operation, if any.
  * @return LY_ERR value.
  */
-LY_ERR lyd_parse_lyb_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
-
-/**
- * @brief Parse binary data as YANG notification.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full notification tree.
- * @param[out] ntf_p Optional pointer to the actual notification. Useful mainly for nested notifications.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_lyb_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p);
-
-/**
- * @brief Parse binary data as YANG RPC/action reply.
- *
- * @param[in] ctx libyang context.
- * @param[in] in Input structure.
- * @param[out] tree_p Parsed full RPC/action reply tree.
- * @param[out] op_p Optional pointer to the reply operation. Useful mainly for action.
- * @return LY_ERR value.
- */
-LY_ERR lyd_parse_lyb_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p);
+LY_ERR lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op);
 
 /**
  * @brief Check that a data node representing the @p snode is suitable based on options.
diff --git a/src/parser_json.c b/src/parser_json.c
index 8fd2f06..bccd286 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -40,8 +40,8 @@
  * Note that the structure maps to the lyd_ctx which is common for all the data parsers
  */
 struct lyd_json_ctx {
-    uint32_t parse_options;        /**< various @ref dataparseroptions. */
-    uint32_t validate_options;     /**< various @ref datavalidationoptions. */
+    uint32_t parse_opts;           /**< various @ref dataparseroptions. */
+    uint32_t val_opts;             /**< various @ref datavalidationoptions. */
     uint32_t int_opts;             /**< internal data parser options */
     uint32_t path_len;             /**< used bytes in the path buffer */
     char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */
@@ -214,12 +214,12 @@
         goto cleanup;
     }
     if (!mod) {
-        if (lydctx->parse_options & LYD_PARSE_STRICT) {
+        if (lydctx->parse_opts & LYD_PARSE_STRICT) {
             LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "No module named \"%.*s\" in the context.", prefix_len, prefix);
             ret = LY_EVALID;
             goto cleanup;
         }
-        if (!(lydctx->parse_options & LYD_PARSE_OPAQ)) {
+        if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
             ret = LY_ENOT;
             goto cleanup;
         }
@@ -229,12 +229,12 @@
     if (mod && (!parent || parent->schema)) {
         *snode_p = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, getnext_opts);
         if (!*snode_p) {
-            if (lydctx->parse_options & LYD_PARSE_STRICT) {
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                 LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.",
                         name_len, name, mod->name);
                 ret = LY_EVALID;
                 goto cleanup;
-            } else if (!(lydctx->parse_options & LYD_PARSE_OPAQ)) {
+            } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
                 /* skip element with children */
                 ret = LY_ENOT;
                 goto cleanup;
@@ -435,7 +435,7 @@
         return LY_SUCCESS;
     }
 
-    if (lydctx->parse_options & LYD_PARSE_OPAQ) {
+    if (lydctx->parse_opts & LYD_PARSE_OPAQ) {
         /* backup parser */
         lyjson_ctx_backup(jsonctx);
         status = lyjson_ctx_status(jsonctx, 0);
@@ -585,7 +585,7 @@
                                 meta->name.name, strlen(meta->name.name), meta->value, ly_strlen(meta->value),
                                 &dynamic, LY_PREF_JSON, NULL, meta->hints);
                         LY_CHECK_GOTO(ret, cleanup);
-                    } else if (lydctx->parse_options & LYD_PARSE_STRICT) {
+                    } else if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                         LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE,
                                 "Unknown (or not implemented) YANG module \"%s\" for metadata \"%s%s%s\".",
                                 meta->name.prefix, meta->name.prefix, ly_strlen(meta->name.prefix) ? ":" : "", meta->name.name);
@@ -594,7 +594,7 @@
                     }
                 }
                 /* add/correct flags */
-                lyd_parse_set_data_flags(node, &lydctx->node_when, &node->meta, lydctx->parse_options);
+                lyd_parse_set_data_flags(node, &lydctx->node_when, &node->meta, lydctx->parse_opts);
 
                 /* done */
                 break;
@@ -743,13 +743,13 @@
         /* get the element module */
         mod = ly_ctx_get_module_implemented2(ctx, prefix, prefix_len);
         if (!mod) {
-            if (lydctx->parse_options & LYD_PARSE_STRICT) {
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                 LOGVAL(ctx, LYVE_REFERENCE, "Prefix \"%.*s\" of the metadata \"%.*s\" does not match any module in the context.",
                         prefix_len, prefix, name_len, name);
                 ret = LY_EVALID;
                 goto cleanup;
             }
-            if (!(lydctx->parse_options & LYD_PARSE_OPAQ)) {
+            if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
                 /* skip element with children */
                 ret = lydjson_data_skip(lydctx->jsonctx);
                 LY_CHECK_GOTO(ret, cleanup);
@@ -772,7 +772,7 @@
             LY_CHECK_GOTO(ret, cleanup);
 
             /* add/correct flags */
-            lyd_parse_set_data_flags(node, &lydctx->node_when, &meta, lydctx->parse_options);
+            lyd_parse_set_data_flags(node, &lydctx->node_when, &meta, lydctx->parse_opts);
         } else {
             /* create attribute */
             const char *module_name;
@@ -845,7 +845,8 @@
     }
 }
 
-static LY_ERR lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node_inner *parent, struct lyd_node **first_p);
+static LY_ERR lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p,
+        struct ly_set *parsed);
 
 /**
  * @brief Parse opaq node from the input.
@@ -900,7 +901,7 @@
     if ((*status_p == LYJSON_OBJECT) || (*status_p == LYJSON_OBJECT_EMPTY)) {
         /* process children */
         while (*status_p != LYJSON_OBJECT_CLOSED && *status_p != LYJSON_OBJECT_EMPTY) {
-            LY_CHECK_RET(lydjson_subtree_r(lydctx, (struct lyd_node_inner *)(*node_p), lyd_node_children_p(*node_p)));
+            LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL));
             *status_p = lyjson_ctx_status(lydctx->jsonctx, 0);
         }
     } else if ((*status_p == LYJSON_ARRAY) || (*status_p == LYJSON_ARRAY_EMPTY)) {
@@ -911,7 +912,7 @@
         if ((*status_inner_p == LYJSON_OBJECT) || (*status_inner_p == LYJSON_OBJECT_EMPTY)) {
             /* but first process children of the object in the array */
             while (*status_inner_p != LYJSON_OBJECT_CLOSED && *status_inner_p != LYJSON_OBJECT_EMPTY) {
-                LY_CHECK_RET(lydjson_subtree_r(lydctx, (struct lyd_node_inner *)(*node_p), lyd_node_children_p(*node_p)));
+                LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL));
                 *status_inner_p = lyjson_ctx_status(lydctx->jsonctx, 0);
             }
         }
@@ -922,12 +923,13 @@
         if (*status_inner_p != LYJSON_ARRAY_CLOSED) {
             assert(node_p);
             lydjson_maintain_children(parent, first_p, node_p);
-            return lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_p, status_inner_p, first_p, node_p);
+            return lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_p, status_inner_p,
+                    first_p, node_p);
         }
     }
 
     /* finish linking metadata */
-    LY_CHECK_RET(lydjson_metadata_finish(lydctx, lyd_node_children_p(*node_p)));
+    LY_CHECK_RET(lydjson_metadata_finish(lydctx, lyd_node_child_p(*node_p)));
 
     /* move after the item */
     return lyjson_ctx_next(lydctx->jsonctx, status_p);
@@ -953,9 +955,8 @@
  */
 static LY_ERR
 lydjson_parse_attribute(struct lyd_json_ctx *lydctx, struct lyd_node *attr_node, const struct lysc_node *snode,
-        const char *name, size_t name_len, const char *prefix, size_t prefix_len,
-        struct lyd_node_inner *parent, enum LYJSON_PARSER_STATUS *status_p, struct lyd_node **first_p,
-        struct lyd_node **node_p)
+        const char *name, size_t name_len, const char *prefix, size_t prefix_len, struct lyd_node *parent,
+        enum LYJSON_PARSER_STATUS *status_p, struct lyd_node **first_p, struct lyd_node **node_p)
 {
     LY_ERR ret = LY_SUCCESS;
     enum LYJSON_PARSER_STATUS status_inner;
@@ -988,15 +989,16 @@
         }
 
         /* backup parser options to parse unknown metadata as opaq nodes and try to resolve them later */
-        prev_opts = lydctx->parse_options;
-        lydctx->parse_options &= ~LYD_PARSE_STRICT;
-        lydctx->parse_options |= LYD_PARSE_OPAQ;
+        prev_opts = lydctx->parse_opts;
+        lydctx->parse_opts &= ~LYD_PARSE_STRICT;
+        lydctx->parse_opts |= LYD_PARSE_OPAQ;
 
         ret = lydjson_parse_opaq(lydctx, prefix ? prefix - 1 : name - 1, prefix ? prefix_len + name_len + 2 : name_len + 1,
-                NULL, 0, parent, status_p, status_inner == LYJSON_ERROR ? status_p : &status_inner, first_p, node_p);
+                NULL, 0, (struct lyd_node_inner *)parent, status_p, status_inner == LYJSON_ERROR ? status_p : &status_inner,
+                first_p, node_p);
 
         /* restore the parser options */
-        lydctx->parse_options = prev_opts;
+        lydctx->parse_opts = prev_opts;
     } else {
         ret = lydjson_metadata(lydctx, snode, attr_node);
     }
@@ -1056,13 +1058,13 @@
 
             /* process children */
             while (*status != LYJSON_OBJECT_CLOSED && *status != LYJSON_OBJECT_EMPTY) {
-                ret = lydjson_subtree_r(lydctx, (struct lyd_node_inner *)*node, lyd_node_children_p(*node));
+                ret = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL);
                 LY_CHECK_ERR_RET(ret, LOG_LOCBACK(1, 1, 0, 0), ret);
                 *status = lyjson_ctx_status(lydctx->jsonctx, 0);
             }
 
             /* finish linking metadata */
-            ret = lydjson_metadata_finish(lydctx, lyd_node_children_p(*node));
+            ret = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node));
             LY_CHECK_ERR_RET(ret, LOG_LOCBACK(1, 1, 0, 0), ret);
 
             if (snode->nodetype == LYS_LIST) {
@@ -1071,14 +1073,14 @@
                 LY_CHECK_ERR_RET(ret, LOG_LOCBACK(1, 1, 0, 0), ret);
             }
 
-            if (!(lydctx->parse_options & LYD_PARSE_ONLY)) {
+            if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
                 /* new node validation, autodelete CANNOT occur, all nodes are new */
-                ret = lyd_validate_new(lyd_node_children_p(*node), snode, NULL, NULL);
+                ret = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, NULL);
                 LY_CHECK_ERR_RET(ret, LOG_LOCBACK(1, 1, 0, 0), ret);
 
                 /* add any missing default children */
-                ret = lyd_new_implicit_r(*node, lyd_node_children_p(*node), NULL, NULL, &lydctx->node_types,
-                        &lydctx->node_when, (lydctx->validate_options & LYD_VALIDATE_NO_STATE) ?
+                ret = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_types,
+                        &lydctx->node_when, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ?
                         LYD_IMPLICIT_NO_STATE : 0, NULL);
                 LY_CHECK_ERR_RET(ret, LOG_LOCBACK(1, 1, 0, 0), ret);
             }
@@ -1094,19 +1096,19 @@
 
             /* parse any data tree with correct options */
             /* first backup the current options and then make the parser to process data as opaq nodes */
-            prev_opts = lydctx->parse_options;
-            lydctx->parse_options &= ~LYD_PARSE_STRICT;
-            lydctx->parse_options |= LYD_PARSE_OPAQ;
+            prev_opts = lydctx->parse_opts;
+            lydctx->parse_opts &= ~LYD_PARSE_STRICT;
+            lydctx->parse_opts |= LYD_PARSE_OPAQ;
 
             /* process the anydata content */
             while (*status != LYJSON_OBJECT_CLOSED && *status != LYJSON_OBJECT_EMPTY) {
-                ret = lydjson_subtree_r(lydctx, NULL, &tree);
+                ret = lydjson_subtree_r(lydctx, NULL, &tree, NULL);
                 LY_CHECK_RET(ret);
                 *status = lyjson_ctx_status(lydctx->jsonctx, 0);
             }
 
             /* restore parser options */
-            lydctx->parse_options = prev_opts;
+            lydctx->parse_opts = prev_opts;
 
             /* finish linking metadata */
             ret = lydjson_metadata_finish(lydctx, &tree);
@@ -1137,10 +1139,11 @@
  * @param[in] lydctx JSON data parser context.
  * @param[in] parent Data parent of the subtree, must be set if @p first is not.
  * @param[in,out] first_p Pointer to the variable holding the first top-level sibling, must be set if @p parent is not.
+ * @param[in,out] parsed Optional set to add all the parsed siblings into.
  * @return LY_ERR value.
  */
 static LY_ERR
-lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node_inner *parent, struct lyd_node **first_p)
+lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
 {
     LY_ERR ret = LY_SUCCESS;
     enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(lydctx->jsonctx, 0);
@@ -1161,7 +1164,7 @@
 
     if (!is_meta || name_len || prefix_len) {
         /* get the schema node */
-        ret = lydjson_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, parent, &snode);
+        ret = lydjson_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, (struct lyd_node_inner *)parent, &snode);
         if (ret == LY_ENOT) {
             /* skip element with children */
             ret = lydjson_data_skip(lydctx->jsonctx);
@@ -1188,7 +1191,7 @@
                 ret = LY_EVALID;
                 goto cleanup;
             }
-            attr_node = &parent->node;
+            attr_node = parent;
             snode = attr_node->schema;
         }
         ret = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status,
@@ -1196,7 +1199,7 @@
         LY_CHECK_GOTO(ret, cleanup);
     } else if (!snode) {
         /* parse as an opaq node */
-        assert((lydctx->parse_options & LYD_PARSE_OPAQ) || (lydctx->int_opts));
+        assert((lydctx->parse_opts & LYD_PARSE_OPAQ) || (lydctx->int_opts));
 
         /* move to the second item in the name/X pair */
         ret = lyjson_ctx_next(lydctx->jsonctx, &status);
@@ -1211,8 +1214,8 @@
             status_inner = LYJSON_ERROR;
         }
 
-        ret = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len,
-                parent, &status, status_inner == LYJSON_ERROR ? &status : &status_inner, first_p, &node);
+        ret = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, (struct lyd_node_inner *)parent, &status,
+                status_inner == LYJSON_ERROR ? &status : &status_inner, first_p, &node);
         LY_CHECK_GOTO(ret, cleanup);
     } else {
         /* parse as a standard lyd_node but it can still turn out to be an opaque node */
@@ -1239,10 +1242,10 @@
 
             /* process all the values/objects */
             do {
-                lydjson_maintain_children(parent, first_p, &node);
+                lydjson_maintain_children((struct lyd_node_inner *)parent, first_p, &node);
 
-                ret = lydjson_parse_instance(lydctx, parent, first_p, snode, name, name_len, prefix, prefix_len,
-                        &status, &node);
+                ret = lydjson_parse_instance(lydctx, (struct lyd_node_inner *)parent, first_p, snode, name, name_len,
+                        prefix, prefix_len, &status, &node);
                 if (ret == LY_EINVAL) {
                     goto representation_error;
                 } else if (ret) {
@@ -1273,7 +1276,8 @@
             }
 
             /* process the value/object */
-            ret = lydjson_parse_instance(lydctx, parent, first_p, snode, name, name_len, prefix, prefix_len, &status, &node);
+            ret = lydjson_parse_instance(lydctx, (struct lyd_node_inner *)parent, first_p, snode, name, name_len,
+                    prefix, prefix_len, &status, &node);
             if (ret == LY_EINVAL) {
                 goto representation_error;
             } else if (ret) {
@@ -1290,13 +1294,18 @@
     }
 
     /* finally connect the parsed node */
-    lydjson_maintain_children(parent, first_p, &node);
+    lydjson_maintain_children((struct lyd_node_inner *)parent, first_p, &node);
+
+    /* rememeber a successfully parsed node */
+    if (parsed) {
+        ly_set_add(parsed, node, 1, NULL);
+    }
 
     /* success */
     goto cleanup;
 
 representation_error:
-    LOG_LOCSET(NULL, &parent->node, NULL, NULL);
+    LOG_LOCSET(NULL, parent, NULL, NULL);
     LOGVAL(ctx, LYVE_SYNTAX_JSON, "The %s \"%s\" is expected to be represented as JSON %s, but input data contains name/%s.",
             lys_nodetype2str(snode->nodetype), snode->name, expected, lyjson_token2str(status));
     LOG_LOCBACK(0, parent ? 1 : 0, 0, 0);
@@ -1312,14 +1321,14 @@
  *
  * @param[in] ctx libyang context
  * @param[in] in Input structure.
- * @param[in] parse_options Options for parser, see @ref dataparseroptions.
- * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
+ * @param[in] parse_opts Options for parser, see @ref dataparseroptions.
+ * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions.
  * @param[out] lydctx_p Data parser context to finish validation.
  * @param[out] status Storage for the current context's status
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
+lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts,
         struct lyd_json_ctx **lydctx_p, enum LYJSON_PARSER_STATUS *status)
 {
     LY_ERR ret = LY_SUCCESS;
@@ -1332,8 +1341,8 @@
     /* init context */
     lydctx = calloc(1, sizeof *lydctx);
     LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM);
-    lydctx->parse_options = parse_options;
-    lydctx->validate_options = validate_options;
+    lydctx->parse_opts = parse_opts;
+    lydctx->val_opts = val_opts;
     lydctx->free = lyd_json_ctx_free;
 
     /* starting top-level */
@@ -1360,394 +1369,79 @@
 }
 
 LY_ERR
-lyd_parse_json_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p)
+lyd_parse_json(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct ly_set *parsed, struct lyd_ctx **lydctx_p)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR rc = LY_SUCCESS;
     struct lyd_json_ctx *lydctx = NULL;
     enum LYJSON_PARSER_STATUS status;
+    uint32_t int_opts;
 
-    assert(tree_p);
-    *tree_p = NULL;
-
-    ret = lyd_parse_json_init(ctx, in, parse_options, validate_options, &lydctx, &status);
-    LY_CHECK_GOTO(ret || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup);
+    rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx, &status);
+    LY_CHECK_GOTO(rc || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup);
 
     assert(status == LYJSON_OBJECT);
 
+    switch (data_type) {
+    case LYD_TYPE_YANG_DATA:
+        int_opts = LYD_INTOPT_WITH_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_RPC:
+        int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_NOTIF:
+        int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_REPLY:
+        int_opts = LYD_INTOPT_REPLY | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    default:
+        LOGINT(ctx);
+        rc = LY_EINT;
+        goto cleanup;
+    }
+    lydctx->int_opts = int_opts;
+
+    /* find the operation node if it exists already */
+    LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup);
+
     /* read subtree(s) */
-    while (lydctx->jsonctx->in->current[0] && status != LYJSON_OBJECT_CLOSED) {
-        ret = lydjson_subtree_r(lydctx, NULL, tree_p);
-        LY_CHECK_GOTO(ret, cleanup);
+    while (lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) {
+        rc = lydjson_subtree_r(lydctx, parent, first_p, parsed);
+        LY_CHECK_GOTO(rc, cleanup);
 
         status = lyjson_ctx_status(lydctx->jsonctx, 0);
+
+        if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
+            break;
+        }
+    }
+
+    if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] &&
+            (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) {
+        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
+        rc = LY_EVALID;
+        goto cleanup;
+    }
+    if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) {
+        LOGVAL(ctx, LYVE_DATA, "Missing the operation node.");
+        rc = LY_EVALID;
+        goto cleanup;
     }
 
     /* finish linking metadata */
-    ret = lydjson_metadata_finish(lydctx, tree_p);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p);
+    LY_CHECK_GOTO(rc, cleanup);
 
 cleanup:
     /* there should be no unresolved types stored */
-    assert(!(parse_options & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
+    assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
             !lydctx->node_when.count));
 
-    if (ret) {
+    if (rc) {
         lyd_json_ctx_free((struct lyd_ctx *)lydctx);
-        lyd_free_all(*tree_p);
-        *tree_p = NULL;
     } else {
         *lydctx_p = (struct lyd_ctx *)lydctx;
     }
-
-    return ret;
-}
-
-#if 0
-/**
- * @brief Parse optional JSON envelope around the Notification data, including the eventTime data.
- *
- * @param[in] jsonctx JSON parser context
- * @param[out] envp_p Pointer to the created envelope opaq container.
- * @param[out] status Storage for the current context's status
- * @return LY_SUCCESS if the envelope present and successfully parsed.
- * @return LY_ENOT in case there is not the expected envelope.
- * @return LY_ERR in case of parsing failure.
- */
-static LY_ERR
-lydjson_notif_envelope(struct lyjson_ctx *jsonctx, struct lyd_node **envp_p, enum LYJSON_PARSER_STATUS *status_p)
-{
-    LY_ERR ret = LY_ENOT, r;
-    const char *name, *prefix, *value = NULL;
-    size_t name_len, prefix_len, value_len;
-    ly_bool is_attr, dynamic = 0;
-    enum LYJSON_PARSER_STATUS status;
-    struct lyd_node *et;
-
-    *envp_p = NULL;
-
-    /* backup the context */
-    lyjson_ctx_backup(jsonctx);
-
-    /* "notification" envelope */
-    lydjson_parse_name(jsonctx->value, jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr);
-    LY_CHECK_GOTO(is_attr, cleanup);
-    LY_CHECK_GOTO(prefix_len != 13 || strncmp(prefix, "ietf-restconf", 13), cleanup);
-    LY_CHECK_GOTO(name_len != 12 || strncmp(name, "notification", name_len), cleanup);
-
-    r = lyjson_ctx_next(jsonctx, &status);
-    LY_CHECK_ERR_GOTO(r, ret = r, cleanup);
-    LY_CHECK_GOTO(status != LYJSON_OBJECT, cleanup);
-
-    /* "eventTime" child */
-    lydjson_parse_name(jsonctx->value, jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr);
-    LY_CHECK_GOTO(prefix_len || is_attr, cleanup);
-    LY_CHECK_GOTO(name_len != 9 || strncmp(name, "eventTime", name_len), cleanup);
-
-    /* go for the data */
-    r = lyjson_ctx_next(jsonctx, &status);
-    LY_CHECK_ERR_GOTO(r, ret = r, cleanup);
-    LY_CHECK_GOTO(status != LYJSON_STRING, cleanup);
-
-    value = jsonctx->value;
-    value_len = jsonctx->value_len;
-    dynamic = jsonctx->dynamic;
-    jsonctx->dynamic = 0;
-
-    r = lyjson_ctx_next(jsonctx, &status);
-    LY_CHECK_ERR_GOTO(r, ret = r, cleanup);
-    LY_CHECK_GOTO(status != LYJSON_OBJECT, cleanup);
-    /* now the notificationContent is expected, which will be parsed by the caller */
-
-    /* create notification envelope */
-    ret = lyd_create_opaq(jsonctx->ctx, "notification", ly_strlen_const("notification"), NULL, 0,
-            "ietf-restconf", ly_strlen_const("ietf-restconf"), "", ly_strlen_const(""), NULL, LY_PREF_JSON, NULL,
-            LYD_NODEHINT_ENVELOPE, envp_p);
-    LY_CHECK_GOTO(ret, cleanup);
-    /* create notification envelope */
-    ret = lyd_create_opaq(jsonctx->ctx, "eventTime", ly_strlen_const("eventTime"), NULL, 0,
-            "ietf-restconf", ly_strlen_const("ietf-restconf"), value, value_len, &dynamic, LY_PREF_JSON, NULL,
-            LYD_VALHINT_STRING, &et);
-    LY_CHECK_GOTO(ret, cleanup);
-    /* insert eventTime into notification */
-    lyd_insert_node(*envp_p, NULL, et);
-
-    ret = LY_SUCCESS;
-    *status_p = status;
-cleanup:
-    if (ret) {
-        /* restore the context */
-        lyjson_ctx_restore(jsonctx);
-        if (dynamic) {
-            free((char *)value);
-        }
-    }
-    return ret;
-}
-
-#endif
-
-LY_ERR
-lyd_parse_json_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_json_ctx *lydctx = NULL;
-    struct lyd_node *tree = NULL;
-    enum LYJSON_PARSER_STATUS status;
-
-    /* init */
-    ret = lyd_parse_json_init(ctx, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, &lydctx, &status);
-    LY_CHECK_GOTO(ret || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup);
-
-    lydctx->int_opts = LYD_INTOPT_NOTIF;
-
-#if 0
-    /* parse "notification" and "eventTime", if present */
-    ret = lydjson_notif_envelope(lydctx->jsonctx, &ntf_e, &status);
-    if (ret == LY_ENOT) {
-        ret = LY_SUCCESS;
-    } else if (ret) {
-        goto cleanup;
-    }
-#endif
-
-    assert(status == LYJSON_OBJECT);
-
-    /* read subtree */
-    ret = lydjson_subtree_r(lydctx, NULL, &tree);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* finish linking metadata */
-    ret = lydjson_metadata_finish(lydctx, &tree);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* make sure we have parsed some notification */
-    if (!lydctx->op_node) {
-        LOGVAL(ctx, LYVE_DATA, "Missing the \"notification\" node.");
-        ret = LY_EVALID;
-        goto cleanup;
-    } else if (lydctx->jsonctx->in->current[0] && (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) {
-        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling element of \"%s\".",
-                tree->schema->name);
-        ret = LY_EVALID;
-        goto cleanup;
-    }
-
-    if (ntf_p) {
-        *ntf_p = lydctx->op_node;
-    }
-    assert(tree);
-    if (tree_p) {
-        *tree_p = tree;
-    }
-
-cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count));
-
-    lyd_json_ctx_free((struct lyd_ctx *)lydctx);
-    if (ret) {
-        lyd_free_all(tree);
-    }
-    return ret;
-}
-
-#if 0
-/**
- * @brief Parse optional JSON envelope around the processed content.
- *
- * @param[in] jsonctx JSON parser context
- * @param[in] parent Parent node (some other envelope).
- * @param[in] module_key Name of the module where the envelope element is expected.
- * @param[in] object_id Name of the envelope object.
- * @param[out] envp_p Pointer to the created envelope opaq container.
- * @param[out] status Storage for the current context's status
- * @return LY_SUCCESS if the envelope present and successfully parsed.
- * @return LY_ENOT in case there is not the expected envelope.
- * @return LY_ERR in case of parsing failure.
- */
-static LY_ERR
-lydjson_object_envelope(struct lyjson_ctx *jsonctx, struct lyd_node *parent, const char *module_key,
-        const char *object_id, struct lyd_node **envp_p, enum LYJSON_PARSER_STATUS *status)
-{
-    LY_ERR ret = LY_ENOT, r;
-    const char *name, *prefix;
-    size_t name_len, prefix_len;
-    ly_bool is_attr;
-
-    *envp_p = NULL;
-
-    /* "id" envelope */
-    lydjson_parse_name(jsonctx->value, jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr);
-    LY_CHECK_GOTO(is_attr, cleanup);
-    LY_CHECK_GOTO(lydjson_get_node_prefix(parent, prefix, prefix_len, &prefix, &prefix_len), cleanup);
-    LY_CHECK_GOTO(prefix_len != strlen(module_key) || strncmp(prefix, module_key, prefix_len), cleanup);
-    LY_CHECK_GOTO(name_len != strlen(object_id) || strncmp(name, object_id, name_len), cleanup);
-
-    r = lyjson_ctx_next(jsonctx, status);
-    LY_CHECK_ERR_GOTO(r, ret = r, cleanup);
-    LY_CHECK_GOTO(*status != LYJSON_OBJECT, cleanup);
-
-    /* create the object envelope */
-    ret = lyd_create_opaq(jsonctx->ctx, object_id, strlen(object_id), NULL, 0, module_key, ly_strlen(module_key), "", 0,
-            NULL, LY_PREF_JSON, NULL, LYD_NODEHINT_ENVELOPE, envp_p);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    if (parent) {
-        lyd_insert_node(parent, NULL, *envp_p);
-    }
-
-    ret = LY_SUCCESS;
-cleanup:
-    return ret;
-}
-
-static LY_ERR
-lydjson_object_envelope_close(struct lyjson_ctx *jsonctx, const char *object_id, enum LYJSON_PARSER_STATUS *status)
-{
-    LY_CHECK_RET(lyjson_ctx_next(jsonctx, status));
-    if (*status == LYJSON_END) {
-        LOGVAL(jsonctx->ctx, LY_VCODE_EOF);
-        return LY_EVALID;
-    } else if (*status != LYJSON_OBJECT_CLOSED) {
-        LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Unexpected sibling member \"%.*s\" of \"%s\".",
-                jsonctx->value_len, jsonctx->value, object_id);
-        return LY_EVALID;
-    }
-    return LY_SUCCESS;
-}
-
-#endif
-
-LY_ERR
-lyd_parse_json_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_json_ctx *lydctx = NULL;
-    struct lyd_node *tree = NULL;
-    enum LYJSON_PARSER_STATUS status;
-
-    /* init */
-    ret = lyd_parse_json_init(ctx, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, &lydctx, &status);
-    LY_CHECK_GOTO(ret || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup);
-
-    lydctx->int_opts = LYD_INTOPT_RPC;
-
-#if 0
-    /* process envelope(s), if present */
-
-    /* process rpc */
-    ret = lydjson_object_envelope(lydctx->jsonctx, NULL, "ietf-netconf", "rpc", &rpc_e, &status);
-    if (ret == LY_ENOT) {
-        ret = LY_SUCCESS;
-        goto parse_content;
-    } else if (ret) {
-        goto cleanup;
-    }
-    /* process action */
-    ret = lydjson_object_envelope(lydctx->jsonctx, rpc_e, "yang", "action", &act_e, &status);
-    if (ret == LY_ENOT) {
-        ret = LY_SUCCESS;
-        goto parse_content;
-    } else if (ret) {
-        goto cleanup;
-    }
-
-parse_content:
-#endif
-    assert(status == LYJSON_OBJECT);
-
-    /* read subtree(s) */
-    ret = lydjson_subtree_r(lydctx, NULL, &tree);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* finish linking metadata */
-    ret = lydjson_metadata_finish(lydctx, &tree);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* make sure we have parsed some operation */
-    if (!lydctx->op_node) {
-        LOGVAL(ctx, LYVE_DATA, "Missing the rpc/action node.");
-        ret = LY_EVALID;
-        goto cleanup;
-    } else if (lydctx->jsonctx->in->current[0] && (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) {
-        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling element of \"%s\".",
-                tree->schema->name);
-        ret = LY_EVALID;
-        goto cleanup;
-    }
-
-    if (op_p) {
-        *op_p = lydctx->op_node;
-    }
-    assert(tree);
-    if (tree_p) {
-        *tree_p = tree;
-    }
-
-cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count));
-
-    lyd_json_ctx_free((struct lyd_ctx *)lydctx);
-    if (ret) {
-        lyd_free_all(tree);
-    }
-    return ret;
-}
-
-LY_ERR
-lyd_parse_json_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_json_ctx *lydctx = NULL;
-    struct lyd_node *tree = NULL;
-    enum LYJSON_PARSER_STATUS status;
-
-    /* init */
-    ret = lyd_parse_json_init(ctx, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, &lydctx, &status);
-    LY_CHECK_GOTO(ret || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup);
-
-    lydctx->int_opts = LYD_INTOPT_REPLY;
-
-#if 0
-    /* parse "rpc-reply", if any */
-    ret = lydjson_object_envelope(lydctx->jsonctx, NULL, "ietf-netconf", "rpc-reply", &rpcr_e, &status);
-    if (ret == LY_ENOT) {
-        ret = LY_SUCCESS;
-    } else if (ret) {
-        goto cleanup;
-    }
-#endif
-
-    assert(status == LYJSON_OBJECT);
-
-    /* read subtree(s) */
-    while (lydctx->jsonctx->in->current[0] && status != LYJSON_OBJECT_CLOSED) {
-        ret = lydjson_subtree_r(lydctx, NULL, &tree);
-        LY_CHECK_GOTO(ret, cleanup);
-
-        status = lyjson_ctx_status(lydctx->jsonctx, 0);
-    }
-
-    /* finish linking metadata */
-    ret = lydjson_metadata_finish(lydctx, &tree);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    if (op_p) {
-        *op_p = lydctx->op_node;
-    }
-    if (tree_p) {
-        *tree_p = tree;
-    }
-
-cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count));
-
-    lyd_json_ctx_free((struct lyd_ctx *)lydctx);
-    if (ret) {
-        lyd_free_all(tree);
-    }
-    return ret;
+    return rc;
 }
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index f75e57c..e755160 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -358,7 +358,7 @@
         LY_CHECK_GOTO(ret, cleanup);
 
         /* find model */
-        ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_options, &mod);
+        ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_opts, &mod);
         LY_CHECK_GOTO(ret, cleanup);
 
         if (!mod) {
@@ -650,7 +650,7 @@
         }
     }
 
-    if (!sibling && (lybctx->parse_options & LYD_PARSE_STRICT)) {
+    if (!sibling && (lybctx->parse_opts & LYD_PARSE_STRICT)) {
         if (mod) {
             LOGVAL(lybctx->lybctx->ctx, LYVE_REFERENCE, "Failed to find matching hash for a top-level node"
                     " from \"%s\".", mod->name);
@@ -693,7 +693,7 @@
  * @return LY_ERR value.
  */
 static LY_ERR
-lyb_parse_subtree_r(struct lyd_lyb_ctx *lybctx, struct lyd_node_inner *parent, struct lyd_node **first)
+lyb_parse_subtree_r(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
 {
     LY_ERR ret = LY_SUCCESS;
     struct lyd_node *node = NULL, *tree;
@@ -715,7 +715,7 @@
 
     if (!parent) {
         /* top-level, read module name */
-        ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_options, &mod);
+        ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_opts, &mod);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* read hash, find the schema node starting from mod */
@@ -727,7 +727,7 @@
         LY_CHECK_GOTO(ret, cleanup);
     }
 
-    if (!snode && !(lybctx->parse_options & LYD_PARSE_OPAQ)) {
+    if (!snode && !(lybctx->parse_opts & LYD_PARSE_OPAQ)) {
         /* unknown data, skip them */
         lyb_skip_subtree(lybctx->lybctx);
         goto stop_subtree;
@@ -777,7 +777,7 @@
 
         /* process children */
         while (LYB_LAST_SUBTREE(lybctx->lybctx).written) {
-            ret = lyb_parse_subtree_r(lybctx, (struct lyd_node_inner *)node, NULL);
+            ret = lyb_parse_subtree_r(lybctx, node, NULL, NULL);
             LY_CHECK_GOTO(ret, cleanup);
         }
     } else if (snode->nodetype & LYD_NODE_TERM) {
@@ -802,19 +802,18 @@
 
         /* process children */
         while (LYB_LAST_SUBTREE(lybctx->lybctx).written) {
-            ret = lyb_parse_subtree_r(lybctx, (struct lyd_node_inner *)node, NULL);
+            ret = lyb_parse_subtree_r(lybctx, node, NULL, NULL);
             LY_CHECK_GOTO(ret, cleanup);
         }
 
-        if (!(lybctx->parse_options & LYD_PARSE_ONLY)) {
+        if (!(lybctx->parse_opts & LYD_PARSE_ONLY)) {
             /* new node validation, autodelete CANNOT occur, all nodes are new */
-            ret = lyd_validate_new(lyd_node_children_p(node), snode, NULL, NULL);
+            ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL);
             LY_CHECK_GOTO(ret, cleanup);
 
             /* add any missing default children */
-            ret = lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, &lybctx->unres_node_type,
-                    &lybctx->when_check, (lybctx->validate_options & LYD_VALIDATE_NO_STATE) ?
-                    LYD_IMPLICIT_NO_STATE : 0, NULL);
+            ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lybctx->node_types,
+                    &lybctx->node_when, (lybctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
             LY_CHECK_GOTO(ret, cleanup);
         }
 
@@ -903,9 +902,14 @@
     }
 
     /* insert, keep first pointer correct */
-    lyd_insert_node(&parent->node, first, node);
-    while (!parent && (*first)->prev->next) {
-        *first = (*first)->prev;
+    lyd_insert_node(parent, first_p, node);
+    while (!parent && (*first_p)->prev->next) {
+        *first_p = (*first_p)->prev;
+    }
+
+    /* rememeber a successfully parsed node */
+    if (parsed) {
+        ly_set_add(parsed, node, 1, NULL);
     }
     node = NULL;
 
@@ -1013,130 +1017,98 @@
     return LY_SUCCESS;
 }
 
-/**
- * @param[in] ctx libyang context for logging
- * @param[in] parent Parent node where to connect the parsed data, required for reply where the reply data are connected
- * with the request operation
- * @param[in] in Input structure.
- * @param[in] parse_options Options for parser, see @ref dataparseroptions.
- * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions.
- * @param[in] data_type Internal data parser flag to distnguish type of the data to parse (RPC/Reply/Notification/regular data].
- * @param[out] tree_p Parsed data tree. Note that NULL can be a valid result.
- * @param[out] op_p Optional pointer to the actual operation. Useful for action and inner notifications.
- * @param[out] lydctx_p Data parser context to finish validation.
- */
-static LY_ERR
-lyd_parse_lyb_(const struct ly_ctx *ctx, struct lyd_node_inner **parent, struct ly_in *in, uint32_t parse_options,
-        uint32_t validate_options, uint32_t data_type, struct lyd_node **tree_p, struct lyd_node **op_p,
-        struct lyd_ctx **lydctx_p)
+LY_ERR
+lyd_parse_lyb(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct ly_set *parsed, struct lyd_ctx **lydctx_p)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR rc = LY_SUCCESS;
     struct lyd_lyb_ctx *lybctx;
-    struct lyd_node *tree = NULL;
+    uint32_t int_opts;
 
-    assert(!(parse_options & ~LYD_PARSE_OPTS_MASK));
-    assert(!(validate_options & ~LYD_VALIDATE_OPTS_MASK));
+    assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK));
+    assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK));
 
     lybctx = calloc(1, sizeof *lybctx);
     LY_CHECK_ERR_RET(!lybctx, LOGMEM(ctx), LY_EMEM);
     lybctx->lybctx = calloc(1, sizeof *lybctx->lybctx);
-    LY_CHECK_ERR_GOTO(!lybctx->lybctx, LOGMEM(ctx); ret = LY_EMEM, cleanup);
+    LY_CHECK_ERR_GOTO(!lybctx->lybctx, LOGMEM(ctx); rc = LY_EMEM, cleanup);
 
     lybctx->lybctx->in = in;
     lybctx->lybctx->ctx = ctx;
-    lybctx->parse_options = parse_options;
-    lybctx->validate_options = validate_options;
-    lybctx->int_opts = data_type;
+    lybctx->parse_opts = parse_opts;
+    lybctx->val_opts = val_opts;
     lybctx->free = lyd_lyb_ctx_free;
 
+    switch (data_type) {
+    case LYD_TYPE_YANG_DATA:
+        int_opts = LYD_INTOPT_WITH_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_RPC:
+        int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_NOTIF:
+        int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_REPLY:
+        int_opts = LYD_INTOPT_REPLY | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    default:
+        LOGINT(ctx);
+        rc = LY_EINT;
+        goto cleanup;
+    }
+    lybctx->int_opts = int_opts;
+
+    /* find the operation node if it exists already */
+    LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lybctx->op_node), cleanup);
+
     /* read magic number */
-    ret = lyb_parse_magic_number(lybctx->lybctx);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lyb_parse_magic_number(lybctx->lybctx);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* read header */
-    ret = lyb_parse_header(lybctx->lybctx);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lyb_parse_header(lybctx->lybctx);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* read used models */
-    ret = lyb_parse_data_models(lybctx->lybctx, lybctx->parse_options);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lyb_parse_data_models(lybctx->lybctx, lybctx->parse_opts);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* read subtree(s) */
     while (lybctx->lybctx->in->current[0]) {
-        ret = lyb_parse_subtree_r(lybctx, parent ? *parent : NULL, &tree);
-        LY_CHECK_GOTO(ret, cleanup);
+        rc = lyb_parse_subtree_r(lybctx, parent, first_p, parsed);
+        LY_CHECK_GOTO(rc, cleanup);
+
+        if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
+            break;
+        }
+    }
+
+    if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lybctx->lybctx->in->current[0]) {
+        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
+        rc = LY_EVALID;
+        goto cleanup;
+    }
+    if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lybctx->op_node) {
+        LOGVAL(ctx, LYVE_DATA, "Missing the operation node.");
+        rc = LY_EVALID;
+        goto cleanup;
     }
 
     /* read the last zero, parsing finished */
     ly_in_skip(lybctx->lybctx->in, 1);
 
-    if (data_type == (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) {
-        /* make sure we have parsed some operation */
-        if (!lybctx->op_node) {
-            LOGVAL(ctx, LYVE_DATA, "Missing the \"rpc\"/\"action\" node.");
-            ret = LY_EVALID;
-            goto cleanup;
-        }
-
-        if (op_p) {
-            *op_p = lybctx->op_node;
-        }
-        assert(tree);
-
-    } else if (data_type == LYD_INTOPT_NOTIF) {
-        /* make sure we have parsed some notification */
-        if (!lybctx->op_node) {
-            LOGVAL(ctx, LYVE_DATA, "Missing the \"notification\" node.");
-            ret = LY_EVALID;
-            goto cleanup;
-        }
-
-        if (op_p) {
-            *op_p = lybctx->op_node;
-        }
-        assert(tree);
-    }
-
 cleanup:
-    if (ret) {
+    /* there should be no unres stored if validation should be skipped */
+    assert(!(parse_opts & LYD_PARSE_ONLY) || (!lybctx->node_types.count && !lybctx->meta_types.count &&
+            !lybctx->node_when.count));
+
+    if (rc) {
         lyd_lyb_ctx_free((struct lyd_ctx *)lybctx);
-        lyd_free_all(tree);
     } else {
-        if (lydctx_p) {
-            *lydctx_p = (struct lyd_ctx *)lybctx;
-        } else {
-            lyd_lyb_ctx_free((struct lyd_ctx *)lybctx);
-        }
-        if (tree_p) {
-            *tree_p = tree;
-        }
+        *lydctx_p = (struct lyd_ctx *)lybctx;
     }
-    return ret;
-}
-
-LY_ERR
-lyd_parse_lyb_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p)
-{
-    return lyd_parse_lyb_(ctx, NULL, in, parse_options, validate_options, 0, tree_p, NULL, lydctx_p);
-}
-
-LY_ERR
-lyd_parse_lyb_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
-{
-    return lyd_parse_lyb_(ctx, NULL, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, LYD_INTOPT_RPC, tree_p, op_p, NULL);
-}
-
-LY_ERR
-lyd_parse_lyb_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p)
-{
-    return lyd_parse_lyb_(ctx, NULL, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, LYD_INTOPT_NOTIF, tree_p, ntf_p, NULL);
-}
-
-LY_ERR
-lyd_parse_lyb_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
-{
-    return lyd_parse_lyb_(ctx, NULL, in, LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, LYD_INTOPT_REPLY, tree_p, op_p, NULL);
+    return rc;
 }
 
 API int
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 2c96b6d..4228dea 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -36,8 +36,8 @@
  * Note that the structure maps to the lyd_ctx which is common for all the data parsers
  */
 struct lyd_xml_ctx {
-    uint32_t parse_options;        /**< various @ref dataparseroptions. */
-    uint32_t validate_options;     /**< various @ref datavalidationoptions. */
+    uint32_t parse_opts;           /**< various @ref dataparseroptions. */
+    uint32_t val_opts;             /**< various @ref datavalidationoptions. */
     uint32_t int_opts;             /**< internal data parser options */
     uint32_t path_len;             /**< used bytes in the path buffer */
     char path[LYD_PARSER_BUFSIZE]; /**< buffer for the generated path */
@@ -78,7 +78,7 @@
         if (!xmlctx->prefix_len) {
             /* in XML, all attributes must be prefixed
              * TODO exception for NETCONF filters which are supposed to map to the ietf-netconf without prefix */
-            if (lydctx->parse_options & LYD_PARSE_STRICT) {
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                 LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Missing mandatory prefix for XML metadata \"%.*s\".",
                         xmlctx->name_len, xmlctx->name);
                 goto cleanup;
@@ -101,7 +101,7 @@
         mod = ly_ctx_get_module_implemented_ns(xmlctx->ctx, ns->uri);
         if (!mod) {
             /* module is not implemented or not present in the schema */
-            if (lydctx->parse_options & LYD_PARSE_STRICT) {
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                 LOGVAL(xmlctx->ctx, LYVE_REFERENCE,
                         "Unknown (or not implemented) YANG module with namespace \"%s\" for metadata \"%.*s%s%.*s\".",
                         ns->uri, xmlctx->prefix_len, xmlctx->prefix, xmlctx->prefix_len ? ":" : "", xmlctx->name_len,
@@ -308,7 +308,7 @@
     size_t pprefix_len, pname_len;
     struct lyxml_ctx *xmlctx = lydctx->xmlctx;
 
-    if ((lydctx->parse_options & LYD_PARSE_OPAQ) && ((*snode)->nodetype & (LYD_NODE_TERM | LYS_LIST))) {
+    if ((lydctx->parse_opts & LYD_PARSE_OPAQ) && ((*snode)->nodetype & (LYD_NODE_TERM | LYS_LIST))) {
         /* backup parser */
         prev_status = xmlctx->status;
         pprefix = xmlctx->prefix;
@@ -362,12 +362,14 @@
  * @brief Parse XML subtree.
  *
  * @param[in] lydctx XML YANG data parser context.
- * @param[in] parent Parent node where the children are inserted. NULL in case of parsing top-level elements.
- * @param[out] node Resulting list of the parsed nodes.
+ * @param[in,out] parent Parent node where the children are inserted. NULL in case of parsing top-level elements.
+ * @param[in,out] first_p Pointer to the first (@p parent or top-level) child. In case there were already some siblings,
+ * this may point to a previously existing node.
+ * @param[in,out] parsed Optional set to add all the parsed siblings into.
  * @return LY_ERR value.
  */
 static LY_ERR
-lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node_inner *parent, struct lyd_node **first_p)
+lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
 {
     LY_ERR ret = LY_SUCCESS;
     const char *prefix, *name;
@@ -385,6 +387,8 @@
     LY_PREFIX_FORMAT format;
     uint32_t getnext_opts;
 
+    assert(parent || first_p);
+
     xmlctx = lydctx->xmlctx;
     ctx = xmlctx->ctx;
     getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0;
@@ -406,12 +410,12 @@
     }
     mod = ly_ctx_get_module_implemented_ns(ctx, ns->uri);
     if (!mod) {
-        if (lydctx->parse_options & LYD_PARSE_STRICT) {
+        if (lydctx->parse_opts & LYD_PARSE_STRICT) {
             LOGVAL(ctx, LYVE_REFERENCE, "No module with namespace \"%s\" in the context.", ns->uri);
             ret = LY_EVALID;
             goto error;
         }
-        if (!(lydctx->parse_options & LYD_PARSE_OPAQ)) {
+        if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
             /* skip element with children */
             LY_CHECK_GOTO(ret = lydxml_data_skip(xmlctx), error);
             return LY_SUCCESS;
@@ -426,11 +430,17 @@
     if (mod && (!parent || parent->schema)) {
         snode = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, getnext_opts);
         if (!snode) {
-            if (lydctx->parse_options & LYD_PARSE_STRICT) {
-                LOGVAL(ctx, LYVE_REFERENCE, "Element \"%.*s\" not found in the \"%s\" module.", name_len, name, mod->name);
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
+                if (parent) {
+                    LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found as a child of \"%s\" node.", name_len, name,
+                            parent->schema->name);
+                } else {
+                    LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", name_len, name,
+                            mod->name);
+                }
                 ret = LY_EVALID;
                 goto error;
-            } else if (!(lydctx->parse_options & LYD_PARSE_OPAQ)) {
+            } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
                 /* skip element with children */
                 LY_CHECK_GOTO(ret = lydxml_data_skip(xmlctx), error);
                 return LY_SUCCESS;
@@ -448,7 +458,7 @@
             ret = lydxml_metadata(lydctx, &meta);
             LY_CHECK_GOTO(ret, error);
         } else {
-            assert(lydctx->parse_options & LYD_PARSE_OPAQ);
+            assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
             ret = lydxml_attrs(xmlctx, &attr);
             LY_CHECK_GOTO(ret, error);
         }
@@ -456,7 +466,7 @@
 
     assert(xmlctx->status == LYXML_ELEM_CONTENT);
     if (!snode) {
-        assert(lydctx->parse_options & LYD_PARSE_OPAQ);
+        assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
 
         if (xmlctx->ws_only) {
             /* ignore WS-only value */
@@ -480,7 +490,7 @@
 
         /* process children */
         while (xmlctx->status == LYXML_ELEMENT) {
-            ret = lydxml_subtree_r(lydctx, (struct lyd_node_inner *)node, lyd_node_children_p(node));
+            ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL);
             LY_CHECK_GOTO(ret, error);
         }
     } else if (snode->nodetype & LYD_NODE_TERM) {
@@ -491,9 +501,9 @@
 
         if (parent && (node->schema->flags & LYS_KEY)) {
             /* check the key order, the anchor must never be a key */
-            anchor = lyd_insert_get_next_anchor(parent->child, node);
+            anchor = lyd_insert_get_next_anchor(lyd_child(parent), node);
             if (anchor && (anchor->schema->flags & LYS_KEY)) {
-                if (lydctx->parse_options & LYD_PARSE_STRICT) {
+                if (lydctx->parse_opts & LYD_PARSE_STRICT) {
                     LOGVAL(ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", node->schema->name);
                     ret = LY_EVALID;
                     goto error;
@@ -533,7 +543,7 @@
 
         /* process children */
         while (xmlctx->status == LYXML_ELEMENT) {
-            ret = lydxml_subtree_r(lydctx, (struct lyd_node_inner *)node, lyd_node_children_p(node));
+            ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL);
             LY_CHECK_GOTO(ret, error);
         }
 
@@ -542,14 +552,14 @@
             LY_CHECK_GOTO(ret = lyd_parse_check_keys(node), error);
         }
 
-        if (!(lydctx->parse_options & LYD_PARSE_ONLY)) {
+        if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
             /* new node validation, autodelete CANNOT occur, all nodes are new */
-            ret = lyd_validate_new(lyd_node_children_p(node), snode, NULL, NULL);
+            ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL);
             LY_CHECK_GOTO(ret, error);
 
             /* add any missing default children */
-            ret = lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, &lydctx->node_types, &lydctx->node_when,
-                    (lydctx->validate_options & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
+            ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lydctx->node_types, &lydctx->node_when,
+                    (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
             LY_CHECK_GOTO(ret, error);
         }
 
@@ -570,15 +580,15 @@
         LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
 
         /* parse any data tree with correct options */
-        prev_opts = lydctx->parse_options;
-        lydctx->parse_options &= ~LYD_PARSE_STRICT;
-        lydctx->parse_options |= LYD_PARSE_OPAQ;
+        prev_opts = lydctx->parse_opts;
+        lydctx->parse_opts &= ~LYD_PARSE_STRICT;
+        lydctx->parse_opts |= LYD_PARSE_OPAQ;
         anchor = NULL;
         while (xmlctx->status == LYXML_ELEMENT) {
-            ret = lydxml_subtree_r(lydctx, NULL, &anchor);
-            LY_CHECK_ERR_GOTO(ret, lydctx->parse_options = prev_opts, error);
+            ret = lydxml_subtree_r(lydctx, NULL, &anchor, NULL);
+            LY_CHECK_ERR_GOTO(ret, lydctx->parse_opts = prev_opts, error);
         }
-        lydctx->parse_options = prev_opts;
+        lydctx->parse_opts = prev_opts;
 
         /* create node */
         ret = lyd_create_any(snode, anchor, LYD_ANYDATA_DATATREE, 1, &node);
@@ -588,7 +598,7 @@
 
     /* add/correct flags */
     if (snode) {
-        lyd_parse_set_data_flags(node, &lydctx->node_when, &meta, lydctx->parse_options);
+        lyd_parse_set_data_flags(node, &lydctx->node_when, &meta, lydctx->parse_opts);
     }
 
     /* parser next */
@@ -603,11 +613,16 @@
     }
 
     /* insert, keep first pointer correct */
-    lyd_insert_node(&parent->node, first_p, node);
+    lyd_insert_node(parent, first_p, node);
     while (!parent && (*first_p)->prev->next) {
         *first_p = (*first_p)->prev;
     }
 
+    /* rememeber a successfully parsed node */
+    if (parsed) {
+        ly_set_add(parsed, node, 1, NULL);
+    }
+
     LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0);
     return LY_SUCCESS;
 
@@ -619,193 +634,31 @@
     return ret;
 }
 
-LY_ERR
-lyd_parse_xml_data(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree_p, struct lyd_ctx **lydctx_p)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_xml_ctx *lydctx;
-
-    assert(!(parse_options & ~LYD_PARSE_OPTS_MASK));
-    assert(!(validate_options & ~LYD_VALIDATE_OPTS_MASK));
-
-    /* init context */
-    lydctx = calloc(1, sizeof *lydctx);
-    LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM);
-    LY_CHECK_GOTO(ret = lyxml_ctx_new(ctx, in, &lydctx->xmlctx), cleanup);
-    lydctx->parse_options = parse_options;
-    lydctx->validate_options = validate_options;
-    lydctx->free = lyd_xml_ctx_free;
-
-    /* parse XML data */
-    while (lydctx->xmlctx->status == LYXML_ELEMENT) {
-        LY_CHECK_GOTO(ret = lydxml_subtree_r(lydctx, NULL, tree_p), cleanup);
-    }
-
-cleanup:
-    /* there should be no unres stored if validation should be skipped */
-    assert(!(parse_options & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
-            !lydctx->node_when.count));
-
-    if (ret) {
-        lyd_xml_ctx_free((struct lyd_ctx *)lydctx);
-        lyd_free_all(*tree_p);
-        *tree_p = NULL;
-    } else {
-        *lydctx_p = (struct lyd_ctx *)lydctx;
-
-        /* the XML context is no more needed, freeing it also stops logging line numbers which would be confusing now */
-        lyxml_ctx_free(lydctx->xmlctx);
-        lydctx->xmlctx = NULL;
-    }
-    return ret;
-}
-
-#if 0
+/**
+ * @brief Parse a specific XML element into an opaque node.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[in] name Name of the element.
+ * @param[in] uri URI of the element.
+ * @param[in] value Whether a value is expected in the element.
+ * @param[out] evnp Parsed envelope (opaque node).
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOT if the specified element did not match.
+ * @return LY_ERR value on error.
+ */
 static LY_ERR
-lydxml_envelope(struct lyxml_ctx *xmlctx, const char *name, const char *uri, struct lyd_node **envp)
+lydxml_envelope(struct lyxml_ctx *xmlctx, const char *name, const char *uri, ly_bool value, struct lyd_node **envp)
 {
-    LY_ERR ret = LY_SUCCESS;
-    const struct lyxml_ns *ns = NULL;
+    LY_ERR rc = LY_SUCCESS;
+    const struct lyxml_ns *ns;
     struct lyd_attr *attr = NULL;
     const char *prefix;
     size_t prefix_len;
 
-    *envp = NULL;
-
     assert(xmlctx->status == LYXML_ELEMENT);
     if (ly_strncmp(name, xmlctx->name, xmlctx->name_len)) {
         /* not the expected element */
-        return LY_SUCCESS;
-    }
-
-    prefix = xmlctx->prefix;
-    prefix_len = xmlctx->prefix_len;
-    ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
-    if (!ns) {
-        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".",
-                prefix_len, prefix);
-        return LY_EVALID;
-    } else if (strcmp(ns->uri, uri)) {
-        /* different namespace */
-        return LY_SUCCESS;
-    }
-
-    LY_CHECK_RET(lyxml_ctx_next(xmlctx));
-
-    /* create attributes */
-    if (xmlctx->status == LYXML_ATTRIBUTE) {
-        LY_CHECK_RET(lydxml_attrs(xmlctx, &attr));
-    }
-
-    if (!xmlctx->ws_only) {
-        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected value \"%.*s\" in the \"%s\" element.",
-                xmlctx->value_len, xmlctx->value, name);
-        ret = LY_EVALID;
-        goto cleanup;
-    }
-
-    /* parser next element */
-    LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
-
-    /* create node */
-    ret = lyd_create_opaq(xmlctx->ctx, name, strlen(name), prefix, prefix_len, uri, strlen(uri), "", 0, NULL,
-            LY_PREF_XML, NULL, LYD_NODEHINT_ENVELOPE, envp);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* assign atributes */
-    ((struct lyd_node_opaq *)(*envp))->attr = attr;
-    attr = NULL;
-
-cleanup:
-    lyd_free_attr_siblings(xmlctx->ctx, attr);
-    return ret;
-}
-
-#endif
-
-LY_ERR
-lyd_parse_xml_rpc(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_xml_ctx lydctx = {0};
-    struct lyd_node *tree = NULL;
-
-    /* init */
-    LY_CHECK_GOTO(ret = lyxml_ctx_new(ctx, in, &lydctx.xmlctx), cleanup);
-    lydctx.parse_options = LYD_PARSE_ONLY | LYD_PARSE_STRICT;
-    lydctx.int_opts = LYD_INTOPT_RPC;
-
-#if 0
-    /* parse "rpc", if any */
-    LY_CHECK_GOTO(ret = lydxml_envelope(lydctx.xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", &rpc_e), cleanup);
-
-    if (rpc_e) {
-        /* parse "action", if any */
-        LY_CHECK_GOTO(ret = lydxml_envelope(lydctx.xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", &act_e), cleanup);
-    }
-#endif
-
-    /* parse the rest of data normally */
-    LY_CHECK_GOTO(ret = lydxml_subtree_r(&lydctx, NULL, &tree), cleanup);
-
-    /* make sure we have parsed some operation and it is the only subtree */
-    if (!lydctx.op_node) {
-        LOGVAL(ctx, LYVE_DATA, "Missing the \"rpc\"/\"action\" node.");
-        ret = LY_EVALID;
-        goto cleanup;
-    } else if (lydctx.xmlctx->status == LYXML_ELEMENT) {
-        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling element of \"%s\".",
-                tree->schema->name);
-        ret = LY_EVALID;
-        goto cleanup;
-    }
-
-    if (op_p) {
-        *op_p = lydctx.op_node;
-    }
-    assert(tree);
-    if (tree_p) {
-        *tree_p = tree;
-    }
-
-cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx.node_types.count && !lydctx.meta_types.count && !lydctx.node_when.count);
-    lyxml_ctx_free(lydctx.xmlctx);
-    if (ret) {
-        lyd_free_all(tree);
-    }
-    return ret;
-}
-
-#if 0
-static LY_ERR
-lydxml_notif_envelope(struct lyxml_ctx *xmlctx, struct lyd_node **envp)
-{
-    LY_ERR ret = LY_SUCCESS;
-    const struct lyxml_ns *ns = NULL;
-    struct lyd_attr *attr = NULL;
-    struct lyd_node *et;
-    const char *prefix;
-    size_t prefix_len;
-
-    *envp = NULL;
-
-    /* container envelope */
-    LY_CHECK_GOTO(ret = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0",
-            envp), cleanup);
-
-    /* no envelope, fine */
-    if (!*envp) {
-        goto cleanup;
-    }
-
-    /* child "eventTime" */
-    if ((xmlctx->status != LYXML_ELEMENT) || ly_strncmp("eventTime", xmlctx->name, xmlctx->name_len)) {
-        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Missing the \"eventTime\" element.");
-        ret = LY_EVALID;
-        goto cleanup;
+        return LY_ENOT;
     }
 
     prefix = xmlctx->prefix;
@@ -813,12 +666,10 @@
     ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
     if (!ns) {
         LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", prefix_len, prefix);
-        ret = LY_EVALID;
-        goto cleanup;
-    } else if (strcmp(ns->uri, "urn:ietf:params:xml:ns:netconf:notification:1.0")) {
-        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid namespace \"%s\" of \"eventTime\".", ns->uri);
-        ret = LY_EVALID;
-        goto cleanup;
+        return LY_EVALID;
+    } else if (strcmp(ns->uri, uri)) {
+        /* different namespace */
+        return LY_ENOT;
     }
 
     LY_CHECK_RET(lyxml_ctx_next(xmlctx));
@@ -828,135 +679,761 @@
         LY_CHECK_RET(lydxml_attrs(xmlctx, &attr));
     }
 
-    /* validate value */
-    /* TODO */
-    /*if (!xmlctx->ws_only) {
-        LOGVAL(xmlctx->ctx, LY_VLOG_LINE, &xmlctx->line, LYVE_SYNTAX, "Unexpected value \"%.*s\" in the \"%s\" element.",
-               xmlctx->value_len, xmlctx->value, name);
-        ret = LY_EVALID;
+    assert(xmlctx->status == LYXML_ELEM_CONTENT);
+    if (!value && !xmlctx->ws_only) {
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected value \"%.*s\" in the \"%s\" element.",
+                xmlctx->value_len, xmlctx->value, name);
+        rc = LY_EVALID;
         goto cleanup;
-    }*/
+    }
 
     /* create node */
-    ret = lyd_create_opaq(xmlctx->ctx, "eventTime", ly_strlen_const("eventTime"), prefix, prefix_len,
-            ns->uri, strlen(ns->uri), xmlctx->value, xmlctx->value_len, NULL, LY_PREF_XML, NULL, LYD_NODEHINT_ENVELOPE, &et);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lyd_create_opaq(xmlctx->ctx, name, strlen(name), prefix, prefix_len, uri, strlen(uri), xmlctx->value,
+            xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_PREF_XML, NULL, 0, envp);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* assign atributes */
-    ((struct lyd_node_opaq *)et)->attr = attr;
+    ((struct lyd_node_opaq *)(*envp))->attr = attr;
     attr = NULL;
 
-    /* insert */
-    lyd_insert_node(*envp, NULL, et);
+    /* parser next element */
+    LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup);
 
-    /* finish parsing */
-    LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
+cleanup:
+    lyd_free_attr_siblings(xmlctx->ctx, attr);
+    if (rc) {
+        lyd_free_tree(*envp);
+        *envp = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of a NETCONF rpc message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[out] evnp Parsed envelope(s) (opaque node).
+ * @param[out] int_opts Internal options for parsing the rest of YANG data.
+ * @param[out] close_elem Number of parsed opened elements that need to be closed.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lydxml_env_netconf_rpc(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem)
+{
+    LY_ERR rc = LY_SUCCESS, r;
+    struct lyd_node *child;
+
+    assert(envp && !*envp);
+
+    /* parse "rpc" */
+    r = lydxml_envelope(xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
+    if (r == LY_ENOT) {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"rpc\".", xmlctx->name_len,
+                xmlctx->name);
+        r = LY_EVALID;
+    }
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+    /* parse "action", if any */
+    r = lydxml_envelope(xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", 0, &child);
+    if (r == LY_SUCCESS) {
+        /* insert */
+        lyd_insert_node(*envp, NULL, child);
+
+        /* NETCONF action */
+        *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_ACTION;
+        *close_elem = 2;
+    } else if (r == LY_ENOT) {
+        /* NETCONF RPC */
+        *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_RPC;
+        *close_elem = 1;
+    } else {
+        rc = r;
+        goto cleanup;
+    }
+
+cleanup:
+    if (rc) {
+        lyd_free_tree(*envp);
+        *envp = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of a NETCONF notification message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[out] evnp Parsed envelope(s) (opaque node).
+ * @param[out] int_opts Internal options for parsing the rest of YANG data.
+ * @param[out] close_elem Number of parsed opened elements that need to be closed.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lydxml_env_netconf_notif(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem)
+{
+    LY_ERR rc = LY_SUCCESS, r;
+    struct lyd_node *child;
+
+    assert(envp && !*envp);
+
+    /* parse "notification" */
+    r = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp);
+    if (r == LY_ENOT) {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"notification\".", xmlctx->name_len,
+                xmlctx->name);
+        r = LY_EVALID;
+    }
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+    /* parse "eventTime" */
+    r = lydxml_envelope(xmlctx, "eventTime", "urn:ietf:params:xml:ns:netconf:notification:1.0", 1, &child);
+    if (r == LY_ENOT) {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"eventTime\".", xmlctx->name_len,
+                xmlctx->name);
+        r = LY_EVALID;
+    }
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+    /* insert */
+    lyd_insert_node(*envp, NULL, child);
+
+    /* validate value */
+    /* TODO validate child->value as yang:date-and-time */
+
+    /* finish child parsing */
     if (xmlctx->status != LYXML_ELEM_CLOSE) {
         assert(xmlctx->status == LYXML_ELEMENT);
-        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected sibling element \"%.*s\" of \"eventTime\".",
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"eventTime\".",
                 xmlctx->name_len, xmlctx->name);
-        ret = LY_EVALID;
+        rc = LY_EVALID;
         goto cleanup;
     }
-    LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
+    LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup);
+
+    /* NETCONF notification */
+    *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_NOTIF;
+    *close_elem = 1;
 
 cleanup:
-    if (ret) {
+    if (rc) {
         lyd_free_tree(*envp);
-        lyd_free_attr_siblings(xmlctx->ctx, attr);
+        *envp = NULL;
     }
-    return ret;
+    return rc;
 }
 
-#endif
-
-LY_ERR
-lyd_parse_xml_notif(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **ntf_p)
+/**
+ * @brief Parse an XML element as an opaque node subtree.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[in] parent Parent to append nodes to.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_opaq_r(struct lyxml_ctx *xmlctx, struct lyd_node *parent)
 {
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_xml_ctx lydctx = {0};
-    struct lyd_node *tree = NULL;
+    LY_ERR rc = LY_SUCCESS;
+    const struct lyxml_ns *ns;
+    struct lyd_attr *attr = NULL;
+    struct lyd_node *child = NULL;
+    const char *name, *prefix;
+    size_t name_len, prefix_len;
 
-    /* init */
-    LY_CHECK_GOTO(ret = lyxml_ctx_new(ctx, in, &lydctx.xmlctx), cleanup);
-    lydctx.parse_options = LYD_PARSE_ONLY | LYD_PARSE_STRICT;
-    lydctx.int_opts = LYD_INTOPT_NOTIF;
+    assert(xmlctx->status == LYXML_ELEMENT);
 
-#if 0
-    /* parse "notification" and "eventTime", if present */
-    LY_CHECK_GOTO(ret = lydxml_notif_envelope(lydctx.xmlctx, &ntf_e), cleanup);
-#endif
+    name = xmlctx->name;
+    name_len = xmlctx->name_len;
+    prefix = xmlctx->prefix;
+    prefix_len = xmlctx->prefix_len;
+    ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
+    if (!ns) {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", prefix_len, prefix);
+        return LY_EVALID;
+    }
 
-    /* parse the rest of data normally */
-    LY_CHECK_GOTO(ret = lydxml_subtree_r(&lydctx, NULL, &tree), cleanup);
+    LY_CHECK_RET(lyxml_ctx_next(xmlctx));
 
-    /* make sure we have parsed some notification */
-    if (!lydctx.op_node) {
-        LOGVAL(ctx, LYVE_DATA, "Missing the \"notification\" node.");
-        ret = LY_EVALID;
-        goto cleanup;
-    } else if (lydctx.xmlctx->status == LYXML_ELEMENT) {
-        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling element of \"%s\".",
-                tree->schema->name);
-        ret = LY_EVALID;
+    /* create attributes */
+    if (xmlctx->status == LYXML_ATTRIBUTE) {
+        LY_CHECK_RET(lydxml_attrs(xmlctx, &attr));
+    }
+
+    /* create node */
+    assert(xmlctx->status == LYXML_ELEM_CONTENT);
+    rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns->uri, strlen(ns->uri), xmlctx->value,
+            xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_PREF_XML, NULL, 0, &child);
+    LY_CHECK_GOTO(rc, cleanup);
+
+    /* assign atributes */
+    ((struct lyd_node_opaq *)child)->attr = attr;
+    attr = NULL;
+
+    /* parser next element */
+    LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup);
+
+    /* parse all the descendants */
+    while (xmlctx->status == LYXML_ELEMENT) {
+        rc = lydxml_opaq_r(xmlctx, child);
+        LY_CHECK_GOTO(rc, cleanup);
+    }
+
+    /* insert */
+    lyd_insert_node(parent, NULL, child);
+
+cleanup:
+    lyd_free_attr_siblings(xmlctx->ctx, attr);
+    if (rc) {
+        lyd_free_tree(child);
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of the error-info element in NETCONF rpc-reply message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[in] parent Parent to append nodes to.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_env_netconf_rpc_reply_error_info(struct lyxml_ctx *xmlctx, struct lyd_node *parent)
+{
+    LY_ERR r;
+    struct lyd_node *child, *iter;
+    const struct lyxml_ns *ns;
+    ly_bool no_dup;
+
+    /* there must be some child */
+    if (xmlctx->status == LYXML_ELEM_CLOSE) {
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"error-info\".");
+        return LY_EVALID;
+    }
+
+    while (xmlctx->status == LYXML_ELEMENT) {
+        child = NULL;
+
+        /*
+         * session-id
+         */
+        r = lydxml_envelope(xmlctx, "session-id", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * bad-attribute
+         */
+        r = lydxml_envelope(xmlctx, "bad-attribute", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * bad-element
+         */
+        r = lydxml_envelope(xmlctx, "bad-element", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * bad-namespace
+         */
+        r = lydxml_envelope(xmlctx, "bad-namespace", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        if (r == LY_ENOT) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+
+            /* learn namespace */
+            ns = lyxml_ns_get(&xmlctx->ns, xmlctx->prefix, xmlctx->prefix_len);
+            if (!ns) {
+                LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", xmlctx->prefix_len, xmlctx->prefix);
+                r = LY_EVALID;
+                goto error;
+            } else if (!strcmp(ns->uri, "urn:ietf:params:xml:ns:netconf:base:1.0")) {
+                LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"error-info\".",
+                    xmlctx->name_len, xmlctx->name);
+                r = LY_EVALID;
+                goto error;
+            }
+
+            /* custom elements */
+            r = lydxml_opaq_r(xmlctx, parent);
+            LY_CHECK_GOTO(r, error);
+
+            no_dup = 0;
+        }
+
+check_child:
+        /* check for duplicates */
+        if (no_dup) {
+            LY_LIST_FOR(lyd_child(parent), iter) {
+                if ((((struct lyd_node_opaq *)iter)->name.name == ((struct lyd_node_opaq *)child)->name.name) &&
+                        (((struct lyd_node_opaq *)iter)->name.module_ns == ((struct lyd_node_opaq *)child)->name.module_ns)) {
+                    LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Duplicate element \"%s\" in \"error-info\".",
+                            ((struct lyd_node_opaq *)child)->name.name);
+                    r = LY_EVALID;
+                    goto error;
+                }
+            }
+        }
+
+        /* finish child parsing */
+        if (xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"error-info\".",
+                    xmlctx->name_len, xmlctx->name);
+            r = LY_EVALID;
+            goto error;
+        }
+        LY_CHECK_GOTO(r = lyxml_ctx_next(xmlctx), error);
+
+        /* insert */
+        lyd_insert_node(parent, NULL, child);
+    }
+
+    return LY_SUCCESS;
+
+error:
+    lyd_free_tree(child);
+    return r;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of the rpc-error element in NETCONF rpc-reply message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[in] parent Parent to append nodes to.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_env_netconf_rpc_reply_error(struct lyxml_ctx *xmlctx, struct lyd_node *parent)
+{
+    LY_ERR r;
+    struct lyd_node *child, *iter;
+    const char *val;
+    ly_bool no_dup;
+
+    /* there must be some child */
+    if (xmlctx->status == LYXML_ELEM_CLOSE) {
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"rpc-error\".");
+        return LY_EVALID;
+    }
+
+    while (xmlctx->status == LYXML_ELEMENT) {
+        child = NULL;
+
+        /*
+         * error-type
+         */
+        r = lydxml_envelope(xmlctx, "error-type", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            val = ((struct lyd_node_opaq *)child)->value;
+            if (strcmp(val, "transport") && strcmp(val, "rpc") && strcmp(val, "protocol") && strcmp(val, "application")) {
+                LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val,
+                        ((struct lyd_node_opaq *)child)->name.name);
+                r = LY_EVALID;
+                goto error;
+            }
+
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * error-tag
+         */
+        r = lydxml_envelope(xmlctx, "error-tag", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            val = ((struct lyd_node_opaq *)child)->value;
+            if (strcmp(val, "in-use") && strcmp(val, "invalid-value") && strcmp(val, "too-big") &&
+                    strcmp(val, "missing-attribute") && strcmp(val, "bad-attribute") &&
+                    strcmp(val, "unknown-attribute") && strcmp(val, "missing-element") && strcmp(val, "bad-element") &&
+                    strcmp(val, "unknown-element") && strcmp(val, "unknown-namespace") && strcmp(val, "access-denied") &&
+                    strcmp(val, "lock-denied") && strcmp(val, "resource-denied") && strcmp(val, "rollback-failed") &&
+                    strcmp(val, "data-exists") && strcmp(val, "data-missing") && strcmp(val, "operation-not-supported") &&
+                    strcmp(val, "operation-failed") && strcmp(val, "malformed-message")) {
+                LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val,
+                        ((struct lyd_node_opaq *)child)->name.name);
+                r = LY_EVALID;
+                goto error;
+            }
+
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * error-severity
+         */
+        r = lydxml_envelope(xmlctx, "error-severity", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            val = ((struct lyd_node_opaq *)child)->value;
+            if (strcmp(val, "error") && strcmp(val, "warning")) {
+                LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val,
+                        ((struct lyd_node_opaq *)child)->name.name);
+                r = LY_EVALID;
+                goto error;
+            }
+
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * error-app-tag
+         */
+        r = lydxml_envelope(xmlctx, "error-app-tag", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * error-path
+         */
+        r = lydxml_envelope(xmlctx, "error-path", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /*
+         * error-message
+         */
+        r = lydxml_envelope(xmlctx, "error-message", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child);
+        if (r == LY_SUCCESS) {
+            no_dup = 1;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        /* error-info */
+        r = lydxml_envelope(xmlctx, "error-info", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child);
+        if (r == LY_SUCCESS) {
+            /* parse all the descendants */
+            LY_CHECK_GOTO(r = lydxml_env_netconf_rpc_reply_error_info(xmlctx, child), error);
+
+            no_dup = 0;
+            goto check_child;
+        } else if (r != LY_ENOT) {
+            goto error;
+        }
+
+        if (r == LY_ENOT) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"rpc-error\".",
+                    xmlctx->name_len, xmlctx->name);
+            r = LY_EVALID;
+            goto error;
+        }
+
+check_child:
+        /* check for duplicates */
+        if (no_dup) {
+            LY_LIST_FOR(lyd_child(parent), iter) {
+                if ((((struct lyd_node_opaq *)iter)->name.name == ((struct lyd_node_opaq *)child)->name.name) &&
+                        (((struct lyd_node_opaq *)iter)->name.module_ns == ((struct lyd_node_opaq *)child)->name.module_ns)) {
+                    LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Duplicate element \"%s\" in \"rpc-error\".",
+                            ((struct lyd_node_opaq *)child)->name.name);
+                    r = LY_EVALID;
+                    goto error;
+                }
+            }
+        }
+
+        /* finish child parsing */
+        if (xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"rpc-error\".",
+                    xmlctx->name_len, xmlctx->name);
+            r = LY_EVALID;
+            goto error;
+        }
+        LY_CHECK_GOTO(r = lyxml_ctx_next(xmlctx), error);
+
+        /* insert */
+        lyd_insert_node(parent, NULL, child);
+    }
+
+    return LY_SUCCESS;
+
+error:
+    lyd_free_tree(child);
+    return r;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of a NETCONF rpc-reply message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[out] evnp Parsed envelope(s) (opaque node).
+ * @param[out] int_opts Internal options for parsing the rest of YANG data.
+ * @param[out] close_elem Number of parsed opened elements that need to be closed.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lydxml_env_netconf_reply(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem)
+{
+    LY_ERR rc = LY_SUCCESS, r;
+    struct lyd_node *child = NULL;
+    const char *parsed_elem = NULL;
+
+    assert(envp && !*envp);
+
+    /* parse "rpc-reply" */
+    r = lydxml_envelope(xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
+    if (r == LY_ENOT) {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"rpc-reply\".", xmlctx->name_len,
+                xmlctx->name);
+        r = LY_EVALID;
+    }
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+    /* there must be some child */
+    if (xmlctx->status == LYXML_ELEM_CLOSE) {
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"rpc-reply\".");
+        rc = LY_EVALID;
         goto cleanup;
     }
 
-    if (ntf_p) {
-        *ntf_p = lydctx.op_node;
-    }
-    assert(tree);
-    if (tree_p) {
-        *tree_p = tree;
+    /* try to parse "ok" */
+    r = lydxml_envelope(xmlctx, "ok", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child);
+    if (r == LY_SUCCESS) {
+        /* insert */
+        lyd_insert_node(*envp, NULL, child);
+
+        /* finish child parsing */
+        if (xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"ok\".",
+                    xmlctx->name_len, xmlctx->name);
+            rc = LY_EVALID;
+            goto cleanup;
+        }
+        LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup);
+
+        /* success */
+        parsed_elem = "ok";
+        goto finish;
+    } else if (r != LY_ENOT) {
+        rc = r;
+        goto cleanup;
     }
 
-cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx.node_types.count && !lydctx.meta_types.count && !lydctx.node_when.count);
-    lyxml_ctx_free(lydctx.xmlctx);
-    if (ret) {
-        lyd_free_all(tree);
+    /* try to parse all "rpc-error" elements */
+    while (xmlctx->status == LYXML_ELEMENT) {
+        r = lydxml_envelope(xmlctx, "rpc-error", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child);
+        if (r == LY_ENOT) {
+            break;
+        } else if (r) {
+            rc = r;
+            goto cleanup;
+        }
+
+        /* insert */
+        lyd_insert_node(*envp, NULL, child);
+
+        /* parse all children of "rpc-error" */
+        LY_CHECK_GOTO(rc = lydxml_env_netconf_rpc_reply_error(xmlctx, child), cleanup);
+
+        /* finish child parsing */
+        assert(xmlctx->status == LYXML_ELEM_CLOSE);
+        LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup);
+
+        parsed_elem = "rpc-error";
     }
-    return ret;
+
+finish:
+    if (parsed_elem) {
+        /* NETCONF rpc-reply with no data */
+        if (xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected sibling element \"%.*s\" of \"%s\".",
+                    xmlctx->name_len, xmlctx->name, parsed_elem);
+            rc = LY_EVALID;
+            goto cleanup;
+        }
+    }
+
+    /* NETCONF rpc-reply */
+    *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_REPLY;
+    *close_elem = 1;
+
+cleanup:
+    if (rc) {
+        lyd_free_tree(*envp);
+        *envp = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse all expected non-data XML elements of a NETCONF rpc-reply or notification message.
+ *
+ * @param[in] xmlctx XML parser context.
+ * @param[out] evnp Parsed envelope(s) (opaque node).
+ * @param[out] int_opts Internal options for parsing the rest of YANG data.
+ * @param[out] close_elem Number of parsed opened elements that need to be closed.
+ * @param[out] parent_p Parent to append to the rest of YANG data, may be unset.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lydxml_env_netconf_reply_or_notif(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts,
+        uint32_t *close_elem, struct lyd_node **parent_p)
+{
+    /* is it a "rpc-reply" or a "notification"? */
+    assert(xmlctx->status == LYXML_ELEMENT);
+    if (!ly_strncmp("rpc-reply", xmlctx->name, xmlctx->name_len)) {
+        LY_CHECK_RET(lydxml_env_netconf_reply(xmlctx, envp, int_opts, close_elem));
+    } else if (!ly_strncmp("notification", xmlctx->name, xmlctx->name_len)) {
+        LY_CHECK_RET(lydxml_env_netconf_notif(xmlctx, envp, int_opts, close_elem));
+
+        /* unset parent, the notification starts from top-level */
+        *parent_p = NULL;
+    } else {
+        LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"rpc-reply\" or \"notification\".",
+                xmlctx->name_len, xmlctx->name);
+        return LY_EVALID;
+    }
+
+    return LY_SUCCESS;
 }
 
 LY_ERR
-lyd_parse_xml_reply(const struct ly_ctx *ctx, struct ly_in *in, struct lyd_node **tree_p, struct lyd_node **op_p)
+lyd_parse_xml(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, struct lyd_node **envp, struct ly_set *parsed,
+        struct lyd_ctx **lydctx_p)
 {
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_xml_ctx lydctx = {0};
-    struct lyd_node *tree = NULL;
+    LY_ERR rc = LY_SUCCESS;
+    struct lyd_xml_ctx *lydctx;
+    uint32_t i, int_opts, close_elem = 0;
+    ly_bool parsed_data_nodes = 0;
 
-    /* init */
-    LY_CHECK_GOTO(ret = lyxml_ctx_new(ctx, in, &lydctx.xmlctx), cleanup);
-    lydctx.parse_options = LYD_PARSE_ONLY | LYD_PARSE_STRICT;
-    lydctx.int_opts = LYD_INTOPT_REPLY;
+    assert(ctx && in && lydctx_p);
+    assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK));
+    assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK));
 
-#if 0
-    /* parse "rpc-reply", if any */
-    LY_CHECK_GOTO(ret = lydxml_envelope(lydctx.xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", &rpcr_e),
-            cleanup);
-#endif
+    /* init context */
+    lydctx = calloc(1, sizeof *lydctx);
+    LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM);
+    LY_CHECK_GOTO(rc = lyxml_ctx_new(ctx, in, &lydctx->xmlctx), cleanup);
+    lydctx->parse_opts = parse_opts;
+    lydctx->val_opts = val_opts;
+    lydctx->free = lyd_xml_ctx_free;
 
-    /* parse the rest of data normally but connect them to the duplicated operation */
-    while (lydctx.xmlctx->status == LYXML_ELEMENT) {
-        ret = lydxml_subtree_r(&lydctx, NULL, &tree);
-        LY_CHECK_GOTO(ret, cleanup);
+    switch (data_type) {
+    case LYD_TYPE_YANG_DATA:
+        int_opts = LYD_INTOPT_WITH_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_RPC:
+        int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_NOTIF:
+        int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_YANG_REPLY:
+        int_opts = LYD_INTOPT_REPLY | LYD_INTOPT_NO_SIBLINGS;
+        break;
+    case LYD_TYPE_NETCONF_RPC:
+        assert(!parent);
+        LY_CHECK_GOTO(rc = lydxml_env_netconf_rpc(lydctx->xmlctx, envp, &int_opts, &close_elem), cleanup);
+        break;
+    case LYD_TYPE_NETCONF_REPLY_OR_NOTIF:
+        assert(parent);
+        LY_CHECK_GOTO(rc = lydxml_env_netconf_reply_or_notif(lydctx->xmlctx, envp, &int_opts, &close_elem, &parent),
+                cleanup);
+        break;
+    }
+    lydctx->int_opts = int_opts;
+
+    /* find the operation node if it exists already */
+    LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup);
+
+    /* parse XML data */
+    while (lydctx->xmlctx->status == LYXML_ELEMENT) {
+        LY_CHECK_GOTO(rc = lydxml_subtree_r(lydctx, parent, first_p, parsed), cleanup);
+        parsed_data_nodes = 1;
+
+        if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
+            break;
+        }
     }
 
-    if (op_p) {
-        *op_p = lydctx.op_node;
+    /* close all opened elements */
+    for (i = 0; i < close_elem; ++i) {
+        if (lydctx->xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(lydctx->xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(lydctx->xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\".", lydctx->xmlctx->name_len,
+                    lydctx->xmlctx->name);
+            rc = LY_EVALID;
+            goto cleanup;
+        }
+
+        LY_CHECK_GOTO(rc = lyxml_ctx_next(lydctx->xmlctx), cleanup);
     }
-    if (tree_p) {
-        *tree_p = tree;
+
+    /* check final state */
+    if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) {
+        LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
+        rc = LY_EVALID;
+        goto cleanup;
+    }
+    if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) {
+        LOGVAL(ctx, LYVE_DATA, "Missing the operation node.");
+        rc = LY_EVALID;
+        goto cleanup;
+    }
+
+    if (!parsed_data_nodes) {
+        /* no data nodes were parsed */
+        lydctx->op_node = NULL;
     }
 
 cleanup:
-    /* we have used parse_only flag */
-    assert(!lydctx.node_types.count && !lydctx.meta_types.count && !lydctx.node_when.count);
-    lyxml_ctx_free(lydctx.xmlctx);
-    if (ret) {
-        lyd_free_all(tree);
+    /* there should be no unres stored if validation should be skipped */
+    assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
+            !lydctx->node_when.count));
+
+    if (rc) {
+        lyd_xml_ctx_free((struct lyd_ctx *)lydctx);
+    } else {
+        *lydctx_p = (struct lyd_ctx *)lydctx;
+
+        /* the XML context is no more needed, freeing it also stops logging line numbers which would be confusing now */
+        lyxml_ctx_free(lydctx->xmlctx);
+        lydctx->xmlctx = NULL;
     }
-    return ret;
+    return rc;
 }
diff --git a/src/tree_data.c b/src/tree_data.c
index 4230160..dbb5c94 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -295,102 +295,117 @@
     return format;
 }
 
-API LY_ERR
-lyd_parse_data(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree)
+/**
+ * @brief Parse YANG data into a data tree.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] parent Parent to connect the parsed nodes to, if any.
+ * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL.
+ * @param[in] in Input handle to read the input from.
+ * @param[in] format Expected format of the data in @p in.
+ * @param[in] parse_opts Options for parser.
+ * @param[in] val_opts Options for validation.
+ * @param[out] op Optional pointer to the parsed operation, if any.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyd_parse(const struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in,
+        LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR rc = LY_SUCCESS;
     struct lyd_ctx *lydctx = NULL;
+    struct ly_set parsed = {0};
+    struct lyd_node *first;
+    uint32_t i;
 
-    LY_CHECK_ARG_RET(ctx, ctx, in, tree, LY_EINVAL);
-    LY_CHECK_ARG_RET(ctx, !(parse_options & ~LYD_PARSE_OPTS_MASK), LY_EINVAL);
-    LY_CHECK_ARG_RET(ctx, !(validate_options & ~LYD_VALIDATE_OPTS_MASK), LY_EINVAL);
+    assert(ctx && (parent || first_p));
 
     format = lyd_parse_get_format(in, format);
     LY_CHECK_ARG_RET(ctx, format, LY_EINVAL);
-
-    /* init */
-    *tree = NULL;
+    if (first_p) {
+        *first_p = NULL;
+    }
 
     /* remember input position */
     in->func_start = in->current;
 
+    /* parse the data */
     switch (format) {
     case LYD_XML:
-        LY_CHECK_RET(lyd_parse_xml_data(ctx, in, parse_options, validate_options, tree, &lydctx));
+        rc = lyd_parse_xml(ctx, parent, first_p, in, parse_opts, val_opts, LYD_TYPE_YANG_DATA, NULL, &parsed, &lydctx);
         break;
     case LYD_JSON:
-        LY_CHECK_RET(lyd_parse_json_data(ctx, in, parse_options, validate_options, tree, &lydctx));
+        rc = lyd_parse_json(ctx, parent, first_p, in, parse_opts, val_opts, LYD_TYPE_YANG_DATA, &parsed, &lydctx);
         break;
     case LYD_LYB:
-        LY_CHECK_RET(lyd_parse_lyb_data(ctx, in, parse_options, validate_options, tree, &lydctx));
+        rc = lyd_parse_lyb(ctx, parent, first_p, in, parse_opts, val_opts, LYD_TYPE_YANG_DATA, &parsed, &lydctx);
         break;
     case LYD_UNKNOWN:
-        LOGINT_RET(ctx);
+        LOGINT(ctx);
+        rc = LY_EINT;
+        break;
+    }
+    LY_CHECK_GOTO(rc, cleanup);
+
+    if (parent) {
+        /* get first top-level sibling */
+        for (first = parent; first->parent; first = lyd_parent(first)) {}
+        first = lyd_first_sibling(first);
+        first_p = &first;
     }
 
-    if (!(parse_options & LYD_PARSE_ONLY)) {
-        uint32_t i = 0;
-        const struct lys_module *mod;
-        struct lyd_node *first, *next, **first2;
+    if (!(parse_opts & LYD_PARSE_ONLY)) {
+        /* validate data */
+        rc = lyd_validate(first_p, NULL, ctx, val_opts, 0, &lydctx->node_when, &lydctx->node_types,
+                &lydctx->meta_types, NULL);
+        LY_CHECK_GOTO(rc, cleanup);
+    }
 
-        next = *tree;
-        while (1) {
-            if (validate_options & LYD_VALIDATE_PRESENT) {
-                mod = lyd_data_next_module(&next, &first);
-            } else {
-                mod = lyd_mod_next_module(next, NULL, ctx, &i, &first);
-            }
-            if (!mod) {
-                break;
-            }
-            if (!first || (first == *tree)) {
-                /* make sure first2 changes are carried to tree */
-                first2 = tree;
-            } else {
-                first2 = &first;
-            }
-
-            /* validate new top-level nodes, autodelete CANNOT occur, all nodes are new */
-            LY_CHECK_GOTO(ret = lyd_validate_new(first2, NULL, mod, NULL), cleanup);
-
-            /* add all top-level defaults for this module */
-            ret = lyd_new_implicit_r(NULL, first2, NULL, mod, &lydctx->node_types, &lydctx->node_when,
-                    (validate_options & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
-            LY_CHECK_GOTO(ret, cleanup);
-
-            /* our first module node pointer may no longer be the first */
-            while (*first2 && (*first2)->prev->next && (lyd_owner_module(*first2) == lyd_owner_module((*first2)->prev))) {
-                *first2 = (*first2)->prev;
-            }
-
-            /* finish incompletely validated terminal values/attributes and when conditions */
-            ret = lyd_validate_unres(first2, mod, &lydctx->node_when, &lydctx->node_types, &lydctx->meta_types, NULL);
-            LY_CHECK_GOTO(ret, cleanup);
-
-            /* perform final validation that assumes the data tree is final */
-            LY_CHECK_GOTO(ret = lyd_validate_final_r(*first2, NULL, NULL, mod, validate_options, 0), cleanup);
-        }
+    /* set the operation node */
+    if (op) {
+        *op = lydctx->op_node;
     }
 
 cleanup:
-    lydctx->free(lydctx);
-    if (ret) {
-        lyd_free_all(*tree);
-        *tree = NULL;
+    if (lydctx) {
+        lydctx->free(lydctx);
     }
-    return ret;
+    if (rc) {
+        if (parent) {
+            /* free all the parsed subtrees */
+            for (i = 0; i < parsed.count; ++i) {
+                lyd_free_tree(parsed.dnodes[i]);
+            }
+        } else {
+            /* free everything */
+            lyd_free_all(*first_p);
+            *first_p = NULL;
+        }
+    }
+    ly_set_erase(&parsed, NULL);
+    return rc;
 }
 
 API LY_ERR
-lyd_parse_data_mem(const struct ly_ctx *ctx, const char *data, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options,
-        struct lyd_node **tree)
+lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format,
+        uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree)
+{
+    LY_CHECK_ARG_RET(ctx, ctx, in, parent || tree, LY_EINVAL);
+    LY_CHECK_ARG_RET(ctx, !(parse_options & ~LYD_PARSE_OPTS_MASK), LY_EINVAL);
+    LY_CHECK_ARG_RET(ctx, !(validate_options & ~LYD_VALIDATE_OPTS_MASK), LY_EINVAL);
+
+    return lyd_parse(ctx, parent, tree, in, format, parse_options, validate_options, NULL);
+}
+
+API LY_ERR
+lyd_parse_data_mem(const struct ly_ctx *ctx, const char *data, LYD_FORMAT format, uint32_t parse_options,
+        uint32_t validate_options, struct lyd_node **tree)
 {
     LY_ERR ret;
     struct ly_in *in;
 
     LY_CHECK_RET(ly_in_new_memory(data, &in));
-    ret = lyd_parse_data(ctx, in, format, parse_options, validate_options, tree);
+    ret = lyd_parse_data(ctx, NULL, in, format, parse_options, validate_options, tree);
 
     ly_in_free(in, 0);
     return ret;
@@ -404,7 +419,7 @@
     struct ly_in *in;
 
     LY_CHECK_RET(ly_in_new_fd(fd, &in));
-    ret = lyd_parse_data(ctx, in, format, parse_options, validate_options, tree);
+    ret = lyd_parse_data(ctx, NULL, in, format, parse_options, validate_options, tree);
 
     ly_in_free(in, 0);
     return ret;
@@ -418,53 +433,23 @@
     struct ly_in *in;
 
     LY_CHECK_RET(ly_in_new_filepath(path, 0, &in));
-    ret = lyd_parse_data(ctx, in, format, parse_options, validate_options, tree);
+    ret = lyd_parse_data(ctx, NULL, in, format, parse_options, validate_options, tree);
 
     ly_in_free(in, 0);
     return ret;
 }
 
 API LY_ERR
-lyd_parse_rpc(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree, struct lyd_node **op)
+lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format,
+        enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op)
 {
-    LY_CHECK_ARG_RET(ctx, ctx, in, tree, LY_EINVAL);
+    LY_ERR rc = LY_SUCCESS;
+    struct lyd_ctx *lydctx = NULL;
+    struct ly_set parsed = {0};
+    struct lyd_node *first = NULL, *envp = NULL;
+    uint32_t i, parse_opts, val_opts;
 
-    format = lyd_parse_get_format(in, format);
-    LY_CHECK_ARG_RET(ctx, format, LY_EINVAL);
-
-    /* init */
-    *tree = NULL;
-    if (op) {
-        *op = NULL;
-    }
-
-    /* remember input position */
-    in->func_start = in->current;
-
-    switch (format) {
-    case LYD_XML:
-        return lyd_parse_xml_rpc(ctx, in, tree, op);
-    case LYD_JSON:
-        return lyd_parse_json_rpc(ctx, in, tree, op);
-    case LYD_LYB:
-        return lyd_parse_lyb_rpc(ctx, in, tree, op);
-    case LYD_UNKNOWN:
-        break;
-    }
-
-    LOGINT_RET(ctx);
-}
-
-API LY_ERR
-lyd_parse_reply(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree,
-        struct lyd_node **op)
-{
-    LY_CHECK_ARG_RET(ctx, ctx, in, tree || op, LY_EINVAL);
-
-    format = lyd_parse_get_format(in, format);
-    LY_CHECK_ARG_RET(ctx, format, LY_EINVAL);
-
-    /* init */
+    LY_CHECK_ARG_RET(ctx, ctx || parent, in, data_type, parent || tree || op, LY_EINVAL);
     if (tree) {
         *tree = NULL;
     }
@@ -472,54 +457,75 @@
         *op = NULL;
     }
 
-    /* remember input position */
-    in->func_start = in->current;
-
-    switch (format) {
-    case LYD_XML:
-        return lyd_parse_xml_reply(ctx, in, tree, op);
-    case LYD_JSON:
-        return lyd_parse_json_reply(ctx, in, tree, op);
-    case LYD_LYB:
-        return lyd_parse_lyb_reply(ctx, in, tree, op);
-    case LYD_UNKNOWN:
-        break;
-    }
-
-    LOGINT_RET(ctx);
-}
-
-API LY_ERR
-lyd_parse_notif(const struct ly_ctx *ctx, struct ly_in *in, LYD_FORMAT format, struct lyd_node **tree, struct lyd_node **ntf)
-{
-    LY_CHECK_ARG_RET(ctx, ctx, in, tree || ntf, LY_EINVAL);
-
     format = lyd_parse_get_format(in, format);
     LY_CHECK_ARG_RET(ctx, format, LY_EINVAL);
 
-    /* init */
-    if (tree) {
-        *tree = NULL;
-    }
-    if (ntf) {
-        *ntf = NULL;
-    }
-
     /* remember input position */
     in->func_start = in->current;
 
+    /* check params based on the data type */
+    if (data_type == LYD_TYPE_NETCONF_RPC) {
+        LY_CHECK_ARG_RET(ctx, format == LYD_XML, !parent, tree, op, LY_EINVAL);
+    } else if (data_type == LYD_TYPE_NETCONF_REPLY_OR_NOTIF) {
+        LY_CHECK_ARG_RET(ctx, format == LYD_XML, parent, parent->schema->nodetype & (LYS_RPC | LYS_ACTION), tree, op, LY_EINVAL);
+    }
+    parse_opts = LYD_PARSE_ONLY | LYD_PARSE_STRICT;
+    val_opts = 0;
+
+    /* parse the data */
     switch (format) {
     case LYD_XML:
-        return lyd_parse_xml_notif(ctx, in, tree, ntf);
+        rc = lyd_parse_xml(ctx, parent, &first, in, parse_opts, val_opts, data_type, &envp, &parsed, &lydctx);
+        break;
     case LYD_JSON:
-        return lyd_parse_json_notif(ctx, in, tree, ntf);
+        rc = lyd_parse_json(ctx, parent, &first, in, parse_opts, val_opts, data_type, &parsed, &lydctx);
+        break;
     case LYD_LYB:
-        return lyd_parse_lyb_notif(ctx, in, tree, ntf);
+        rc = lyd_parse_lyb(ctx, parent, &first, in, parse_opts, val_opts, data_type, &parsed, &lydctx);
+        break;
     case LYD_UNKNOWN:
+        LOGINT(ctx);
+        rc = LY_EINT;
         break;
     }
+    LY_CHECK_GOTO(rc, cleanup);
 
-    LOGINT_RET(ctx);
+    /* set out params correctly */
+    if (tree) {
+        if (envp) {
+            /* special out param meaning */
+            *tree = envp;
+        } else {
+            *tree = parent ? NULL : first;
+        }
+    }
+    if (op) {
+        *op = lydctx->op_node;
+    }
+
+cleanup:
+    if (lydctx) {
+        lydctx->free(lydctx);
+    }
+    if (rc) {
+        if (parent) {
+            /* free all the parsed subtrees */
+            for (i = 0; i < parsed.count; ++i) {
+                lyd_free_tree(parsed.dnodes[i]);
+            }
+        } else {
+            /* free everything (cannot occur in the current code, a safety) */
+            lyd_free_all(first);
+            if (tree) {
+                *tree = NULL;
+            }
+            if (op) {
+                *op = NULL;
+            }
+        }
+    }
+    ly_set_erase(&parsed, NULL);
+    return rc;
 }
 
 LY_ERR
@@ -1586,7 +1592,7 @@
                 }
 
                 /* create any default children */
-                LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, node_types, node_when,
+                LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, node_types, node_when,
                         impl_opts, diff));
             }
             break;
@@ -1670,7 +1676,7 @@
         /* skip added default nodes */
         if (((node->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) &&
                 (node->schema->nodetype & LYD_NODE_INNER)) {
-            LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, NULL,
+            LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL,
                     &node_when, implicit_options, diff), cleanup);
         }
 
@@ -2995,7 +3001,7 @@
         } else {
             /* check descendants, recursively */
             LY_LIST_FOR_SAFE(lyd_child_no_keys(sibling_src), tmp, child_src) {
-                LY_CHECK_RET(lyd_merge_sibling_r(lyd_node_children_p(match_trg), match_trg, &child_src, options));
+                LY_CHECK_RET(lyd_merge_sibling_r(lyd_node_child_p(match_trg), match_trg, &child_src, options));
             }
         }
     } else {
diff --git a/src/tree_data_helpers.c b/src/tree_data_helpers.c
index ab4b0e5..7aaf1fb 100644
--- a/src/tree_data_helpers.c
+++ b/src/tree_data_helpers.c
@@ -66,7 +66,7 @@
 }
 
 struct lyd_node **
-lyd_node_children_p(struct lyd_node *node)
+lyd_node_child_p(struct lyd_node *node)
 {
     assert(node);
 
@@ -100,7 +100,7 @@
         return ((struct lyd_node_opaq *)node)->child;
     }
 
-    children = lyd_node_children_p((struct lyd_node *)node);
+    children = lyd_node_child_p((struct lyd_node *)node);
     if (children) {
         struct lyd_node *child = *children;
         while (child && child->schema && (child->schema->flags & LYS_KEY)) {
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index ce690bb..bf06c4b 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -34,13 +34,6 @@
 #define LY_LYB_SUFFIX_LEN 4
 
 /**
- * @brief Internal data parser flags.
- */
-#define LYD_INTOPT_RPC      0x01    /**< RPC/action invocation is being parsed */
-#define LYD_INTOPT_NOTIF    0x02    /**< notification is being parsed */
-#define LYD_INTOPT_REPLY    0x04    /**< RPC/action reply is being parsed */
-
-/**
  * @brief Hash schema sibling to be used for LYB data.
  *
  * @param[in] sibling Sibling to hash.
@@ -76,7 +69,7 @@
  * @return Address of the node's child member,
  * @return NULL if there is no child pointer.
  */
-struct lyd_node **lyd_node_children_p(struct lyd_node *node);
+struct lyd_node **lyd_node_child_p(struct lyd_node *node);
 
 /**
  * @brief Just like ::lys_getnext() but iterates over all data instances of the schema nodes.
diff --git a/src/validation.c b/src/validation.c
index 6427727..cb24d54 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -1057,12 +1057,12 @@
  * @param[in] sparent Schema parent of the nodes to check.
  * @param[in] mod Module of the nodes to check.
  * @param[in] val_opts Validation options, see @ref datavalidationoptions.
- * @param[in] op Operation being validated, if any.
+ * @param[in] int_opts Internal parser options.
  * @return LY_ERR value.
  */
 static LY_ERR
 lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_node *parent,
-        const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, LYD_VALIDATE_OP op)
+        const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, uint32_t int_opts)
 {
     LY_ERR ret = LY_SUCCESS;
     const struct lysc_node *snode = NULL, *scase;
@@ -1070,7 +1070,7 @@
     struct lysc_node_leaflist *sllist;
     uint32_t getnext_opts;
 
-    getnext_opts = LYS_GETNEXT_WITHCHOICE | (op == LYD_VALIDATE_OP_REPLY ? LYS_GETNEXT_OUTPUT : 0);
+    getnext_opts = LYS_GETNEXT_WITHCHOICE | (int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0);
 
     /* disabled nodes are skipped by lys_getnext */
     while ((snode = lys_getnext(snode, sparent, mod, getnext_opts))) {
@@ -1114,7 +1114,7 @@
             LY_LIST_FOR(lysc_node_child(snode), scase) {
                 if (lys_getnext_data(NULL, first, NULL, scase, NULL)) {
                     /* validate only this case */
-                    ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, op);
+                    ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts);
                     LY_CHECK_GOTO(ret, error);
                     break;
                 }
@@ -1156,11 +1156,11 @@
  * @brief Validate must conditions of a data node.
  *
  * @param[in] node Node to validate.
- * @param[in] op Operation being validated, if any.
+ * @param[in] int_opts Internal parser options.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_must(const struct lyd_node *node, LYD_VALIDATE_OP op)
+lyd_validate_must(const struct lyd_node *node, uint32_t int_opts)
 {
     struct lyxp_set xp_set;
     struct lysc_must *musts;
@@ -1168,10 +1168,13 @@
     const struct lysc_node *schema;
     LY_ARRAY_COUNT_TYPE u;
 
+    assert((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) != (LYD_INTOPT_RPC | LYD_INTOPT_REPLY));
+    assert((int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) != (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY));
+
     if (node->schema->nodetype & (LYS_ACTION | LYS_RPC)) {
-        if (op == LYD_VALIDATE_OP_RPC) {
+        if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) {
             schema = &((struct lysc_node_action *)node->schema)->input.node;
-        } else if (op == LYD_VALIDATE_OP_REPLY) {
+        } else if (int_opts & LYD_INTOPT_REPLY) {
             schema = &((struct lysc_node_action *)node->schema)->output.node;
         } else {
             LOGINT(LYD_CTX(node));
@@ -1208,9 +1211,20 @@
     return LY_SUCCESS;
 }
 
-LY_ERR
+/**
+ * @brief Perform all remaining validation tasks, the data tree must be final when calling this function.
+ *
+ * @param[in] first First sibling.
+ * @param[in] parent Data parent.
+ * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings.
+ * @param[in] mod Module of the siblings, NULL for nested siblings.
+ * @param[in] val_opts Validation options (@ref datavalidationoptions).
+ * @param[in] int_opts Internal parser options.
+ * @return LY_ERR value.
+ */
+static LY_ERR
 lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent,
-        const struct lys_module *mod, uint32_t val_opts, LYD_VALIDATE_OP op)
+        const struct lys_module *mod, uint32_t val_opts, uint32_t int_opts)
 {
     const char *innode = NULL;
     struct lyd_node *next = NULL, *node;
@@ -1235,10 +1249,10 @@
         if ((val_opts & LYD_VALIDATE_NO_STATE) && (node->schema->flags & LYS_CONFIG_R)) {
             innode = "state";
             goto invalid_node;
-        } else if ((op == LYD_VALIDATE_OP_RPC) && (node->schema->flags & LYS_IS_OUTPUT)) {
+        } else if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) && (node->schema->flags & LYS_IS_OUTPUT)) {
             innode = "output";
             goto invalid_node;
-        } else if ((op == LYD_VALIDATE_OP_REPLY) && (node->schema->flags & LYS_IS_INPUT)) {
+        } else if ((int_opts & LYD_INTOPT_REPLY) && (node->schema->flags & LYS_IS_INPUT)) {
             innode = "input";
             goto invalid_node;
         }
@@ -1247,7 +1261,7 @@
         lyd_validate_obsolete(node);
 
         /* node's musts */
-        LY_CHECK_RET(lyd_validate_must(node, op));
+        LY_CHECK_RET(lyd_validate_must(node, int_opts));
 
         /* node value was checked by plugins */
 
@@ -1255,11 +1269,11 @@
     }
 
     /* validate schema-based restrictions */
-    LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, op));
+    LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts));
 
     LY_LIST_FOR(first, node) {
         /* validate all children recursively */
-        LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, op));
+        LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts));
 
         /* set default for containers */
         if ((node->schema->nodetype == LYS_CONTAINER) && !(node->schema->flags & LYS_PRESENCE)) {
@@ -1286,16 +1300,16 @@
  * @brief Validate the whole data subtree.
  *
  * @param[in] root Subtree root.
+ * @param[in,out] node_when Set for nodes with when conditions.
  * @param[in,out] node_types Set for unres node types.
  * @param[in,out] meta_types Set for unres metadata types.
- * @param[in,out] node_when Set for nodes with when conditions.
  * @param[in] impl_opts Implicit options, see @ref implicitoptions.
  * @param[in,out] diff Validation diff.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_types, struct ly_set *meta_types,
-        struct ly_set *node_when, uint32_t impl_opts, struct lyd_node **diff)
+lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_set *node_types,
+        struct ly_set *meta_types, uint32_t impl_opts, struct lyd_node **diff)
 {
     const struct lyd_meta *meta;
     struct lyd_node *node;
@@ -1313,10 +1327,10 @@
             LY_CHECK_RET(ly_set_add(node_types, (void *)node, 1, NULL));
         } else if (node->schema->nodetype & LYD_NODE_INNER) {
             /* new node validation, autodelete */
-            LY_CHECK_RET(lyd_validate_new(lyd_node_children_p(node), node->schema, NULL, diff));
+            LY_CHECK_RET(lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, diff));
 
             /* add nested defaults */
-            LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, NULL, NULL, impl_opts, diff));
+            LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, impl_opts, diff));
         }
 
         if (lysc_node_when(node->schema)) {
@@ -1330,19 +1344,9 @@
     return LY_SUCCESS;
 }
 
-/**
- * @brief Validate data tree.
- *
- * @param[in,out] tree Data tree to validate, nodes may be autodeleted.
- * @param[in] modules Array of modules to validate, NULL for all modules.
- * @param[in] mod_count Count of @p modules.
- * @param[in] ly_ctx libyang context.
- * @param[in] val_opts Validation options, see @ref datavalidationoptions.
- * @param[out] diff Generated validation diff, not generated if NULL.
- * @return LY_ERR value.
- */
-static LY_ERR
+LY_ERR
 lyd_validate(struct lyd_node **tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t val_opts,
+        ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p,
         struct lyd_node **diff)
 {
     LY_ERR ret = LY_SUCCESS;
@@ -1351,12 +1355,13 @@
     struct ly_set node_types = {0}, meta_types = {0}, node_when = {0};
     uint32_t i = 0;
 
-    LY_CHECK_ARG_RET(NULL, tree, *tree || ctx || module, LY_EINVAL);
-    if (!ctx && !module) {
-        ctx = LYD_CTX(*tree);
-    }
-    if (diff) {
-        *diff = NULL;
+    assert(tree && ctx);
+    assert((node_when_p && node_types_p && meta_types_p) || (!node_when_p && !node_types_p && !meta_types_p));
+
+    if (!node_when_p) {
+        node_when_p = &node_when;
+        node_types_p = &node_types;
+        meta_types_p = &meta_types;
     }
 
     next = *tree;
@@ -1381,7 +1386,7 @@
         LY_CHECK_GOTO(ret, cleanup);
 
         /* add all top-level defaults for this module, do not add into unres sets, will occur in the next step */
-        ret = lyd_new_implicit_r(NULL, first2, NULL, mod, NULL, NULL, val_opts & LYD_VALIDATE_NO_STATE ?
+        ret = lyd_new_implicit_r(NULL, first2, NULL, mod, NULL, NULL, (val_opts & LYD_VALIDATE_NO_STATE) ?
                 LYD_IMPLICIT_NO_STATE : 0, diff);
         LY_CHECK_GOTO(ret, cleanup);
 
@@ -1390,15 +1395,17 @@
             *first2 = (*first2)->prev;
         }
 
-        /* process nested nodes */
-        LY_LIST_FOR(*first2, iter) {
-            ret = lyd_validate_subtree(iter, &node_types, &meta_types, &node_when, val_opts & LYD_VALIDATE_NO_STATE ?
-                    LYD_IMPLICIT_NO_STATE : 0, diff);
-            LY_CHECK_GOTO(ret, cleanup);
+        if (validate_subtree) {
+            /* process nested nodes */
+            LY_LIST_FOR(*first2, iter) {
+                ret = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p,
+                        (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
         }
 
         /* finish incompletely validated terminal values/attributes and when conditions */
-        ret = lyd_validate_unres(first2, mod, &node_when, &node_types, &meta_types, diff);
+        ret = lyd_validate_unres(first2, mod, node_when_p, node_types_p, meta_types_p, diff);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* perform final validation that assumes the data tree is final */
@@ -1407,22 +1414,35 @@
     }
 
 cleanup:
+    ly_set_erase(&node_when, NULL);
     ly_set_erase(&node_types, NULL);
     ly_set_erase(&meta_types, NULL);
-    ly_set_erase(&node_when, NULL);
     return ret;
 }
 
 API LY_ERR
 lyd_validate_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t val_opts, struct lyd_node **diff)
 {
-    return lyd_validate(tree, NULL, ctx, val_opts, diff);
+    LY_CHECK_ARG_RET(NULL, tree, *tree || ctx, LY_EINVAL);
+    if (!ctx) {
+        ctx = LYD_CTX(*tree);
+    }
+    if (diff) {
+        *diff = NULL;
+    }
+
+    return lyd_validate(tree, NULL, ctx, val_opts, 1, NULL, NULL, NULL, diff);
 }
 
 API LY_ERR
 lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, struct lyd_node **diff)
 {
-    return lyd_validate(tree, module, NULL, val_opts, diff);
+    LY_CHECK_ARG_RET(NULL, tree, *tree || module, LY_EINVAL);
+    if (diff) {
+        *diff = NULL;
+    }
+
+    return lyd_validate(tree, module, (*tree) ? LYD_CTX(*tree) : module->ctx, val_opts, 1, NULL, NULL, NULL, diff);
 }
 
 /**
@@ -1488,29 +1508,124 @@
     }
 }
 
-API LY_ERR
-lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *tree, LYD_VALIDATE_OP op, struct lyd_node **diff)
+/**
+ * @brief Validate an RPC/action request, reply, or notification.
+ *
+ * @param[in] op_tree Full operation data tree.
+ * @param[in] op_node Operation node itself.
+ * @param[in] dep_tree Tree to be used for validating references from the operation subtree.
+ * @param[in] int_opts Internal parser options.
+ * @param[in] validate_subtree Whether subtree was already validated (as part of data parsing) or not (separate validation).
+ * @param[in] node_when_p Set of nodes with when conditions, if NULL a local set is used.
+ * @param[in] node_types_p Set of unres node types, if NULL a local set is used.
+ * @param[in] meta_types_p Set of unres metadata types, if NULL a local set is used.
+ * @param[out] diff Optional diff with any changes made by the validation.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR error on error.
+ */
+static LY_ERR
+_lyd_validate_op(struct lyd_node *op_tree, struct lyd_node *op_node, const struct lyd_node *dep_tree,
+        uint32_t int_opts, ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p,
+        struct ly_set *meta_types_p, struct lyd_node **diff)
 {
-    LY_ERR ret;
-    struct lyd_node *tree_sibling, *tree_parent, *op_subtree, *op_node, *op_parent, *child;
-    struct ly_set type_check = {0}, type_meta_check = {0}, when_check = {0};
+    LY_ERR rc = LY_SUCCESS;
+    struct lyd_node *tree_sibling, *tree_parent, *op_subtree, *op_parent, *child;
+    struct ly_set node_types = {0}, meta_types = {0}, node_when = {0};
 
-    LY_CHECK_ARG_RET(NULL, op_tree, !op_tree->parent, !tree || !tree->parent,
-            (op == LYD_VALIDATE_OP_NOTIF) || (op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY), LY_EINVAL);
+    assert(op_tree && op_node);
+    assert((node_when_p && node_types_p && meta_types_p) || (!node_when_p && !node_types_p && !meta_types_p));
+
+    if (!node_when_p) {
+        node_when_p = &node_when;
+        node_types_p = &node_types;
+        meta_types_p = &meta_types;
+    }
+
+    /* merge op_tree into dep_tree */
+    lyd_val_op_merge_find(op_tree, op_node, dep_tree, &op_subtree, &tree_sibling, &tree_parent);
+    op_parent = lyd_parent(op_subtree);
+    lyd_unlink_tree(op_subtree);
+    lyd_insert_node(tree_parent, &tree_sibling, op_subtree);
+    if (!dep_tree) {
+        dep_tree = tree_sibling;
+    }
+
+    LOG_LOCSET(NULL, op_node, NULL, NULL);
+
+    if (int_opts & LYD_INTOPT_REPLY) {
+        /* add output children defaults */
+        rc = lyd_new_implicit_r(op_node, lyd_node_child_p(op_node), NULL, NULL, node_types_p, node_when_p,
+                LYD_IMPLICIT_OUTPUT, diff);
+        LY_CHECK_GOTO(rc, cleanup);
+
+        if (validate_subtree) {
+            /* skip validating the operation itself, go to children directly */
+            LY_LIST_FOR(lyd_child(op_node), child) {
+                LY_CHECK_GOTO(rc = lyd_validate_subtree(child, node_when_p, node_types_p, meta_types_p, 0, diff), cleanup);
+            }
+        }
+    } else {
+        if (validate_subtree) {
+            /* prevalidate whole operation subtree */
+            LY_CHECK_GOTO(rc = lyd_validate_subtree(op_node, node_when_p, node_types_p, meta_types_p, 0, diff), cleanup);
+        }
+    }
+
+    /* finish incompletely validated terminal values/attributes and when conditions on the full tree */
+    LY_CHECK_GOTO(rc = lyd_validate_unres((struct lyd_node **)&dep_tree, NULL, node_when_p, node_types_p, meta_types_p,
+            diff), cleanup);
+
+    /* perform final validation of the operation/notification */
+    lyd_validate_obsolete(op_node);
+    LY_CHECK_GOTO(rc = lyd_validate_must(op_node, int_opts), cleanup);
+
+    /* final validation of all the descendants */
+    LY_CHECK_GOTO(rc = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, int_opts), cleanup);
+
+cleanup:
+    LOG_LOCBACK(0, 1, 0, 0);
+    /* restore operation tree */
+    lyd_unlink_tree(op_subtree);
+    if (op_parent) {
+        lyd_insert_node(op_parent, NULL, op_subtree);
+    }
+
+    ly_set_erase(&node_when, NULL);
+    ly_set_erase(&node_types, NULL);
+    ly_set_erase(&meta_types, NULL);
+    return rc;
+}
+
+API LY_ERR
+lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *dep_tree, enum lyd_type data_type, struct lyd_node **diff)
+{
+    struct lyd_node *op_node;
+    uint32_t int_opts;
+
+    LY_CHECK_ARG_RET(NULL, op_tree, !op_tree->parent, !dep_tree || !dep_tree->parent, (data_type == LYD_TYPE_YANG_RPC) ||
+            (data_type == LYD_TYPE_YANG_NOTIF) || (data_type == LYD_TYPE_YANG_REPLY), LY_EINVAL);
     if (diff) {
         *diff = NULL;
     }
+    if (data_type == LYD_TYPE_YANG_RPC) {
+        int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION;
+    } else if (data_type == LYD_TYPE_YANG_NOTIF) {
+        int_opts = LYD_INTOPT_NOTIF;
+    } else {
+        int_opts = LYD_INTOPT_REPLY;
+    }
 
     /* find the operation/notification */
     LYD_TREE_DFS_BEGIN(op_tree, op_node) {
-        if (((op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY)) && (op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
+        if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) &&
+                (op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
             break;
-        } else if ((op == LYD_VALIDATE_OP_NOTIF) && (op_node->schema->nodetype == LYS_NOTIF)) {
+        } else if ((int_opts & LYD_INTOPT_NOTIF) && (op_node->schema->nodetype == LYS_NOTIF)) {
             break;
         }
         LYD_TREE_DFS_END(op_tree, op_node);
     }
-    if ((op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY)) {
+    if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) {
         if (!(op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
             LOGERR(LYD_CTX(op_tree), LY_EINVAL, "No RPC/action to validate found.");
             return LY_EINVAL;
@@ -1522,56 +1637,6 @@
         }
     }
 
-    /* move op_tree to top-level node */
-    while (op_tree->parent) {
-        op_tree = lyd_parent(op_tree);
-    }
-
-    /* merge op_tree into tree */
-    lyd_val_op_merge_find(op_tree, op_node, tree, &op_subtree, &tree_sibling, &tree_parent);
-    op_parent = lyd_parent(op_subtree);
-    lyd_unlink_tree(op_subtree);
-    lyd_insert_node(tree_parent, &tree_sibling, op_subtree);
-    if (!tree) {
-        tree = tree_sibling;
-    }
-
-    LOG_LOCSET(NULL, op_node, NULL, NULL);
-
-    if (op == LYD_VALIDATE_OP_REPLY) {
-        /* add output children defaults */
-        LY_CHECK_RET(lyd_new_implicit_r(op_node, lyd_node_children_p(op_node), NULL, NULL, NULL, NULL, LYD_IMPLICIT_OUTPUT, diff));
-
-        /* skip validating the operation itself, go to children directly */
-        LY_LIST_FOR(lyd_child(op_node), child) {
-            LY_CHECK_GOTO(ret = lyd_validate_subtree(child, &type_check, &type_meta_check, &when_check, 0, diff), cleanup);
-        }
-    } else {
-        /* prevalidate whole operation subtree */
-        LY_CHECK_GOTO(ret = lyd_validate_subtree(op_node, &type_check, &type_meta_check, &when_check, 0, diff), cleanup);
-    }
-
-    /* finish incompletely validated terminal values/attributes and when conditions on the full tree */
-    LY_CHECK_GOTO(ret = lyd_validate_unres((struct lyd_node **)&tree, NULL, &when_check, &type_check, &type_meta_check,
-            diff), cleanup);
-
-    /* perform final validation of the operation/notification */
-    lyd_validate_obsolete(op_node);
-    LY_CHECK_GOTO(ret = lyd_validate_must(op_node, op), cleanup);
-
-    /* final validation of all the descendants */
-    LY_CHECK_GOTO(ret = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, op), cleanup);
-
-cleanup:
-    LOG_LOCBACK(0, 1, 0, 0);
-    /* restore operation tree */
-    lyd_unlink_tree(op_subtree);
-    if (op_parent) {
-        lyd_insert_node(op_parent, NULL, op_subtree);
-    }
-
-    ly_set_erase(&type_check, NULL);
-    ly_set_erase(&type_meta_check, NULL);
-    ly_set_erase(&when_check, NULL);
-    return ret;
+    /* validate */
+    return _lyd_validate_op(op_tree, op_node, dep_tree, int_opts, 1, NULL, NULL, NULL, diff);
 }
diff --git a/src/validation.h b/src/validation.h
index ae286be..c255270 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -70,17 +70,21 @@
         struct lyd_node **diff);
 
 /**
- * @brief Perform all remaining validation tasks, the data tree must be final when calling this function.
+ * @brief Validate a data tree.
  *
- * @param[in] first First sibling.
- * @param[in] parent Data parent.
- * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings.
- * @param[in] mod Module of the siblings, NULL for nested siblings.
- * @param[in] val_opts Validation options (@ref datavalidationoptions).
- * @param[in] op Operation to validate (@ref datavalidateop) or 0 for data tree
+ * @param[in,out] tree Data tree to validate, nodes may be autodeleted.
+ * @param[in] module Module whose data (and schema restrictions) to validate, NULL for all modules.
+ * @param[in] ctx libyang context.
+ * @param[in] val_opts Validation options, see @ref datavalidationoptions.
+ * @param[in] validate_subtree Whether subtree was already validated (as part of data parsing) or not (separate validation).
+ * @param[in] node_when_p Set of nodes with when conditions, if NULL a local set is used.
+ * @param[in] node_types_p Set of unres node types, if NULL a local set is used.
+ * @param[in] meta_types_p Set of unres metadata types, if NULL a local set is used.
+ * @param[out] diff Generated validation diff, not generated if NULL.
  * @return LY_ERR value.
  */
-LY_ERR lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent,
-        const struct lys_module *mod, uint32_t val_opts, LYD_VALIDATE_OP op);
+LY_ERR lyd_validate(struct lyd_node **tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t val_opts,
+        ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p,
+        struct lyd_node **diff);
 
 #endif /* LY_VALIDATION_H_ */
diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c
index cca266c..fba4d53 100644
--- a/tests/utests/data/test_parser_json.c
+++ b/tests/utests/data/test_parser_json.c
@@ -436,7 +436,7 @@
             "\"a:l1\":[{\"@\":{\"ietf-netconf:operation\":\"replace\"},\"a\":\"val_a\",\"b\":\"val_b\",\"c\":\"val_c\"}]}"
             "}}";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_rpc(UTEST_LYCTX, in, LYD_JSON, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_YANG_RPC, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
@@ -479,7 +479,7 @@
 
     data = "{\"a:c\":{\"act\":{\"al\":\"value\"}}}";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_rpc(UTEST_LYCTX, in, LYD_JSON, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_YANG_RPC, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
@@ -503,7 +503,7 @@
 
     data = "{\"a:c\":{\"n1\":{\"nl\":\"value\"}}}";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_notif(UTEST_LYCTX, in, LYD_JSON, &tree, &ntf));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_YANG_NOTIF, &tree, &ntf));
     ly_in_free(in, 0);
 
     assert_non_null(ntf);
@@ -516,7 +516,7 @@
 
     data = "{\"a:n2\":{}}";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_notif(UTEST_LYCTX, in, LYD_JSON, &tree, &ntf));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_YANG_NOTIF, &tree, &ntf));
     ly_in_free(in, 0);
 
     assert_non_null(ntf);
@@ -542,7 +542,7 @@
 
     data = "{\"a:c\":{\"act\":{\"al\":25}}}";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_reply(UTEST_LYCTX, in, LYD_JSON, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_YANG_REPLY, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c
index b2e2805..35b0e09 100644
--- a/tests/utests/data/test_parser_xml.c
+++ b/tests/utests/data/test_parser_xml.c
@@ -321,7 +321,7 @@
             "  </config>\n"
             "</edit-config>\n";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_rpc(UTEST_LYCTX, in, LYD_XML, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_RPC, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
@@ -387,7 +387,7 @@
             "  </act>\n"
             "</c>\n";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_rpc(UTEST_LYCTX, in, LYD_XML, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_RPC, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
@@ -421,7 +421,7 @@
             "  </n1>\n"
             "</c>\n";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_notif(UTEST_LYCTX, in, LYD_XML, &tree, &ntf));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_NOTIF, &tree, &ntf));
     ly_in_free(in, 0);
 
     assert_non_null(ntf);
@@ -435,7 +435,7 @@
     /* top-level notif without envelope */
     data = "<n2 xmlns=\"urn:tests:a\"/>\n";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_notif(UTEST_LYCTX, in, LYD_XML, &tree, &ntf));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_NOTIF, &tree, &ntf));
     ly_in_free(in, 0);
 
     assert_non_null(ntf);
@@ -465,7 +465,7 @@
             "  </act>\n"
             "</c>\n";
     assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_reply(UTEST_LYCTX, in, LYD_XML, &tree, &op));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_REPLY, &tree, &op));
     ly_in_free(in, 0);
 
     assert_non_null(op);
@@ -486,6 +486,260 @@
     /* TODO */
 }
 
+static void
+test_netconf_rpc(void **state)
+{
+    const char *data;
+    struct ly_in *in;
+    struct lyd_node *tree, *op;
+    const struct lyd_node *node;
+    const char *dsc = "The <edit-config> operation loads all or part of a specified\n"
+            "configuration to the specified target configuration.";
+    const char *ref = "RFC 6241, Section 7.2";
+    const char *feats[] = {"writable-running", NULL};
+
+    assert_non_null((ly_ctx_load_module(UTEST_LYCTX, "ietf-netconf", "2011-06-01", feats)));
+
+    data = "<rpc message-id=\"25\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
+            "<edit-config>\n"
+            "  <target>\n"
+            "    <running/>\n"
+            "  </target>\n"
+            "  <config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "    <l1 xmlns=\"urn:tests:a\" nc:operation=\"replace\">\n"
+            "      <a>val_a</a>\n"
+            "      <b>val_b</b>\n"
+            "      <c>val_c</c>\n"
+            "    </l1>\n"
+            "    <cp xmlns=\"urn:tests:a\">\n"
+            "      <z nc:operation=\"delete\"/>\n"
+            "    </cp>\n"
+            "  </config>\n"
+            "</edit-config>\n"
+            "</rpc>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_NETCONF_RPC, &tree, &op));
+    ly_in_free(in, 0);
+
+    assert_non_null(op);
+
+    node = tree;
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 1, 0, LY_PREF_XML, "rpc", 0, 0, 0, 0, "");
+
+    assert_non_null(tree);
+
+    node = op;
+    CHECK_LYSC_ACTION((struct lysc_node_action *)node->schema, dsc, 0, LYS_STATUS_CURR,
+            1, 0, 0, 1, "edit-config", LYS_RPC,
+            0, 0, 0, 0, 0, ref, 0);
+    node = lyd_child(node)->next;
+    dsc = "Inline Config content.";
+    CHECK_LYSC_NODE(node->schema, dsc, 0, LYS_STATUS_CURR | LYS_IS_INPUT, 1, "config", 0, LYS_ANYXML, 1, 0, NULL, 0);
+
+    node = ((struct lyd_node_any *)node)->value.tree;
+    CHECK_LYSC_NODE(node->schema, NULL, 0, LYS_CONFIG_W | LYS_STATUS_CURR | LYS_PRESENCE | LYS_SET_PRESENCE, 1, "cp",
+            1, LYS_CONTAINER, 0, 0, NULL, 0);
+
+    node = lyd_child(node);
+    /* z has no value */
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0, LY_PREF_XML, "z", 0, 0, NULL,  0,  "");
+    node = node->parent->next;
+    /* l1 key c has invalid value so it is at the end */
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0x1, LY_PREF_XML, "l1", 0, 0, NULL,  0,  "");
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS,
+            "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"25\"/>\n");
+    CHECK_LYD_STRING(op, LYD_PRINT_WITHSIBLINGS,
+            "<edit-config xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "  <target>\n"
+            "    <running/>\n"
+            "  </target>\n"
+            "  <config>\n"
+            "    <cp xmlns=\"urn:tests:a\">\n"
+            "      <z xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"delete\"/>\n"
+            "    </cp>\n"
+            "    <l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"replace\">\n"
+            "      <a>val_a</a>\n"
+            "      <b>val_b</b>\n"
+            "      <c>val_c</c>\n"
+            "    </l1>\n"
+            "  </config>\n"
+            "</edit-config>\n");
+
+    lyd_free_all(tree);
+    lyd_free_all(op);
+
+    /* wrong namespace, element name, whatever... */
+    /* TODO */
+}
+
+static void
+test_netconf_action(void **state)
+{
+    const char *data;
+    struct ly_in *in;
+    struct lyd_node *tree, *op;
+
+    data = "<rpc message-id=\"25\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
+            "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">"
+            "<c xmlns=\"urn:tests:a\">\n"
+            "  <act>\n"
+            "    <al>value</al>\n"
+            "  </act>\n"
+            "</c>\n"
+            "</action>\n"
+            "</rpc>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_NETCONF_RPC, &tree, &op));
+    ly_in_free(in, 0);
+
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 1, 1, LY_PREF_XML, "rpc", 0, 0, 0, 0, "");
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)lyd_child(tree), 0, 0, LY_PREF_XML, "action", 0, 0, 0, 0, "");
+
+    assert_non_null(op);
+    CHECK_LYSC_ACTION((struct lysc_node_action *)op->schema, NULL, 0, LYS_STATUS_CURR,
+            1, 0, 0, 1, "act", LYS_ACTION,
+            1, 0, 0, 1, 0, NULL, 0);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS,
+            "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"25\">\n"
+            "  <action xmlns=\"urn:ietf:params:xml:ns:yang:1\"/>\n"
+            "</rpc>\n");
+    CHECK_LYD_STRING(op, LYD_PRINT_WITHSIBLINGS,
+            "<act xmlns=\"urn:tests:a\">\n"
+            "  <al>value</al>\n"
+            "</act>\n");
+
+    lyd_free_all(tree);
+    lyd_free_all(op);
+
+    /* wrong namespace, element name, whatever... */
+    /* TODO */
+}
+
+static void
+test_netconf_reply_or_notification(void **state)
+{
+    const char *data;
+    struct ly_in *in;
+    struct lyd_node *action, *tree, *op, *op2;
+
+    /* parse the action */
+    data = "<c xmlns=\"urn:tests:a\">\n"
+            "  <act>\n"
+            "    <al>value</al>\n"
+            "  </act>\n"
+            "</c>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_RPC, &action, &op));
+    ly_in_free(in, 0);
+
+    /* parse notification first */
+    data = "<notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n"
+            "<eventTime>2010-12-06T08:00:01Z</eventTime>\n"
+            "<c xmlns=\"urn:tests:a\">\n"
+            "  <n1>\n"
+            "    <nl>value</nl>\n"
+            "  </n1>\n"
+            "</c>\n"
+            "</notification>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, op, in, LYD_XML, LYD_TYPE_NETCONF_REPLY_OR_NOTIF, &tree, &op2));
+    ly_in_free(in, 0);
+
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 1, LY_PREF_XML, "notification", 0, 0, 0, 0, "");
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)lyd_child(tree), 0, 0, LY_PREF_XML, "eventTime", 0, 0, 0, 0,
+            "2010-12-06T08:00:01Z");
+
+    assert_non_null(op2);
+    CHECK_LYSC_NOTIF((struct lysc_node_notif *)op2->schema, 1, NULL, 0, 0x4, 1, 0, "n1", 1, 0, NULL, 0);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS,
+            "<notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n"
+            "  <eventTime>2010-12-06T08:00:01Z</eventTime>\n"
+            "</notification>\n");
+    CHECK_LYD_STRING(op2, LYD_PRINT_WITHSIBLINGS,
+            "<n1 xmlns=\"urn:tests:a\">\n"
+            "  <nl>value</nl>\n"
+            "</n1>\n");
+
+    lyd_free_all(tree);
+    lyd_free_all(op2);
+
+    /* parse a data reply */
+    data = "<rpc-reply message-id=\"55\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "  <al xmlns=\"urn:tests:a\">25</al>\n"
+            "</rpc-reply>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, op, in, LYD_XML, LYD_TYPE_NETCONF_REPLY_OR_NOTIF, &tree, &op2));
+    ly_in_free(in, 0);
+
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 1, 0, LY_PREF_XML, "rpc-reply", 0, 0, 0, 0, "");
+
+    assert_non_null(op2);
+    CHECK_LYSC_ACTION((struct lysc_node_action *)op2->schema, NULL, 0, LYS_STATUS_CURR,
+            1, 0, 0, 1, "act", LYS_ACTION,
+            1, 0, 0, 1, 0, NULL, 0);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS,
+            "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"55\"/>\n");
+    CHECK_LYD_STRING(op2, LYD_PRINT_WITHSIBLINGS,
+            "<act xmlns=\"urn:tests:a\">\n"
+            "  <al>25</al>\n"
+            "  <al>value</al>\n"
+            "</act>\n");
+
+    lyd_free_all(tree);
+    /* it was connected to the action, do not free */
+
+    /* parse an ok reply */
+    data = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"55\">\n"
+            "  <ok/>\n"
+            "</rpc-reply>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, op, in, LYD_XML, LYD_TYPE_NETCONF_REPLY_OR_NOTIF, &tree, &op2));
+    ly_in_free(in, 0);
+
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 1, 1, LY_PREF_XML, "rpc-reply", 0, 0, 0, 0, "");
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)lyd_child(tree), 0, 0, LY_PREF_XML, "ok", 0, 0, 0, 0, "");
+
+    assert_null(op2);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data);
+
+    lyd_free_all(tree);
+
+    /* parse an error reply */
+    data = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"55\">\n"
+            "  <rpc-error>\n"
+            "    <error-type>rpc</error-type>\n"
+            "    <error-tag>missing-attribute</error-tag>\n"
+            "    <error-severity>error</error-severity>\n"
+            "    <error-info>\n"
+            "      <bad-attribute>message-id</bad-attribute>\n"
+            "      <bad-element>rpc</bad-element>\n"
+            "    </error-info>\n"
+            "  </rpc-error>\n"
+            "</rpc-reply>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, op, in, LYD_XML, LYD_TYPE_NETCONF_REPLY_OR_NOTIF, &tree, &op2));
+    ly_in_free(in, 0);
+
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 1, 1, LY_PREF_XML, "rpc-reply", 0, 0, 0, 0, "");
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)lyd_child(tree), 0, 1, LY_PREF_XML, "rpc-error", 0, 0, 0, 0, "");
+
+    assert_null(op2);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data);
+
+    lyd_free_all(tree);
+
+    lyd_free_all(action);
+
+    /* wrong namespace, element name, whatever... */
+    /* TODO */
+}
+
 int
 main(void)
 {
@@ -499,6 +753,9 @@
         UTEST(test_action, setup),
         UTEST(test_notification, setup),
         UTEST(test_reply, setup),
+        UTEST(test_netconf_rpc, setup),
+        UTEST(test_netconf_action, setup),
+        UTEST(test_netconf_reply_or_notification, setup),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/data/test_validation.c b/tests/utests/data/test_validation.c
index cb0a306..3dbdf23 100644
--- a/tests/utests/data/test_validation.c
+++ b/tests/utests/data/test_validation.c
@@ -1132,11 +1132,11 @@
             "    </act>\n"
             "  </l1>\n"
             "</cont>\n", &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_rpc(UTEST_LYCTX, in, LYD_XML, &op_tree, NULL));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_RPC, &op_tree, NULL));
     assert_non_null(op_tree);
 
     /* missing leafref */
-    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, NULL, LYD_VALIDATE_OP_RPC, NULL));
+    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, NULL, LYD_TYPE_YANG_RPC, NULL));
     CHECK_LOG_CTX("Invalid leafref value \"target\" - no existing target instance \"/lf3\".",
             "Schema location /j:cont/l1/act/input/lf2, data location /j:cont/l1[k='val1']/act/lf2.");
     ly_in_free(in, 0);
@@ -1148,7 +1148,7 @@
             LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree);
 
     /* input must false */
-    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, tree, LYD_VALIDATE_OP_RPC, NULL));
+    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, tree, LYD_TYPE_YANG_RPC, NULL));
     CHECK_LOG_CTX("Must condition \"../../lf1 = 'true'\" not satisfied.",
             "Data location /j:cont/l1[k='val1']/act.");
 
@@ -1160,7 +1160,7 @@
             LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree);
 
     /* success */
-    assert_int_equal(LY_SUCCESS, lyd_validate_op(op_tree, tree, LYD_VALIDATE_OP_RPC, NULL));
+    assert_int_equal(LY_SUCCESS, lyd_validate_op(op_tree, tree, LYD_TYPE_YANG_RPC, NULL));
 
     lyd_free_tree(op_tree);
     lyd_free_siblings(tree);
@@ -1183,12 +1183,12 @@
             "    </act>\n"
             "  </l1>\n"
             "</cont>\n", &in));
-    assert_int_equal(LY_SUCCESS, lyd_parse_reply(UTEST_LYCTX, in, LYD_XML, &op_tree, NULL));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_YANG_REPLY, &op_tree, NULL));
     assert_non_null(op_tree);
     ly_in_free(in, 0);
 
     /* missing leafref */
-    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, NULL, LYD_VALIDATE_OP_REPLY, NULL));
+    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, NULL, LYD_TYPE_YANG_REPLY, NULL));
     CHECK_LOG_CTX("Invalid leafref value \"target\" - no existing target instance \"/lf4\".",
             "Schema location /j:cont/l1/act/output/lf2, data location /j:cont/l1[k='val1']/act/lf2.");
 
@@ -1199,7 +1199,7 @@
             LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree);
 
     /* input must false */
-    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, tree, LYD_VALIDATE_OP_REPLY, NULL));
+    assert_int_equal(LY_EVALID, lyd_validate_op(op_tree, tree, LYD_TYPE_YANG_REPLY, NULL));
     CHECK_LOG_CTX("Must condition \"../../lf1 = 'true2'\" not satisfied.", "Data location /j:cont/l1[k='val1']/act.");
 
     lyd_free_all(tree);
@@ -1210,7 +1210,7 @@
             LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree);
 
     /* success */
-    assert_int_equal(LY_SUCCESS, lyd_validate_op(op_tree, tree, LYD_VALIDATE_OP_REPLY, NULL));
+    assert_int_equal(LY_SUCCESS, lyd_validate_op(op_tree, tree, LYD_TYPE_YANG_REPLY, NULL));
 
     lyd_free_tree(op_tree);
     lyd_free_all(tree);
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
index b73d778..2029ff1 100644
--- a/tools/lint/cmd_data.c
+++ b/tools/lint/cmd_data.c
@@ -125,7 +125,7 @@
     uint32_t options_print = 0;
     uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS;
     uint32_t options_validate = 0;
-    uint8_t data_type = 0;
+    enum lyd_type data_type = 0;
     uint8_t data_type_set = 0;
     LYD_FORMAT outformat = LYD_UNKNOWN;
     LYD_FORMAT informat = LYD_UNKNOWN;
@@ -232,11 +232,11 @@
             } else if (!strcasecmp(optarg, "edit")) {
                 options_parse |= LYD_PARSE_ONLY;
             } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) {
-                data_type = LYD_VALIDATE_OP_RPC;
+                data_type = LYD_TYPE_YANG_RPC;
             } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) {
-                data_type = LYD_VALIDATE_OP_REPLY;
+                data_type = LYD_TYPE_YANG_REPLY;
             } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) {
-                data_type = LYD_VALIDATE_OP_NOTIF;
+                data_type = LYD_TYPE_YANG_NOTIF;
             } else if (!strcasecmp(optarg, "data")) {
                 /* default option */
             } else {
diff --git a/tools/lint/common.c b/tools/lint/common.c
index ed28665..db8c1f8 100644
--- a/tools/lint/common.c
+++ b/tools/lint/common.c
@@ -437,7 +437,7 @@
 }
 
 LY_ERR
-process_data(struct ly_ctx *ctx, uint8_t data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
+process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
         uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
         struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *xpaths)
 {
@@ -447,7 +447,7 @@
 
     /* additional operational datastore */
     if (operational_f && operational_f->in) {
-        ret = lyd_parse_data(ctx, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
+        ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
         if (ret) {
             YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
             goto cleanup;
@@ -457,17 +457,13 @@
     for (uint32_t u = 0; u < inputs->count; ++u) {
         struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
         switch (data_type) {
-        case 0:
-            ret = lyd_parse_data(ctx, input_f->in, input_f->format, options_parse, options_validate, &tree);
+        case LYD_TYPE_YANG_DATA:
+            ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
             break;
-        case LYD_VALIDATE_OP_RPC:
-            ret = lyd_parse_rpc(ctx, input_f->in, input_f->format, &tree, NULL);
-            break;
-        case LYD_VALIDATE_OP_REPLY:
-            ret = lyd_parse_reply(ctx, input_f->in, input_f->format, &tree, NULL);
-            break;
-        case LYD_VALIDATE_OP_NOTIF:
-            ret = lyd_parse_notif(ctx, input_f->in, input_f->format, &tree, NULL);
+        case LYD_TYPE_YANG_RPC:
+        case LYD_TYPE_YANG_REPLY:
+        case LYD_TYPE_YANG_NOTIF:
+            ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, NULL);
             break;
         default:
             YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
diff --git a/tools/lint/common.h b/tools/lint/common.h
index 85f7997..0a62db9 100644
--- a/tools/lint/common.h
+++ b/tools/lint/common.h
@@ -177,8 +177,7 @@
  * @brief Process the input data files - parse, validate and print according to provided options.
  *
  * @param[in] ctx libyang context with schema.
- * @param[in] data_type The type of data in the input files, can be 0 for standard data tree and ::LYD_VALIDATE_OP values for
- * the operations.
+ * @param[in] data_type The type of data in the input files.
  * @param[in] merge Flag if the data should be merged before validation.
  * @param[in] format Data format for printing.
  * @param[in] out The output handler for printing.
@@ -192,7 +191,7 @@
  * is printed. Alternative to data printing.
  * return LY_ERR value.
  */
-LY_ERR process_data(struct ly_ctx *ctx, uint8_t data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
+LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
         uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
         struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *xpaths);
 
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
index 7c794d6..e1cb12f 100644
--- a/tools/lint/main_ni.c
+++ b/tools/lint/main_ni.c
@@ -67,7 +67,7 @@
      * data
      */
     /* various options based on --type option */
-    uint8_t data_type; /* values taken from LYD_VALIDATE_OP and extended by 0 for standard data tree */
+    enum lyd_type data_type;
     uint32_t data_parse_options;
     uint32_t data_validate_options;
     uint32_t data_print_options;
@@ -538,11 +538,11 @@
             } else if (!strcasecmp(optarg, "edit")) {
                 c->data_parse_options |= LYD_PARSE_ONLY;
             } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) {
-                c->data_type = LYD_VALIDATE_OP_RPC;
+                c->data_type = LYD_TYPE_YANG_RPC;
             } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) {
-                c->data_type = LYD_VALIDATE_OP_REPLY;
+                c->data_type = LYD_TYPE_YANG_REPLY;
             } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) {
-                c->data_type = LYD_VALIDATE_OP_NOTIF;
+                c->data_type = LYD_TYPE_YANG_NOTIF;
             } else if (!strcasecmp(optarg, "data")) {
                 /* default option */
             } else {