xpath UPDATE full axes support
diff --git a/src/xpath.c b/src/xpath.c
index 8681a85..e90d9c0 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -3,7 +3,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief YANG XPath evaluation functions
  *
- * Copyright (c) 2015 - 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2022 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.
@@ -47,6 +47,10 @@
         struct lyxp_set *set, uint32_t options);
 static LY_ERR moveto_resolve_model(const char **qname, uint16_t *qname_len, const struct lyxp_set *set,
         const struct lysc_node *ctx_scnode, const struct lys_module **moveto_mod);
+static LY_ERR moveto_node(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname,
+        enum lyxp_axis axis, uint32_t options);
+static LY_ERR moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname,
+        enum lyxp_axis axis, uint32_t options);
 
 /* Functions are divided into the following basic classes:
  *
@@ -94,7 +98,7 @@
 }
 
 const char *
-lyxp_print_token(enum lyxp_token tok)
+lyxp_token2str(enum lyxp_token tok)
 {
     switch (tok) {
     case LYXP_TOKEN_PAR1:
@@ -148,6 +152,65 @@
 }
 
 /**
+ * @brief Transform string into an axis.
+ *
+ * @param[in] str String to transform.
+ * @param[in] str_len Length of @p str.
+ * @return Transformed axis.
+ */
+static enum lyxp_axis
+str2axis(const char *str, uint16_t str_len)
+{
+    switch (str_len) {
+    case 4:
+        assert(!strncmp("self", str, str_len));
+        return LYXP_AXIS_SELF;
+    case 5:
+        assert(!strncmp("child", str, str_len));
+        return LYXP_AXIS_CHILD;
+    case 6:
+        assert(!strncmp("parent", str, str_len));
+        return LYXP_AXIS_PARENT;
+    case 8:
+        assert(!strncmp("ancestor", str, str_len));
+        return LYXP_AXIS_ANCESTOR;
+    case 9:
+        if (str[0] == 'a') {
+            assert(!strncmp("attribute", str, str_len));
+            return LYXP_AXIS_ATTRIBUTE;
+        } else if (str[0] == 'f') {
+            assert(!strncmp("following", str, str_len));
+            return LYXP_AXIS_FOLLOWING;
+        } else {
+            assert(!strncmp("preceding", str, str_len));
+            return LYXP_AXIS_PRECEDING;
+        }
+        break;
+    case 10:
+        assert(!strncmp("descendant", str, str_len));
+        return LYXP_AXIS_DESCENDANT;
+    case 16:
+        assert(!strncmp("ancestor-or-self", str, str_len));
+        return LYXP_AXIS_ANCESTOR_OR_SELF;
+    case 17:
+        if (str[0] == 'f') {
+            assert(!strncmp("following-sibling", str, str_len));
+            return LYXP_AXIS_FOLLOWING_SIBLING;
+        } else {
+            assert(!strncmp("preceding-sibling", str, str_len));
+            return LYXP_AXIS_PRECEDING_SIBLING;
+        }
+        break;
+    case 18:
+        assert(!strncmp("descendant-or-self", str, str_len));
+        return LYXP_AXIS_DESCENDANT_OR_SELF;
+    }
+
+    LOGINT(NULL);
+    return 0;
+}
+
+/**
  * @brief Print the whole expression \p exp to debug output.
  *
  * @param[in] exp Expression to use.
@@ -165,7 +228,7 @@
 
     LOGDBG(LY_LDGXPATH, "expression \"%s\":", exp->expr);
     for (i = 0; i < exp->used; ++i) {
-        sprintf(tmp, "\ttoken %s, in expression \"%.*s\"", lyxp_print_token(exp->tokens[i]), exp->tok_len[i],
+        sprintf(tmp, "\ttoken %s, in expression \"%.*s\"", lyxp_token2str(exp->tokens[i]), exp->tok_len[i],
                 &exp->expr[exp->tok_pos[i]]);
         if (exp->repeat && exp->repeat[i]) {
             sprintf(tmp + strlen(tmp), " (repeat %d", exp->repeat[i][0]);
@@ -1626,11 +1689,9 @@
     return LY_SUCCESS;
 }
 
-#ifndef NDEBUG
-
 /**
  * @brief Bubble sort @p set into XPath document order.
- *        Context position aware. Unused in the 'Release' build target.
+ *        Context position aware.
  *
  * @param[in] set Set to sort.
  * @return How many times the whole set was traversed - 1 (if set was sorted, returns 0).
@@ -1715,8 +1776,6 @@
     return ret - 1;
 }
 
-#endif
-
 /**
  * @brief Merge 2 sorted sets into one.
  *
@@ -1855,8 +1914,8 @@
 
     if (want_tok && (exp->tokens[tok_idx] != want_tok)) {
         if (ctx) {
-            LOGVAL(ctx, LY_VCODE_XP_INTOK2, lyxp_print_token(exp->tokens[tok_idx]),
-                    &exp->expr[exp->tok_pos[tok_idx]], lyxp_print_token(want_tok));
+            LOGVAL(ctx, LY_VCODE_XP_INTOK2, lyxp_token2str(exp->tokens[tok_idx]),
+                    &exp->expr[exp->tok_pos[tok_idx]], lyxp_token2str(want_tok));
         }
         return LY_ENOT;
     }
@@ -1889,7 +1948,7 @@
 
     if ((exp->tokens[tok_idx] != want_tok1) && (exp->tokens[tok_idx] != want_tok2)) {
         if (ctx) {
-            LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_print_token(exp->tokens[tok_idx]),
+            LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[tok_idx]),
                     &exp->expr[exp->tok_pos[tok_idx]]);
         }
         return LY_ENOT;
@@ -2004,21 +2063,32 @@
             ++(*tok_idx);
             break;
 
+        case LYXP_TOKEN_AXISNAME:
+            ++(*tok_idx);
+
+            rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_DCOLON);
+            LY_CHECK_RET(rc);
+
+        /* fall through */
         case LYXP_TOKEN_AT:
             ++(*tok_idx);
 
             rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE);
             LY_CHECK_RET(rc);
             if ((exp->tokens[*tok_idx] != LYXP_TOKEN_NAMETEST) && (exp->tokens[*tok_idx] != LYXP_TOKEN_NODETYPE)) {
-                LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_print_token(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
+                LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
                 return LY_EVALID;
             }
+            if (exp->tokens[*tok_idx] == LYXP_TOKEN_NODETYPE) {
+                goto reparse_nodetype;
+            }
         /* fall through */
         case LYXP_TOKEN_NAMETEST:
             ++(*tok_idx);
             goto reparse_predicate;
 
         case LYXP_TOKEN_NODETYPE:
+reparse_nodetype:
             ++(*tok_idx);
 
             /* '(' */
@@ -2039,7 +2109,7 @@
             }
             break;
         default:
-            LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_print_token(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
+            LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
             return LY_EVALID;
         }
     } while (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH));
@@ -2076,6 +2146,7 @@
         switch (exp->tokens[*tok_idx]) {
         case LYXP_TOKEN_DOT:
         case LYXP_TOKEN_DDOT:
+        case LYXP_TOKEN_AXISNAME:
         case LYXP_TOKEN_AT:
         case LYXP_TOKEN_NAMETEST:
         case LYXP_TOKEN_NODETYPE:
@@ -2341,6 +2412,7 @@
         goto predicate;
     case LYXP_TOKEN_DOT:
     case LYXP_TOKEN_DDOT:
+    case LYXP_TOKEN_AXISNAME:
     case LYXP_TOKEN_AT:
     case LYXP_TOKEN_NAMETEST:
     case LYXP_TOKEN_NODETYPE:
@@ -2372,7 +2444,7 @@
         ++(*tok_idx);
         goto predicate;
     default:
-        LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_print_token(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
+        LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
         return LY_EVALID;
     }
 
@@ -2597,12 +2669,12 @@
  * @param[in] ncname Name to parse.
  * @return Length of @p ncname valid bytes.
  */
-static long int
+static ssize_t
 parse_ncname(const char *ncname)
 {
     uint32_t uc;
     size_t size;
-    long int len = 0;
+    ssize_t len = 0;
 
     LY_CHECK_RET(ly_getutf8(&ncname, &uc, &size), 0);
     if (!is_xmlqnamestartchar(uc) || (uc == ':')) {
@@ -2680,6 +2752,77 @@
     free(expr);
 }
 
+/**
+ * @brief Parse Axis name.
+ *
+ * @param[in] str String to parse.
+ * @param[in] str_len Length of @p str.
+ * @return LY_SUCCESS if an axis.
+ * @return LY_ENOT otherwise.
+ */
+static LY_ERR
+expr_parse_axis(const char *str, size_t str_len)
+{
+    switch (str_len) {
+    case 4:
+        if (!strncmp("self", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 5:
+        if (!strncmp("child", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 6:
+        if (!strncmp("parent", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 8:
+        if (!strncmp("ancestor", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 9:
+        if (!strncmp("attribute", str, str_len)) {
+            return LY_SUCCESS;
+        } else if (!strncmp("following", str, str_len)) {
+            return LY_SUCCESS;
+        } else if (!strncmp("namespace", str, str_len)) {
+            LOGERR(NULL, LY_EINVAL, "Axis \"namespace\" not supported.");
+            return LY_ENOT;
+        } else if (!strncmp("preceding", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 10:
+        if (!strncmp("descendant", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 16:
+        if (!strncmp("ancestor-or-self", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 17:
+        if (!strncmp("following-sibling", str, str_len)) {
+            return LY_SUCCESS;
+        } else if (!strncmp("preceding-sibling", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    case 18:
+        if (!strncmp("descendant-or-self", str, str_len)) {
+            return LY_SUCCESS;
+        }
+        break;
+    }
+
+    return LY_ENOT;
+}
+
 LY_ERR
 lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, ly_bool reparse, struct lyxp_expr **expr_p)
 {
@@ -2687,8 +2830,9 @@
     struct lyxp_expr *expr;
     size_t parsed = 0, tok_len;
     enum lyxp_token tok_type;
-    ly_bool prev_function_check = 0;
+    ly_bool prev_func_check = 0, prev_ntype_check = 0, has_axis;
     uint16_t tok_idx = 0;
+    ssize_t ncname_len;
 
     assert(expr_p);
 
@@ -2734,18 +2878,23 @@
             tok_len = 1;
             tok_type = LYXP_TOKEN_PAR1;
 
-            if (prev_function_check && expr->used && (expr->tokens[expr->used - 1] == LYXP_TOKEN_NAMETEST)) {
-                /* it is a NodeType/FunctionName after all */
-                if (((expr->tok_len[expr->used - 1] == 4) &&
-                        (!strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "node", 4) ||
-                        !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "text", 4))) ||
-                        ((expr->tok_len[expr->used - 1] == 7) &&
-                        !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "comment", 7))) {
-                    expr->tokens[expr->used - 1] = LYXP_TOKEN_NODETYPE;
-                } else {
-                    expr->tokens[expr->used - 1] = LYXP_TOKEN_FUNCNAME;
-                }
-                prev_function_check = 0;
+            if (prev_ntype_check && expr->used && (expr->tokens[expr->used - 1] == LYXP_TOKEN_NAMETEST) &&
+                    (((expr->tok_len[expr->used - 1] == 4) &&
+                    (!strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "node", 4) ||
+                    !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "text", 4))) ||
+                    ((expr->tok_len[expr->used - 1] == 7) &&
+                    !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "comment", 7)))) {
+                /* it is NodeType after all */
+                expr->tokens[expr->used - 1] = LYXP_TOKEN_NODETYPE;
+
+                prev_ntype_check = 0;
+                prev_func_check = 0;
+            } else if (prev_func_check && expr->used && (expr->tokens[expr->used - 1] == LYXP_TOKEN_NAMETEST)) {
+                /* it is FunctionName after all */
+                expr->tokens[expr->used - 1] = LYXP_TOKEN_FUNCNAME;
+
+                prev_ntype_check = 0;
+                prev_func_check = 0;
             }
 
         } else if (expr_str[parsed] == ')') {
@@ -2824,7 +2973,7 @@
 
             /* VariableReference */
             parsed++;
-            long int ncname_len = parse_ncname(&expr_str[parsed]);
+            ncname_len = parse_ncname(&expr_str[parsed]);
             LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len],
                     parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error);
             tok_len = ncname_len;
@@ -2910,7 +3059,7 @@
                 tok_len = 3;
                 tok_type = LYXP_TOKEN_OPER_MATH;
 
-            } else if (prev_function_check) {
+            } else if (prev_ntype_check || prev_func_check) {
                 LOGVAL(ctx, LYVE_XPATH, "Invalid character 0x%x ('%c'), perhaps \"%.*s\" is supposed to be a function call.",
                         expr_str[parsed], expr_str[parsed], expr->tok_len[expr->used - 1], &expr->expr[expr->tok_pos[expr->used - 1]]);
                 ret = LY_EVALID;
@@ -2920,20 +3069,47 @@
                 ret = LY_EVALID;
                 goto error;
             }
-        } else if (expr_str[parsed] == '*') {
-
-            /* NameTest '*' */
-            tok_len = 1;
-            tok_type = LYXP_TOKEN_NAMETEST;
-
         } else {
 
-            /* NameTest (NCName ':' '*' | QName) or NodeType/FunctionName */
-            long int ncname_len = parse_ncname(&expr_str[parsed]);
-            LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len],
-                    parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error);
+            /* (AxisName '::')? ((NCName ':')? '*' | QName) or NodeType/FunctionName */
+            if (expr_str[parsed] == '*') {
+                ncname_len = 1;
+            } else {
+                ncname_len = parse_ncname(&expr_str[parsed]);
+                LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len],
+                        parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error);
+            }
             tok_len = ncname_len;
 
+            has_axis = 0;
+            if (!strncmp(&expr_str[parsed + tok_len], "::", 2)) {
+                /* axis */
+                LY_CHECK_ERR_GOTO(expr_parse_axis(&expr_str[parsed], ncname_len),
+                        LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], parsed + 1, expr_str); ret = LY_EVALID, error);
+                tok_type = LYXP_TOKEN_AXISNAME;
+
+                LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error);
+                parsed += tok_len;
+
+                /* '::' */
+                tok_len = 2;
+                tok_type = LYXP_TOKEN_DCOLON;
+
+                LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error);
+                parsed += tok_len;
+
+                if (expr_str[parsed] == '*') {
+                    ncname_len = 1;
+                } else {
+                    ncname_len = parse_ncname(&expr_str[parsed]);
+                    LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len],
+                            parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error);
+                }
+                tok_len = ncname_len;
+
+                has_axis = 1;
+            }
+
             if (expr_str[parsed + tok_len] == ':') {
                 ++tok_len;
                 if (expr_str[parsed + tok_len] == '*') {
@@ -2944,12 +3120,14 @@
                             parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error);
                     tok_len += ncname_len;
                 }
