tree_data FEATURE add XPath variable bindings
diff --git a/src/tree_data.h b/src/tree_data.h
index 54a8575..eda8484 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -41,6 +41,7 @@
 struct lyd_node_opaq;
 struct lyd_node_term;
 struct timespec;
+struct lyxp_var;
 
 /**
  * @page howtoData Data Instances
@@ -2315,6 +2316,26 @@
 LY_ERR lyd_find_sibling_opaq_next(const struct lyd_node *first, const char *name, struct lyd_node **match);
 
 /**
+ * @brief Set a new XPath variable to @p vars.
+ *
+ * @param[in,out] vars Pointer to [sized array](@ref sizedarrays) of XPath variables.
+ * To create a new array, set the @p vars target pointer to NULL.
+ * Otherwise variable named @p name with a value @p value will be added to the @p vars
+ * or its value will be changed if the variable is already defined.
+ * @param[in] name Name of the added/edited variable.
+ * @param[in] value Value of the variable.
+ * @return LY_ERR value.
+ */
+LY_ERR lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value);
+
+/**
+ * @brief Free the XPath variables.
+ *
+ * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables.
+ */
+void lyxp_vars_free(struct lyxp_var *vars);
+
+/**
  * @brief Search in the given data for instances of nodes matching the provided XPath.
  *
  * If a list instance is being selected with all its key values specified (but not necessarily ordered)
diff --git a/src/tree_data_helpers.c b/src/tree_data_helpers.c
index 6bf1a6d..3863ade 100644
--- a/src/tree_data_helpers.c
+++ b/src/tree_data_helpers.c
@@ -39,6 +39,7 @@
 #include "tree_schema_internal.h"
 #include "validation.h"
 #include "xml.h"
+#include "xpath.h"
 
 /**
  * @brief Find an entry in duplicate instance cache for an instance. Create it if it does not exist.
@@ -163,6 +164,61 @@
     }
 }
 
+API LY_ERR
+lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value)
+{
+    LY_ERR ret = LY_SUCCESS;
+    char *var_name = NULL, *var_value = NULL;
+    struct lyxp_var *item;
+
+    if (!vars || !name || !value) {
+        return LY_EINVAL;
+    }
+
+    /* If variable is already defined then change its value. */
+    if (*vars && !lyxp_vars_find(*vars, name, 0, &item)) {
+        var_value = strdup(value);
+        LY_CHECK_RET(!var_value, LY_EMEM);
+
+        /* Set new value. */
+        free(item->value);
+        item->value = var_value;
+    } else {
+        var_name = strdup(name);
+        var_value = strdup(value);
+        LY_CHECK_ERR_GOTO(!var_name || !var_value, ret = LY_EMEM, error);
+
+        /* Add new variable. */
+        LY_ARRAY_NEW_GOTO(NULL, *vars, item, ret, error);
+        item->name = var_name;
+        item->value = var_value;
+    }
+
+    return LY_SUCCESS;
+
+error:
+    free(var_name);
+    free(var_value);
+    return ret;
+}
+
+API void
+lyxp_vars_free(struct lyxp_var *vars)
+{
+    LY_ARRAY_COUNT_TYPE u;
+
+    if (!vars) {
+        return;
+    }
+
+    LY_ARRAY_FOR(vars, u) {
+        free(vars[u].name);
+        free(vars[u].value);
+    }
+
+    LY_ARRAY_FREE(vars);
+}
+
 API struct lyd_node *
 lyd_child_no_keys(const struct lyd_node *node)
 {
diff --git a/src/xpath.c b/src/xpath.c
index 53835f5..e1eadc0 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -7733,6 +7733,30 @@
     return LY_SUCCESS;
 }
 
