data tree FEATURE lyd_new_path() and lyd_change_term()

With tests included. Also, another missing API
function lyd_new_opaq() added.
diff --git a/src/context.c b/src/context.c
index 52a34a5..9f18510 100644
--- a/src/context.c
+++ b/src/context.c
@@ -771,10 +771,9 @@
         root_bis = 0;
     }
 
-    /* TODO uncomment once lefref validation works
     if (lyd_validate(&root, NULL, LYD_VALOPT_DATA_ONLY)) {
         goto error;
-    }*/
+    }
 
     return root;
 
diff --git a/src/path.c b/src/path.c
index 5138fa7..bcbfe01 100644
--- a/src/path.c
+++ b/src/path.c
@@ -308,6 +308,7 @@
 /**
  * @brief Parse prefix from a NameTest, if any, and node name, and return expected module of the node.
  *
+ * @param[in] ctx libyang context.
  * @param[in] cur_mod Module of the current (original context) node. Needed for ::LYD_SCHEMA.
  * @param[in] prev_ctx_node Previous context node. Needed for ::LYD_JSON.
  * @param[in] expr Parsed path.
@@ -322,9 +323,9 @@
  * @return LY_ERR value.
  */
 static LY_ERR
-ly_path_compile_prefix(const struct lys_module *cur_mod, const struct lysc_node *prev_ctx_node, const struct lyxp_expr *expr,
-                       uint16_t tok_idx, uint8_t lref, ly_clb_resolve_prefix resolve_prefix, void *prefix_data,
-                       LYD_FORMAT format, const struct lys_module **mod, const char **name, size_t *name_len)
+ly_path_compile_prefix(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *prev_ctx_node,
+                       const struct lyxp_expr *expr, uint16_t tok_idx, uint8_t lref, ly_clb_resolve_prefix resolve_prefix,
+                       void *prefix_data, LYD_FORMAT format, const struct lys_module **mod, const char **name, size_t *name_len)
 {
     const char *ptr;
     size_t len;
@@ -337,14 +338,14 @@
 
     /* find next node module */
     if (ptr) {
-        *mod = resolve_prefix(cur_mod->ctx, expr->expr + expr->tok_pos[tok_idx], len, prefix_data);
+        *mod = resolve_prefix(ctx, expr->expr + expr->tok_pos[tok_idx], len, prefix_data);
         if (!*mod) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Prefix \"%.*s\" not found of a module in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Prefix \"%.*s\" not found of a module in path.",
                    len, expr->expr + expr->tok_pos[tok_idx]);
             return LY_EINVAL;
         } else if (!(*mod)->implemented) {
             if (lref == LY_PATH_LREF_FALSE) {
-                LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not implemented module \"%s\" in path.", (*mod)->name);
+                LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not implemented module \"%s\" in path.", (*mod)->name);
                 return LY_EINVAL;
             }
             lys_set_implemented_internal((struct lys_module *)*mod, 2);
@@ -356,13 +357,13 @@
             break;
         case LYD_JSON:
             if (!prev_ctx_node) {
-                LOGINT_RET(cur_mod->ctx);
+                LOGINT_RET(ctx);
             }
             *mod = prev_ctx_node->module;
             break;
         case LYD_XML:
             /* not really defined */
-            LOGINT_RET(cur_mod->ctx);
+            LOGINT_RET(ctx);
         }
     }
 
@@ -379,9 +380,10 @@
 }
 
 LY_ERR
-ly_path_compile_predicate(const struct lys_module *cur_mod, const struct lysc_node *ctx_node, const struct lyxp_expr *expr,
-                          uint16_t *tok_idx, ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format,
-                          struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type)
+ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node,
+                          const struct lyxp_expr *expr, uint16_t *tok_idx, ly_clb_resolve_prefix resolve_prefix,
+                          void *prefix_data, LYD_FORMAT format, struct ly_path_predicate **predicates,
+                          enum ly_path_pred_type *pred_type)
 {
     struct ly_path_predicate *p;
     const struct lysc_node *key;
@@ -389,6 +391,8 @@
     const char *name;
     size_t name_len, key_count;
 
+    assert(ctx && ctx_node);
+
     *pred_type = 0;
 
     /* '[' */
@@ -399,25 +403,25 @@
 
     if (expr->tokens[*tok_idx] == LYXP_TOKEN_NAMETEST) {
         if (ctx_node->nodetype != LYS_LIST) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "List predicate defined for %s \"%s\" in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "List predicate defined for %s \"%s\" in path.",
                    lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
             return LY_EINVAL;
         } else if (ctx_node->flags & LYS_KEYLESS) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "List predicate defined for keyless %s \"%s\" in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "List predicate defined for keyless %s \"%s\" in path.",
                    lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
             return LY_EINVAL;
         }
 
         do {
             /* NameTest, find the key */
-            LY_CHECK_RET(ly_path_compile_prefix(cur_mod, ctx_node, expr, *tok_idx, LY_PATH_LREF_FALSE, resolve_prefix,
+            LY_CHECK_RET(ly_path_compile_prefix(ctx, cur_mod, ctx_node, expr, *tok_idx, LY_PATH_LREF_FALSE, resolve_prefix,
                                                 prefix_data, format, &mod, &name, &name_len));
             key = lys_find_child(ctx_node, mod, name, name_len, 0, LYS_GETNEXT_NOSTATECHECK);
             if (!key) {
-                LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.", name_len, name);
+                LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.", name_len, name);
                 return LY_EINVAL;
             } else if ((key->nodetype != LYS_LEAF) || !(key->flags & LYS_KEY)) {
-                LOGVAL(cur_mod->ctx, LY_VLOG_LYSC, key, LYVE_XPATH, "Key expected instead of %s \"%s\" in path.",
+                LOGVAL(ctx, LY_VLOG_LYSC, key, LYVE_XPATH, "Key expected instead of %s \"%s\" in path.",
                        lys_nodetype2str(key->nodetype), key->name);
                 return LY_EINVAL;
             }
@@ -428,7 +432,7 @@
                 *pred_type = LY_PATH_PREDTYPE_LIST;
             }
             assert(*pred_type == LY_PATH_PREDTYPE_LIST);
-            LY_ARRAY_NEW_RET(cur_mod->ctx, *predicates, p, LY_EMEM);
+            LY_ARRAY_NEW_RET(ctx, *predicates, p, LY_EMEM);
             p->key = key;
 
             /* '=' */
@@ -455,16 +459,16 @@
         }
         if (LY_ARRAY_SIZE(*predicates) != key_count) {
             /* names (keys) are unique - it was checked when parsing */
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for a key of %s \"%s\" in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for a key of %s \"%s\" in path.",
                    lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
-            ly_path_predicates_free(cur_mod->ctx, LY_PATH_PREDTYPE_LIST, NULL, *predicates);
+            ly_path_predicates_free(ctx, LY_PATH_PREDTYPE_LIST, NULL, *predicates);
             *predicates = NULL;
             return LY_EINVAL;
         }
 
     } else if (expr->tokens[*tok_idx] == LYXP_TOKEN_DOT) {
         if (ctx_node->nodetype != LYS_LEAFLIST) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Leaf-list predicate defined for %s \"%s\" in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Leaf-list predicate defined for %s \"%s\" in path.",
                    lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
             return LY_EINVAL;
         }
@@ -472,7 +476,7 @@
 
         /* new predicate */
         *pred_type = LY_PATH_PREDTYPE_LEAFLIST;
-        LY_ARRAY_NEW_RET(cur_mod->ctx, *predicates, p, LY_EMEM);
+        LY_ARRAY_NEW_RET(ctx, *predicates, p, LY_EMEM);
 
         /* '=' */
         assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL);
@@ -490,22 +494,22 @@
     } else {
         assert(expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER);
         if (!(ctx_node->nodetype & (LYS_LEAFLIST | LYS_LIST))) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Positional predicate defined for %s \"%s\" in path.",
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Positional predicate defined for %s \"%s\" in path.",
                    lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
             return LY_EINVAL;
         } else if (ctx_node->flags & LYS_CONFIG_W) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Positional predicate defined for configuration"
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Positional predicate defined for configuration"
                    " %s \"%s\" in path.", lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
             return LY_EINVAL;
         }
-        ++(*tok_idx);
 
         /* new predicate */
         *pred_type = LY_PATH_PREDTYPE_POSITION;
-        LY_ARRAY_NEW_RET(cur_mod->ctx, *predicates, p, LY_EMEM);
+        LY_ARRAY_NEW_RET(ctx, *predicates, p, LY_EMEM);
 
         /* syntax was already checked */
         p->position = strtoull(expr->expr + expr->tok_pos[*tok_idx], (char **)&name, 10);
+        ++(*tok_idx);
 
         /* ']' */
         assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2);