-                /* remove old flag to prevent ambiguities */
-                prev_function_check = 0;
+                /* remove old flags to prevent ambiguities */
+                prev_ntype_check = 0;
+                prev_func_check = 0;
                 tok_type = LYXP_TOKEN_NAMETEST;
             } else {
-                /* there is no prefix so it can still be NodeType/FunctionName, we can't finally decide now */
-                prev_function_check = 1;
+                /* if not '*', there is no prefix so it can still be NodeType/FunctionName, we can't finally decide now */
+                prev_ntype_check = (expr_str[parsed] == '*') ? 0 : 1;
+                prev_func_check = (prev_ntype_check && !has_axis) ? 1 : 0;
                 tok_type = LYXP_TOKEN_NAMETEST;
             }
         }
@@ -4425,31 +4603,6 @@
 }
 
 /**
- * @brief Execute the XPath node() function (node type). Returns LYXP_SET_NODE_SET
- *        with only nodes from the context. In practice it either leaves the context
- *        as it is or returns an empty node set.
- *
- * @param[in] args Array of arguments.
- * @param[in] arg_count Count of elements in @p args.
- * @param[in,out] set Context and result set at the same time.
- * @param[in] options XPath options.
- * @return LY_ERR
- */
-static LY_ERR
-xpath_node(struct lyxp_set **UNUSED(args), uint16_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options)
-{
-    if (options & LYXP_SCNODE_ALL) {
-        set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_NODE_SET) {
-        lyxp_set_free_content(set);
-    }
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Execute the XPath normalize-space(string?) function. Returns LYXP_SET_STRING
  *        with normalized value (no leading, trailing, double white spaces) of the node
  *        from the argument or the context.
@@ -5165,54 +5318,6 @@
 }
 
 /**
- * @brief Execute the XPath text() function (node type). Returns LYXP_SET_NODE_SET
- *        with the text content of the nodes in the context.
- *
- * @param[in] args Array of arguments.
- * @param[in] arg_count Count of elements in @p args.
- * @param[in,out] set Context and result set at the same time.
- * @param[in] options XPath options.
- * @return LY_ERR
- */
-static LY_ERR
-xpath_text(struct lyxp_set **UNUSED(args), uint16_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options)
-{
-    uint32_t i;
-
-    if (options & LYXP_SCNODE_ALL) {
-        set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL);
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_NODE_SET) {
-        LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "text()");
-        return LY_EVALID;
-    }
-
-    for (i = 0; i < set->used; ) {
-        switch (set->val.nodes[i].type) {
-        case LYXP_NODE_NONE:
-            LOGINT_RET(set->ctx);
-        case LYXP_NODE_ELEM:
-            if (set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
-                set->val.nodes[i].type = LYXP_NODE_TEXT;
-                ++i;
-                break;
-            }
-        /* fall through */
-        case LYXP_NODE_ROOT:
-        case LYXP_NODE_ROOT_CONFIG:
-        case LYXP_NODE_TEXT:
-        case LYXP_NODE_META:
-            set_remove_node(set, i);
-            break;
-        }
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Execute the XPath translate(string, string, string) function.
  *        Returns LYXP_SET_STRING with the first argument with the characters
  *        from the second argument replaced by those on the corresponding
@@ -5335,6 +5440,84 @@
 }
 
 /**
+ * @brief Execute the XPath node() processing instruction (node type). Returns LYXP_SET_NODE_SET
+ *        with only nodes from the context.
+ *
+ * @param[in,out] set Context and result set at the same time.
+ * @param[in] axis Axis to search on.
+ * @param[in] options XPath options.
+ * @return LY_ERR
+ */
+static LY_ERR
+xpath_pi_node(struct lyxp_set *set, enum lyxp_axis axis, uint32_t options)
+{
+    if (options & LYXP_SCNODE_ALL) {
+        return moveto_scnode(set, NULL, NULL, axis, options);
+    }
+
+    if (set->type != LYXP_SET_NODE_SET) {
+        lyxp_set_free_content(set);
+        return LY_SUCCESS;
+    }
+
+    /* just like moving to a node with no restrictions */
+    return moveto_node(set, NULL, NULL, axis, options);
+}
+
+/**
+ * @brief Execute the XPath text() processing instruction (node type). Returns LYXP_SET_NODE_SET
+ *        with the text content of the nodes in the context.
+ *
+ * @param[in,out] set Context and result set at the same time.
+ * @param[in] axis Axis to search on.
+ * @param[in] options XPath options.
+ * @return LY_ERR
+ */
+static LY_ERR
+xpath_pi_text(struct lyxp_set *set, enum lyxp_axis axis, uint32_t options)
+{
+    uint32_t i;
+
+    if (options & LYXP_SCNODE_ALL) {
+        set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL);
+        return LY_SUCCESS;
+    }
+
+    if (set->type != LYXP_SET_NODE_SET) {
+        LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "text()");
+        return LY_EVALID;
+    }
+
+    if (axis != LYXP_AXIS_CHILD) {
+        /* even following and preceding axescan return text nodes, but whatever */
+        lyxp_set_free_content(set);
+        return LY_SUCCESS;
+    }
+
+    for (i = 0; i < set->used; ++i) {
+        switch (set->val.nodes[i].type) {
+        case LYXP_NODE_NONE:
+            LOGINT_RET(set->ctx);
+        case LYXP_NODE_ELEM:
+            if (set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
+                set->val.nodes[i].type = LYXP_NODE_TEXT;
+                break;
+            }
+        /* fall through */
+        case LYXP_NODE_ROOT:
+        case LYXP_NODE_ROOT_CONFIG:
+        case LYXP_NODE_TEXT:
+        case LYXP_NODE_META:
+            set_remove_node_none(set, i);
+            break;
+        }
+    }
+    set_remove_nodes_none(set);
+
+    return LY_SUCCESS;
+}
+
+/**
  * @brief Skip prefix and return corresponding model if there is a prefix. Logs directly.
  *
  * XPath @p set is expected to be a (sc)node set!
@@ -5430,6 +5613,7 @@
  * @brief Check @p node as a part of NameTest processing.
  *
  * @param[in] node Node to check.
+ * @param[in] node_type Node type of @p node.
  * @param[in] set Set to read general context from.
  * @param[in] node_name Node name in the dictionary to move to, NULL for any node.
  * @param[in] moveto_mod Expected module of the node, NULL for no prefix.
@@ -5438,9 +5622,22 @@
  * LY_EINVAL if netither node nor any children match)
  */
 static LY_ERR
-moveto_node_check(const struct lyd_node *node, const struct lyxp_set *set, const char *node_name,
-        const struct lys_module *moveto_mod, uint32_t options)
+moveto_node_check(const struct lyd_node *node, enum lyxp_node_type node_type, const struct lyxp_set *set,
+        const char *node_name, const struct lys_module *moveto_mod, uint32_t options)
 {
+    if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) {
+        assert(node_type == set->root_type);
+
+        if (node_name || moveto_mod) {
+            /* root will not match a specific node */
+            return LY_ENOT;
+        }
+        return LY_SUCCESS;
+    } else if (node_type != LYXP_NODE_ELEM) {
+        /* other types will not match */
+        return LY_ENOT;
+    }
+
     if (!node->schema) {
         /* opaque node never matches */
         return LY_ENOT;
@@ -5460,7 +5657,7 @@
     }
 
     /* name check */
-    if (node_name && strcmp(node_name, "*") && (node->schema->name != node_name)) {
+    if (node_name && (node->schema->name != node_name)) {
         return LY_ENOT;
     }
 
@@ -5474,78 +5671,307 @@
 }
 
 /**
- * @brief Check @p node as a part of schema NameTest processing.
+ * @brief Get the next node in a forward DFS.
  *
- * @param[in] node Schema node to check.
- * @param[in] ctx_scnode Context node.
- * @param[in] set Set to read general context from.
- * @param[in] node_name Node name in the dictionary to move to, NULL for any nodes.
- * @param[in] moveto_mod Expected module of the node, NULL for no prefix.
- * @return LY_ERR (LY_ENOT if node does not match, LY_EINVAL if neither node nor any children match)
+ * @param[in] iter Last returned node.
+ * @param[in] stop Node to stop the search on and not return.
+ * @return Next node, NULL if there are no more.
  */
