tree data UPDATE xpath trim function

Refs #2148
diff --git a/src/tree_data.c b/src/tree_data.c
index 5676da8..809a257 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -3015,6 +3015,139 @@
     return ret;
 }
 
+/**
+ * @brief Hash table node equal callback.
+ */
+static ly_bool
+lyd_trim_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data))
+{
+    struct lyd_node *node1, *node2;
+
+    node1 = *(struct lyd_node **)val1_p;
+    node2 = *(struct lyd_node **)val2_p;
+
+    return node1 == node2;
+}
+
+LIBYANG_API_DEF LY_ERR
+lyd_trim_xpath(struct lyd_node **tree, const char *xpath, const struct lyxp_var *vars)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct ly_ctx *ctx;
+    struct lyxp_set xp_set = {0};
+    struct lyxp_expr *exp = NULL;
+    struct lyd_node *node, *parent;
+    struct lyxp_set_hash_node hnode;
+    struct ly_ht *parent_ht = NULL;
+    struct ly_set free_set = {0};
+    uint32_t i, hash;
+    ly_bool is_result;
+
+    LY_CHECK_ARG_RET(NULL, tree, xpath, LY_EINVAL);
+
+    if (!*tree) {
+        /* nothing to do */
+        goto cleanup;
+    }
+
+    *tree = lyd_first_sibling(*tree);
+    ctx = (struct ly_ctx *)LYD_CTX(*tree);
+
+    /* parse expression */
+    ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    /* evaluate expression */
+    ret = lyxp_eval(ctx, exp, NULL, LY_VALUE_JSON, NULL, *tree, *tree, *tree, vars, &xp_set, LYXP_IGNORE_WHEN);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    /* create hash table for all the parents of results */
+    parent_ht = lyht_new(32, sizeof *node, lyd_trim_equal_cb, NULL, 1);
+    LY_CHECK_GOTO(!parent_ht, cleanup);
+
+    for (i = 0; i < xp_set.used; ++i) {
+        if (xp_set.val.nodes[i].type != LYXP_NODE_ELEM) {
+            /* ignore */
+            continue;
+        }
+
+        for (parent = lyd_parent(xp_set.val.nodes[i].node); parent; parent = lyd_parent(parent)) {
+            /* add the parent into parent_ht */
+            ret = lyht_insert(parent_ht, &parent, parent->hash, NULL);
+            if (ret == LY_EEXIST) {
+                /* shared parent, we are done */
+                break;
+            }
+            LY_CHECK_GOTO(ret, cleanup);
+        }
+    }
+
+    hnode.type = LYXP_NODE_ELEM;
+    LY_LIST_FOR(*tree, parent) {
+        LYD_TREE_DFS_BEGIN(parent, node) {
+            if (lysc_is_key(node->schema)) {
+                /* ignore */
+                goto next_iter;
+            }
+
+            /* check the results */
+            is_result = 0;
+            if (xp_set.ht) {
+                hnode.node = node;
+                hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node);
+                hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type);
+                hash = lyht_hash_multi(hash, NULL, 0);
+
+                if (!lyht_find(xp_set.ht, &hnode, hash, NULL)) {
+                    is_result = 1;
+                }
+            } else {
+                /* not enough elements for a hash table */
+                for (i = 0; i < xp_set.used; ++i) {
+                    if (xp_set.val.nodes[i].type != LYXP_NODE_ELEM) {
+                        /* ignore */
+                        continue;
+                    }
+
+                    if (xp_set.val.nodes[i].node == node) {
+                        is_result = 1;
+                        break;
+                    }
+                }
+            }
+
+            if (is_result) {
+                /* keep the whole subtree if the node is in the results */
+                LYD_TREE_DFS_continue = 1;
+            } else if (lyht_find(parent_ht, &node, node->hash, NULL)) {
+                /* free the whole subtree if the node is not even among the selected parents */
+                ret = ly_set_add(&free_set, node, 1, NULL);
+                LY_CHECK_GOTO(ret, cleanup);
+                LYD_TREE_DFS_continue = 1;
+            } /* else keep the parent node because a subtree is in the results */
+
+next_iter:
+            LYD_TREE_DFS_END(parent, node);
+        }
+    }
+
+    /* free */
+    for (i = 0; i < free_set.count; ++i) {
+        node = free_set.dnodes[i];
+        if (*tree == node) {
+            *tree = (*tree)->next;
+        }
+        lyd_free_tree(node);
+    }
+
+cleanup:
+    lyxp_set_free_content(&xp_set);
+    lyxp_expr_free(ctx, exp);
+    lyht_free(parent_ht, NULL);
+    ly_set_erase(&free_set, NULL);
+    return ret;
+}
+
 LIBYANG_API_DEF LY_ERR
 lyd_find_path(const struct lyd_node *ctx_node, const char *path, ly_bool output, struct lyd_node **match)
 {
diff --git a/src/tree_data.h b/src/tree_data.h
index fd8137c..4d87fba 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -2607,6 +2607,17 @@
         long double *number, ly_bool *boolean);
 
 /**
+ * @brief Evaluate an XPath on data and free all the nodes except the subtrees selected by the expression.
+ *
+ * @param[in,out] tree Data tree to evaluate on and trim.
+ * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format.
+ * @param[in] vars Optional [sized array](@ref sizedarrays) of XPath variables.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LIBYANG_API_DEF LY_ERR lyd_trim_xpath(struct lyd_node **tree, const char *xpath, const struct lyxp_var *vars);
+
+/**
  * @brief Search in given data for a node uniquely identified by a path.
  *
  * Always works in constant (*O(1)*) complexity. To be exact, it is *O(n)* where *n* is the depth
diff --git a/tests/utests/basic/test_xpath.c b/tests/utests/basic/test_xpath.c
index 1639aed..754abf2 100644
--- a/tests/utests/basic/test_xpath.c
+++ b/tests/utests/basic/test_xpath.c
@@ -1092,6 +1092,154 @@
     lyd_free_all(tree);
 }
 
+static void
+test_trim(void **state)
+{
+    const char *data;
+    char *str1;
+    struct lyd_node *tree;
+
+    data =
+            "<l1 xmlns=\"urn:tests:a\">"
+            "  <a>a1</a>"
+            "  <b>b1</b>"
+            "  <c>c1</c>"
+            "</l1>"
+            "<l1 xmlns=\"urn:tests:a\">"
+            "  <a>a2</a>"
+            "  <b>b2</b>"
+            "</l1>"
+            "<l1 xmlns=\"urn:tests:a\">"
+            "  <a>a3</a>"
+            "  <b>b3</b>"
+            "</l1>"
+            "<l1 xmlns=\"urn:tests:a\">"
+            "  <a>a4</a>"
+            "  <b>b4</b>"
+            "  <c>c4</c>"
+            "</l1>"
+            "<l1 xmlns=\"urn:tests:a\">"
+            "  <a>a5</a>"
+            "  <b>b5</b>"
+            "  <c>c5</c>"
+            "</l1>"
+            "<foo2 xmlns=\"urn:tests:a\">50</foo2>"
+            "<c xmlns=\"urn:tests:a\">"
+            "  <x>key2</x>"
+            "  <ll>"
+            "    <a>key1</a>"
+            "    <ll>"
+            "      <a>key11</a>"
+            "      <b>val11</b>"
+            "    </ll>"
+            "    <ll>"
+            "      <a>key12</a>"
+            "      <b>val12</b>"
+            "    </ll>"
+            "    <ll>"
+            "      <a>key13</a>"
+            "      <b>val13</b>"
+            "    </ll>"
+            "  </ll>"
+            "  <ll>"
+            "    <a>key2</a>"
+            "    <ll>"
+            "      <a>key21</a>"
+            "      <b>val21</b>"
+            "    </ll>"
+            "    <ll>"
+            "      <a>key22</a>"
+            "      <b>val22</b>"
+            "    </ll>"
+            "  </ll>"
+            "  <ll>"
+            "    <a>key3</a>"
+            "    <ll>"
+            "      <a>key31</a>"
+            "      <b>val31</b>"
+            "    </ll>"
+            "    <ll>"
+            "      <a>key32</a>"
+            "      <b>val32</b>"
+            "    </ll>"
+            "  </ll>"
+            "</c>";
+
+    /* trim #1 */
+    assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree));
+    assert_non_null(tree);
+
+    assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/a:c/ll/ll[a='key11']", NULL));
+    lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS);
+    assert_string_equal(str1,
+            "<c xmlns=\"urn:tests:a\">\n"
+            "  <ll>\n"
+            "    <a>key1</a>\n"
+            "    <ll>\n"
+            "      <a>key11</a>\n"
+            "      <b>val11</b>\n"
+            "    </ll>\n"
+            "  </ll>\n"
+            "</c>\n");
+
+    free(str1);
+    lyd_free_all(tree);
+
+    /* trim #2 */
+    assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree));
+    assert_non_null(tree);
+
+    assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/a:c/ll/ll[contains(.,'2')]", NULL));
+    lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS);
+    assert_string_equal(str1,
+            "<c xmlns=\"urn:tests:a\">\n"
+            "  <ll>\n"
+            "    <a>key1</a>\n"
+            "    <ll>\n"
+            "      <a>key12</a>\n"
+            "      <b>val12</b>\n"
+            "    </ll>\n"
+            "  </ll>\n"
+            "  <ll>\n"
+            "    <a>key2</a>\n"
+            "    <ll>\n"
+            "      <a>key21</a>\n"
+            "      <b>val21</b>\n"
+            "    </ll>\n"
+            "    <ll>\n"
+            "      <a>key22</a>\n"
+            "      <b>val22</b>\n"
+            "    </ll>\n"
+            "  </ll>\n"
+            "  <ll>\n"
+            "    <a>key3</a>\n"
+            "    <ll>\n"
+            "      <a>key32</a>\n"
+            "      <b>val32</b>\n"
+            "    </ll>\n"
+            "  </ll>\n"
+            "</c>\n");
+
+    free(str1);
+    lyd_free_all(tree);
+
+    /* trim #3 */
+    assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree));
+    assert_non_null(tree);
+
+    assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/l1[4]//.", NULL));
+    lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS);
+    assert_string_equal(str1,
+            "<l1 xmlns=\"urn:tests:a\">\n"
+            "  <a>a4</a>\n"
+            "  <b>b4</b>\n"
+            "  <c>c4</c>\n"
+            "</l1>\n");
+
+    free(str1);
+    lyd_free_all(tree);
+}
+
 int
 main(void)
 {
@@ -1108,6 +1256,7 @@
         UTEST(test_augment, setup),
         UTEST(test_variables, setup),
         UTEST(test_axes, setup),
+        UTEST(test_trim, setup),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);