@@ -555,8 +559,8 @@
 
     do {
         /* NameTest, find the key */
-        LY_CHECK_RET(ly_path_compile_prefix(cur_node->module, ctx_node, expr, *tok_idx, LY_PATH_LREF_TRUE, resolve_prefix,
-                                            prefix_data, format, &mod, &name, &name_len));
+        LY_CHECK_RET(ly_path_compile_prefix(cur_node->module->ctx, cur_node->module, ctx_node, expr, *tok_idx,
+                                            LY_PATH_LREF_TRUE, resolve_prefix, prefix_data, format, &mod, &name, &name_len));
         key = lys_find_child(ctx_node, mod, name, name_len, 0, LYS_GETNEXT_NOSTATECHECK);
         if (!key) {
             LOGVAL(cur_node->module->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.", name_len, name);
@@ -614,8 +618,8 @@
 
             /* NameTest */
             assert(expr->tokens[*tok_idx] == LYXP_TOKEN_NAMETEST);
-            LY_CHECK_RET(ly_path_compile_prefix(cur_node->module, node, expr, *tok_idx, LY_PATH_LREF_TRUE, resolve_prefix,
-                                                prefix_data, format, &mod, &name, &name_len));
+            LY_CHECK_RET(ly_path_compile_prefix(cur_node->module->ctx, cur_node->module, node, expr, *tok_idx,
+                                                LY_PATH_LREF_TRUE, resolve_prefix, prefix_data, format, &mod, &name, &name_len));
             node2 = lys_find_child(node, mod, name, name_len, 0, LYS_GETNEXT_NOSTATECHECK);
             if (!node2) {
                 LOGVAL(cur_node->module->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.",
@@ -647,21 +651,24 @@
 }
 
 LY_ERR
-ly_path_compile(const struct lys_module *cur_mod, const struct lysc_node *ctx_node, const struct lyxp_expr *expr,
-                uint8_t lref, ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format,
-                struct ly_path **path)
+ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node,
+                const struct lyxp_expr *expr, uint8_t lref, uint8_t oper, uint8_t target,
+                ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format, struct ly_path **path)
 {
     LY_ERR ret = LY_SUCCESS;
     uint16_t tok_idx = 0;
     const struct lys_module *mod;
     const struct lysc_node *node2, *cur_node;
-    struct ly_path *p;
+    struct ly_path *p = NULL;
+    int getnext_opts;
     const char *name;
     size_t name_len;
 
-    assert(cur_mod);
-    assert((expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) || ctx_node);
+    assert(ctx);
+    assert((expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) || (lref == LY_PATH_LREF_FALSE) || ctx_node);
     assert((lref == LY_PATH_LREF_TRUE) || (lref == LY_PATH_LREF_FALSE));
+    assert((oper == LY_PATH_OPER_INPUT) || (oper == LY_PATH_OPER_OUTPUT));
+    assert((target == LY_PATH_TARGET_SINGLE) || (target == LY_PATH_TARGET_MANY));
 
     if (lref == LY_PATH_LREF_TRUE) {
         /* remember original context node */
@@ -669,6 +676,12 @@
     }
     *path = NULL;
 
+    if (oper == LY_PATH_OPER_OUTPUT) {
+        getnext_opts = LYS_GETNEXT_NOSTATECHECK | LYS_GETNEXT_OUTPUT;
+    } else {
+        getnext_opts = LYS_GETNEXT_NOSTATECHECK;
+    }
+
     if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
         /* absolute path */
         ctx_node = NULL;
@@ -678,7 +691,7 @@
         /* relative path */
         while ((lref == LY_PATH_LREF_TRUE) && (expr->tokens[tok_idx] == LYXP_TOKEN_DDOT)) {
             if (!ctx_node) {
-                LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Too many parent references in path.");
+                LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Too many parent references in path.");
                 return LY_EINVAL;
             }
 
@@ -691,64 +704,68 @@
             ++tok_idx;
         }
 
-        if (ctx_node) {
-            /* store the parent correctly */
-            LY_ARRAY_NEW_GOTO(cur_mod->ctx, *path, p, ret, cleanup);
-            p->node = lysc_data_parent(ctx_node) ? ctx_node : NULL;
-        }
-        /* if there is no node meaning the relative path went up to the document root, the path meaning is exactly
-         * the same as an absolute path and we will even store it that way */
+        /* we are not storing the parent */
+        (void)ctx_node;
     }
 
     do {
+        /* check last compiled inner node, whether it is uniquely identified (even key-less list) */
+        if (p && (lref == LY_PATH_LREF_FALSE) && (p->node->nodetype == LYS_LIST) && !p->predicates) {
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.",
+                   lys_nodetype2str(p->node->nodetype), p->node->name);
+            return LY_EINVAL;
+        }
+
         /* get module and node name */
-        LY_CHECK_GOTO(ret = ly_path_compile_prefix(cur_mod, ctx_node, expr, tok_idx, lref, resolve_prefix, prefix_data,
-                                                   format, &mod, &name, &name_len), cleanup);
+        LY_CHECK_GOTO(ret = ly_path_compile_prefix(ctx, cur_mod, ctx_node, expr, tok_idx, lref, resolve_prefix,
+                                                   prefix_data, format, &mod, &name, &name_len), cleanup);
         ++tok_idx;
 
         /* find the next node */
-        node2 = lys_find_child(ctx_node, mod, name, name_len, 0, LYS_GETNEXT_NOSTATECHECK);
+        node2 = lys_find_child(ctx_node, mod, name, name_len, 0, getnext_opts);
         if (!node2) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.", name_len, name);
+            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Not found node \"%.*s\" in path.", name_len, name);
             ret = LY_EINVAL;
             goto cleanup;
         }
         ctx_node = node2;
 
         /* new path segment */
-        LY_ARRAY_NEW_GOTO(cur_mod->ctx, *path, p, ret, cleanup);
+        LY_ARRAY_NEW_GOTO(ctx, *path, p, ret, cleanup);
         p->node = ctx_node;
 
         /* compile any predicates */
         if (lref == LY_PATH_LREF_TRUE) {
             ret = ly_path_compile_predicate_leafref(ctx_node, cur_node, expr, &tok_idx, resolve_prefix, prefix_data, format);
         } else {
-            ret = ly_path_compile_predicate(cur_mod, ctx_node, expr, &tok_idx, resolve_prefix, prefix_data, format,
+            ret = ly_path_compile_predicate(ctx, cur_mod, ctx_node, expr, &tok_idx, resolve_prefix, prefix_data, format,
                                             &p->predicates, &p->pred_type);
         }
         LY_CHECK_GOTO(ret, cleanup);
-
-        /* check whether node is uniquely identified */
-        if ((lref == LY_PATH_LREF_FALSE) && (ctx_node->nodetype & (LYS_LEAFLIST | LYS_LIST)) && !p->predicates) {
-            LOGVAL(cur_mod->ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.",
-                   lys_nodetype2str(ctx_node->nodetype), ctx_node->name);
-            return LY_EINVAL;
-        }
     } while (!lyxp_next_token(NULL, expr, &tok_idx, LYXP_TOKEN_OPER_PATH));
 
+    /* check last compiled node */
+    if ((lref == LY_PATH_LREF_FALSE) && (target == LY_PATH_TARGET_SINGLE)
+            && (p->node->nodetype & (LYS_LIST | LYS_LEAFLIST)) && !p->predicates) {
+        LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.",
+               lys_nodetype2str(p->node->nodetype), p->node->name);
+        return LY_EINVAL;
+    }
+
 cleanup:
     if (ret) {
-        ly_path_free(cur_mod->ctx, *path);
+        ly_path_free(ctx, *path);
         *path = NULL;
     }
     return ret;
 }
 
 LY_ERR
-ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match)
+ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_SIZE_TYPE *path_idx,
+                     struct lyd_node **match)
 {
     LY_ARRAY_SIZE_TYPE u;
-    struct lyd_node *node, *target;
+    struct lyd_node *prev_node = NULL, *node, *target;
     uint64_t pos;
 
     assert(path && start);
@@ -802,16 +819,63 @@
             break;
         }
 
+        /* rememeber previous node */
+        prev_node = node;
+
         /* next path segment, if any */
         start = lyd_node_children(node);
     }
 
-    /* result */
-    if (match) {
-        *match = node;
-    }
     if (node) {
+        /* we have found the full path */
+        if (path_idx) {
+            *path_idx = u;
+        }
+        if (match) {
+            *match = node;
+        }
         return LY_SUCCESS;
+
+    } else if (prev_node) {
+        /* we have found only some partial match */
+        if (path_idx) {
+            *path_idx = u - 1;
+        }
+        if (match) {
+            *match = prev_node;
+        }
+        return LY_EINCOMPLETE;
+    }
+
+    /* we have not found any nodes */
+    if (path_idx) {
+        *path_idx = 0;
+    }
+    if (match) {
+        *match = NULL;
+    }
+    return LY_ENOTFOUND;
+}
+
+LY_ERR
+ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match)
+{
+    LY_ERR ret;
+    struct lyd_node *m;
+
+    ret = ly_path_eval_partial(path, start, NULL, &m);
+
+    if (ret == LY_SUCCESS) {
+        /* last node was found */
+        if (match) {
+            *match = m;
+        }
+        return LY_SUCCESS;
+    }
+
+    /* not a full match */
+    if (match) {
+        *match = NULL;
     }
     return LY_ENOTFOUND;
 }