-static LY_ERR
-moveto_scnode_check(const struct lysc_node *node, const struct lysc_node *ctx_scnode, const struct lyxp_set *set,
-        const char *node_name, const struct lys_module *moveto_mod)
+static const struct lyd_node *
+moveto_axis_node_next_dfs_forward(const struct lyd_node *iter, const struct lyd_node *stop)
 {
-    if (!moveto_mod && node_name && strcmp(node_name, "*")) {
-        switch (set->format) {
-        case LY_VALUE_SCHEMA:
-        case LY_VALUE_SCHEMA_RESOLVED:
-            /* use current module */
-            moveto_mod = set->cur_mod;
-            break;
-        case LY_VALUE_JSON:
-        case LY_VALUE_LYB:
-        case LY_VALUE_STR_NS:
-            /* inherit module of the context node, if any */
-            if (ctx_scnode) {
-                moveto_mod = ctx_scnode->module;
-            }
-            break;
-        case LY_VALUE_CANON:
-        case LY_VALUE_XML:
-            /* not defined */
-            LOGINT(set->ctx);
-            return LY_EINVAL;
+    const struct lyd_node *next = NULL;
+
+    /* 1) child */
+    next = lyd_child(iter);
+    if (!next) {
+        if (iter == stop) {
+            /* reached stop, no more descendants */
+            return NULL;
         }
+        /* 2) child next sibling */
+        next = iter->next;
+    }
+    while (!next) {
+        iter = lyd_parent(iter);
+        if ((!stop && !iter) || (stop && (lyd_parent(iter) == lyd_parent(stop)))) {
+            return NULL;
+        }
+        next = iter->next;
     }
 
-    /* module check */
-    if (moveto_mod && (node->module != moveto_mod)) {
-        return LY_ENOT;
-    }
-
-    /* context check */
-    if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (node->flags & LYS_CONFIG_R)) {
-        return LY_EINVAL;
-    } else if (set->context_op && (node->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && (node != set->context_op)) {
-        return LY_EINVAL;
-    }
-
-    /* name check */
-    if (node_name && strcmp(node_name, "*") && (node->name != node_name)) {
-        return LY_ENOT;
-    }
-
-    /* match */
-    return LY_SUCCESS;
+    return next;
 }
 
 /**
- * @brief Move context @p set to a node. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY). Context position aware.
+ * @brief Get the next node in a backward DFS.
+ *
+ * @param[in] iter Last returned node.
+ * @param[in] stop Node to stop the search on and not return.
+ * @return Next node, NULL if there are no more.
+ */
+static const struct lyd_node *
+moveto_axis_node_next_dfs_backward(const struct lyd_node *iter, const struct lyd_node *stop)
+{
+    const struct lyd_node *next = NULL;
+
+    /* 1) previous sibling innermost last child */
+    next = iter->prev->next ? iter->prev : NULL;
+    while (next && lyd_child(next)) {
+        next = lyd_child(next);
+        next = next->prev;
+    }
+
+    if (!next) {
+        /* 2) parent */
+        iter = lyd_parent(iter);
+        if ((!stop && !iter) || (stop && (lyd_parent(iter) == lyd_parent(stop)))) {
+            return NULL;
+        }
+        next = iter;
+    }
+
+    return next;
+}
+
+/**
+ * @brief Get the first node on an axis for a context node.
+ *
+ * @param[in,out] iter NULL, updated to the next node.
+ * @param[in,out] iter_type Node type 0 of @p iter, updated to the node type of the next node.
+ * @param[in] node Context node.
+ * @param[in] node_type Type of @p node.
+ * @param[in] axis Axis to use.
+ * @param[in] set XPath set with the general context.
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOTFOUND if no next node found.
+ */
+static LY_ERR
+moveto_axis_node_next_first(const struct lyd_node **iter, enum lyxp_node_type *iter_type, const struct lyd_node *node,
+        enum lyxp_node_type node_type, enum lyxp_axis axis, struct lyxp_set *set)
+{
+    const struct lyd_node *next = NULL;
+    enum lyxp_node_type next_type = 0;
+
+    assert(!*iter);
+    assert(!*iter_type);
+
+    switch (axis) {
+    case LYXP_AXIS_ANCESTOR_OR_SELF:
+    case LYXP_AXIS_DESCENDANT_OR_SELF:
+    case LYXP_AXIS_SELF:
+        /* return the context node */
+        next = node;
+        next_type = node_type;
+        break;
+
+    case LYXP_AXIS_ANCESTOR:
+    case LYXP_AXIS_PARENT:
+        if (node_type == LYXP_NODE_ELEM) {
+            next = lyd_parent(node);
+            next_type = next ? LYXP_NODE_ELEM : set->root_type;
+        } else if (node_type == LYXP_NODE_TEXT) {
+            next = node;
+            next_type = LYXP_NODE_ELEM;
+        } else if (node_type == LYXP_NODE_META) {
+            next = ((struct lyd_meta *)node)->parent;
+            next_type = LYXP_NODE_ELEM;
+        } /* else root does not have a parent */
+        break;
+
+    case LYXP_AXIS_CHILD:
+        if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) {
+            assert(!node);
+
+            /* search in all the trees */
+            next = set->tree;
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } else {
+            /* search in children */
+            next = lyd_child(node);
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        }
+        break;
+
+    case LYXP_AXIS_DESCENDANT:
+        if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) {
+            /* top-level nodes */
+            next = set->tree;
+            next_type = LYXP_NODE_ELEM;
+        } else if (node_type == LYXP_NODE_ELEM) {
+            /* start from the context node */
+            next = moveto_axis_node_next_dfs_forward(node, node);
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } /* else no children */
+        break;
+
+    case LYXP_AXIS_FOLLOWING:
+    case LYXP_AXIS_FOLLOWING_SIBLING:
+        if (node_type == LYXP_NODE_ELEM) {
+            /* first next sibling */
+            next = node->next;
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } /* else no sibling */
+        break;
+
+    case LYXP_AXIS_PRECEDING:
+        if ((node_type == LYXP_NODE_ELEM) && node->prev->next) {
+            /* skip ancestors */
+            next = moveto_axis_node_next_dfs_backward(node, NULL);
+            assert(next);
+            next_type = LYXP_NODE_ELEM;
+        } /* else no sibling */
+        break;
+
+    case LYXP_AXIS_PRECEDING_SIBLING:
+        if (node_type == LYXP_NODE_ELEM) {
+            /* first previous sibling */
+            next = node->prev->next ? node->prev : NULL;
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } /* else no sibling */
+        break;
+
+    case LYXP_AXIS_ATTRIBUTE:
+        /* handled specially */
+        assert(0);
+        LOGINT(set->ctx);
+        break;
+    }
+
+    *iter = next;
+    *iter_type = next_type;
+    return next_type ? LY_SUCCESS : LY_ENOTFOUND;
+}
+
+/**
+ * @brief Iterate over all nodes on an axis for a context node.
+ *
+ * @param[in,out] iter Last returned node, start with NULL, updated to the next node.
+ * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node.
+ * @param[in] node Context node.
+ * @param[in] node_type Type of @p node.
+ * @param[in] axis Axis to use.
+ * @param[in] set XPath set with the general context.
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOTFOUND if no next node found.
+ */
+static LY_ERR
+moveto_axis_node_next(const struct lyd_node **iter, enum lyxp_node_type *iter_type, const struct lyd_node *node,
+        enum lyxp_node_type node_type, enum lyxp_axis axis, struct lyxp_set *set)
+{
+    const struct lyd_node *next = NULL;
+    enum lyxp_node_type next_type = 0;
+
+    if (!*iter_type) {
+        /* first returned node */
+        return moveto_axis_node_next_first(iter, iter_type, node, node_type, axis, set);
+    }
+
+    switch (axis) {
+    case LYXP_AXIS_ANCESTOR_OR_SELF:
+        if ((*iter == node) && (*iter_type == node_type)) {
+            /* fake first ancestor, we returned self before */
+            *iter = NULL;
+            *iter_type = 0;
+            return moveto_axis_node_next_first(iter, iter_type, node, node_type, LYXP_AXIS_ANCESTOR, set);
+        } /* else continue ancestor */
+
+    /* fallthrough */
+    case LYXP_AXIS_ANCESTOR:
+        if (*iter_type == LYXP_NODE_ELEM) {
+            /* iter parent */
+            next = lyd_parent(*iter);
+            next_type = next ? LYXP_NODE_ELEM : set->root_type;
+        } /* else root, no ancestors */
+        break;
+
+    case LYXP_AXIS_CHILD:
+        assert(*iter_type == LYXP_NODE_ELEM);
+
+        /* next sibling (child) */
+        next = (*iter)->next;
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_DESCENDANT_OR_SELF:
+        if ((*iter == node) && (*iter_type == node_type)) {
+            /* fake first descendant, we returned self before */
+            *iter = NULL;
+            *iter_type = 0;
+            return moveto_axis_node_next_first(iter, iter_type, node, node_type, LYXP_AXIS_DESCENDANT, set);
+        } /* else continue descendant */
+
+    /* fallthrough */
+    case LYXP_AXIS_DESCENDANT:
+        assert(*iter_type == LYXP_NODE_ELEM);
+        next = moveto_axis_node_next_dfs_forward(*iter, node);
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_FOLLOWING:
+        assert(*iter_type == LYXP_NODE_ELEM);
+        next = moveto_axis_node_next_dfs_forward(*iter, NULL);
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_FOLLOWING_SIBLING:
+        assert(*iter_type == LYXP_NODE_ELEM);
+
+        /* next sibling */
+        next = (*iter)->next;
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_PARENT:
+    case LYXP_AXIS_SELF:
+        /* parent/self was returned before */
+        break;
+
+    case LYXP_AXIS_PRECEDING:
+        assert(*iter_type == LYXP_NODE_ELEM);
+        next = moveto_axis_node_next_dfs_backward(*iter, NULL);
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_PRECEDING_SIBLING:
+        assert(*iter_type == LYXP_NODE_ELEM);
+
+        /* previous sibling */
+        next = (*iter)->prev->next ? (*iter)->prev : NULL;
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_ATTRIBUTE:
+        /* handled specially */
+        assert(0);
+        LOGINT(set->ctx);
+        break;
+    }
+
+    *iter = next;
+    *iter_type = next_type;
+    return next_type ? LY_SUCCESS : LY_ENOTFOUND;
+}
+
+/**
+ * @brief Move context @p set to a node. Result is LYXP_SET_NODE_SET. Context position aware.
  *
  * @param[in,out] set Set to use.
  * @param[in] moveto_mod Matching node module, NULL for no prefix.
  * @param[in] ncname Matching node name in the dictionary, NULL for any.
+ * @param[in] axis Axis to search on.
  * @param[in] options XPath options.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-moveto_node(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
+moveto_node(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, enum lyxp_axis axis,
+        uint32_t options)
 {
     LY_ERR r, rc = LY_SUCCESS;
-    const struct lyd_node *siblings, *sub;
+    const struct lyd_node *iter;
+    enum lyxp_node_type iter_type;
     struct lyxp_set result;
+    uint32_t i;
 
     if (options & LYXP_SKIP_EXPR) {
         return LY_SUCCESS;
@@ -5559,26 +5985,46 @@
     /* init result set */
     set_init(&result, set);
 