+LY_ERR
+lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var)
+{
+    LY_ERR ret = LY_ENOTFOUND;
+    LY_ARRAY_COUNT_TYPE u;
+
+    assert(vars && name);
+
+    name_len = name_len ? name_len : strlen(name);
+
+    LY_ARRAY_FOR(vars, u) {
+        if (!strncmp(vars[u].name, name, name_len)) {
+            ret = LY_SUCCESS;
+            break;
+        }
+    }
+
+    if (var && !ret) {
+        *var = &vars[u];
+    }
+
+    return ret;
+}
+
 /**
  * @brief Evaluate PathExpr. Logs directly on error.
  *
diff --git a/src/xpath.h b/src/xpath.h
index de0a7d3..eb832eb 100644
--- a/src/xpath.h
+++ b/src/xpath.h
@@ -222,6 +222,15 @@
 } _PACKED;
 
 /**
+ * @brief XPath variable bindings.
+ */
+struct lyxp_var {
+    char *name;     /**< Variable name. In the XPath expression, the name is preceded by a '$' character. */
+    char *value;    /**< The value of a variable is an object, which can be of any of the type that are possible
+                         for the value of an expression. */
+};
+
+/**
  * @brief XPath set - (partial) result.
  */
 struct lyxp_set {
@@ -460,6 +469,17 @@
         enum lyxp_token want_tok1, enum lyxp_token want_tok2);
 
 /**
+ * @brief Find variable named @name in @p vars.
+ *
+ * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables.
+ * @param[in] name Name of the variable being searched.
+ * @param[in] name_len Name length can be set to 0 if @p name is terminated by null byte.
+ * @param[out] var Variable that was found. The parameter is optional.
+ * @return LY_SUCCESS if the variable was found, otherwise LY_ENOTFOUND.
+ */
+LY_ERR lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var);
+
+/**
  * @brief Frees a parsed XPath expression. @p expr should not be used afterwards.
  *
  * @param[in] ctx libyang context of the expression.
diff --git a/tests/utests/data/test_tree_data.c b/tests/utests/data/test_tree_data.c
index 9a79a64..5f6fe7e 100644
--- a/tests/utests/data/test_tree_data.c
+++ b/tests/utests/data/test_tree_data.c
@@ -528,6 +528,56 @@
     lyd_free_all(tree);
 }
 
+static void
+test_lyxp_vars(void **UNUSED(state))
+{
+    struct lyxp_var *vars;
+
+    /* Test free. */
+    vars = NULL;
+    lyxp_vars_free(vars);
+
+    /* Bad arguments for lyxp_vars_add(). */
+    assert_int_equal(LY_EINVAL, lyxp_vars_set(NULL, "var1", "val1"));
+    assert_int_equal(LY_EINVAL, lyxp_vars_set(&vars, NULL, "val1"));
+    assert_int_equal(LY_EINVAL, lyxp_vars_set(&vars, "var1", NULL));
+    lyxp_vars_free(vars);
+    vars = NULL;
+
+    /* Add one item. */
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var1", "val1"));
+    assert_int_equal(LY_ARRAY_COUNT(vars), 1);
+    assert_string_equal(vars[0].name, "var1");
+    assert_string_equal(vars[0].value, "val1");
+    lyxp_vars_free(vars);
+    vars = NULL;
+
+    /* Add three items. */
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var1", "val1"));
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var2", "val2"));
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var3", "val3"));
+    assert_int_equal(LY_ARRAY_COUNT(vars), 3);
+    assert_string_equal(vars[0].name, "var1");
+    assert_string_equal(vars[0].value, "val1");
+    assert_string_equal(vars[1].name, "var2");
+    assert_string_equal(vars[1].value, "val2");
+    assert_string_equal(vars[2].name, "var3");
+    assert_string_equal(vars[2].value, "val3");
+    lyxp_vars_free(vars);
+    vars = NULL;
+
+    /* Change value of a variable. */
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var1", "val1"));
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var2", "val2"));
+    assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var1", "new_value"));
+    assert_string_equal(vars[0].name, "var1");
+    assert_string_equal(vars[0].value, "new_value");
+    assert_string_equal(vars[1].name, "var2");
+    assert_string_equal(vars[1].value, "val2");
+    lyxp_vars_free(vars);
+    vars = NULL;
+}
+
 int
 main(void)
 {
@@ -540,6 +590,7 @@
         UTEST(test_first_sibling, setup),
         UTEST(test_find_path, setup),
         UTEST(test_data_hash, setup),
+        UTEST(test_lyxp_vars),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);