use crate::document::{Node, NodeKind};
fn is_attribute_or_namespace(kind: NodeKind) -> bool {
matches!(kind, NodeKind::Attribute | NodeKind::Namespace)
}
fn ancestor_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
let mut out = Vec::new();
let mut cur = n.parent();
while let Some(p) = cur {
out.push(p);
cur = p.parent();
}
out
}
fn descendant_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
let mut out = Vec::new();
for c in n.children() {
out.push(c);
out.extend(descendant_vec(c));
}
out
}
fn descendant_or_self_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
let mut out = vec![n];
out.extend(descendant_vec(n));
out
}
pub(crate) fn root_of<'a, N: Node<'a>>(n: N) -> N {
let mut cur = n;
while let Some(p) = cur.parent() {
cur = p;
}
cur
}
pub fn child<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
n.children()
}
pub fn descendant<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
descendant_vec(n).into_iter()
}
pub fn descendant_or_self<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
descendant_or_self_vec(n).into_iter()
}
pub fn parent<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
n.parent().into_iter()
}
pub fn ancestor<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
ancestor_vec(n).into_iter()
}
pub fn ancestor_or_self<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
let mut out = vec![n];
out.extend(ancestor_vec(n));
out.into_iter()
}
fn siblings_of<'a, N: Node<'a>>(n: N) -> Vec<N> {
if is_attribute_or_namespace(n.kind()) {
return Vec::new();
}
match n.parent() {
Some(parent) => parent.children().collect(),
None => Vec::new(),
}
}
pub fn following_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
let mut out = Vec::new();
let mut found = false;
for c in siblings_of(n) {
if found {
out.push(c);
} else if c == n {
found = true;
}
}
out.into_iter()
}
pub fn preceding_sibling<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
let mut out = Vec::new();
for c in siblings_of(n) {
if c == n {
break;
}
out.push(c);
}
out.reverse();
out.into_iter()
}
fn document_order_listing<'a, N: Node<'a>>(n: N) -> (Vec<N>, usize) {
let all = descendant_or_self_vec(root_of(n));
let idx = all
.iter()
.position(|x| *x == n)
.expect("n must be reachable from its own document root via children()");
(all, idx)
}
fn following_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
let (all, idx) = document_order_listing(n);
let subtree_len = 1 + descendant_vec(n).len();
all.into_iter().skip(idx + subtree_len).collect()
}
fn preceding_vec<'a, N: Node<'a>>(n: N) -> Vec<N> {
let (all, idx) = document_order_listing(n);
let ancestors = ancestor_vec(n);
let mut out: Vec<N> = all[..idx]
.iter()
.copied()
.filter(|c| !ancestors.contains(c))
.collect();
out.reverse();
out
}
fn owner_element<'a, N: Node<'a>>(n: N) -> N {
n.parent()
.expect("attribute/namespace node must have an owner element as parent")
}
pub fn following<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
let owner = owner_element(n);
let mut v = descendant_vec(owner);
v.extend(following_vec(owner));
v
} else {
following_vec(n)
};
out.into_iter()
}
pub fn preceding<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
let out: Vec<N> = if is_attribute_or_namespace(n.kind()) {
let owner = owner_element(n);
preceding_vec(owner)
} else {
preceding_vec(n)
};
out.into_iter()
}
fn collect_if_element<'a, N: Node<'a>>(n: N, collect: impl FnOnce(N) -> Vec<N>) -> Vec<N> {
if n.kind() == NodeKind::Element {
collect(n)
} else {
Vec::new()
}
}
pub fn attribute<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
collect_if_element(n, |n| n.attributes().collect()).into_iter()
}
pub fn namespace<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
collect_if_element(n, |n| n.namespaces().collect()).into_iter()
}
pub fn self_axis<'a, N: Node<'a>>(n: N) -> impl Iterator<Item = N> + 'a {
std::iter::once(n)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::document::fixture::build;
#[test]
fn child_axis_on_nested_tree() {
let f = build();
let body = f.doc.node(f.body);
let got: Vec<_> = child(body).collect();
assert_eq!(
got,
vec![
f.doc.node(f.text0),
f.doc.node(f.span),
f.doc.node(f.comment),
f.doc.node(f.pi),
f.doc.node(f.text2),
]
);
}
#[test]
fn descendant_axis_on_nested_tree() {
let f = build();
let html = f.doc.node(f.html);
let got: Vec<_> = descendant(html).collect();
assert_eq!(
got,
vec![
f.doc.node(f.body),
f.doc.node(f.text0),
f.doc.node(f.span),
f.doc.node(f.text1),
f.doc.node(f.comment),
f.doc.node(f.pi),
f.doc.node(f.text2),
]
);
}
#[test]
fn descendant_or_self_axis_on_nested_tree() {
let f = build();
let span = f.doc.node(f.span);
let got: Vec<_> = descendant_or_self(span).collect();
assert_eq!(got, vec![span, f.doc.node(f.text1)]);
}
#[test]
fn parent_ancestor_ancestor_or_self_from_leaf_nearest_first() {
let f = build();
let text1 = f.doc.node(f.text1); let span = f.doc.node(f.span);
let body = f.doc.node(f.body);
let html = f.doc.node(f.html);
let root = f.doc.node(0);
assert_eq!(parent(text1).collect::<Vec<_>>(), vec![span]);
assert_eq!(
ancestor(text1).collect::<Vec<_>>(),
vec![span, body, html, root],
"ancestor must be nearest-parent-first, not root-first"
);
assert_eq!(
ancestor_or_self(text1).collect::<Vec<_>>(),
vec![text1, span, body, html, root]
);
}
#[test]
fn ancestor_of_root_is_empty() {
let f = build();
let root = f.doc.node(0);
assert_eq!(ancestor(root).collect::<Vec<_>>(), Vec::new());
}
#[test]
fn following_and_preceding_sibling_order_on_multiple_siblings() {
let f = build();
let comment = f.doc.node(f.comment);
assert_eq!(
following_sibling(comment).collect::<Vec<_>>(),
vec![f.doc.node(f.pi), f.doc.node(f.text2)]
);
assert_eq!(
preceding_sibling(comment).collect::<Vec<_>>(),
vec![f.doc.node(f.span), f.doc.node(f.text0)],
"preceding-sibling must be nearest-sibling-first"
);
}
#[test]
fn following_excludes_attribute_and_namespace_nodes() {
let f = build();
let span = f.doc.node(f.span);
let got: Vec<_> = following(span).collect();
assert_eq!(
got,
vec![f.doc.node(f.comment), f.doc.node(f.pi), f.doc.node(f.text2)]
);
assert!(!got.contains(&f.doc.node(f.span_attr)));
}
#[test]
fn preceding_excludes_attribute_and_namespace_nodes() {
let f = build();
let text0 = f.doc.node(f.text0);
let got: Vec<_> = preceding(text0).collect();
assert_eq!(got, Vec::new());
}
#[test]
fn preceding_excludes_a_preceding_siblings_own_attribute_node() {
let f = build();
let text1 = f.doc.node(f.text1);
let got: Vec<_> = preceding(text1).collect();
assert_eq!(got, vec![f.doc.node(f.text0)]);
assert!(!got.contains(&f.doc.node(f.span_attr)));
}
#[test]
fn preceding_reverse_document_order_excluding_ancestors() {
let f = build();
let text2 = f.doc.node(f.text2);
let got: Vec<_> = preceding(text2).collect();
assert_eq!(
got,
vec![
f.doc.node(f.pi),
f.doc.node(f.comment),
f.doc.node(f.text1),
f.doc.node(f.span),
f.doc.node(f.text0),
],
"preceding must be reverse document order and exclude ancestors (body, html, root)"
);
assert!(!got.contains(&f.doc.node(f.body)));
assert!(!got.contains(&f.doc.node(f.html)));
}
#[test]
fn attribute_and_namespace_axes_on_element_with_both() {
let f = build();
let html = f.doc.node(f.html);
assert_eq!(
attribute(html).collect::<Vec<_>>(),
vec![f.doc.node(f.html_lang), f.doc.node(f.html_id)]
);
assert_eq!(
namespace(html).collect::<Vec<_>>(),
vec![f.doc.node(f.html_ns)]
);
let body = f.doc.node(f.body);
assert_eq!(
attribute(body).collect::<Vec<_>>(),
vec![f.doc.node(f.body_class)]
);
assert_eq!(
namespace(body).collect::<Vec<_>>(),
vec![f.doc.node(f.body_ns)]
);
}
#[test]
fn self_axis_is_trivial() {
let f = build();
let body = f.doc.node(f.body);
assert_eq!(self_axis(body).collect::<Vec<_>>(), vec![body]);
}
#[test]
fn attribute_node_has_no_attribute_namespace_or_sibling_axes() {
let f = build();
let lang = f.doc.node(f.html_lang);
assert_eq!(attribute(lang).collect::<Vec<_>>(), Vec::new());
assert_eq!(namespace(lang).collect::<Vec<_>>(), Vec::new());
assert_eq!(following_sibling(lang).collect::<Vec<_>>(), Vec::new());
assert_eq!(preceding_sibling(lang).collect::<Vec<_>>(), Vec::new());
}
#[test]
fn following_and_preceding_from_an_attribute_context_node() {
let f = build();
let lang = f.doc.node(f.html_lang);
assert_eq!(
following(lang).collect::<Vec<_>>(),
vec![
f.doc.node(f.body),
f.doc.node(f.text0),
f.doc.node(f.span),
f.doc.node(f.text1),
f.doc.node(f.comment),
f.doc.node(f.pi),
f.doc.node(f.text2),
]
);
assert_eq!(preceding(lang).collect::<Vec<_>>(), Vec::new());
}
}