-    for (uint32_t i = 0; i < set->used; ++i) {
-        if ((set->val.nodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.nodes[i].type == LYXP_NODE_ROOT)) {
-            assert(!set->val.nodes[i].node);
-
-            /* search in all the trees */
-            siblings = set->tree;
-        } else {
-            /* search in children */
-            siblings = lyd_child(set->val.nodes[i].node);
-        }
-
-        for (sub = siblings; sub; sub = sub->next) {
-            r = moveto_node_check(sub, set, ncname, moveto_mod, options);
-            if (r == LY_SUCCESS) {
-                /* matching node */
-                set_insert_node(&result, sub, 0, LYXP_NODE_ELEM, result.used);
-            } else if (r == LY_EINCOMPLETE) {
+    for (i = 0; i < set->used; ++i) {
+        /* iterate over all the nodes on the axis of the node */
+        iter = NULL;
+        iter_type = 0;
+        while (!moveto_axis_node_next(&iter, &iter_type, set->val.nodes[i].node, set->val.nodes[i].type, axis, set)) {
+            r = moveto_node_check(iter, iter_type, set, ncname, moveto_mod, options);
+            if (r == LY_EINCOMPLETE) {
                 rc = r;
                 goto cleanup;
+            } else if (r) {
+                continue;
             }
+
+            /* check for duplicates if they are possible */
+            switch (axis) {
+            case LYXP_AXIS_ANCESTOR:
+            case LYXP_AXIS_ANCESTOR_OR_SELF:
+            case LYXP_AXIS_DESCENDANT:
+            case LYXP_AXIS_DESCENDANT_OR_SELF:
+            case LYXP_AXIS_FOLLOWING:
+            case LYXP_AXIS_FOLLOWING_SIBLING:
+            case LYXP_AXIS_PARENT:
+            case LYXP_AXIS_PRECEDING:
+            case LYXP_AXIS_PRECEDING_SIBLING:
+                if (set_dup_node_check(&result, iter, iter_type, -1)) {
+                    continue;
+                }
+                break;
+            case LYXP_AXIS_CHILD:
+            case LYXP_AXIS_SELF:
+                break;
+            case LYXP_AXIS_ATTRIBUTE:
+                /* handled specially */
+                assert(0);
+                LOGINT(set->ctx);
+                break;
+            }
+
+            /* matching node */
+            set_insert_node(&result, iter, 0, iter_type, result.used);
         }
     }
 
@@ -5586,7 +6032,9 @@
     lyxp_set_free_content(set);
     *set = result;
     result.type = LYXP_SET_NUMBER;
-    assert(!set_sort(set));
+
+    /* sort the final set */
+    set_sort(set);
 
 cleanup:
     lyxp_set_free_content(&result);
@@ -5594,8 +6042,7 @@
 }
 
 /**
- * @brief Move context @p set to a node using hashes. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
- * Context position aware.
+ * @brief Move context @p set to child nodes using hashes. Result is LYXP_SET_NODE_SET. Context position aware.
  *
  * @param[in,out] set Set to use.
  * @param[in] scnode Matching node schema.
@@ -5604,7 +6051,7 @@
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-moveto_node_hash(struct lyxp_set *set, const struct lysc_node *scnode, const struct ly_path_predicate *predicates,
+moveto_node_hash_child(struct lyxp_set *set, const struct lysc_node *scnode, const struct ly_path_predicate *predicates,
         uint32_t options)
 {
     LY_ERR ret = LY_SUCCESS, r;
@@ -5691,23 +6138,469 @@
 }
 
 /**
+ * @brief Check @p node as a part of schema NameTest processing.
+ *
+ * @param[in] node Schema node to check.
+ * @param[in] ctx_scnode Context node.
+ * @param[in] set Set to read general context from.
+ * @param[in] node_name Node name in the dictionary to move to, NULL for any nodes.
+ * @param[in] moveto_mod Expected module of the node, NULL for no prefix.
+ * @return LY_ERR (LY_ENOT if node does not match, LY_EINVAL if neither node nor any children match)
+ */
+static LY_ERR
+moveto_scnode_check(const struct lysc_node *node, const struct lysc_node *ctx_scnode, const struct lyxp_set *set,
+        const char *node_name, const struct lys_module *moveto_mod)
+{
+    if (!moveto_mod && node_name) {
+        switch (set->format) {
+        case LY_VALUE_SCHEMA:
+        case LY_VALUE_SCHEMA_RESOLVED:
+            /* use current module */
+            moveto_mod = set->cur_mod;
+            break;
+        case LY_VALUE_JSON:
+        case LY_VALUE_LYB:
+        case LY_VALUE_STR_NS:
+            /* inherit module of the context node, if any */
+            if (ctx_scnode) {
+                moveto_mod = ctx_scnode->module;
+            }
+            break;
+        case LY_VALUE_CANON:
+        case LY_VALUE_XML:
+            /* not defined */
+            LOGINT(set->ctx);
+            return LY_EINVAL;
+        }
+    }
+
+    if (!node) {
+        /* root will not match a specific node */
+        if (node_name || moveto_mod) {
+            return LY_ENOT;
+        }
+        return LY_SUCCESS;
+    }
+
+    /* module check */
+    if (moveto_mod && (node->module != moveto_mod)) {
+        return LY_ENOT;
+    }
+
+    /* context check */
+    if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (node->flags & LYS_CONFIG_R)) {
+        return LY_EINVAL;
+    } else if (set->context_op && (node->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && (node != set->context_op)) {
+        return LY_EINVAL;
+    }
+
+    /* name check */
+    if (node_name && (node->name != node_name)) {
+        return LY_ENOT;
+    }
+
+    /* match */
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Get the next node in a forward schema node DFS.
+ *
+ * @param[in] iter Last returned node.
+ * @param[in] stop Node to stop the search on and not return.
+ * @param[in] getnext_opts Options for ::lys_getnext().
+ * @return Next node, NULL if there are no more.
+ */
+static const struct lysc_node *
+moveto_axis_scnode_next_dfs_forward(const struct lysc_node *iter, const struct lysc_node *stop, uint32_t getnext_opts)
+{
+    const struct lysc_node *next = NULL;
+
+    next = lysc_node_child(iter);
+    if (!next) {
+        /* no children, try siblings */
+        if (iter == stop) {
+            /* we are done, no next element to process */
+            return NULL;
+        }
+
+        next = lys_getnext(iter, lysc_data_parent(iter), NULL, getnext_opts);
+    }
+    while (!next && iter) {
+        /* parent is already processed, go to its sibling */
+        iter = iter->parent;
+        if (iter == stop) {
+            /* we are done, no next element to process */
+            return NULL;
+        }
+        next = lys_getnext(iter, lysc_data_parent(iter), NULL, getnext_opts);
+    }
+
+    return next;
+}
+
+/**
+ * @brief Consider schema node based on its in_ctx enum value.
+ *
+ * @param[in,out] in_ctx In_ctx enum of the schema node, may be updated.
+ * @param[in] axis Axis to use.
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOT if the node should not be returned.
+ */
+static LY_ERR
+moveto_axis_scnode_next_in_ctx(int32_t *in_ctx, enum lyxp_axis axis)
+{
+    switch (axis) {
+    case LYXP_AXIS_SELF:
+        if ((*in_ctx == LYXP_SET_SCNODE_START) || (*in_ctx == LYXP_SET_SCNODE_ATOM_CTX)) {
+            /* additionally put the start node into context */
+            *in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
+            return LY_SUCCESS;
+        }
+        break;
+    case LYXP_AXIS_PARENT:
+    case LYXP_AXIS_ANCESTOR_OR_SELF:
+    case LYXP_AXIS_ANCESTOR:
+    case LYXP_AXIS_DESCENDANT_OR_SELF:
+    case LYXP_AXIS_DESCENDANT:
+    case LYXP_AXIS_FOLLOWING:
+    case LYXP_AXIS_FOLLOWING_SIBLING:
+    case LYXP_AXIS_PRECEDING:
+    case LYXP_AXIS_PRECEDING_SIBLING:
+    case LYXP_AXIS_CHILD:
+        if (*in_ctx == LYXP_SET_SCNODE_START) {
+            /* remember that context node was used */
+            *in_ctx = LYXP_SET_SCNODE_START_USED;
+            return LY_SUCCESS;
+        } else if (*in_ctx == LYXP_SET_SCNODE_ATOM_CTX) {
+            /* traversed */
+            *in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
+            return LY_SUCCESS;
+        }
+        break;
+    case LYXP_AXIS_ATTRIBUTE:
+        /* unreachable */
+        assert(0);
+        LOGINT(NULL);
+        break;
+    }
+
+    return LY_ENOT;
+}
+
+/**
+ * @brief Get previous sibling for a schema node.
+ *
+ * @param[in] scnode Schema node.
+ * @param[in] getnext_opts Options for ::lys_getnext().
+ * @return Previous sibling, NULL if none.
+ */
+static const struct lysc_node *
+moveto_axis_scnode_preceding_sibling(const struct lysc_node *scnode, uint32_t getnext_opts)
+{
+    const struct lysc_node *next = NULL, *prev = NULL;
+
+    while ((next = lys_getnext(next, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts))) {
+        if (next == scnode) {
+            break;
+        }
+
+        prev = next;
+    }
+
+    return prev;
+}
+
+/**
+ * @brief Get the first schema node on an axis for a context node.
+ *
+ * @param[in,out] iter Last returned node, start with NULL, updated to the next node.
+ * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node.
+ * @param[in,out] iter_mod Internal module iterator, do not change.
+ * @param[in,out] iter_mod_idx Internal module index iterator, do not change.
+ * @param[in] scnode Context node.
+ * @param[in] node_type Type of @p scnode.
+ * @param[in] in_ctx In_ctx enum of @p scnode.
+ * @param[in] axis Axis to use.
+ * @param[in] set XPath set with the general context.
+ * @param[in] getnext_opts Options for ::lys_getnext().
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOTFOUND if no next node found.
+ */
+static LY_ERR
+moveto_axis_scnode_next_first(const struct lysc_node **iter, enum lyxp_node_type *iter_type, const struct lys_module **iter_mod,
+        uint32_t *iter_mod_idx, const struct lysc_node *scnode, enum lyxp_node_type node_type, enum lyxp_axis axis,
+        struct lyxp_set *set, uint32_t getnext_opts)
+{
+    const struct lysc_node *next = NULL;
+    enum lyxp_node_type next_type = 0;
+
+    assert(!*iter);
+    assert(!*iter_type);
+
+    *iter_mod = NULL;
+    *iter_mod_idx = 0;
+
+    switch (axis) {
+    case LYXP_AXIS_ANCESTOR_OR_SELF:
+    case LYXP_AXIS_DESCENDANT_OR_SELF:
+    case LYXP_AXIS_SELF:
+        if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT) || (node_type == LYXP_NODE_ELEM)) {
+            /* just return the node */
+            next = scnode;
+            next_type = node_type;
+        }
+        break;
+
+    case LYXP_AXIS_ANCESTOR:
+    case LYXP_AXIS_PARENT:
+        if (node_type == LYXP_NODE_ELEM) {
+            next = lysc_data_parent(scnode);
+            next_type = next ? LYXP_NODE_ELEM : set->root_type;
+        } /* else no parent */
+        break;
+
+    case LYXP_AXIS_DESCENDANT:
+    case LYXP_AXIS_CHILD:
+        if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) {
+            /* it can actually be in any module, it's all <running>, and even if it's moveto_mod (if set),
+             * it can be in a top-level augment */
+            while ((*iter_mod = ly_ctx_get_module_iter(set->ctx, iter_mod_idx))) {
+                /* module may not be implemented or not compiled yet */
+                if (!(*iter_mod)->compiled) {
+                    continue;
+                }
+
+                /* get next node */
+                if ((next = lys_getnext(NULL, NULL, (*iter_mod)->compiled, getnext_opts))) {
+                    next_type = LYXP_NODE_ELEM;
+                    break;
+                }
+            }
+        } else if (node_type == LYXP_NODE_ELEM) {
+            /* get next node */
+            next = lys_getnext(NULL, scnode, NULL, getnext_opts);
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        }
+        break;
+
+    case LYXP_AXIS_FOLLOWING:
+    case LYXP_AXIS_FOLLOWING_SIBLING:
+        if (node_type == LYXP_NODE_ELEM) {
+            /* first next sibling */
+            next = lys_getnext(scnode, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts);
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } /* else no sibling */
+        break;
+
+    case LYXP_AXIS_PRECEDING:
+    case LYXP_AXIS_PRECEDING_SIBLING:
+        if (node_type == LYXP_NODE_ELEM) {
+            /* first parent sibling */
+            next = lys_getnext(NULL, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts);
+            if (next == scnode) {
+                /* no preceding sibling */
+                next = NULL;
+            }
+            next_type = next ? LYXP_NODE_ELEM : 0;
+        } /* else no sibling */
+        break;
+
+    case LYXP_AXIS_ATTRIBUTE:
+        /* unreachable */
+        assert(0);
+        LOGINT(set->ctx);
+        break;
+    }
+
+    *iter = next;
+    *iter_type = next_type;
+    return next_type ? LY_SUCCESS : LY_ENOTFOUND;
+}
+
+/**
+ * @brief Iterate over all schema nodes on an axis for a context node.
+ *
+ * @param[in,out] iter Last returned node, start with NULL, updated to the next node.
+ * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node.
+ * @param[in,out] iter_mod Internal module iterator, do not change.
+ * @param[in,out] iter_mod_idx Internal module index iterator, do not change.
+ * @param[in] scnode Context node.
+ * @param[in] node_type Type of @p scnode.
+ * @param[in] axis Axis to use.
+ * @param[in] set XPath set with the general context.
+ * @param[in] getnext_opts Options for ::lys_getnext().
+ * @return LY_SUCCESS on success.
+ * @return LY_ENOTFOUND if no next node found.
+ */
+static LY_ERR
+moveto_axis_scnode_next(const struct lysc_node **iter, enum lyxp_node_type *iter_type, const struct lys_module **iter_mod,
+        uint32_t *iter_mod_idx, const struct lysc_node *scnode, enum lyxp_node_type node_type, enum lyxp_axis axis,
+        struct lyxp_set *set, uint32_t getnext_opts)
+{
+    const struct lysc_node *next = NULL, *dfs_stop;
+    enum lyxp_node_type next_type = 0;
+
+    if (!*iter_type) {
+        /* first returned node */
+        return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type, axis, set,
+                getnext_opts);
+    }
+
+    switch (axis) {
+    case LYXP_AXIS_PARENT:
+    case LYXP_AXIS_SELF:
+        /* parent/self was returned before */
+        break;
+
+    case LYXP_AXIS_ANCESTOR_OR_SELF:
+        if ((*iter == scnode) && (*iter_type == node_type)) {
+            /* fake first ancestor, we returned self before */
+            *iter = NULL;
+            *iter_type = 0;
+            return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type,
+                    LYXP_AXIS_ANCESTOR, set, getnext_opts);
+        } /* else continue ancestor */
+
+    /* fallthrough */
+    case LYXP_AXIS_ANCESTOR:
+        if (*iter_type == LYXP_NODE_ELEM) {
+            next = lysc_data_parent(*iter);
+            next_type = next ? LYXP_NODE_ELEM : set->root_type;
+        } /* else no ancestor */
+        break;
+
+    case LYXP_AXIS_DESCENDANT_OR_SELF:
+        if ((*iter == scnode) && (*iter_type == node_type)) {
+            /* fake first descendant, we returned self before */
+            *iter = NULL;
+            *iter_type = 0;
+            return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type,
+                    LYXP_AXIS_DESCENDANT, set, getnext_opts);
+        } /* else DFS until context node */
+        dfs_stop = scnode;
+
+    /* fallthrough */
+    case LYXP_AXIS_DESCENDANT:
+        if (axis == LYXP_AXIS_DESCENDANT) {
+            /* DFS until the context node */
+            dfs_stop = scnode;
+        }
+
+    /* fallthrough */
+    case LYXP_AXIS_PRECEDING:
+        if (axis == LYXP_AXIS_PRECEDING) {
+            /* DFS until the previous sibling */
+            dfs_stop = moveto_axis_scnode_preceding_sibling(scnode, getnext_opts);
+            assert(dfs_stop);
+
+            if (*iter == dfs_stop) {
+                /* we are done */
+                break;
+            }
+        }
+
+    /* fallthrough */
+    case LYXP_AXIS_FOLLOWING:
+        if (axis == LYXP_AXIS_FOLLOWING) {
+            /* DFS through the whole module */
+            dfs_stop = NULL;
+        }
+
+        /* nested nodes */
+        assert(*iter);
+        next = moveto_axis_scnode_next_dfs_forward(*iter, dfs_stop, getnext_opts);
+        if (next) {
+            next_type = LYXP_NODE_ELEM;
+            break;
+        } /* else get next top-level node just like a child */
+
+    /* fallthrough */
+    case LYXP_AXIS_CHILD:
+    case LYXP_AXIS_FOLLOWING_SIBLING:
+        if (!*iter_mod) {
+            /* nodes from a single module */
+            if ((next = lys_getnext(*iter, lysc_data_parent(*iter), (*iter)->module->compiled, getnext_opts))) {
+                next_type = LYXP_NODE_ELEM;
+                break;
+            }
+
+            assert(scnode);
+            if (!lysc_data_parent(scnode)) {
+                /* iterating over top-level nodes, find next */
+                while (lysc_data_parent(*iter)) {
+                    *iter = lysc_data_parent(*iter);
+                }
+                if ((next = lys_getnext(*iter, NULL, (*iter)->module->compiled, getnext_opts))) {
+                    next_type = LYXP_NODE_ELEM;
+                    break;
+                }
+            }
+        }
+
+        while (*iter_mod) {
+            /* module top-level nodes */
+            if ((next = lys_getnext(*iter, NULL, (*iter_mod)->compiled, getnext_opts))) {
+                next_type = LYXP_NODE_ELEM;
+                break;
+            }
+
+            /* get next module */
+            while ((*iter_mod = ly_ctx_get_module_iter(set->ctx, iter_mod_idx))) {
+                /* module may not be implemented or not compiled yet */
+                if ((*iter_mod)->compiled) {
+                    break;
+                }
+            }
+
+            /* new module, start over */
+            *iter = NULL;
+        }
+        break;
+
+    case LYXP_AXIS_PRECEDING_SIBLING:
+        assert(*iter);
+
+        /* next parent sibling until scnode */
+        next = lys_getnext(*iter, lysc_data_parent(*iter), (*iter)->module->compiled, getnext_opts);
+        if (next == scnode) {
+            /* no previous sibling */
+            next = NULL;
+        }
+        next_type = next ? LYXP_NODE_ELEM : 0;
+        break;
+
+    case LYXP_AXIS_ATTRIBUTE:
+        /* unreachable */
+        assert(0);
+        LOGINT(set->ctx);
+        break;
+    }
+
+    *iter = next;
+    *iter_type = next_type;
+    return next_type ? LY_SUCCESS : LY_ENOTFOUND;
+}
+
+/**
  * @brief Move context @p set to a schema node. Result is LYXP_SET_SCNODE_SET (or LYXP_SET_EMPTY).
  *
  * @param[in,out] set Set to use.
  * @param[in] moveto_mod Matching node module, NULL for no prefix.
  * @param[in] ncname Matching node name in the dictionary, NULL for any.
+ * @param[in] axis Axis to search on.
  * @param[in] options XPath options.
  * @return LY_ERR
  */
 static LY_ERR
-moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
+moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, enum lyxp_axis axis,
+        uint32_t options)
 {
     ly_bool temp_ctx = 0;
-    uint32_t getnext_opts;
-    uint32_t orig_used, i;
-    uint32_t mod_idx;
-    const struct lysc_node *iter, *start_parent;
+    uint32_t getnext_opts, orig_used, i, mod_idx, idx;
     const struct lys_module *mod;
+    const struct lysc_node *iter;
+    enum lyxp_node_type iter_type;
 
     if (options & LYXP_SKIP_EXPR) {
         return LY_SUCCESS;
@@ -5726,52 +6619,27 @@
 
     orig_used = set->used;
     for (i = 0; i < orig_used; ++i) {
-        uint32_t idx;
+        /* update in_ctx first */
+        if (moveto_axis_scnode_next_in_ctx(&set->val.scnodes[i].in_ctx, axis)) {
+            /* not usable, skip */
+            continue;
+        }
 
-        if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_ATOM_CTX) {
-            if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START) {
+        iter = NULL;
+        iter_type = 0;
+        while (!moveto_axis_scnode_next(&iter, &iter_type, &mod, &mod_idx, set->val.scnodes[i].scnode,
+                set->val.scnodes[i].type, axis, set, getnext_opts)) {
+            if (moveto_scnode_check(iter, NULL, set, ncname, moveto_mod)) {
                 continue;
             }
 
-            /* remember context node */
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_START_USED;
-        } else {
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
-        }
+            /* insert */
+            LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, iter_type, &idx));
 
-        start_parent = set->val.scnodes[i].scnode;
-
-        if ((set->val.scnodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.scnodes[i].type == LYXP_NODE_ROOT)) {
-            /* it can actually be in any module, it's all <running>, and even if it's moveto_mod (if set),
-             * it can be in a top-level augment (the root node itself is useless in this case) */
-            mod_idx = 0;
-            while ((mod = ly_ctx_get_module_iter(set->ctx, &mod_idx))) {
-                iter = NULL;
-                /* module may not be implemented or not compiled yet */
-                while (mod->compiled && (iter = lys_getnext(iter, NULL, mod->compiled, getnext_opts))) {
-                    if (!moveto_scnode_check(iter, NULL, set, ncname, moveto_mod)) {
-                        LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, &idx));
-
-                        /* we need to prevent these nodes from being considered in this moveto */
-                        if ((idx < orig_used) && (idx > i)) {
-                            set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
-                            temp_ctx = 1;
-                        }
-                    }
-                }
-            }
-
-        } else if (set->val.scnodes[i].type == LYXP_NODE_ELEM) {
-            iter = NULL;
-            while ((iter = lys_getnext(iter, start_parent, NULL, getnext_opts))) {
-                if (!moveto_scnode_check(iter, start_parent, set, ncname, moveto_mod)) {
-                    LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, &idx));
-
-                    if ((idx < orig_used) && (idx > i)) {
-                        set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
-                        temp_ctx = 1;
-                    }
-                }
+            /* we need to prevent these nodes from being considered in this moveto */
+            if ((idx < orig_used) && (idx > i)) {
+                set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
+                temp_ctx = 1;
             }
         }
     }
@@ -5789,7 +6657,7 @@
 }
 
 /**
- * @brief Move context @p set to a node and all its descendants. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to a child node and all its descendants. Result is LYXP_SET_NODE_SET.
  *        Context position aware.
  *
  * @param[in] set Set to use.
@@ -5799,7 +6667,7 @@
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-moveto_node_alldesc(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
+moveto_node_alldesc_child(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
 {
     uint32_t i;
     const struct lyd_node *next, *elem, *start;
@@ -5816,7 +6684,7 @@
     }
 
     /* replace the original nodes (and throws away all text and meta nodes, root is replaced by a child) */
-    rc = moveto_node(set, NULL, NULL, options);
+    rc = xpath_pi_node(set, LYXP_AXIS_CHILD, options);
     LY_CHECK_RET(rc);
 
     /* this loop traverses all the nodes in the set and adds/keeps only those that match qname */
