Skip to main content

xpath_eval/
axes.rs

1//! The 13 XPath 1.0 axes (§2.2), derived generically from `Node`'s minimal
2//! navigation primitives (`parent()`/`children()`/`attributes()`/
3//! `namespaces()`) — see `plan/03-document-trait.md` for the verbatim spec
4//! quotes each function below is checked against.
5//!
6//! Axes are free functions, not `Node` trait methods (see the plan's
7//! module-split rationale). Reverse axes (`ancestor`, `ancestor-or-self`,
8//! `preceding`, `preceding-sibling`) return their results in *reverse*
9//! document order (nearest node first), per §2.4 proximity position.
10//!
11//! Implementation note: several axes (`following`, `preceding`) are built
12//! on top of a full document-order listing of the tree, which is O(n) per
13//! call — acceptable for this phase (correctness of a test-only reference
14//! implementation), not a performance-tuned production axis evaluator.
15
16use crate::document::{Node, NodeKind};
17
18fn is_attribute_or_namespace(kind: NodeKind) -> bool {
19    matches!(kind, NodeKind::Attribute | NodeKind::Namespace)
20}
21
22/// The nearest-first chain of ancestors (`parent()`, `parent().parent()`,
23/// ...), i.e. already in reverse document order.
24fn ancestor_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
25    let mut out = Vec::new();
26    let mut cur = n.parent();
27    while let Some(p) = cur {
28        out.push(p);
29        cur = p.parent();
30    }
31    out
32}
33
34/// All descendants of `n`, in document order.
35fn descendant_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
36    let mut out = Vec::new();
37    for c in n.children() {
38        out.push(c);
39        out.extend(descendant_vec(c));
40    }
41    out
42}
43
44/// `n` followed by all its descendants, in document order.
45fn descendant_or_self_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
46    let mut out = vec![n];
47    out.extend(descendant_vec(n));
48    out
49}
50
51/// The root of the document `n` belongs to (walks `parent()` to the top).
52pub(crate) fn root_of<'a, N: Node<'a>>(n: N) -> N {
53    let mut cur = n;
54    while let Some(p) = cur.parent() {
55        cur = p;
56    }
57    cur
58}
59
60/// `child` axis: "contains the children of the context node".
61pub fn child<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
62    n.children()
63}
64
65/// `descendant` axis: "contains the descendants of the context node; ...
66/// thus the descendant axis never contains attribute or namespace nodes".
67pub fn descendant<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
68    descendant_vec(n).into_iter()
69}
70
71/// `descendant-or-self` axis: "contains the context node and the
72/// descendants of the context node".
73pub fn descendant_or_self<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
74    descendant_or_self_vec(n).into_iter()
75}
76
77/// `parent` axis: "contains the parent of the context node, if there is
78/// one".
79pub fn parent<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
80    n.parent().into_iter()
81}
82
83/// `ancestor` axis (reverse): "contains the ancestors of the context node;
84/// ... the parent of the context node and the parent's parent and so on;
85/// thus, the ancestor axis will always include the root node, unless the
86/// context node is the root node". Results are nearest-ancestor-first
87/// (reverse document order).
88pub fn ancestor<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
89    ancestor_vec(n).into_iter()
90}
91
92/// `ancestor-or-self` axis (reverse): "contains the context node and the
93/// ancestors of the context node". Results are context-node-first, then
94/// nearest ancestor first (reverse document order).
95pub fn ancestor_or_self<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
96    let mut out = vec![n];
97    out.extend(ancestor_vec(n));
98    out.into_iter()
99}
100
101/// The parent's children of `n`, or empty if `n` is an attribute/namespace
102/// node or has no parent — the shared guard for `following-sibling`/
103/// `preceding-sibling`.
104fn siblings_of<'a, N: Node<'a>>(n: N) -> Vec<N> {
105    if is_attribute_or_namespace(n.kind()) {
106        return Vec::new();
107    }
108    match n.parent() {
109        Some(parent) => parent.children().collect(),
110        None => Vec::new(),
111    }
112}
113
114/// `following-sibling` axis: "contains all the following siblings of the
115/// context node; if the context node is an attribute node or namespace
116/// node, the following-sibling axis is empty".
117pub fn following_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
118    let mut out = Vec::new();
119    let mut found = false;
120    for c in siblings_of(n) {
121        if found {
122            out.push(c);
123        } else if c == n {
124            found = true;
125        }
126    }
127    out.into_iter()
128}
129
130/// `preceding-sibling` axis (reverse): "contains all the preceding
131/// siblings of the context node; if the context node is an attribute node
132/// or namespace node, the preceding-sibling axis is empty". Results are
133/// nearest-preceding-sibling-first (reverse document order).
134pub fn preceding_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
135    let mut out = Vec::new();
136    for c in siblings_of(n) {
137        if c == n {
138            break;
139        }
140        out.push(c);
141    }
142    out.reverse();
143    out.into_iter()
144}
145
146/// The full document-order listing of `n`'s document, plus `n`'s index in
147/// it — the shared setup for `following`/`preceding`.
148fn document_order_listing<'a, N: Node<'a>>(n: N) -> (Vec<N>, usize) {
149    let all = descendant_or_self_vec(root_of(n));
150    let idx = all
151        .iter()
152        .position(|x| *x == n)
153        .expect("n must be reachable from its own document root via children()");
154    (all, idx)
155}
156
157/// All nodes after `n`'s entire subtree, in document order. Never contains
158/// attribute/namespace nodes, since the underlying preorder listing is
159/// built purely from `children()`.
160fn following_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
161    let (all, idx) = document_order_listing(n);
162    let subtree_len = 1 + descendant_vec(n).len();
163    all.into_iter().skip(idx + subtree_len).collect()
164}
165
166/// All nodes before `n` in document order, excluding `n`'s ancestors, in
167/// reverse document order (nearest-first).
168fn preceding_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
169    let (all, idx) = document_order_listing(n);
170    let ancestors = ancestor_vec(n);
171    let mut out: Vec<N> = all[..idx]
172        .iter()
173        .copied()
174        .filter(|c| !ancestors.contains(c))
175        .collect();
176    out.reverse();
177    out
178}
179
180/// The owner element of an attribute/namespace node — used by `following`/
181/// `preceding` to reduce a context node that is itself an attribute or
182/// namespace node to its owner element.
183fn owner_element<'a, N: Node<'a>>(n: N) -> N {
184    n.parent()
185        .expect("attribute/namespace node must have an owner element as parent")
186}
187
188/// `following` axis: "contains all nodes in the same document as the
189/// context node that are after the context node in document order,
190/// excluding any descendants and excluding attribute nodes and namespace
191/// nodes".
192///
193/// Attribute/namespace nodes are never children, so they never appear in
194/// the underlying preorder listing at all — but per document order they
195/// still sit *before* their owner element's children, so a context node
196/// that is itself an attribute/namespace node is handled by reducing to
197/// its owner element: `following(attr) == descendant(owner) ++
198/// following(owner)`.
199pub fn following<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
200    let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
201        let owner = owner_element(n);
202        let mut v = descendant_vec(owner);
203        v.extend(following_vec(owner));
204        v
205    } else {
206        following_vec(n)
207    };
208    out.into_iter()
209}
210
211/// `preceding` axis (reverse): "contains all nodes in the same document as
212/// the context node that are before the context node in document order,
213/// excluding any ancestors and excluding attribute nodes and namespace
214/// nodes".
215///
216/// A context node that is itself an attribute/namespace node reduces to
217/// its owner element: since attribute/namespace nodes are excluded from
218/// the result set regardless, and everything else before the attribute in
219/// document order is exactly everything before its owner element,
220/// `preceding(attr) == preceding(owner)`.
221pub fn preceding<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
222    let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
223        let owner = owner_element(n);
224        preceding_vec(owner)
225    } else {
226        preceding_vec(n)
227    };
228    out.into_iter()
229}
230
231/// Runs `collect` if `n` is an element, otherwise yields nothing — the
232/// shared "axis is empty unless the context node is an element" guard for
233/// `attribute`/`namespace`.
234fn collect_if_element<'a, N: Node<'a>>(n: N, collect: impl FnOnce(N) -> Vec<N>) -> Vec<N> {
235    if n.kind() == NodeKind::Element {
236        collect(n)
237    } else {
238        Vec::new()
239    }
240}
241
242/// `attribute` axis: "contains the attributes of the context node; the
243/// axis will be empty unless the context node is an element".
244pub fn attribute<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
245    collect_if_element(n, |n| n.attributes().collect()).into_iter()
246}
247
248/// `namespace` axis: "contains the namespace nodes of the context node;
249/// the axis will be empty unless the context node is an element".
250pub fn namespace<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
251    collect_if_element(n, |n| n.namespaces().collect()).into_iter()
252}
253
254/// `self` axis: "contains just the context node itself".
255pub fn self_axis<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
256    std::iter::once(n)
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::document::fixture::build;
263
264    #[test]
265    fn child_axis_on_nested_tree() {
266        let f = build();
267        let body = f.doc.node(f.body);
268        let got: Vec<_> = child(body).collect();
269        assert_eq!(
270            got,
271            vec![
272                f.doc.node(f.text0),
273                f.doc.node(f.span),
274                f.doc.node(f.comment),
275                f.doc.node(f.pi),
276                f.doc.node(f.text2),
277            ]
278        );
279    }
280
281    #[test]
282    fn descendant_axis_on_nested_tree() {
283        let f = build();
284        let html = f.doc.node(f.html);
285        let got: Vec<_> = descendant(html).collect();
286        assert_eq!(
287            got,
288            vec![
289                f.doc.node(f.body),
290                f.doc.node(f.text0),
291                f.doc.node(f.span),
292                f.doc.node(f.text1),
293                f.doc.node(f.comment),
294                f.doc.node(f.pi),
295                f.doc.node(f.text2),
296            ]
297        );
298    }
299
300    #[test]
301    fn descendant_or_self_axis_on_nested_tree() {
302        let f = build();
303        let span = f.doc.node(f.span);
304        let got: Vec<_> = descendant_or_self(span).collect();
305        assert_eq!(got, vec![span, f.doc.node(f.text1)]);
306    }
307
308    #[test]
309    fn parent_ancestor_ancestor_or_self_from_leaf_nearest_first() {
310        let f = build();
311        let text1 = f.doc.node(f.text1); // leaf: text inside span inside body inside html
312        let span = f.doc.node(f.span);
313        let body = f.doc.node(f.body);
314        let html = f.doc.node(f.html);
315        let root = f.doc.node(0);
316
317        assert_eq!(parent(text1).collect::<Vec<_>>(), vec![span]);
318        assert_eq!(
319            ancestor(text1).collect::<Vec<_>>(),
320            vec![span, body, html, root],
321            "ancestor must be nearest-parent-first, not root-first"
322        );
323        assert_eq!(
324            ancestor_or_self(text1).collect::<Vec<_>>(),
325            vec![text1, span, body, html, root]
326        );
327    }
328
329    #[test]
330    fn ancestor_of_root_is_empty() {
331        let f = build();
332        let root = f.doc.node(0);
333        assert_eq!(ancestor(root).collect::<Vec<_>>(), Vec::new());
334    }
335
336    #[test]
337    fn following_and_preceding_sibling_order_on_multiple_siblings() {
338        let f = build();
339        let comment = f.doc.node(f.comment);
340        // body's children: text0, span, comment, pi, text2
341        assert_eq!(
342            following_sibling(comment).collect::<Vec<_>>(),
343            vec![f.doc.node(f.pi), f.doc.node(f.text2)]
344        );
345        assert_eq!(
346            preceding_sibling(comment).collect::<Vec<_>>(),
347            vec![f.doc.node(f.span), f.doc.node(f.text0)],
348            "preceding-sibling must be nearest-sibling-first"
349        );
350    }
351
352    #[test]
353    fn following_excludes_attribute_and_namespace_nodes() {
354        let f = build();
355        let span = f.doc.node(f.span);
356        // span's own `data-x` attribute sits in document order strictly
357        // between span and text1, i.e. it is after span but is not among
358        // span's descendants (the descendant axis never contains attribute
359        // nodes) — a `following` that excludes only descendants without
360        // also checking node kind would wrongly include it here.
361        let got: Vec<_> = following(span).collect();
362        assert_eq!(
363            got,
364            vec![f.doc.node(f.comment), f.doc.node(f.pi), f.doc.node(f.text2)]
365        );
366        assert!(!got.contains(&f.doc.node(f.span_attr)));
367    }
368
369    #[test]
370    fn preceding_excludes_attribute_and_namespace_nodes() {
371        let f = build();
372        let text0 = f.doc.node(f.text0);
373        // A naive ancestor-exclusion-only `preceding` would wrongly include
374        // html's and body's attribute/namespace nodes here, since none of
375        // them are literal ancestors of text0.
376        let got: Vec<_> = preceding(text0).collect();
377        assert_eq!(got, Vec::new());
378    }
379
380    #[test]
381    fn preceding_excludes_a_preceding_siblings_own_attribute_node() {
382        let f = build();
383        let text1 = f.doc.node(f.text1);
384        // span's `data-x` attribute is before text1 in document order and
385        // is not an ancestor of text1 (span, its owner, is the ancestor;
386        // the attribute node itself is not on the ancestor chain) — a
387        // `preceding` that excludes only ancestors without also checking
388        // node kind would wrongly include it here.
389        let got: Vec<_> = preceding(text1).collect();
390        assert_eq!(got, vec![f.doc.node(f.text0)]);
391        assert!(!got.contains(&f.doc.node(f.span_attr)));
392    }
393
394    #[test]
395    fn preceding_reverse_document_order_excluding_ancestors() {
396        let f = build();
397        let text2 = f.doc.node(f.text2);
398        let got: Vec<_> = preceding(text2).collect();
399        assert_eq!(
400            got,
401            vec![
402                f.doc.node(f.pi),
403                f.doc.node(f.comment),
404                f.doc.node(f.text1),
405                f.doc.node(f.span),
406                f.doc.node(f.text0),
407            ],
408            "preceding must be reverse document order and exclude ancestors (body, html, root)"
409        );
410        assert!(!got.contains(&f.doc.node(f.body)));
411        assert!(!got.contains(&f.doc.node(f.html)));
412    }
413
414    #[test]
415    fn attribute_and_namespace_axes_on_element_with_both() {
416        let f = build();
417        let html = f.doc.node(f.html);
418        assert_eq!(
419            attribute(html).collect::<Vec<_>>(),
420            vec![f.doc.node(f.html_lang), f.doc.node(f.html_id)]
421        );
422        assert_eq!(
423            namespace(html).collect::<Vec<_>>(),
424            vec![f.doc.node(f.html_ns)]
425        );
426
427        let body = f.doc.node(f.body);
428        assert_eq!(
429            attribute(body).collect::<Vec<_>>(),
430            vec![f.doc.node(f.body_class)]
431        );
432        assert_eq!(
433            namespace(body).collect::<Vec<_>>(),
434            vec![f.doc.node(f.body_ns)]
435        );
436    }
437
438    #[test]
439    fn self_axis_is_trivial() {
440        let f = build();
441        let body = f.doc.node(f.body);
442        assert_eq!(self_axis(body).collect::<Vec<_>>(), vec![body]);
443    }
444
445    #[test]
446    fn attribute_node_has_no_attribute_namespace_or_sibling_axes() {
447        let f = build();
448        let lang = f.doc.node(f.html_lang);
449        assert_eq!(attribute(lang).collect::<Vec<_>>(), Vec::new());
450        assert_eq!(namespace(lang).collect::<Vec<_>>(), Vec::new());
451        assert_eq!(following_sibling(lang).collect::<Vec<_>>(), Vec::new());
452        assert_eq!(preceding_sibling(lang).collect::<Vec<_>>(), Vec::new());
453    }
454
455    #[test]
456    fn following_and_preceding_from_an_attribute_context_node() {
457        let f = build();
458        let lang = f.doc.node(f.html_lang);
459        // following(attr) reduces to descendant(owner) ++ following(owner);
460        // for html's `lang` attribute that's everything inside html.
461        assert_eq!(
462            following(lang).collect::<Vec<_>>(),
463            vec![
464                f.doc.node(f.body),
465                f.doc.node(f.text0),
466                f.doc.node(f.span),
467                f.doc.node(f.text1),
468                f.doc.node(f.comment),
469                f.doc.node(f.pi),
470                f.doc.node(f.text2),
471            ]
472        );
473        // preceding(attr) reduces to preceding(owner); html has nothing
474        // before it.
475        assert_eq!(preceding(lang).collect::<Vec<_>>(), Vec::new());
476    }
477}