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/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);
     }