@@ -5826,7 +6694,7 @@
         /* TREE DFS */
         start = set->val.nodes[i].node;
         for (elem = next = start; elem; elem = next) {
-            rc = moveto_node_check(elem, set, ncname, moveto_mod, options);
+            rc = moveto_node_check(elem, LYXP_NODE_ELEM, set, ncname, moveto_mod, options);
             if (!rc) {
                 /* add matching node into result set */
                 set_insert_node(&ret_set, elem, 0, LYXP_NODE_ELEM, ret_set.used);
@@ -5876,20 +6744,96 @@
 }
 
 /**
- * @brief Move context @p set to a schema node and all its descendants. Result is LYXP_SET_NODE_SET.
+ * @brief Move context @p set to a child schema node and all its descendants starting from a node.
+ * Result is LYXP_SET_NODE_SET.
+ *
+ * @param[in] set Set to use.
+ * @param[in] start Start node whose subtree to add.
+ * @param[in] start_idx Index of @p start in @p set.
+ * @param[in] moveto_mod Matching node module, NULL for no prefix.
+ * @param[in] ncname Matching node name in the dictionary, NULL for any.
+ * @param[in] options XPath options.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+moveto_scnode_dfs(struct lyxp_set *set, const struct lysc_node *start, uint32_t start_idx,
+        const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
+{
+    const struct lysc_node *next, *elem;
+    uint32_t idx;
+    LY_ERR rc;
+
+    /* TREE DFS */
+    for (elem = next = start; elem; elem = next) {
+        if ((elem == start) || (elem->nodetype & (LYS_CHOICE | LYS_CASE))) {
+            /* schema-only nodes, skip root */
+            goto next_iter;
+        }
+
+        rc = moveto_scnode_check(elem, start, set, ncname, moveto_mod);
+        if (!rc) {
+            if (lyxp_set_scnode_contains(set, elem, LYXP_NODE_ELEM, start_idx, &idx)) {
+                set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
+                if (idx > start_idx) {
+                    /* we will process it later in the set */
+                    goto skip_children;
+                }
+            } else {
+                LY_CHECK_RET(lyxp_set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, NULL));
+            }
+        } else if (rc == LY_EINVAL) {
+            goto skip_children;
+        }
+
+next_iter:
+        /* TREE DFS NEXT ELEM */
+        /* select element for the next run - children first */
+        next = lysc_node_child(elem);
+        if (next && (next->nodetype == LYS_INPUT) && (options & LYXP_SCNODE_OUTPUT)) {
+            next = next->next;
+        } else if (next && (next->nodetype == LYS_OUTPUT) && !(options & LYXP_SCNODE_OUTPUT)) {
+            next = next->next;
+        }
+        if (!next) {
+skip_children:
+            /* no children, so try siblings, but only if it's not the start,
+             * that is considered to be the root and it's siblings are not traversed */
+            if (elem != start) {
+                next = elem->next;
+            } else {
+                break;
+            }
+        }
+        while (!next) {
+            /* no siblings, go back through the parents */
+            if (elem->parent == start) {
+                /* we are done, no next element to process */
+                break;
+            }
+            /* parent is already processed, go to its sibling */
+            elem = elem->parent;
+            next = elem->next;
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Move context @p set to a child schema node and all its descendants. Result is LYXP_SET_NODE_SET.
  *
  * @param[in] set Set to use.
  * @param[in] moveto_mod Matching node module, NULL for no prefix.
  * @param[in] ncname Matching node name in the dictionary, NULL for any.
  * @param[in] options XPath options.
- * @return LY_ERR
+ * @return LY_ERR value.
  */
 static LY_ERR
-moveto_scnode_alldesc(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
+moveto_scnode_alldesc_child(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options)
 {
-    uint32_t i, orig_used;
-    const struct lysc_node *next, *elem, *start;
-    LY_ERR rc;
+    uint32_t i, orig_used, mod_idx;
+    const struct lys_module *mod;
+    const struct lysc_node *root;
 
     if (options & LYXP_SKIP_EXPR) {
         return LY_SUCCESS;
@@ -5913,60 +6857,25 @@
             set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
         }
 
-        /* TREE DFS */
-        start = set->val.scnodes[i].scnode;
-        for (elem = next = start; elem; elem = next) {
-            if ((elem == start) || (elem->nodetype & (LYS_CHOICE | LYS_CASE))) {
-                /* schema-only nodes, skip root */
-                goto next_iter;
-            }
-
-            rc = moveto_scnode_check(elem, start, set, ncname, moveto_mod);
-            if (!rc) {
-                uint32_t idx;
-
-                if (lyxp_set_scnode_contains(set, elem, LYXP_NODE_ELEM, i, &idx)) {
-                    set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
-                    if ((uint32_t)idx > i) {
-                        /* we will process it later in the set */
-                        goto skip_children;
-                    }
-                } else {
-                    LY_CHECK_RET(lyxp_set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, NULL));
+        if ((set->val.scnodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.scnodes[i].type == LYXP_NODE_ROOT)) {
+            /* traverse all top-level nodes in all the modules */
+            mod_idx = 0;
+            while ((mod = ly_ctx_get_module_iter(set->ctx, &mod_idx))) {
+                /* module may not be implemented or not compiled yet */
+                if (!mod->compiled) {
+                    continue;
                 }
-            } else if (rc == LY_EINVAL) {
-                goto skip_children;
-            }
 
-next_iter:
-            /* TREE DFS NEXT ELEM */
-            /* select element for the next run - children first */
-            next = lysc_node_child(elem);
-            if (next && (next->nodetype == LYS_INPUT) && (options & LYXP_SCNODE_OUTPUT)) {
-                next = next->next;
-            } else if (next && (next->nodetype == LYS_OUTPUT) && !(options & LYXP_SCNODE_OUTPUT)) {
-                next = next->next;
-            }
-            if (!next) {
-skip_children:
-                /* no children, so try siblings, but only if it's not the start,
-                 * that is considered to be the root and it's siblings are not traversed */
-                if (elem != start) {
-                    next = elem->next;
-                } else {
-                    break;
+                root = NULL;
+                /* no getnext opts needed */
+                while ((root = lys_getnext(root, NULL, mod->compiled, 0))) {
+                    LY_CHECK_RET(moveto_scnode_dfs(set, root, i, moveto_mod, ncname, options));
                 }
             }
-            while (!next) {
-                /* no siblings, go back through the parents */
-                if (elem->parent == start) {
-                    /* we are done, no next element to process */
-                    break;
-                }
-                /* parent is already processed, go to its sibling */
-                elem = elem->parent;
-                next = elem->next;
-            }
+
+        } else if (set->val.scnodes[i].type == LYXP_NODE_ELEM) {
+            /* add all the descendants recursively */
+            LY_CHECK_RET(moveto_scnode_dfs(set, set->val.scnodes[i].scnode, i, moveto_mod, ncname, options));
         }
     }
 
@@ -6111,7 +7020,7 @@
     /* copy the context */
     set_all_desc = set_copy(set);
     /* get all descendant nodes (the original context nodes are removed) */
-    rc = moveto_node_alldesc(set_all_desc, NULL, NULL, options);
+    rc = moveto_node_alldesc_child(set_all_desc, NULL, NULL, options);
     if (rc != LY_SUCCESS) {
         lyxp_set_free(set_all_desc);
         return rc;
@@ -6161,385 +7070,6 @@
 }
 
 /**
- * @brief Move context @p set to self and al chilren, recursively. Handles '/' or '//' and '.'. Result is LYXP_SET_NODE_SET.
- *        Context position aware.
- *
- * @param[in] parent Current parent.
- * @param[in] parent_pos Position of @p parent.
- * @param[in] parent_type Node type of @p parent.
- * @param[in,out] to_set Set to use.
- * @param[in] dup_check_set Set for checking duplicities.
- * @param[in] options XPath options.
- * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
- */
-static LY_ERR
-moveto_self_add_children_r(const struct lyd_node *parent, uint32_t parent_pos, enum lyxp_node_type parent_type,
-        struct lyxp_set *to_set, const struct lyxp_set *dup_check_set, uint32_t options)
-{
-    const struct lyd_node *iter, *first;
-    LY_ERR rc;
-
-    switch (parent_type) {
-    case LYXP_NODE_ROOT:
-    case LYXP_NODE_ROOT_CONFIG:
-        assert(!parent);
-
-        /* add all top-level nodes as elements */
-        first = to_set->tree;
-        break;
-    case LYXP_NODE_ELEM:
-        /* add just the text node of this term element node */
-        if (parent->schema->nodetype & (LYD_NODE_TERM | LYD_NODE_ANY)) {
-            if (!set_dup_node_check(dup_check_set, parent, LYXP_NODE_TEXT, -1)) {
-                set_insert_node(to_set, parent, parent_pos, LYXP_NODE_TEXT, to_set->used);
-            }
-            return LY_SUCCESS;
-        }
-
-        /* add all the children of this node */
-        first = lyd_child(parent);
-        break;
-    default:
-        LOGINT_RET(parent->schema->module->ctx);
-    }
-
-    /* add all top-level nodes as elements */
-    LY_LIST_FOR(first, iter) {
-        /* context check */
-        if ((parent_type == LYXP_NODE_ROOT_CONFIG) && (iter->schema->flags & LYS_CONFIG_R)) {
-            continue;
-        }
-
-        /* when check */
-        if (!(options & LYXP_IGNORE_WHEN) && lysc_has_when(iter->schema) && !(iter->flags & LYD_WHEN_TRUE)) {
-            return LY_EINCOMPLETE;
-        }
-
-        if (!set_dup_node_check(dup_check_set, iter, LYXP_NODE_ELEM, -1)) {
-            set_insert_node(to_set, iter, 0, LYXP_NODE_ELEM, to_set->used);
-
-            /* also add all the children of this node, recursively */
-            rc = moveto_self_add_children_r(iter, 0, LYXP_NODE_ELEM, to_set, dup_check_set, options);
-            LY_CHECK_RET(rc);
-        }
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
- * @brief Move context @p set to self. Handles '/' or '//' and '.'. Result is LYXP_SET_NODE_SET
- *        (or LYXP_SET_EMPTY). Context position aware.
- *
- * @param[in,out] set Set to use.
- * @param[in] all_desc Whether to go to all descendants ('//') or not ('/').
- * @param[in] options XPath options.
- * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
- */
-static LY_ERR
-moveto_self(struct lyxp_set *set, ly_bool all_desc, uint32_t options)
-{
-    struct lyxp_set ret_set;
-    LY_ERR rc;
-
-    if (options & LYXP_SKIP_EXPR) {
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_NODE_SET) {
-        LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
-        return LY_EVALID;
-    }
-
-    /* nothing to do */
-    if (!all_desc) {
-        return LY_SUCCESS;
-    }
-
-    /* add all the children, they get added recursively */
-    set_init(&ret_set, set);
-    for (uint32_t i = 0; i < set->used; ++i) {
-        /* copy the current node to tmp */
-        set_insert_node(&ret_set, set->val.nodes[i].node, set->val.nodes[i].pos, set->val.nodes[i].type, ret_set.used);
-
-        /* do not touch attributes and text nodes */
-        if ((set->val.nodes[i].type == LYXP_NODE_TEXT) || (set->val.nodes[i].type == LYXP_NODE_META)) {
-            continue;
-        }
-
-        /* add all the children */
-        rc = moveto_self_add_children_r(set->val.nodes[i].node, set->val.nodes[i].pos, set->val.nodes[i].type, &ret_set,
-                set, options);
-        if (rc != LY_SUCCESS) {
-            lyxp_set_free_content(&ret_set);
-            return rc;
-        }
-    }
-
-    /* use the temporary set as the current one */
-    ret_set.ctx_pos = set->ctx_pos;
-    ret_set.ctx_size = set->ctx_size;
-    lyxp_set_free_content(set);
-    memcpy(set, &ret_set, sizeof *set);
-
-    return LY_SUCCESS;
-}
-
-/**
- * @brief Move context schema @p set to self. Handles '/' or '//' and '.'. Result is LYXP_SET_SCNODE_SET
- *        (or LYXP_SET_EMPTY).
- *
- * @param[in,out] set Set to use.
- * @param[in] all_desc Whether to go to all descendants ('//') or not ('/').
- * @param[in] options XPath options.
- * @return LY_ERR
- */
-static LY_ERR
-moveto_scnode_self(struct lyxp_set *set, ly_bool all_desc, uint32_t options)
-{
-    uint32_t getnext_opts;
-    uint32_t mod_idx;
-    const struct lysc_node *iter, *start_parent;
-    const struct lys_module *mod;
-
-    if (options & LYXP_SKIP_EXPR) {
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_SCNODE_SET) {
-        LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
-        return LY_EVALID;
-    }
-
-    /* getnext opts */
-    getnext_opts = 0;
-    if (options & LYXP_SCNODE_OUTPUT) {
-        getnext_opts |= LYS_GETNEXT_OUTPUT;
-    }
-
-    /* add all the children, recursively as they are being added into the same set */
-    for (uint32_t i = 0; i < set->used; ++i) {
-        if (!all_desc) {
-            /* traverse the start node */
-            if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START) {
-                set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
-            }
-            continue;
-        }
-
-        if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_ATOM_CTX) {
-            if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START) {
-                continue;
-            }
-
-            /* remember context node */
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_START_USED;
-        } else {
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
-        }
-
-        start_parent = set->val.scnodes[i].scnode;
-
-        if ((set->val.scnodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.scnodes[i].type == LYXP_NODE_ROOT)) {
-            /* it can actually be in any module, it's all <running> */
-            mod_idx = 0;
-            while ((mod = ly_ctx_get_module_iter(set->ctx, &mod_idx))) {
-                iter = NULL;
-                /* module may not be implemented */
-                while (mod->implemented && (iter = lys_getnext(iter, NULL, mod->compiled, getnext_opts))) {
-                    /* context check */
-                    if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (iter->flags & LYS_CONFIG_R)) {
-                        continue;
-                    }
-
-                    LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, NULL));
-                    /* throw away the insert index, we want to consider that node again, recursively */
-                }
-            }
-
-        } else if (set->val.scnodes[i].type == LYXP_NODE_ELEM) {
-            iter = NULL;
-            while ((iter = lys_getnext(iter, start_parent, NULL, getnext_opts))) {
-                /* context check */
-                if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (iter->flags & LYS_CONFIG_R)) {
-                    continue;
-                }
-
-                LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, NULL));
-            }
-        }
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
- * @brief Move context @p set to parent. Handles '/' or '//' and '..'. Result is LYXP_SET_NODE_SET
- *        (or LYXP_SET_EMPTY). Context position aware.
- *
- * @param[in] set Set to use.
- * @param[in] all_desc Whether to go to all descendants ('//') or not ('/').
- * @param[in] options XPath options.
- * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
- */
-static LY_ERR
-moveto_parent(struct lyxp_set *set, ly_bool all_desc, uint32_t options)
-{
-    LY_ERR rc = LY_SUCCESS;
-    struct lyd_node *node, *new_node;
-    enum lyxp_node_type new_type;
-    struct lyxp_set result;
-
-    if (options & LYXP_SKIP_EXPR) {
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_NODE_SET) {
-        LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
-        return LY_EVALID;
-    }
-
-    if (all_desc) {
-        /* <path>//.. == <path>//./.. */
-        rc = moveto_self(set, 1, options);
-        LY_CHECK_RET(rc);
-    }
-
-    /* init result set */
-    set_init(&result, set);
-
-    for (uint32_t i = 0; i < set->used; ++i) {
-        node = set->val.nodes[i].node;
-
-        if (set->val.nodes[i].type == LYXP_NODE_ELEM) {
-            new_node = lyd_parent(node);
-        } else if (set->val.nodes[i].type == LYXP_NODE_TEXT) {
-            new_node = node;
-        } else if (set->val.nodes[i].type == LYXP_NODE_META) {
-            new_node = set->val.meta[i].meta->parent;
-            if (!new_node) {
-                LOGINT(set->ctx);
-                rc = LY_EINT;
-                goto cleanup;
-            }
-        } else {
-            /* root does not have a parent */
-            continue;
-        }
-
-        /* when check */
-        if (!(options & LYXP_IGNORE_WHEN) && new_node && lysc_has_when(new_node->schema) &&
-                !(new_node->flags & LYD_WHEN_TRUE)) {
-            rc = LY_EINCOMPLETE;
-            goto cleanup;
-        }
-
-        if (!new_node) {
-            /* node already there can also be the root */
-            new_type = set->root_type;
-        } else {
-            /* node has a standard parent (it can equal the root, it's not the root yet since they are fake) */
-            new_type = LYXP_NODE_ELEM;
-        }
-
-        /* check for duplicates, several nodes may have the same parent */
-        if (!set_dup_node_check(&result, new_node, new_type, -1)) {
-            set_insert_node(&result, new_node, 0, new_type, result.used);
-        }
-    }
-
-    /* move result to the set */
-    lyxp_set_free_content(set);
-    *set = result;
-    result.type = LYXP_SET_NUMBER;
-    assert(!set_sort(set));
-
-cleanup:
-    lyxp_set_free_content(&result);
-    return rc;
-}
-
-/**
- * @brief Move context schema @p set to parent. Handles '/' or '//' and '..'. Result is LYXP_SET_SCNODE_SET
- *        (or LYXP_SET_EMPTY).
- *
- * @param[in] set Set to use.
- * @param[in] all_desc Whether to go to all descendants ('//') or not ('/').
- * @param[in] options XPath options.
- * @return LY_ERR
- */
-static LY_ERR
-moveto_scnode_parent(struct lyxp_set *set, ly_bool all_desc, uint32_t options)
-{
-    uint32_t i, orig_used, idx;
-    ly_bool temp_ctx = 0;
-    const struct lysc_node *node, *new_node;
-    enum lyxp_node_type new_type;
-
-    if (options & LYXP_SKIP_EXPR) {
-        return LY_SUCCESS;
-    }
-
-    if (set->type != LYXP_SET_SCNODE_SET) {
-        LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
-        return LY_EVALID;
-    }
-
-    if (all_desc) {
-        /* <path>//.. == <path>//./.. */
-        LY_CHECK_RET(moveto_scnode_self(set, 1, options));
-    }
-
-    orig_used = set->used;
-    for (i = 0; i < orig_used; ++i) {
-        if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_ATOM_CTX) {
-            if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START) {
-                continue;
-            }
-
-            /* remember context node */
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_START_USED;
-        } else {
-            set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
-        }
-
-        node = set->val.scnodes[i].scnode;
-
-        if (set->val.scnodes[i].type == LYXP_NODE_ELEM) {
-            new_node = lysc_data_parent(node);
-        } else {
-            /* root does not have a parent */
-            continue;
-        }
-
-        if (!new_node) {
-            /* node has no parent */
-            new_type = set->root_type;
-
-        } else {
-            /* node has a standard parent (it can equal the root, it's not the root yet since they are fake) */
-            new_type = LYXP_NODE_ELEM;
-        }
-
-        LY_CHECK_RET(lyxp_set_scnode_insert_node(set, new_node, new_type, &idx));
-        if ((idx < orig_used) && (idx > i)) {
-            set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
-            temp_ctx = 1;
-        }
-    }
-
-    if (temp_ctx) {
-        for (i = 0; i < orig_used; ++i) {
-            if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_NEW_CTX) {
-                set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
-            }
-        }
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Move context @p set to the result of a comparison. Handles '=', '!=', '<=', '<', '>=', or '>'.
  *        Result is LYXP_SET_BOOLEAN. Indirectly context position aware.
  *