diff --git a/src/path.h b/src/path.h
index c973add..c75387c 100644
--- a/src/path.h
+++ b/src/path.h
@@ -18,6 +18,7 @@
 #include <stdint.h>
 
 #include "log.h"
+#include "tree.h"
 #include "tree_data.h"
 
 struct lysc_node;
@@ -116,25 +117,47 @@
                                uint8_t pred, struct lyxp_expr **expr);
 
 /**
+ * @defgroup path_oper_options Path operation options.
+ * @{
+ */
+#define LY_PATH_OPER_INPUT  0x01    /**< if any RPC/action is traversed, its input nodes are used */
+#define LY_PATH_OPER_OUTPUT 0x02    /**< if any RPC/action is traversed, its output nodes are used */
+/** @} */
+
+/* lref */
+
+/**
+ * @defgroup path_target_options Path target options.
+ * @{
+ */
+#define LY_PATH_TARGET_SINGLE  0x10    /**< last (target) node must identify an exact instance */
+#define LY_PATH_TARGET_MANY    0x20    /**< last (target) node may identify all instances (of leaf-list/list) */
+/** @} */
+
+/**
  * @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled.
  *
+ * @param[in] ctx libyang context.
  * @param[in] cur_mod Module of the current (original context) node. Used for nodes without prefix for ::LYD_SCHEMA format.
  * @param[in] ctx_node Context node. Can be NULL for absolute paths.
  * @param[in] expr Parsed path.
  * @param[in] lref Lref option (@ref path_lref_options).
+ * @param[in] oper Oper option (@ref path_oper_options).
+ * @param[in] target Target option (@ref path_target_options).
  * @param[in] resolve_prefix Callback for prefix resolution.
  * @param[in] prefix_data Data for @p resolve_prefix.
  * @param[in] format Format of the path.
  * @param[out] path Compiled path.
  * @return LY_ERR value.
  */
-LY_ERR ly_path_compile(const struct lys_module *cur_mod, const struct lysc_node *ctx_node, const struct lyxp_expr *expr,
-                       uint8_t lref, ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format,
-                       struct ly_path **path);
+LY_ERR ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node,
+                       const struct lyxp_expr *expr, uint8_t lref, uint8_t oper, uint8_t target,
+                       ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format, struct ly_path **path);
 
 /**
  * @brief Compile predicate into ly_path_predicate structure. Only simple predicates (not leafref) are supported.
  *
+ * @param[in] ctx libyang context.
  * @param[in] cur_mod Module of the current (original context) node. Used for nodes without prefix for ::LYD_SCHEMA format.
  * @param[in] ctx_node Context node, node for which the predicate is defined.
  * @param[in] expr Parsed path.
@@ -146,10 +169,25 @@
  * @param[out] pred_type Type of the compiled predicate(s).
  * @return LY_ERR value.
  */
-LY_ERR ly_path_compile_predicate(const struct lys_module *cur_mod, const struct lysc_node *ctx_node,
-                                 const struct lyxp_expr *expr, uint16_t *tok_idx, ly_clb_resolve_prefix resolve_prefix,
-                                 void *prefix_data, LYD_FORMAT format, struct ly_path_predicate **predicates,
-                                 enum ly_path_pred_type *pred_type);
+LY_ERR ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lys_module *cur_mod,
+                                 const struct lysc_node *ctx_node, const struct lyxp_expr *expr, uint16_t *tok_idx,
+                                 ly_clb_resolve_prefix resolve_prefix, void *prefix_data, LYD_FORMAT format,
+                                 struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type);
+
+/**
+ * @brief Resolve at least partially the target defined by ly_path structure. Not supported for leafref!
+ *
+ * @param[in] path Path structure specifying the target.
+ * @param[in] start Starting node for relative paths, can be any for absolute paths.
+ * @param[out] path_idx Last found path segment index, can be NULL, set to 0 if not found.
+ * @param[out] match Last found matching node, can be NULL, set to NULL if not found.
+ * @return LY_ENOTFOUND if no nodes were found,
+ * @return LY_EINCOMPLETE if some node was found but not the last one,
+ * @return LY_SUCCESS when the last node in the path was found,
+ * @return LY_ERR on another error.
+ */
+LY_ERR ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_SIZE_TYPE *path_idx,
+                            struct lyd_node **match);
 
 /**
  * @brief Resolve the target defined by ly_path structure. Not supported for leafref!
diff --git a/src/plugins_types.c b/src/plugins_types.c
index 894c6b6..2ec1d2a 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -1420,7 +1420,7 @@
     struct ly_path *path = NULL;
     struct ly_set predicates = {0};
     struct lyxp_expr *exp = NULL;
-    const struct lys_module *cur_mod;
+    const struct lysc_node *ctx_scnode;
 
     /* init */
     *err = NULL;
@@ -1464,9 +1464,11 @@
     }
 
     /* resolve it on schema tree */
-    cur_mod = (options & (LY_TYPE_OPTS_SCHEMA | LY_TYPE_OPTS_INCOMPLETE_DATA)) ?
-              ((struct lysc_node *)context_node)->module : ((struct lyd_node *)context_node)->schema->module;
-    ret = ly_path_compile(cur_mod, NULL, exp, LY_PATH_LREF_FALSE, ly_type_stored_prefixes_clb, prefixes, format, &path);
+    ctx_scnode = (options & (LY_TYPE_OPTS_SCHEMA | LY_TYPE_OPTS_INCOMPLETE_DATA)) ?
+                 (struct lysc_node *)context_node : ((struct lyd_node *)context_node)->schema;
+    ret = ly_path_compile(ctx, ctx_scnode->module, NULL, exp, LY_PATH_LREF_FALSE, lysc_is_output(ctx_scnode) ?
+                          LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_SINGLE, ly_type_stored_prefixes_clb,
+                          prefixes, format, &path);
     if (ret) {
         asprintf(&errmsg, "Invalid instance-identifier \"%.*s\" value - semantic error.", (int)value_len, value);
         goto error;
diff --git a/src/tree_data.c b/src/tree_data.c
index eb6a5fe..351bb9d 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -46,16 +46,6 @@
 #include "xml.h"
 #include "xpath.h"
 
-struct ly_keys {
-    char *str;
-    struct {
-        const struct lysc_node_leaf *schema;
-        char *value;
-        struct lyd_value val;
-    } *keys;
-    size_t key_count;
-};
-
 LY_ERR
 lyd_value_parse(struct lyd_node_term *node, const char *value, size_t value_len, int *dynamic, int second,
                 ly_clb_resolve_prefix get_prefix, void *parser, LYD_FORMAT format, const struct lyd_node *tree)
@@ -95,7 +85,7 @@
     return ret;
 }
 
-/* similar to lyd_value_parse except can be used just to store the value, hence does also not support a second call */
+/* similar to lyd_value_parse except can be used just to store the value, hence also does not support a second call */
 LY_ERR
 lyd_value_store(struct lyd_value *val, const struct lysc_node *schema, const char *value, size_t value_len, int *dynamic,
                 ly_clb_resolve_prefix get_prefix, void *parser, LYD_FORMAT format)
@@ -434,6 +424,7 @@
     struct lysc_type *type;
 
     assert(schema->nodetype & LYD_NODE_TERM);
+    assert(val && val->realtype);
 
     term = calloc(1, sizeof *term);
     LY_CHECK_ERR_RET(!term, LOGMEM(schema->module->ctx), LY_EMEM);
@@ -449,6 +440,7 @@
         free(term);
         return ret;
     }
+    term->value.realtype = val->realtype;
     lyd_hash((struct lyd_node *)term);
 
     *node = (struct lyd_node *)term;
@@ -478,174 +470,6 @@
     return LY_SUCCESS;
 }
 
