tree data FEATURE generic XPath evaluation function

Refs #1980
diff --git a/src/tree_data.c b/src/tree_data.c
index 69ce8e1..6c2061d 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -2765,58 +2765,19 @@
 }
 
 LIBYANG_API_DEF LY_ERR
-lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, LY_VALUE_FORMAT format,
-        void *prefix_data, const struct lyxp_var *vars, struct ly_set **set)
+lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set)
 {
-    LY_ERR ret = LY_SUCCESS;
-    struct lyxp_set xp_set = {0};
-    struct lyxp_expr *exp = NULL;
-    uint32_t i;
+    LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL);
 
-    LY_CHECK_ARG_RET(NULL, tree, xpath, format, set, LY_EINVAL);
+    return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, NULL, set);
+}
 
-    *set = NULL;
+LIBYANG_API_DEF LY_ERR
+lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, struct ly_set **set)
+{
+    LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL);
 
-    /* parse expression */
-    ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(tree), xpath, 0, 1, &exp);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* evaluate expression */
-    ret = lyxp_eval(LYD_CTX(tree), exp, NULL, format, prefix_data, ctx_node, ctx_node, tree, vars, &xp_set,
-            LYXP_IGNORE_WHEN);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    if (xp_set.type != LYXP_SET_NODE_SET) {
-        LOGERR(LYD_CTX(tree), LY_EINVAL, "XPath \"%s\" result is not a node set.", xpath);
-        ret = LY_EINVAL;
-        goto cleanup;
-    }
-
-    /* allocate return set */
-    ret = ly_set_new(set);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* transform into ly_set, allocate memory for all the elements once (even though not all items must be
-     * elements but most likely will be) */
-    (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
-    LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(LYD_CTX(tree)); ret = LY_EMEM, cleanup);
-    (*set)->size = xp_set.used;
-
-    for (i = 0; i < xp_set.used; ++i) {
-        if (xp_set.val.nodes[i].type == LYXP_NODE_ELEM) {
-            ret = ly_set_add(*set, xp_set.val.nodes[i].node, 1, NULL);
-            LY_CHECK_GOTO(ret, cleanup);
-        }
-    }
-
-cleanup:
-    lyxp_set_free_content(&xp_set);
-    lyxp_expr_free((struct ly_ctx *)LYD_CTX(tree), exp);
-    if (ret) {
-        ly_set_free(*set, NULL);
-        *set = NULL;
-    }
-    return ret;
+    return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, vars, set);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -2829,51 +2790,20 @@
 }
 
 LIBYANG_API_DEF LY_ERR
-lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, struct ly_set **set)
+lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, LY_VALUE_FORMAT format,
+        void *prefix_data, const struct lyxp_var *vars, struct ly_set **set)
 {
-    LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL);
+    LY_CHECK_ARG_RET(NULL, tree, xpath, set, LY_EINVAL);
 
-    return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, vars, set);
+    *set = NULL;
+
+    return lyd_eval_xpath4(ctx_node, tree, NULL, xpath, format, prefix_data, vars, NULL, set, NULL, NULL, NULL);
 }
 
 LIBYANG_API_DEF LY_ERR
-lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set)
+lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result)
 {
-    LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL);
-
-    return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, NULL, set);
-}
-
-LIBYANG_API_DEF LY_ERR
-lyd_eval_xpath3(const struct lyd_node *ctx_node, const struct lys_module *cur_mod, const char *xpath,
-        LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyxp_set xp_set = {0};
-    struct lyxp_expr *exp = NULL;
-
-    LY_CHECK_ARG_RET(NULL, ctx_node, xpath, result, LY_EINVAL);
-
-    /* compile expression */
-    ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(ctx_node), xpath, 0, 1, &exp);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* evaluate expression */
-    ret = lyxp_eval(LYD_CTX(ctx_node), exp, cur_mod, format, prefix_data, ctx_node, ctx_node, ctx_node, vars, &xp_set,
-            LYXP_IGNORE_WHEN);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* transform into boolean */
-    ret = lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN);
-    LY_CHECK_GOTO(ret, cleanup);
-
-    /* set result */
-    *result = xp_set.val.bln;
-
-cleanup:
-    lyxp_set_free_content(&xp_set);
-    lyxp_expr_free((struct ly_ctx *)LYD_CTX(ctx_node), exp);
-    return ret;
+    return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, NULL, result);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -2883,9 +2813,107 @@
 }
 
 LIBYANG_API_DEF LY_ERR
-lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result)
+lyd_eval_xpath3(const struct lyd_node *ctx_node, const struct lys_module *cur_mod, const char *xpath,
+        LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result)
 {
-    return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, NULL, result);
+    return lyd_eval_xpath4(ctx_node, ctx_node, cur_mod, xpath, format, prefix_data, vars, NULL, NULL, NULL, NULL, result);
+}
+
+LIBYANG_API_DEF LY_ERR
+lyd_eval_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const struct lys_module *cur_mod,
+        const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, LY_XPATH_TYPE *ret_type,
+        struct ly_set **node_set, char **string, long double *number, ly_bool *boolean)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct lyxp_set xp_set = {0};
+    struct lyxp_expr *exp = NULL;
+    uint32_t i;
+
+    LY_CHECK_ARG_RET(NULL, tree, xpath, ((ret_type && node_set && string && number && boolean) ||
+            (node_set && !string && !number && !boolean) || (!node_set && string && !number && !boolean) ||
+            (!node_set && !string && number && !boolean) || (!node_set && !string && !number && boolean)), LY_EINVAL);
+
+    /* parse expression */
+    ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(tree), xpath, 0, 1, &exp);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    /* evaluate expression */
+    ret = lyxp_eval(LYD_CTX(tree), exp, cur_mod, format, prefix_data, ctx_node, ctx_node, tree, vars, &xp_set,
+            LYXP_IGNORE_WHEN);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    /* return expected result type without or with casting */
+    if (node_set) {
+        /* node set */
+        if (xp_set.type == LYXP_SET_NODE_SET) {
+            /* transform into a set */
+            LY_CHECK_GOTO(ret = ly_set_new(node_set), cleanup);
+            (*node_set)->objs = malloc(xp_set.used * sizeof *(*node_set)->objs);
+            LY_CHECK_ERR_GOTO(!(*node_set)->objs, LOGMEM(LYD_CTX(tree)); ret = LY_EMEM, cleanup);
+            (*node_set)->size = xp_set.used;
+            for (i = 0; i < xp_set.used; ++i) {
+                if (xp_set.val.nodes[i].type == LYXP_NODE_ELEM) {
+                    ret = ly_set_add(*node_set, xp_set.val.nodes[i].node, 1, NULL);
+                    LY_CHECK_GOTO(ret, cleanup);
+                }
+            }
+            if (ret_type) {
+                *ret_type = LY_XPATH_NODE_SET;
+            }
+        } else if (!string && !number && !boolean) {
+            LOGERR(LYD_CTX(tree), LY_EINVAL, "XPath \"%s\" result is not a node set.", xpath);
+            ret = LY_EINVAL;
+            goto cleanup;
+        }
+    }
+
+    if (string) {
+        if ((xp_set.type != LYXP_SET_STRING) && !node_set) {
+            /* cast into string */
+            LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_STRING), cleanup);
+        }
+        if (xp_set.type == LYXP_SET_STRING) {
+            /* string */
+            *string = xp_set.val.str;
+            xp_set.val.str = NULL;
+            if (ret_type) {
+                *ret_type = LY_XPATH_STRING;
+            }
+        }
+    }
+
+    if (number) {
+        if ((xp_set.type != LYXP_SET_NUMBER) && !node_set) {
+            /* cast into number */
+            LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_NUMBER), cleanup);
+        }
+        if (xp_set.type == LYXP_SET_NUMBER) {
+            /* number */
+            *number = xp_set.val.num;
+            if (ret_type) {
+                *ret_type = LY_XPATH_NUMBER;
+            }
+        }
+    }
+
+    if (boolean) {
+        if ((xp_set.type != LYXP_SET_BOOLEAN) && !node_set) {
+            /* cast into boolean */
+            LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN), cleanup);
+        }
+        if (xp_set.type == LYXP_SET_BOOLEAN) {
+            /* boolean */
+            *boolean = xp_set.val.bln;
+            if (ret_type) {
+                *ret_type = LY_XPATH_BOOLEAN;
+            }
+        }
+    }
+
+cleanup:
+    lyxp_set_free_content(&xp_set);
+    lyxp_expr_free((struct ly_ctx *)LYD_CTX(tree), exp);
+    return ret;
 }
 
 LIBYANG_API_DEF LY_ERR
diff --git a/src/tree_data.h b/src/tree_data.h
index 0531f46..a9e85fe 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -2527,6 +2527,43 @@
         const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result);
 
 /**
+ * @brief XPath result type.
+ */
+typedef enum {
+    LY_XPATH_NODE_SET,  /**< XPath node set */
+    LY_XPATH_STRING,    /**< XPath string */
+    LY_XPATH_NUMBER,    /**< XPath number */
+    LY_XPATH_BOOLEAN    /**< XPath boolean */
+} LY_XPATH_TYPE;
+
+/**
+ * @brief Evaluate an XPath on data and return the result or convert it first to an expected result type.
+ *
+ * Either all return type parameters @p node_set, @p string, @p number, and @p boolean with @p ret_type
+ * are provided or exactly one of @p node_set, @p string, @p number, and @p boolean is provided with @p ret_type
+ * being obvious and hence optional.
+ *
+ * @param[in] ctx_node XPath context node, NULL for the root node.
+ * @param[in] tree Data tree to evaluate on.
+ * @param[in] cur_mod Current module of @p xpath, needed for some kinds of @p format.
+ * @param[in] xpath [XPath](@ref howtoXPath) to select.
+ * @param[in] format Format of any prefixes in @p xpath.
+ * @param[in] prefix_data Format-specific prefix data.
+ * @param[in] vars Optional [sized array](@ref sizedarrays) of XPath variables.
+ * @param[out] ret_type XPath type of the result selecting which of @p node_set, @p string, @p number, and @p boolean to use.
+ * @param[out] node_set XPath node set result.
+ * @param[out] string XPath string result.
+ * @param[out] number XPath number result.
+ * @param[out] boolean XPath boolean result.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LIBYANG_API_DECL LY_ERR lyd_eval_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree,
+        const struct lys_module *cur_mod, const char *xpath, LY_VALUE_FORMAT format, void *prefix_data,
+        const struct lyxp_var *vars, LY_XPATH_TYPE *ret_type, struct ly_set **node_set, char **string,
+        long double *number, ly_bool *boolean);
+
+/**
  * @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