@@ -6798,22 +7328,22 @@
  * @param[in] tok_idx Position in the expression @p exp.
  * @param[in,out] set Context and result set.
  * @param[in] options XPath options.
- * @param[in] parent_pos_pred Whether parent predicate was a positional one.
+ * @param[in] axis Axis to search on.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-eval_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, struct lyxp_set *set, uint32_t options, ly_bool parent_pos_pred)
+eval_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, struct lyxp_set *set, uint32_t options, enum lyxp_axis axis)
 {
     LY_ERR rc;
     uint16_t orig_exp;
     uint32_t i, orig_pos, orig_size;
     int32_t pred_in_ctx;
+    ly_bool reverse_axis;
     struct lyxp_set set2 = {0};
-    struct lyd_node *orig_parent;
 
     /* '[' */
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     if (options & LYXP_SKIP_EXPR) {
@@ -6829,22 +7359,35 @@
             goto only_parse;
         }
 
+        /* decide forward or reverse axis */
+        switch (axis) {
+        case LYXP_AXIS_ANCESTOR:
+        case LYXP_AXIS_ANCESTOR_OR_SELF:
+        case LYXP_AXIS_PRECEDING:
+        case LYXP_AXIS_PRECEDING_SIBLING:
+            reverse_axis = 1;
+            break;
+        case LYXP_AXIS_DESCENDANT:
+        case LYXP_AXIS_DESCENDANT_OR_SELF:
+        case LYXP_AXIS_FOLLOWING:
+        case LYXP_AXIS_FOLLOWING_SIBLING:
+        case LYXP_AXIS_PARENT:
+        case LYXP_AXIS_CHILD:
+        case LYXP_AXIS_SELF:
+        case LYXP_AXIS_ATTRIBUTE:
+            reverse_axis = 0;
+            break;
+        }
+
         orig_exp = *tok_idx;
-        orig_pos = 0;
+        orig_pos = reverse_axis ? set->used + 1 : 0;
         orig_size = set->used;
-        orig_parent = NULL;
         for (i = 0; i < set->used; ++i) {
             set_init(&set2, set);
             set_insert_node(&set2, set->val.nodes[i].node, set->val.nodes[i].pos, set->val.nodes[i].type, 0);
-            /* remember the node context position for position() and context size for last(),
-             * predicates should always be evaluated with respect to the child axis (since we do
-             * not support explicit axes) so we assign positions based on their parents */
-            if (parent_pos_pred && (lyd_parent(set->val.nodes[i].node) != orig_parent)) {
-                orig_parent = lyd_parent(set->val.nodes[i].node);
-                orig_pos = 1;
-            } else {
-                ++orig_pos;
-            }
+
+            /* remember the node context position for position() and context size for last() */
+            orig_pos += reverse_axis ? -1 : 1;
 
             set2.ctx_pos = orig_pos;
             set2.ctx_size = orig_size;
@@ -6856,7 +7399,7 @@
                 return rc;
             }
 
-            /* number is a position */
+            /* number is a proximity position */
             if (set2.type == LYXP_SET_NUMBER) {
                 if ((long long)set2.val.num == orig_pos) {
                     set2.val.num = 1;
@@ -6934,7 +7477,7 @@
     /* ']' */
     assert(exp->tokens[*tok_idx] == LYXP_TOKEN_BRACK2);
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     return LY_SUCCESS;
@@ -6958,7 +7501,7 @@
         }
     }
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 }
 
@@ -7209,14 +7752,14 @@
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] tok_idx Position in the expression @p exp.
- * @param[in] attr_axis Whether to search attributes or standard nodes.
+ * @param[in] axis What axis to search on.
  * @param[in] all_desc Whether to search all the descendants or children only.
  * @param[in,out] set Context and result set.
  * @param[in] options XPath options.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when, LY_ENOT for not found schema node)
  */
 static LY_ERR
-eval_name_test_with_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, ly_bool attr_axis, ly_bool all_desc,
+eval_name_test_with_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, enum lyxp_axis axis, ly_bool all_desc,
         struct lyxp_set *set, uint32_t options)
 {
     LY_ERR rc = LY_SUCCESS, r;
@@ -7229,7 +7772,7 @@
     int scnode_skip_pred = 0;
 
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     if (options & LYXP_SKIP_EXPR) {
@@ -7248,7 +7791,13 @@
     rc = moveto_resolve_model(&ncname, &ncname_len, set, NULL, &moveto_mod);
     LY_CHECK_GOTO(rc, cleanup);
 
-    if (((set->format == LY_VALUE_JSON) || moveto_mod) && !attr_axis && !all_desc && (set->type == LYXP_SET_NODE_SET)) {
+    if ((ncname[0] == '*') && (ncname_len == 1)) {
+        /* all nodes from the module will match */
+        goto moveto;
+    }
+
+    if (((set->format == LY_VALUE_JSON) || moveto_mod) && (axis == LYXP_AXIS_CHILD) && !all_desc &&
+            (set->type == LYXP_SET_NODE_SET)) {
         /* find the matching schema node in some parent in the context */
         for (uint32_t i = 0; i < set->used; ++i) {
             if (eval_name_test_with_predicate_get_scnode(set->ctx, set->val.nodes[i].node, ncname, ncname_len,
@@ -7277,7 +7826,7 @@
 
 moveto:
     /* move to the attribute(s), data node(s), or schema node(s) */
-    if (attr_axis) {
+    if (axis == LYXP_AXIS_ATTRIBUTE) {
         if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) {
             set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
         } else {
@@ -7307,10 +7856,16 @@
                 }
             }
 
-            if (all_desc) {
-                rc = moveto_scnode_alldesc(set, moveto_mod, ncname_dict, options);
+            if (all_desc && (axis == LYXP_AXIS_CHILD)) {
+                /* efficient evaluation that does not add all the descendants into the set */
+                rc = moveto_scnode_alldesc_child(set, moveto_mod, ncname_dict, options);
             } else {
-                rc = moveto_scnode(set, moveto_mod, ncname_dict, options);
+                if (all_desc) {
+                    /* "//" == "/descendant-or-self::node()/" */
+                    rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options);
+                    LY_CHECK_GOTO(rc, cleanup);
+                }
+                rc = moveto_scnode(set, moveto_mod, ncname_dict, axis, options);
             }
             LY_CHECK_GOTO(rc, cleanup);
 
@@ -7334,15 +7889,19 @@
                 scnode_skip_pred = 1;
             }
         } else {
-            if (all_desc) {
-                rc = moveto_node_alldesc(set, moveto_mod, ncname_dict, options);
+            if (all_desc && (axis == LYXP_AXIS_CHILD)) {
+                /* efficient evaluation */
+                rc = moveto_node_alldesc_child(set, moveto_mod, ncname_dict, options);
+            } else if (scnode && (axis == LYXP_AXIS_CHILD)) {
+                /* we can find the child nodes using hashes */
+                rc = moveto_node_hash_child(set, scnode, predicates, options);
             } else {
-                if (scnode) {
-                    /* we can find the nodes using hashes */
-                    rc = moveto_node_hash(set, scnode, predicates, options);
-                } else {
-                    rc = moveto_node(set, moveto_mod, ncname_dict, options);
+                if (all_desc) {
+                    /* "//" == "/descendant-or-self::node()/" */
+                    rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options);
+                    LY_CHECK_GOTO(rc, cleanup);
                 }
+                rc = moveto_node(set, moveto_mod, ncname_dict, axis, options);
             }
             LY_CHECK_GOTO(rc, cleanup);
         }
@@ -7355,7 +7914,7 @@
 
     /* Predicate* */
     while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) {
-        r = eval_predicate(exp, tok_idx, set, options, 1);
+        r = eval_predicate(exp, tok_idx, set, options, axis);
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     }
 
@@ -7380,56 +7939,49 @@
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] tok_idx Position in the expression @p exp.
- * @param[in] attr_axis Whether to search attributes or standard nodes.
- * @param[in] all_desc Whether to search all the descendants or children only.
+ * @param[in] axis Axis to search on.
+ * @param[in] all_desc Whether to search all the descendants or axis only.
  * @param[in,out] set Context and result set.
  * @param[in] options XPath options.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-eval_node_type_with_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, ly_bool attr_axis, ly_bool all_desc,
+eval_node_type_with_predicate(const struct lyxp_expr *exp, uint16_t *tok_idx, enum lyxp_axis axis, ly_bool all_desc,
         struct lyxp_set *set, uint32_t options)
 {
     LY_ERR rc;
 
-    /* TODO */
-    (void)attr_axis;
     (void)all_desc;
 
     if (!(options & LYXP_SKIP_EXPR)) {
         assert(exp->tok_len[*tok_idx] == 4);
-        if (set->type == LYXP_SET_SCNODE_SET) {
-            set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
-            options |= LYXP_SKIP_EXPR;
+        if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "node", 4)) {
+            rc = xpath_pi_node(set, axis, options);
         } else {
-            if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "node", 4)) {
-                rc = xpath_node(NULL, 0, set, options);
-            } else {
-                assert(!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "text", 4));
-                rc = xpath_text(NULL, 0, set, options);
-            }
-            LY_CHECK_RET(rc);
+            assert(!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "text", 4));
+            rc = xpath_pi_text(set, axis, options);
         }
+        LY_CHECK_RET(rc);
     }
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     /* '(' */
     assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR1);
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     /* ')' */
     assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2);
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     /* Predicate* */
     while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) {
-        rc = eval_predicate(exp, tok_idx, set, options, 1);
+        rc = eval_predicate(exp, tok_idx, set, options, axis);
         LY_CHECK_RET(rc);
     }
 