-static void
-ly_keys_clean(struct ly_keys *keys)
-{
-    size_t i;
-
-    for (i = 0; i < keys->key_count; ++i) {
-        keys->keys[i].schema->type->plugin->free(keys->keys[i].schema->module->ctx, &keys->keys[i].val);
-    }
-    free(keys->str);
-    free(keys->keys);
-}
-
-static char *
-ly_keys_parse_next(char **next_key, char **key_name)
-{
-    char *ptr, *ptr2, *val, quot;
-    const char *pref;
-    size_t pref_len, key_len;
-    int have_equal = 0;
-
-    ptr = *next_key;
-
-    /* "[" */
-    LY_CHECK_GOTO(ptr[0] != '[', error);
-    ++ptr;
-
-    /* skip WS */
-    while (isspace(ptr[0])) {
-        ++ptr;
-    }
-
-    /* key name without prefix */
-    LY_CHECK_GOTO(ly_parse_nodeid((const char **)&ptr, &pref, &pref_len, (const char **)key_name, &key_len), error);
-    if (pref) {
-        goto error;
-    }
-
-    /* terminate it */
-    LY_CHECK_GOTO((ptr[0] != '=') && !isspace(ptr[0]), error);
-    if (ptr[0] == '=') {
-        have_equal = 1;
-    }
-    ptr[0] = '\0';
-    ++ptr;
-
-    if (!have_equal) {
-        /* skip WS */
-        while (isspace(ptr[0])) {
-            ++ptr;
-        }
-
-        /* '=' */
-        LY_CHECK_GOTO(ptr[0] != '=', error);
-        ++ptr;
-    }
-
-    /* skip WS */
-    while (isspace(ptr[0])) {
-        ++ptr;
-    }
-
-    /* quote */
-    LY_CHECK_GOTO((ptr[0] != '\'') && (ptr[0] != '\"'), error);
-    quot = ptr[0];
-    ++ptr;
-
-    /* value, terminate it */
-    val = ptr;
-    ptr2 = strchr(ptr, quot);
-    LY_CHECK_GOTO(!ptr2, error);
-    ptr2[0] = '\0';
-
-    /* \0, was quote */
-    ptr = ptr2 + 1;
-
-    /* skip WS */
-    while (isspace(ptr[0])) {
-        ++ptr;
-    }
-
-    /* "]" */
-    LY_CHECK_GOTO(ptr[0] != ']', error);
-    ++ptr;
-
-    *next_key = ptr;
-    return val;
-
-error:
-    *next_key = ptr;
-    return NULL;
-}
-
-/* fill keys structure that is expected to be zeroed and must always be cleaned (even on error);
- * if store is set, fill also each val */
-static LY_ERR
-ly_keys_parse(const struct lysc_node *list, const char *keys_str, size_t keys_len, int store, int log,
-              struct ly_keys *keys)
-{
-    LY_ERR ret = LY_SUCCESS;
-    char *next_key, *name;
-    const struct lysc_node *key;
-    size_t i;
-
-    assert(list->nodetype == LYS_LIST);
-
-    if (!keys_str) {
-        /* nothing to parse */
-        return LY_SUCCESS;
-    }
-
-    keys->str = strndup(keys_str, keys_len);
-    LY_CHECK_ERR_GOTO(!keys->str, LOGMEM(list->module->ctx); ret = LY_EMEM, cleanup);
-
-    next_key = keys->str;
-    while (next_key[0]) {
-        /* new key */
-        keys->keys = ly_realloc(keys->keys, (keys->key_count + 1) * sizeof *keys->keys);
-        LY_CHECK_ERR_GOTO(!keys->keys, LOGMEM(list->module->ctx); ret = LY_EMEM, cleanup);
-
-        /* fill */
-        keys->keys[keys->key_count].value = ly_keys_parse_next(&next_key, &name);
-        if (!keys->keys[keys->key_count].value) {
-            if (log) {
-                LOGERR(list->module->ctx, LY_EINVAL, "Invalid keys string (at \"%s\").", next_key);
-            }
-            ret = LY_EINVAL;
-            goto cleanup;
-        }
-
-        /* find schema node */
-        key = lys_find_child(list, list->module, name, 0, LYS_LEAF, 0);
-        if (!key) {
-            if (log) {
-                LOGERR(list->module->ctx, LY_EINVAL, "List \"%s\" has no key \"%s\".", list->name, name);
-            }
-            ret = LY_EINVAL;
-            goto cleanup;
-        }
-        keys->keys[keys->key_count].schema = (const struct lysc_node_leaf *)key;
-
-        /* check that we do not have it already */
-        for (i = 0; i < keys->key_count; ++i) {
-            if (keys->keys[i].schema == keys->keys[keys->key_count].schema) {
-                if (log) {
-                    LOGERR(list->module->ctx, LY_EINVAL, "Duplicit key \"%s\" value.", name);
-                }
-                ret = LY_EINVAL;
-                goto cleanup;
-            }
-        }
-
-        if (store) {
-            /* store the value */
-            ret = lyd_value_store(&keys->keys[keys->key_count].val, key, keys->keys[keys->key_count].value, 0, 0,
-                                  lydjson_resolve_prefix, NULL, LYD_JSON);
-            LY_CHECK_GOTO(ret, cleanup);
-        } else {
-            memset(&keys->keys[keys->key_count].val, 0, sizeof keys->keys[keys->key_count].val);
-        }
-
-        /* another valid key */
-        ++keys->key_count;
-    }
-
-cleanup:
-    return ret;
-}
-
 LY_ERR
 lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, struct lyd_node **node)
 {
@@ -690,8 +514,8 @@
                                                 LY_PATH_PRED_KEYS, &expr), cleanup);
 
     /* compile them */
-    LY_CHECK_GOTO(ret = ly_path_compile_predicate(schema->module, schema, expr, &exp_idx, lydjson_resolve_prefix, NULL,
-                                                  LYD_JSON, &predicates, &pred_type), cleanup);
+    LY_CHECK_GOTO(ret = ly_path_compile_predicate(schema->module->ctx, NULL, schema, expr, &exp_idx, lydjson_resolve_prefix,
+                                                  NULL, LYD_JSON, &predicates, &pred_type), cleanup);
 
     /* create the list node */
     LY_CHECK_GOTO(ret = lyd_create_list(schema, predicates, node), cleanup);
@@ -916,16 +740,81 @@
     return ret;
 }
 
+static LY_ERR
+lyd_new_path_update(struct lyd_node *node, const void *value, LYD_ANYDATA_VALUETYPE value_type,
+                    struct lyd_node **new_parent, struct lyd_node **new_node)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct lyd_node *new_any;
+
+    switch (node->schema->nodetype) {
+    case LYS_CONTAINER:
+    case LYS_NOTIF:
+    case LYS_RPC:
+    case LYS_ACTION:
+    case LYS_LIST:
+    case LYS_LEAFLIST:
+        /* if it exists, there is nothing to update */
+        *new_parent = NULL;
+        *new_node = NULL;
+        break;
+    case LYS_LEAF:
+        ret = lyd_change_term(node, value);
+        if ((ret == LY_SUCCESS) || (ret == LY_EEXIST)) {
+            /* there was an actual change (at least of the default flag) */
+            *new_parent = node;
+            *new_node = node;
+            ret = LY_SUCCESS;
+        } else if (ret == LY_ENOT) {
+            /* no change */
+            *new_parent = NULL;
+            *new_node = NULL;
+            ret = LY_SUCCESS;
+        } /* else error */
+        break;
+    case LYS_ANYDATA:
+    case LYS_ANYXML:
+        /* create a new any node */
+        LY_CHECK_RET(lyd_create_any(node->schema, value, value_type, &new_any));
+
+        /* compare with the existing one */
+        if (lyd_compare(node, new_any, 0)) {
+            /* not equal, switch values (so that we can use generic node free) */
+            ((struct lyd_node_any *)new_any)->value = ((struct lyd_node_any *)node)->value;
+            ((struct lyd_node_any *)new_any)->value_type = ((struct lyd_node_any *)node)->value_type;
+            ((struct lyd_node_any *)node)->value.str = value;
+            ((struct lyd_node_any *)node)->value_type = value_type;
+
+            *new_parent = node;
+            *new_node = node;
+        } else {
+            /* they are equal */
+            *new_parent = NULL;
+            *new_node = NULL;
+        }
+        lyd_free_tree(new_any);
+        break;
+    default:
+        LOGINT(LYD_NODE_CTX(node));
+        ret = LY_EINT;
+        break;
+    }
+
+    return ret;
+}
+
 API struct lyd_meta *
 lyd_new_meta(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *val_str)
 {
     struct lyd_meta *ret = NULL;
-    struct ly_ctx *ctx = parent->schema->module->ctx;
+    const struct ly_ctx *ctx;
     const char *prefix, *tmp;
     char *str;
     size_t pref_len, name_len;
 
-    LY_CHECK_ARG_RET(ctx, parent, name, module || strchr(name, ':'), NULL);
+    LY_CHECK_ARG_RET(NULL, parent, name, module || strchr(name, ':'), NULL);
+
+    ctx = LYD_NODE_CTX(parent);
 
     /* parse the name */
     tmp = name;
@@ -952,6 +841,330 @@
     return ret;
 }
 
