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).
52fn 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/// `following-sibling` axis: "contains all the following siblings of the
102/// context node; if the context node is an attribute node or namespace
103/// node, the following-sibling axis is empty".
104pub fn following_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
105    let mut out = Vec::new();
106    if !is_attribute_or_namespace(n.kind())
107        && let Some(parent) = n.parent()
108    {
109        let mut found = false;
110        for c in parent.children() {
111            if found {
112                out.push(c);
113            } else if c == n {
114                found = true;
115            }
116        }
117    }
118    out.into_iter()
119}
120
121/// `preceding-sibling` axis (reverse): "contains all the preceding
122/// siblings of the context node; if the context node is an attribute node
123/// or namespace node, the preceding-sibling axis is empty". Results are
124/// nearest-preceding-sibling-first (reverse document order).
125pub fn preceding_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
126    let mut out = Vec::new();
127    if !is_attribute_or_namespace(n.kind())
128        && let Some(parent) = n.parent()
129    {
130        for c in parent.children() {
131            if c == n {
132                break;
133            }
134            out.push(c);
135        }
136    }
137    out.reverse();
138    out.into_iter()
139}
140
141/// All nodes after `n`'s entire subtree, in document order. Never contains
142/// attribute/namespace nodes, since the underlying preorder listing is
143/// built purely from `children()`.
144fn following_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
145    let all = descendant_or_self_vec(root_of(n));
146    let idx = all
147        .iter()
148        .position(|x| *x == n)
149        .expect("n must be reachable from its own document root via children()");
150    let subtree_len = 1 + descendant_vec(n).len();
151    all.into_iter().skip(idx + subtree_len).collect()
152}
153
154/// All nodes before `n` in document order, excluding `n`'s ancestors, in
155/// reverse document order (nearest-first).
156fn preceding_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
157    let all = descendant_or_self_vec(root_of(n));
158    let idx = all
159        .iter()
160        .position(|x| *x == n)
161        .expect("n must be reachable from its own document root via children()");
162    let ancestors = ancestor_vec(n);
163    let mut out: Vec<N> = all[..idx]
164        .iter()
165        .copied()
166        .filter(|c| !ancestors.contains(c))
167        .collect();
168    out.reverse();
169    out
170}
171
172/// `following` axis: "contains all nodes in the same document as the
173/// context node that are after the context node in document order,
174/// excluding any descendants and excluding attribute nodes and namespace
175/// nodes".
176///
177/// Attribute/namespace nodes are never children, so they never appear in
178/// the underlying preorder listing at all — but per document order they
179/// still sit *before* their owner element's children, so a context node
180/// that is itself an attribute/namespace node is handled by reducing to
181/// its owner element: `following(attr) == descendant(owner) ++
182/// following(owner)`.
183pub fn following<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
184    let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
185        let owner = n
186            .parent()
187            .expect("attribute/namespace node must have an owner element as parent");
188        let mut v = descendant_vec(owner);
189        v.extend(following_vec(owner));
190        v
191    } else {
192        following_vec(n)
193    };
194    out.into_iter()
195}
196
197/// `preceding` axis (reverse): "contains all nodes in the same document as
198/// the context node that are before the context node in document order,
199/// excluding any ancestors and excluding attribute nodes and namespace
200/// nodes".
201///
202/// A context node that is itself an attribute/namespace node reduces to
203/// its owner element: since attribute/namespace nodes are excluded from
204/// the result set regardless, and everything else before the attribute in
205/// document order is exactly everything before its owner element,
206/// `preceding(attr) == preceding(owner)`.
207pub fn preceding<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
208    let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
209        let owner = n
210            .parent()
211            .expect("attribute/namespace node must have an owner element as parent");
212        preceding_vec(owner)
213    } else {
214        preceding_vec(n)
215    };
216    out.into_iter()
217}
218
219/// `attribute` axis: "contains the attributes of the context node; the
220/// axis will be empty unless the context node is an element".
221pub fn attribute<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
222    let out: Vec<N> = if n.kind() == NodeKind::Element {
223        n.attributes().collect()
224    } else {
225        Vec::new()
226    };
227    out.into_iter()
228}
229
230/// `namespace` axis: "contains the namespace nodes of the context node;
231/// the axis will be empty unless the context node is an element".
232pub fn namespace<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
233    let out: Vec<N> = if n.kind() == NodeKind::Element {
234        n.namespaces().collect()
235    } else {
236        Vec::new()
237    };
238    out.into_iter()
239}
240
241/// `self` axis: "contains just the context node itself".
242pub fn self_axis<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
243    std::iter::once(n)
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::document::fixture::build;
250
251    #[test]
252    fn child_axis_on_nested_tree() {
253        let f = build();
254        let body = f.doc.node(f.body);
255        let got: Vec<_> = child(body).collect();
256        assert_eq!(
257            got,
258            vec![
259                f.doc.node(f.text0),
260                f.doc.node(f.span),
261                f.doc.node(f.comment),
262                f.doc.node(f.pi),
263                f.doc.node(f.text2),
264            ]
265        );
266    }
267
268    #[test]
269    fn descendant_axis_on_nested_tree() {
270        let f = build();
271        let html = f.doc.node(f.html);
272        let got: Vec<_> = descendant(html).collect();
273        assert_eq!(
274            got,
275            vec![
276                f.doc.node(f.body),
277                f.doc.node(f.text0),
278                f.doc.node(f.span),
279                f.doc.node(f.text1),
280                f.doc.node(f.comment),
281                f.doc.node(f.pi),
282                f.doc.node(f.text2),
283            ]
284        );
285    }
286
287    #[test]
288    fn descendant_or_self_axis_on_nested_tree() {
289        let f = build();
290        let span = f.doc.node(f.span);
291        let got: Vec<_> = descendant_or_self(span).collect();
292        assert_eq!(got, vec![span, f.doc.node(f.text1)]);
293    }
294
295    #[test]
296    fn parent_ancestor_ancestor_or_self_from_leaf_nearest_first() {
297        let f = build();
298        let text1 = f.doc.node(f.text1); // leaf: text inside span inside body inside html
299        let span = f.doc.node(f.span);
300        let body = f.doc.node(f.body);
301        let html = f.doc.node(f.html);
302        let root = f.doc.node(0);
303
304        assert_eq!(parent(text1).collect::<Vec<_>>(), vec![span]);
305        assert_eq!(
306            ancestor(text1).collect::<Vec<_>>(),
307            vec![span, body, html, root],
308            "ancestor must be nearest-parent-first, not root-first"
309        );
310        assert_eq!(
311            ancestor_or_self(text1).collect::<Vec<_>>(),
312            vec![text1, span, body, html, root]
313        );
314    }
315
316    #[test]
317    fn ancestor_of_root_is_empty() {
318        let f = build();
319        let root = f.doc.node(0);
320        assert_eq!(ancestor(root).collect::<Vec<_>>(), Vec::new());
321    }
322
323    #[test]
324    fn following_and_preceding_sibling_order_on_multiple_siblings() {
325        let f = build();
326        let comment = f.doc.node(f.comment);
327        // body's children: text0, span, comment, pi, text2
328        assert_eq!(
329            following_sibling(comment).collect::<Vec<_>>(),
330            vec![f.doc.node(f.pi), f.doc.node(f.text2)]
331        );
332        assert_eq!(
333            preceding_sibling(comment).collect::<Vec<_>>(),
334            vec![f.doc.node(f.span), f.doc.node(f.text0)],
335            "preceding-sibling must be nearest-sibling-first"
336        );
337    }
338
339    #[test]
340    fn following_excludes_attribute_and_namespace_nodes() {
341        let f = build();
342        let span = f.doc.node(f.span);
343        // span's own `data-x` attribute sits in document order strictly
344        // between span and text1, i.e. it is after span but is not among
345        // span's descendants (the descendant axis never contains attribute
346        // nodes) — a `following` that excludes only descendants without
347        // also checking node kind would wrongly include it here.
348        let got: Vec<_> = following(span).collect();
349        assert_eq!(
350            got,
351            vec![f.doc.node(f.comment), f.doc.node(f.pi), f.doc.node(f.text2)]
352        );
353        assert!(!got.contains(&f.doc.node(f.span_attr)));
354    }
355
356    #[test]
357    fn preceding_excludes_attribute_and_namespace_nodes() {
358        let f = build();
359        let text0 = f.doc.node(f.text0);
360        // A naive ancestor-exclusion-only `preceding` would wrongly include
361        // html's and body's attribute/namespace nodes here, since none of
362        // them are literal ancestors of text0.
363        let got: Vec<_> = preceding(text0).collect();
364        assert_eq!(got, Vec::new());
365    }
366
367    #[test]
368    fn preceding_excludes_a_preceding_siblings_own_attribute_node() {
369        let f = build();
370        let text1 = f.doc.node(f.text1);
371        // span's `data-x` attribute is before text1 in document order and
372        // is not an ancestor of text1 (span, its owner, is the ancestor;
373        // the attribute node itself is not on the ancestor chain) — a
374        // `preceding` that excludes only ancestors without also checking
375        // node kind would wrongly include it here.
376        let got: Vec<_> = preceding(text1).collect();
377        assert_eq!(got, vec![f.doc.node(f.text0)]);
378        assert!(!got.contains(&f.doc.node(f.span_attr)));
379    }
380
381    #[test]
382    fn preceding_reverse_document_order_excluding_ancestors() {
383        let f = build();
384        let text2 = f.doc.node(f.text2);
385        let got: Vec<_> = preceding(text2).collect();
386        assert_eq!(
387            got,
388            vec![
389                f.doc.node(f.pi),
390                f.doc.node(f.comment),
391                f.doc.node(f.text1),
392                f.doc.node(f.span),
393                f.doc.node(f.text0),
394            ],
395            "preceding must be reverse document order and exclude ancestors (body, html, root)"
396        );
397        assert!(!got.contains(&f.doc.node(f.body)));
398        assert!(!got.contains(&f.doc.node(f.html)));
399    }
400
401    #[test]
402    fn attribute_and_namespace_axes_on_element_with_both() {
403        let f = build();
404        let html = f.doc.node(f.html);
405        assert_eq!(
406            attribute(html).collect::<Vec<_>>(),
407            vec![f.doc.node(f.html_lang), f.doc.node(f.html_id)]
408        );
409        assert_eq!(
410            namespace(html).collect::<Vec<_>>(),
411            vec![f.doc.node(f.html_ns)]
412        );
413
414        let body = f.doc.node(f.body);
415        assert_eq!(
416            attribute(body).collect::<Vec<_>>(),
417            vec![f.doc.node(f.body_class)]
418        );
419        assert_eq!(
420            namespace(body).collect::<Vec<_>>(),
421            vec![f.doc.node(f.body_ns)]
422        );
423    }
424
425    #[test]
426    fn self_axis_is_trivial() {
427        let f = build();
428        let body = f.doc.node(f.body);
429        assert_eq!(self_axis(body).collect::<Vec<_>>(), vec![body]);
430    }
431
432    #[test]
433    fn attribute_node_has_no_attribute_namespace_or_sibling_axes() {
434        let f = build();
435        let lang = f.doc.node(f.html_lang);
436        assert_eq!(attribute(lang).collect::<Vec<_>>(), Vec::new());
437        assert_eq!(namespace(lang).collect::<Vec<_>>(), Vec::new());
438        assert_eq!(following_sibling(lang).collect::<Vec<_>>(), Vec::new());
439        assert_eq!(preceding_sibling(lang).collect::<Vec<_>>(), Vec::new());
440    }
441
442    #[test]
443    fn following_and_preceding_from_an_attribute_context_node() {
444        let f = build();
445        let lang = f.doc.node(f.html_lang);
446        // following(attr) reduces to descendant(owner) ++ following(owner);
447        // for html's `lang` attribute that's everything inside html.
448        assert_eq!(
449            following(lang).collect::<Vec<_>>(),
450            vec![
451                f.doc.node(f.body),
452                f.doc.node(f.text0),
453                f.doc.node(f.span),
454                f.doc.node(f.text1),
455                f.doc.node(f.comment),
456                f.doc.node(f.pi),
457                f.doc.node(f.text2),
458            ]
459        );
460        // preceding(attr) reduces to preceding(owner); html has nothing
461        // before it.
462        assert_eq!(preceding(lang).collect::<Vec<_>>(), Vec::new());
463    }
464}