@@ -7454,8 +8006,8 @@
 eval_relative_location_path(const struct lyxp_expr *exp, uint16_t *tok_idx, ly_bool all_desc, struct lyxp_set *set,
         uint32_t options)
 {
-    ly_bool attr_axis;
     LY_ERR rc = LY_SUCCESS;
+    enum lyxp_axis axis;
     int scnode_skip_path = 0;
 
     goto step;
@@ -7468,52 +8020,82 @@
             all_desc = 1;
         }
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
 step:
-        /* evaluate abbreviated axis '@'? if any */
-        if (exp->tokens[*tok_idx] == LYXP_TOKEN_AT) {
-            attr_axis = 1;
+        /* AxisSpecifier */
+        if (exp->tokens[*tok_idx] == LYXP_TOKEN_AXISNAME) {
+            axis = str2axis(exp->expr + exp->tok_pos[*tok_idx], exp->tok_len[*tok_idx]);
 
             LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                    lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                    lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            ++(*tok_idx);
+
+            assert(exp->tokens[*tok_idx] == LYXP_TOKEN_DCOLON);
+            LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
+                    lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            ++(*tok_idx);
+        } else if (exp->tokens[*tok_idx] == LYXP_TOKEN_AT) {
+            axis = LYXP_AXIS_ATTRIBUTE;
+
+            LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
+                    lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
             ++(*tok_idx);
         } else {
-            attr_axis = 0;
+            /* default */
+            axis = LYXP_AXIS_CHILD;
         }
 
-        /* Step */
+        /* NodeTest Predicate* */
         switch (exp->tokens[*tok_idx]) {
         case LYXP_TOKEN_DOT:
             /* evaluate '.' */
-            if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) {
-                rc = moveto_scnode_self(set, all_desc, options);
-            } else {
-                rc = moveto_self(set, all_desc, options);
+            if (!(options & LYXP_SKIP_EXPR)) {
+                if (((options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_SCNODE_SET)) ||
+                        (!(options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_NODE_SET))) {
+                    LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
+                    rc = LY_EVALID;
+                    goto cleanup;
+                }
+
+                if (all_desc) {
+                    rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options);
+                    LY_CHECK_GOTO(rc, cleanup);
+                }
+                rc = xpath_pi_node(set, LYXP_AXIS_SELF, options);
+                LY_CHECK_GOTO(rc, cleanup);
             }
-            LY_CHECK_GOTO(rc, cleanup);
             LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-                    lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                    lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
             ++(*tok_idx);
             break;
 
         case LYXP_TOKEN_DDOT:
             /* evaluate '..' */
-            if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) {
-                rc = moveto_scnode_parent(set, all_desc, options);
-            } else {
-                rc = moveto_parent(set, all_desc, options);
+            if (!(options & LYXP_SKIP_EXPR)) {
+                if (((options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_SCNODE_SET)) ||
+                        (!(options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_NODE_SET))) {
+                    LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
+                    rc = LY_EVALID;
+                    goto cleanup;
+                }
+
+                if (all_desc) {
+                    rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options);
+                    LY_CHECK_GOTO(rc, cleanup);
+                }
+                rc = xpath_pi_node(set, LYXP_AXIS_PARENT, options);
+                LY_CHECK_GOTO(rc, cleanup);
             }
-            LY_CHECK_GOTO(rc, cleanup);
             LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                    lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                    lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
             ++(*tok_idx);
             break;
 
         case LYXP_TOKEN_NAMETEST:
             /* evaluate NameTest Predicate* */
-            rc = eval_name_test_with_predicate(exp, tok_idx, attr_axis, all_desc, set, options);
+            rc = eval_name_test_with_predicate(exp, tok_idx, axis, all_desc, set, options);
             if (rc == LY_ENOT) {
                 assert(options & LYXP_SCNODE_ALL);
                 /* skip the rest of this path */
@@ -7526,7 +8108,7 @@
 
         case LYXP_TOKEN_NODETYPE:
             /* evaluate NodeType Predicate* */
-            rc = eval_node_type_with_predicate(exp, tok_idx, attr_axis, all_desc, set, options);
+            rc = eval_node_type_with_predicate(exp, tok_idx, axis, all_desc, set, options);
             LY_CHECK_GOTO(rc, cleanup);
             break;
 
@@ -7570,7 +8152,7 @@
         /* evaluate '/' - deferred */
         all_desc = 0;
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_NONE)) {
@@ -7579,6 +8161,7 @@
         switch (exp->tokens[*tok_idx]) {
         case LYXP_TOKEN_DOT:
         case LYXP_TOKEN_DDOT:
+        case LYXP_TOKEN_AXISNAME:
         case LYXP_TOKEN_AT:
         case LYXP_TOKEN_NAMETEST:
         case LYXP_TOKEN_NODETYPE:
@@ -7593,7 +8176,7 @@
         /* evaluate '//' - deferred so as not to waste memory by remembering all the nodes */
         all_desc = 1;
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         LY_CHECK_RET(eval_relative_location_path(exp, tok_idx, all_desc, set, options));
@@ -7742,13 +8325,13 @@
     }
 
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     /* '(' */
     assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR1);
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     /* ( Expr ( ',' Expr )* )? */
@@ -7772,7 +8355,7 @@
     }
     while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_COMMA)) {
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (!(options & LYXP_SKIP_EXPR)) {
@@ -7797,7 +8380,7 @@
     /* ')' */
     assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2);
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
 
     if (!(options & LYXP_SKIP_EXPR)) {
@@ -7857,7 +8440,7 @@
     }
 
     LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-            lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+            lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
     ++(*tok_idx);
     return LY_SUCCESS;
 }
@@ -7908,17 +8491,17 @@
 
     vars = set->vars;
 
-    /* Find out the name and value of the variable. */
+    /* find out the name and value of the variable */
     name = &exp->expr[exp->tok_pos[*tok_idx]];
     ret = lyxp_vars_find((struct lyxp_var *)vars, name, exp->tok_len[*tok_idx], &var);
     LY_CHECK_ERR_RET(ret, LOGERR(set->ctx, ret,
             "XPath variable \"%s\" not defined.", name), ret);
 
-    /* Parse value. */
+    /* parse value */
     ret = lyxp_expr_parse(set->ctx, var->value, 0, 1, &tokens);
     LY_CHECK_GOTO(ret, cleanup);
 
-    /* Evaluate value. */
+    /* evaluate value */
     token_index = 0;
     ret = eval_expr_select(tokens, &token_index, 0, set, options);
     LY_CHECK_GOTO(ret, cleanup);
@@ -7947,7 +8530,7 @@
 static LY_ERR
 eval_path_expr(const struct lyxp_expr *exp, uint16_t *tok_idx, struct lyxp_set *set, uint32_t options)
 {
-    ly_bool all_desc, parent_pos_pred;
+    ly_bool all_desc;
     LY_ERR rc;
 
     switch (exp->tokens[*tok_idx]) {
@@ -7956,7 +8539,7 @@
 
         /* '(' */
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         /* Expr */
@@ -7966,14 +8549,14 @@
         /* ')' */
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
-        parent_pos_pred = 0;
         goto predicate;
 
     case LYXP_TOKEN_DOT:
     case LYXP_TOKEN_DDOT:
+    case LYXP_TOKEN_AXISNAME:
     case LYXP_TOKEN_AT:
     case LYXP_TOKEN_NAMETEST:
     case LYXP_TOKEN_NODETYPE:
@@ -7988,7 +8571,6 @@
         LY_CHECK_RET(rc);
         ++(*tok_idx);
 
-        parent_pos_pred = 1;
         goto predicate;
 
     case LYXP_TOKEN_FUNCNAME:
@@ -7996,7 +8578,6 @@
         rc = eval_function_call(exp, tok_idx, set, options);
         LY_CHECK_RET(rc);
 
-        parent_pos_pred = 1;
         goto predicate;
 
     case LYXP_TOKEN_OPER_PATH:
@@ -8017,7 +8598,6 @@
             eval_literal(exp, tok_idx, set);
         }
 
-        parent_pos_pred = 1;
         goto predicate;
 
     case LYXP_TOKEN_NUMBER:
@@ -8032,11 +8612,10 @@
         }
         LY_CHECK_RET(rc);
 
-        parent_pos_pred = 1;
         goto predicate;
 
     default:
-        LOGVAL(set->ctx, LY_VCODE_XP_INTOK, lyxp_print_token(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
+        LOGVAL(set->ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]);
         return LY_EVALID;
     }
 
@@ -8045,7 +8624,7 @@
 predicate:
     /* Predicate* */
     while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) {
-        rc = eval_predicate(exp, tok_idx, set, options, parent_pos_pred);
+        rc = eval_predicate(exp, tok_idx, set, options, LYXP_AXIS_CHILD);
         LY_CHECK_RET(rc);
     }
 
@@ -8060,7 +8639,7 @@
         }
 
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         rc = eval_relative_location_path(exp, tok_idx, all_desc, set, options);
@@ -8103,7 +8682,7 @@
     for (i = 0; i < repeat; ++i) {
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_UNI);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (options & LYXP_SKIP_EXPR) {
@@ -8157,7 +8736,7 @@
         assert(!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_MATH) && (exp->expr[exp->tok_pos[*tok_idx]] == '-'));
 
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
     }
 
@@ -8216,7 +8795,7 @@
 
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_MATH);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (options & LYXP_SKIP_EXPR) {
@@ -8284,7 +8863,7 @@
 
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_MATH);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (options & LYXP_SKIP_EXPR) {
@@ -8354,7 +8933,7 @@
 
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_COMP);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (options & LYXP_SKIP_EXPR) {
@@ -8421,7 +9000,7 @@
 
         assert((exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL) || (exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_NEQUAL));
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         if (options & LYXP_SKIP_EXPR) {
@@ -8493,7 +9072,7 @@
     for (i = 0; i < repeat; ++i) {
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_LOG);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, ((options & LYXP_SKIP_EXPR) || !set->val.bln ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         /* lazy evaluation */
@@ -8563,7 +9142,7 @@
     for (i = 0; i < repeat; ++i) {
         assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_LOG);
         LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, ((options & LYXP_SKIP_EXPR) || set->val.bln ? "skipped" : "parsed"),
-                lyxp_print_token(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
+                lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]);
         ++(*tok_idx);
 
         /* lazy evaluation */