+API struct lyd_node *
+lyd_new_opaq(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value,
+             const char *module_name)
+{
+    struct lyd_node *ret = NULL;
+
+    LY_CHECK_ARG_RET(ctx, parent || ctx, name, module_name, NULL);
+
+    if (!ctx) {
+        ctx = LYD_NODE_CTX(parent);
+    }
+    if (!value) {
+        value = "";
+    }
+
+    if (!lyd_create_opaq(ctx, name, strlen(name), value, strlen(value), NULL, LYD_JSON, NULL, NULL, 0, module_name, &ret)
+            && parent) {
+        lyd_insert_node(parent, NULL, ret);
+    }
+    return ret;
+}
+
+API struct ly_attr *
+lyd_new_attr(struct lyd_node *parent, const char *module_name, const char *name, const char *val_str)
+{
+    struct ly_attr *ret = NULL;
+    const struct ly_ctx *ctx;
+    const char *prefix, *tmp;
+    size_t pref_len, name_len;
+
+    LY_CHECK_ARG_RET(NULL, parent, !parent->schema, name, NULL);
+
+    ctx = LYD_NODE_CTX(parent);
+
+    /* parse the name */
+    tmp = name;
+    if (ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len) || tmp[0]) {
+        LOGERR(ctx, LY_EINVAL, "Metadata name \"%s\" is not valid.", name);
+        return NULL;
+    }
+
+    /* set value if none */
+    if (!val_str) {
+        val_str = "";
+    }
+
+    ly_create_attr(parent, &ret, ctx, name, name_len, val_str, strlen(val_str), NULL, LYD_JSON, NULL, prefix,
+                   pref_len, module_name);
+    return ret;
+}
+
+API LY_ERR
+lyd_change_term(struct lyd_node *term, const char *val_str)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct lysc_type *type;
+    struct lyd_node_term *t;
+    struct lyd_node *parent;
+    struct lyd_value val = {0};
+    int dflt_change, val_change;
+
+    LY_CHECK_ARG_RET(NULL, term, term->schema, term->schema->nodetype & LYD_NODE_TERM, LY_EINVAL);
+
+    if (!val_str) {
+        val_str = "";
+    }
+    t = (struct lyd_node_term *)term;
+    type = ((struct lysc_node_leaf *)term->schema)->type;
+
+    /* parse the new value */
+    LY_CHECK_GOTO(ret = lyd_value_store(&val, term->schema, val_str, strlen(val_str), NULL, lydjson_resolve_prefix, NULL,
+                                        LYD_JSON), cleanup);
+
+    /* compare original and new value */
+    if (type->plugin->compare(&t->value, &val)) {
+        /* values differ, switch them */
+        type->plugin->free(LYD_NODE_CTX(term), &t->value);
+        t->value = val;
+        memset(&val, 0, sizeof val);
+        val_change = 1;
+    } else {
+        val_change = 0;
+    }
+
+    /* always clear the default flag */
+    if (term->flags & LYD_DEFAULT) {
+        for (parent = term; parent; parent = (struct lyd_node *)parent->parent) {
+            parent->flags &= ~LYD_DEFAULT;
+        }
+        dflt_change = 1;
+    } else {
+        dflt_change = 0;
+    }
+
+    if (val_change || dflt_change) {
+        /* make the node non-validated */
+        term->flags &= LYD_NEW;
+    }
+
+    if (val_change) {
+        if (term->schema->nodetype == LYS_LEAFLIST) {
+            /* leaf-list needs to be hashed again and re-inserted into parent */
+            lyd_unlink_hash(term);
+            lyd_hash(term);
+            LY_CHECK_GOTO(ret = lyd_insert_hash(term), cleanup);
+        } else if ((term->schema->flags & LYS_KEY) && term->parent) {
+            /* list needs to be updated if its key was changed */
+            assert(term->parent->schema->nodetype == LYS_LIST);
+            lyd_unlink_hash((struct lyd_node *)term->parent);
+            lyd_hash((struct lyd_node *)term->parent);
+            LY_CHECK_GOTO(ret = lyd_insert_hash((struct lyd_node *)term->parent), cleanup);
+        } /* else leaf that is not a key, its value is not used for its hash so it does not change */
+    }
+
+    /* retrun value */
+    if (!val_change) {
+        if (dflt_change) {
+            /* only default flag change */
+            ret = LY_EEXIST;
+        } else {
+            /* no change */
+            ret = LY_ENOT;
+        }
+    } /* else value changed, LY_SUCCESS */
+
+cleanup:
+    type->plugin->free(LYD_NODE_CTX(term), &val);
+    return ret;
+}
+
+API struct lyd_node *
+lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value, int options)
+{
+    struct lyd_node *new_node = NULL;
+
+    lyd_new_path2(parent, ctx, path, value, 0, options, NULL, &new_node);
+    return new_node;
+}
+
+API struct lyd_node *
+lyd_new_path_any(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value,
+                 LYD_ANYDATA_VALUETYPE value_type, int options)
+{
+    struct lyd_node *new_node = NULL;
+
+    lyd_new_path2(parent, ctx, path, value, value_type, options, NULL, &new_node);
+    return new_node;
+}
+
+API LY_ERR
+lyd_new_path2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value,
+              LYD_ANYDATA_VALUETYPE value_type, int options, struct lyd_node **new_parent, struct lyd_node **new_node)
+{
+    LY_ERR ret = LY_SUCCESS, r;
+    struct lyxp_expr *exp = NULL;
+    struct ly_path *p = NULL;
+    struct lyd_node *nparent = NULL, *nnode = NULL, *node = NULL, *cur_parent;
+    const struct lysc_node *schema;
+    LY_ARRAY_SIZE_TYPE path_idx = 0;
+    struct ly_path_predicate *pred;
+
+    LY_CHECK_ARG_RET(ctx, parent || ctx, path, (path[0] == '/') || parent, LY_EINVAL);
+
+    if (!ctx) {
+        ctx = LYD_NODE_CTX(parent);
+    }
+
+    /* parse path */
+    LY_CHECK_GOTO(ret = ly_path_parse(ctx, path, strlen(path), LY_PATH_BEGIN_EITHER, LY_PATH_LREF_FALSE,
+                                      LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_SIMPLE, &exp), cleanup);
+
+    /* compile path */
+    LY_CHECK_GOTO(ret = ly_path_compile(ctx, NULL, parent ? parent->schema : NULL, exp, LY_PATH_LREF_FALSE,
+                                        options & LYD_NEWOPT_OUTPUT ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT,
+                                        LY_PATH_TARGET_MANY, lydjson_resolve_prefix, NULL, LYD_JSON, &p), cleanup);
+
+    schema = p[LY_ARRAY_SIZE(p) - 1].node;
+    if ((schema->nodetype == LYS_LIST) && (p[LY_ARRAY_SIZE(p) - 1].pred_type == LY_PATH_PREDTYPE_NONE)
+            && !(options & LYD_NEWOPT_OPAQ)) {
+        LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.",
+               lys_nodetype2str(schema->nodetype), schema->name);
+        ret = LY_EINVAL;
+        goto cleanup;
+    } else if ((schema->nodetype == LYS_LEAFLIST) && (p[LY_ARRAY_SIZE(p) - 1].pred_type == LY_PATH_PREDTYPE_NONE)) {
+        /* parse leafref value into a predicate, if not defined in the path */
+        p[LY_ARRAY_SIZE(p) - 1].pred_type = LY_PATH_PREDTYPE_LEAFLIST;
+        LY_ARRAY_NEW_GOTO(ctx, p[LY_ARRAY_SIZE(p) - 1].predicates, pred, ret, cleanup);
+
+        if (!value) {
+            value = "";
+        }
+
+        r = LY_SUCCESS;
+        if (options & LYD_NEWOPT_OPAQ) {
+            r = lys_value_validate(NULL, schema, value, strlen(value), lydjson_resolve_prefix, NULL, LYD_JSON);
+        }
+        if (!r) {
+            LY_CHECK_GOTO(ret = lyd_value_store(&pred->value, schema, value, strlen(value), NULL, lydjson_resolve_prefix,
+                                                NULL, LYD_JSON), cleanup);
+        } /* else we have opaq flag and the value is not valid, leavne no predicate and then create an opaque node */
+    }
+
+    /* try to find any existing nodes in the path */
+    if (parent) {
+        ret = ly_path_eval_partial(p, parent, &path_idx, &node);
+        if (ret == LY_SUCCESS) {
+            /* the node exists, are we supposed to update it or is it just a default? */
+            if (!(options & LYD_NEWOPT_UPDATE) && !(node->flags & LYD_DEFAULT)) {
+                LOGERR(ctx, LY_EEXIST, "Path \"%s\" already exists", path);
+                ret = LY_EEXIST;
+                goto cleanup;
+            }
+
+            /* update the existing node */
+            ret = lyd_new_path_update(node, value, value_type, &nparent, &nnode);
+            goto cleanup;
+        } else if (ret == LY_EINCOMPLETE) {
+            /* some nodes were found, adjust the iterator to the next segment */
+            ++path_idx;
+        } else if (ret == LY_ENOTFOUND) {
+            /* we will create the nodes from top-level, default behavior (absolute path), or from the parent (relative path) */
+            if (lysc_data_parent(p[LY_ARRAY_SIZE(p) - 1].node)) {
+                node = parent;
+            }
+        } else {
+            /* error */
+            goto cleanup;
+        }
+    }
+
+    /* create all the non-existing nodes in a loop */
+    for (; path_idx < LY_ARRAY_SIZE(p); ++path_idx) {
+        cur_parent = node;
+        schema = p[path_idx].node;
+
+        switch (schema->nodetype) {
+        case LYS_LIST:
+            if (!(schema->flags & LYS_KEYLESS)) {
+                if ((options & LYD_NEWOPT_OPAQ) && (p[path_idx].pred_type == LY_PATH_PREDTYPE_NONE)) {
+                    /* creating opaque list without keys */
+                    LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), NULL, 0, NULL,
+                                                        LYD_JSON, NULL, NULL, 0, schema->module->name, &node), cleanup);
+                } else {
+                    assert(p[path_idx].pred_type == LY_PATH_PREDTYPE_LIST);
+                    LY_CHECK_GOTO(ret = lyd_create_list(schema, p[path_idx].predicates, &node), cleanup);
+                }
+                break;
+            }
+            /* fallthrough */
+        case LYS_CONTAINER:
+        case LYS_NOTIF:
+        case LYS_RPC:
+        case LYS_ACTION:
+            LY_CHECK_GOTO(ret = lyd_create_inner(schema, &node), cleanup);
+            break;
+        case LYS_LEAFLIST:
+            if ((options & LYD_NEWOPT_OPAQ) && (p[path_idx].pred_type == LY_PATH_PREDTYPE_NONE)) {
+                /* creating opaque leaf-list without value */
+                LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), NULL, 0, NULL,
+                                                    LYD_JSON, NULL, NULL, 0, schema->module->name, &node), cleanup);
+            } else {
+                assert(p[path_idx].pred_type == LY_PATH_PREDTYPE_LEAFLIST);
+                LY_CHECK_GOTO(ret = lyd_create_term2(schema, &p[path_idx].predicates[0].value, &node), cleanup);
+            }
+            break;
+        case LYS_LEAF:
+            /* make there is some value */
+            if (!value) {
+                value = "";
+            }
+
+            r = LY_SUCCESS;
+            if (options & LYD_NEWOPT_OPAQ) {
+                r = lys_value_validate(NULL, schema, value, strlen(value), lydjson_resolve_prefix, NULL, LYD_JSON);
+            }
+            if (!r) {
+                LY_CHECK_GOTO(ret = lyd_create_term(schema, value, strlen(value), NULL, lydjson_resolve_prefix, NULL,
+                                                    LYD_JSON, &node), cleanup);
+            } else {
+                /* creating opaque leaf without value */
+                LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), NULL, 0, NULL,
+                                                    LYD_JSON, NULL, NULL, 0, schema->module->name, &node), cleanup);
+            }
+            break;
+        case LYS_ANYDATA:
+        case LYS_ANYXML:
+            LY_CHECK_GOTO(ret = lyd_create_any(schema, value, value_type, &node), cleanup);
+            break;
+        default:
+            LOGINT(ctx);
+            ret = LY_EINT;
+            goto cleanup;
+        }
+
+        if (cur_parent) {
+            /* connect to the parent */
+            lyd_insert_node(cur_parent, NULL, node);
+        } else if (parent) {
+            /* connect to top-level siblings */
+            lyd_insert_node(NULL, &parent, node);
+        }
+
+        /* update remembered nodes */
+        if (!nparent) {
+            nparent = node;
+        }
+        nnode = node;
+    }
+
+cleanup:
+    lyxp_expr_free(ctx, exp);
+    ly_path_free(ctx, p);
+    if (!ret) {
+        /* set out params only on success */
+        if (new_parent) {
+            *new_parent = nparent;
+        }
+        if (new_node) {
+            *new_node = nnode;
+        }
+    }
+    return ret;
+}
+
 struct lyd_node *
 lyd_get_prev_key_anchor(const struct lyd_node *first_sibling, const struct lysc_node *new_key)
 {
@@ -2214,9 +2427,12 @@
     LY_ERR rc;
     const struct lyd_node *node = NULL;
     struct lyd_node_term *term;
-    struct ly_keys keys = {0};
+    struct lyxp_expr *expr = NULL;
+    uint16_t exp_idx = 0;
+    struct ly_path_predicate *predicates = NULL;
+    enum ly_path_pred_type pred_type = 0;
     struct lyd_value val = {0};
-    size_t i;
+    LY_ARRAY_SIZE_TYPE u;
 
     LY_CHECK_ARG_RET(NULL, schema, LY_EINVAL);
 
@@ -2236,8 +2452,13 @@
         /* store the value */
         LY_CHECK_GOTO(rc = lyd_value_store(&val, schema, key_or_value, val_len, 0, lydjson_resolve_prefix, NULL, LYD_JSON), cleanup);
     } else if (key_or_value && (schema->nodetype == LYS_LIST)) {
-        /* parse keys into canonical values */
-        LY_CHECK_GOTO(rc = ly_keys_parse(schema, key_or_value, val_len, 1, 1, &keys), cleanup);
+        /* parse keys */
+        LY_CHECK_GOTO(rc = ly_path_parse_predicate(schema->module->ctx, key_or_value, val_len, LY_PATH_PREFIX_OPTIONAL,
+                                                   LY_PATH_PRED_KEYS, &expr), cleanup);
+
+        /* compile them */
+        LY_CHECK_GOTO(rc = ly_path_compile_predicate(schema->module->ctx, NULL, schema, expr, &exp_idx, lydjson_resolve_prefix,
+                                                     NULL, LYD_JSON, &predicates, &pred_type), cleanup);
     }
 
     /* find first matching value */
@@ -2246,12 +2467,11 @@
             continue;
         }
 
-        if ((schema->nodetype == LYS_LIST) && keys.str) {
+        if ((schema->nodetype == LYS_LIST) && predicates) {
             /* compare all set keys */
-            for (i = 0; i < keys.key_count; ++i) {
+            LY_ARRAY_FOR(predicates, u) {
                 /* find key */
-                rc = lyd_find_sibling_val(lyd_node_children(node), (struct lysc_node *)keys.keys[i].schema, NULL, 0,
-                                          (struct lyd_node **)&term);
+                rc = lyd_find_sibling_val(lyd_node_children(node), predicates[u].key, NULL, 0, (struct lyd_node **)&term);
                 if (rc == LY_ENOTFOUND) {
                     /* all keys must always exist */
                     LOGINT_RET(schema->module->ctx);
@@ -2259,12 +2479,12 @@
                 LY_CHECK_GOTO(rc, cleanup);
 
                 /* compare values */
-                if (!term->value.realtype->plugin->compare(&term->value, &keys.keys[i].val)) {
+                if (!term->value.realtype->plugin->compare(&term->value, &predicates[u].value)) {
                     break;
                 }
             }
 
-            if (i < keys.key_count) {
+            if (u < LY_ARRAY_SIZE(predicates)) {
                 /* not a match */
                 continue;
             }
@@ -2297,7 +2517,8 @@
     rc = LY_SUCCESS;
 
 cleanup:
-    ly_keys_clean(&keys);
+    ly_path_predicates_free(schema->module->ctx, pred_type, NULL, predicates);
+    lyxp_expr_free(schema->module->ctx, expr);
     if (val.realtype) {
         val.realtype->plugin->free(schema->module->ctx, &val);
     }
diff --git a/src/tree_data.h b/src/tree_data.h
index 34524dc..803b610 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -401,7 +401,7 @@
     void *priv;                      /**< private user data, not used by libyang */
 #endif
 
-    union {
+    union lyd_any_value {
         struct lyd_node *tree;       /**< data tree */
         const char *str;             /**< Generic string data */
         const char *xml;             /**< Serialized XML data */
@@ -629,7 +629,7 @@
 LY_ERR lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *tree, int val_opts);
 
 /**
- * @brief Create a new inner node in a data tree.
+ * @brief Create a new inner node in the data tree.
  *
  * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element.
  * @param[in] module Module of the node being created. If NULL, @p parent module will be used.
@@ -640,7 +640,7 @@
 struct lyd_node *lyd_new_inner(struct lyd_node *parent, const struct lys_module *module, const char *name);
 
 /**
- * @brief Create a new list node in a data tree.
+ * @brief Create a new list node in the data tree.
  *
  * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element.
  * @param[in] module Module of the node being created. If NULL, @p parent module will be used.
@@ -654,7 +654,7 @@
 struct lyd_node *lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, ...);
 
 /**
- * @brief Create a new list node in a data tree.
+ * @brief Create a new list node in the data tree.
  *
  * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element.
  * @param[in] module Module of the node being created. If NULL, @p parent module will be used.
@@ -668,7 +668,7 @@
 struct lyd_node *lyd_new_list2(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *keys);
 
 /**
- * @brief Create a new term node in a data tree.
+ * @brief Create a new term node in the data tree.
  *
  * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element.
  * @param[in] module Module of the node being created. If NULL, @p parent module will be used.
@@ -681,7 +681,7 @@
 struct lyd_node *lyd_new_term(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *val_str);
 
 /**
- * @brief Create a new any node in a data tree.
+ * @brief Create a new any node in the data tree.
  *
  * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element.
  * @param[in] module Module of the node being created. If NULL, @p parent module will be used.
@@ -698,18 +698,142 @@
  * @brief Create new metadata for a data node.
  *
  * @param[in] parent Parent node for the metadata being created.
- * @param[in] module Module of the metdata being created. If NULL, @p name must include module name as the prefix.
+ * @param[in] module Module of the metadata being created. If NULL, @p name must include module name as the prefix.
  * @param[in] name Annotation name of the new metadata. It can include the annotation module as the prefix.
  *            If the prefix is specified it is always used but if not specified, @p module must be set.
- * @param[in] val_str String form of the value of the metadata being created. In case of an instance-identifier or identityref
+ * @param[in] val_str String form of the value of the metadata. In case of an instance-identifier or identityref
  * value, the JSON format is expected (module names instead of prefixes).
- * @return New created metadata in the @p parent.
+ * @return New created metadata of @p parent.
  * @return NULL on error.
  */
 struct lyd_meta *lyd_new_meta(struct lyd_node *parent, const struct lys_module *module, const char *name,
                               const char *val_str);
 
 /**
+ * @brief Create a new opaque node in the data tree.
+ *
+ * @param[in] parent Parent node for the node beaing created. NULL in case of creating a top level element.
+ * @param[in] ctx libyang context. If NULL, @p parent context will be used.
+ * @param[in] name Node name.
+ * @param[in] value Node value, may be NULL.
+ * @param[in] module_name Node module name.
+ * @return New created node.
+ * @return NULL on error.
+ */
+struct lyd_node *lyd_new_opaq(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value,
+                              const char *module_name);
+
+/**
+ * @brief Create new attribute for an opaque data node.
+ *
+ * @param[in] parent Parent opaque node for the attribute being created.
+ * @param[in] module Module name of the attribute being created. There may be none.
+ * @param[in] name Attribute name. It can include the module name as the prefix.
+ * @param[in] val_str String value of the attribute. Is stored directly.
+ * @return New created attribute of @p parent.
+ * @return NULL on error.
+ */
+struct ly_attr *lyd_new_attr(struct lyd_node *parent, const char *module_name, const char *name, const char *val_str);
+
+/**
+ * @defgroup pathoptions Data path creation options
+ * @ingroup datatree
+ *
+ * Various options to change lyd_new_path*() behavior.
+ *
+ * Default behavior:
+ * - if the target node already exists (and is not default), an error is returned.
+ * - the whole path to the target node is created (with any missing parents) if necessary.
+ * - RPC output schema children are completely ignored in all modules. Input is searched and nodes created normally.
+ * @{
+ */
+
+#define LYD_NEWOPT_UPDATE 0x01 /**< If the target node exists, is a leaf, and it is updated with a new value or its
+                                    default flag is changed, it is returned. If the target node exists and is not
+                                    a leaf or generally no change occurs in the @p parent tree, NULL is returned and
+                                    no error set. */
+#define LYD_NEWOPT_OUTPUT 0x02 /**< Changes the behavior to ignoring RPC/action input schema nodes and using only
+                                    output ones. */
+#define LYD_NEWOPT_OPAQ   0x04 /**< Enables the creation of opaque nodes with some specific rules. If the __last node__
+                                    in the path is not uniquely defined ((leaf-)list without a predicate) or has an
+                                    invalid value (leaf/leaf-list), it is created as opaque. */
+
+/** @} pathoptions */
+
+/**
+ * @brief Create a new node in the data tree based on a path. Cannot be used for anyxml/anydata nodes,
+ * for those use ::lyd_new_path_any.
+ *
+ * If @p path points to a list key and the list instance does not exist, the key value from the predicate is used
+ * and @p value is ignored. Also, if a leaf-list is being created and both a predicate is defined in @p path
+ * and @p value is set, the predicate is preferred.
+ *
+ * @param[in] parent Data parent to add to/modify, can be NULL.
+ * @param[in] ctx libyang context, must be set if @p parent is NULL.
+ * @param[in] path Path to create (TODO ref path).
+ * @param[in] value Value of the new leaf/leaf-list. For other node types, it is ignored.
+ * @param[in] options Bitmask of options, see @ref pathoptions.
+ * @return (Last) created node.
+ * @return NULL on error.
+ */
+struct lyd_node *lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value,
+                              int options);
+
+/**
+ * @brief Create a new node in the data tree based on a path. All node types can be created.
+ *
+ * If @p path points to a list key and the list instance does not exist, the key value from the predicate is used
+ * and @p value is ignored. Also, if a leaf-list is being created and both a predicate is defined in @p path
+ * and @p value is set, the predicate is preferred.
+ *
+ * @param[in] parent Data parent to add to/modify, can be NULL.
+ * @param[in] ctx libyang context, must be set if @p parent is NULL.
+ * @param[in] path Path to create (TODO ref path).
+ * @param[in] value Value of the new leaf/leaf-list/anyxml/anydata. For other node types, it is ignored.
+ * @param[in] value_type Anyxml/anydata node @p value type.
+ * @param[in] options Bitmask of options, see @ref pathoptions.
+ * @return (Last) created node.
+ * @return NULL on error.
+ */
+struct lyd_node *lyd_new_path_any(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value,
+                                  LYD_ANYDATA_VALUETYPE value_type, int options);
+
+/**
+ * @brief Create a new node in the data tree based on a path. All node types can be created.
+ *
+ * If @p path points to a list key and the list instance does not exist, the key value from the predicate is used
+ * and @p value is ignored. Also, if a leaf-list is being created and both a predicate is defined in @p path
+ * and @p value is set, the predicate is preferred.
+ *
+ * @param[in] parent Data parent to add to/modify, can be NULL.
+ * @param[in] ctx libyang context, must be set if @p parent is NULL.
+ * @param[in] path Path to create (TODO ref path).
+ * @param[in] value Value of the new leaf/leaf-list/anyxml/anydata. For other node types, it is ignored.
+ * @param[in] value_type Anyxml/anydata node @p value type.
+ * @param[in] options Bitmask of options, see @ref pathoptions.
+ * @param[out] new_parent First parent node created, can be NULL. If only one node was created, equals to @p new_node.
+ * @param[out] new_node Last node created, can be NULL.
+ * @return LY_ERR value.
+ */
+LY_ERR lyd_new_path2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value,
+                     LYD_ANYDATA_VALUETYPE value_type, int options, struct lyd_node **new_parent, struct lyd_node **new_node);
+
+/**
+ * @brief Change the value of a term (leaf or leaf-list) node.
+ *
+ * Node changed this way is always considered explicitly set, meaning its default flag
+ * is always cleared.
+ *
+ * @param[in] term Term node to change.
+ * @param[in] val_str New value to set, any prefixes are expected in JSON format.
+ * @return LY_SUCCESS if value was changed,
+ * @return LY_EEXIST if value was the same and only the default flag was cleared,
+ * @return LY_ENOT if the values were equal and no change occured,
+ * @return LY_ERR value on other errors.
+ */
+LY_ERR lyd_change_term(struct lyd_node *term, const char *val_str);
+
+/**
  * @brief Insert a child into a parent. It is inserted as the last child.
  *
  * - if the node is part of some other tree, it is automatically unlinked.
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 73849b8..bb70f31 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -6862,8 +6862,9 @@
     assert(node->nodetype & (LYS_LEAF | LYS_LEAFLIST));
 
     /* try to find the target */
-    LY_CHECK_RET(ly_path_compile(node->module, node, lref->path, LY_PATH_LREF_TRUE, lys_resolve_prefix, lref->path_context,
-                                 LYD_SCHEMA, &p));
+    LY_CHECK_RET(ly_path_compile(ctx->ctx, node->module, node, lref->path, LY_PATH_LREF_TRUE,
+                                 lysc_is_output(node) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY,
+                                 lys_resolve_prefix, lref->path_context, LYD_SCHEMA, &p));
 
     /* get the target node */
     target = p[LY_ARRAY_SIZE(p) - 1].node;
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index f1ad8b9..16bc09e 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -1693,3 +1693,17 @@
 
     return parent;
 }
+
+int
+lysc_is_output(const struct lysc_node *schema)
+{
+    const struct lysc_node *parent;
+
+    assert(schema);
+
+    for (parent = schema->parent; parent && !(parent->nodetype & (LYS_RPC | LYS_ACTION)); parent = parent->parent);
+    if (parent && (schema->flags & LYS_CONFIG_R)) {
+        return 1;
+    }
+    return 0;
+}
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index ddc7966..ed07a25 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -802,4 +802,13 @@
  */
 const struct lysc_node *lysc_data_parent(const struct lysc_node *schema);
 
+/**
+ * @brief Learn whether a node is in an operation output.
+ *
+ * @param[in] schema Schema node to examine.
+ * @return non-zero is the node is in output,
+ * @return 0 if it is not.
+ */
+int lysc_is_output(const struct lysc_node *schema);
+
 #endif /* LY_TREE_SCHEMA_INTERNAL_H_ */
diff --git a/src/xpath.c b/src/xpath.c
index 459c78e..39c7365 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -3611,6 +3611,7 @@
     char *errmsg = NULL;
     const char *val;
     int dynamic;
+    uint8_t oper;
     LY_ERR rc = LY_SUCCESS;
 
     if (options & LYXP_SCNODE_ALL) {
@@ -3625,15 +3626,16 @@
         set_scnode_clear_ctx(set);
         if (sleaf && (sleaf->type->basetype == LY_TYPE_LEAFREF)) {
             lref = (struct lysc_type_leafref *)sleaf->type;
+            oper = lysc_is_output((struct lysc_node *)sleaf) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT;
 
             /* it was already evaluated on schema, it must succeed */
             if (set->format == LYD_JSON) {
-                rc = ly_path_compile(sleaf->module, (struct lysc_node *)sleaf, lref->path, LY_PATH_LREF_TRUE,
-                                     lydjson_resolve_prefix, NULL, LYD_JSON, &p);
+                rc = ly_path_compile(set->ctx, sleaf->module, (struct lysc_node *)sleaf, lref->path, LY_PATH_LREF_TRUE,
+                                     oper, LY_PATH_TARGET_MANY, lydjson_resolve_prefix, NULL, LYD_JSON, &p);
             } else {
                 assert(set->format == LYD_SCHEMA);
-                rc = ly_path_compile(sleaf->module, (struct lysc_node *)sleaf, lref->path, LY_PATH_LREF_TRUE,
-                                     lys_resolve_prefix, lref->path_context, LYD_SCHEMA, &p);
+                rc = ly_path_compile(set->ctx, sleaf->module, (struct lysc_node *)sleaf, lref->path, LY_PATH_LREF_TRUE,
+                                     oper, LY_PATH_TARGET_MANY, lys_resolve_prefix, lref->path_context, LYD_SCHEMA, &p);
             }
             assert(!rc);
 
@@ -6906,12 +6908,12 @@
     /* compile */
     switch (format) {
     case LYD_SCHEMA:
-        ret = ly_path_compile_predicate(scnode->module, scnode, exp2, &pred_idx, lys_resolve_prefix, scnode->module,
-                                        LYD_SCHEMA, predicates, pred_type);
+        ret = ly_path_compile_predicate(scnode->module->ctx, scnode->module, scnode, exp2, &pred_idx, lys_resolve_prefix,
+                                        scnode->module, LYD_SCHEMA, predicates, pred_type);
         break;
     case LYD_JSON:
-        ret = ly_path_compile_predicate(scnode->module, scnode, exp2, &pred_idx, lydjson_resolve_prefix, NULL, LYD_JSON,
-                                        predicates, pred_type);
+        ret = ly_path_compile_predicate(scnode->module->ctx, scnode->module, scnode, exp2, &pred_idx,
+                                        lydjson_resolve_prefix, NULL, LYD_JSON, predicates, pred_type);
         break;
     case LYD_XML:
         ret = LY_EINT;
diff --git a/tests/utests/data/test_new.c b/tests/utests/data/test_new.c
index 8d96df1..ee42a0b 100644
--- a/tests/utests/data/test_new.c
+++ b/tests/utests/data/test_new.c
@@ -204,10 +204,129 @@
     *state = NULL;
 }
 
+static void
+test_opaq(void **state)
+{
+    *state = test_opaq;
+
+    struct lyd_node *root, *node;
+    struct lyd_node_opaq *opq;
+
+    root = lyd_new_opaq(NULL, ctx, "node1", NULL, "my-module");
+    assert_non_null(root);
+    assert_null(root->schema);
+    opq = (struct lyd_node_opaq *)root;
+    assert_string_equal(opq->name, "node1");
+    assert_string_equal(opq->value, "");
+    assert_string_equal(opq->prefix.ns, "my-module");
+
+    node = lyd_new_opaq(root, NULL, "node2", "value", "my-module2");
+    assert_non_null(node);
+    assert_null(node->schema);
+    opq = (struct lyd_node_opaq *)node;
+    assert_string_equal(opq->name, "node2");
+    assert_string_equal(opq->value, "value");
+    assert_string_equal(opq->prefix.ns, "my-module2");
+    assert_ptr_equal(opq->parent, root);
+
+    lyd_free_tree(root);
+
+    *state = NULL;
+}
+
+static void
+test_path(void **state)
+{
+    *state = test_path;
+
+    LY_ERR ret;
+    struct lyd_node *root, *node, *parent;
+    int dynamic;
+
+    /* create 2 nodes */
+    ret = lyd_new_path2(NULL, ctx, "/a:c/x[.='val']", "vvv", 0, 0, &root, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_non_null(root);
+    assert_string_equal(root->schema->name, "c");
+    assert_non_null(node);
+    assert_string_equal(node->schema->name, "x");
+    assert_string_equal("val", lyd_value2str((struct lyd_node_term *)node, &dynamic));
+    assert_int_equal(dynamic, 0);
+
+    /* append another */
+    ret = lyd_new_path2(root, NULL, "/a:c/x", "val2", 0, 0, &parent, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_ptr_equal(parent, node);
+    assert_string_equal(node->schema->name, "x");
+    assert_string_equal("val2", lyd_value2str((struct lyd_node_term *)node, &dynamic));
+    assert_int_equal(dynamic, 0);
+
+    /* and a last one */
+    ret = lyd_new_path2(root, NULL, "x", "val3", 0, 0, &parent, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_ptr_equal(parent, node);
+    assert_string_equal(node->schema->name, "x");
+    assert_string_equal("val3", lyd_value2str((struct lyd_node_term *)node, &dynamic));
+    assert_int_equal(dynamic, 0);
+
+    lyd_free_tree(root);
+
+    /* try LYD_NEWOPT_OPAQ */
+    ret = lyd_new_path2(NULL, ctx, "/a:l1", NULL, 0, 0, NULL, NULL);
+    assert_int_equal(ret, LY_EINVAL);
+    logbuf_assert("Predicate missing for list \"l1\" in path.");
+
+    ret = lyd_new_path2(NULL, ctx, "/a:l1", NULL, 0, LYD_NEWOPT_OPAQ, NULL, &root);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_non_null(root);
+    assert_null(root->schema);
+
+    lyd_free_tree(root);
+
+    ret = lyd_new_path2(NULL, ctx, "/a:foo", NULL, 0, 0, NULL, NULL);
+    assert_int_equal(ret, LY_EVALID);
+    logbuf_assert("Invalid empty uint16 value. /a:foo");
+
+    ret = lyd_new_path2(NULL, ctx, "/a:foo", NULL, 0, LYD_NEWOPT_OPAQ, NULL, &root);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_non_null(root);
+    assert_null(root->schema);
+
+    lyd_free_tree(root);
+
+    /* try LYD_NEWOPT_UPDATE */
+    ret = lyd_new_path2(NULL, ctx, "/a:l2[1]/c/x", "val", 0, 0, &root, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_non_null(root);
+    assert_string_equal(node->schema->name, "x");
+    assert_string_equal("val", lyd_value2str((struct lyd_node_term *)node, &dynamic));
+    assert_int_equal(dynamic, 0);
+
+    ret = lyd_new_path2(root, NULL, "/a:l2[1]/c/x", "val", 0, 0, NULL, &node);
+    assert_int_equal(ret, LY_EEXIST);
+
+    ret = lyd_new_path2(root, NULL, "/a:l2[1]/c/x", "val", 0, LYD_NEWOPT_UPDATE, NULL, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_null(node);
+
+    ret = lyd_new_path2(root, NULL, "/a:l2[1]/c/x", "val2", 0, LYD_NEWOPT_UPDATE, NULL, &node);
+    assert_int_equal(ret, LY_SUCCESS);
+    assert_non_null(node);
+    assert_string_equal(node->schema->name, "x");
+    assert_string_equal("val2", lyd_value2str((struct lyd_node_term *)node, &dynamic));
+    assert_int_equal(dynamic, 0);
+
+    lyd_free_tree(root);
+
+    *state = NULL;
+}
+
 int main(void)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test_setup_teardown(test_top_level, setup, teardown),
+        cmocka_unit_test_setup_teardown(test_opaq, setup, teardown),
+        cmocka_unit_test_setup_teardown(test_path, setup, teardown),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/test_inout.c b/tests/utests/test_inout.c
index 1542d0f..c621022 100644
--- a/tests/utests/test_inout.c
+++ b/tests/utests/test_inout.c
@@ -1,9 +1,9 @@
-/*
+/**
  * @file test_inout.c
  * @author: Radek Krejci <rkrejci@cesnet.cz>
  * @brief unit tests for input and output handlers functions
  *
- * Copyright (c) 2018 CESNET, z.s.p.o.
+ * Copyright (c) 2020 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.