use alloc::vec::Vec;
use super::ast::{Axis, NodeTest};
use crate::tree::{Document, NodeId, NodeKind};
pub(crate) fn for_each_on_axis(
doc: &Document,
node: NodeId,
axis: Axis,
f: &mut dyn FnMut(NodeId),
) {
match axis {
Axis::Child => doc.children(node).iter().copied().for_each(f),
Axis::SelfAxis => f(node),
Axis::Parent => {
if let Some(parent) = doc.parent(node) {
f(parent);
}
}
Axis::Attribute => {
doc.attribute_nodes(node).iter().copied().for_each(f);
}
Axis::Namespace => visit_in_scope_namespaces(doc, node, f),
_ => for_each_on_walking_axis(doc, node, axis, f),
}
}
fn for_each_on_walking_axis(
doc: &Document,
node: NodeId,
axis: Axis,
f: &mut dyn FnMut(NodeId),
) {
match axis {
Axis::Descendant | Axis::DescendantOrSelf => {
visit_descendants(doc, node, axis == Axis::DescendantOrSelf, f);
}
Axis::Ancestor | Axis::AncestorOrSelf => {
visit_ancestors(doc, node, axis == Axis::AncestorOrSelf, f);
}
Axis::FollowingSibling | Axis::PrecedingSibling => {
visit_siblings(doc, node, axis.is_reverse(), f);
}
Axis::Following => visit_following(doc, node, f),
Axis::Preceding => visit_preceding(doc, node, f),
_ => {}
}
}
fn visit_descendants(
doc: &Document,
node: NodeId,
include_self: bool,
f: &mut dyn FnMut(NodeId),
) {
if include_self {
f(node);
}
doc.descendants_of(node).for_each(f);
}
fn visit_ancestors(
doc: &Document,
node: NodeId,
include_self: bool,
f: &mut dyn FnMut(NodeId),
) {
if include_self {
f(node);
}
let mut at = doc.parent(node);
while let Some(parent) = at {
f(parent);
at = doc.parent(parent);
}
}
fn visit_siblings(
doc: &Document,
node: NodeId,
reverse: bool,
f: &mut dyn FnMut(NodeId),
) {
let Some((siblings, index)) = position_among_siblings(doc, node) else {
return;
};
if reverse {
siblings[..index].iter().rev().copied().for_each(f);
} else {
siblings[index + 1..].iter().copied().for_each(f);
}
}
fn position_among_siblings(
doc: &Document,
node: NodeId,
) -> Option<(&[NodeId], usize)> {
let siblings = doc.children(doc.parent(node)?);
let index = siblings.iter().position(|&s| s == node)?;
Some((siblings, index))
}
fn visit_following(doc: &Document, node: NodeId, f: &mut dyn FnMut(NodeId)) {
let mut at = node;
if is_attached(doc, node) {
let Some(owner) = doc.parent(node) else {
return;
};
doc.descendants_of(owner).for_each(&mut *f);
at = owner;
}
while let Some((siblings, index)) = position_among_siblings(doc, at) {
for &sibling in &siblings[index + 1..] {
f(sibling);
doc.descendants_of(sibling).for_each(&mut *f);
}
at = doc.parent(at).unwrap_or(at);
}
}
fn visit_preceding(doc: &Document, node: NodeId, f: &mut dyn FnMut(NodeId)) {
let mut at = node;
if is_attached(doc, node) {
let Some(owner) = doc.parent(node) else {
return;
};
at = owner;
}
let mut stack = Vec::new();
while let Some((siblings, index)) = position_among_siblings(doc, at) {
for &sibling in siblings[..index].iter().rev() {
visit_subtree_reversed(doc, sibling, &mut stack, f);
}
at = doc.parent(at).unwrap_or(at);
}
}
fn visit_subtree_reversed(
doc: &Document,
root: NodeId,
stack: &mut Vec<(NodeId, bool)>,
f: &mut dyn FnMut(NodeId),
) {
stack.push((root, false));
while let Some((node, expanded)) = stack.pop() {
if expanded {
f(node);
continue;
}
stack.push((node, true));
stack.extend(doc.children(node).iter().map(|&child| (child, false)));
}
}
fn visit_in_scope_namespaces(
doc: &Document,
node: NodeId,
f: &mut dyn FnMut(NodeId),
) {
if !doc.is_element(node) {
return;
}
let mut seen: Vec<&str> = Vec::new();
let mut at = Some(node);
while let Some(current) = at {
for &ns in doc.namespace_nodes(current) {
let Some(NodeKind::Namespace { prefix, uri }) = doc.kind(ns) else {
continue;
};
if seen.contains(&prefix) {
continue;
}
seen.push(prefix);
if !uri.is_empty() {
f(ns);
}
}
at = doc.parent(current);
}
}
pub(crate) fn following_union(
doc: &Document,
context: &[NodeId],
test: &NodeTest,
) -> Vec<NodeId> {
let marks = Marks::new(context);
let mut out = Vec::new();
let mut started = 0_usize;
let mut among_ancestors = 0_usize;
let mut stack = alloc::vec![(doc.root(), false)];
while let Some((node, leaving)) = stack.pop() {
let is_context = usize::from(marks.has(node));
if leaving {
among_ancestors -= is_context;
continue;
}
if started > among_ancestors
&& test_matches(doc, node, test, Axis::Following)
{
out.push(node);
}
started += is_context + marks.attached_to(doc, node);
among_ancestors += is_context;
stack.push((node, true));
stack.extend(doc.children(node).iter().rev().map(|&c| (c, false)));
}
out
}
pub(crate) fn preceding_union(
doc: &Document,
context: &[NodeId],
test: &NodeTest,
) -> Vec<NodeId> {
let marks = Marks::new(context);
let mut out = Vec::new();
let mut started = 0_usize;
let mut stack = alloc::vec![(doc.root(), false)];
while let Some((node, leaving)) = stack.pop() {
if leaving {
if context.len() > started
&& test_matches(doc, node, test, Axis::Preceding)
{
out.push(node);
}
continue;
}
started += usize::from(marks.has(node)) + marks.attached_to(doc, node);
stack.push((node, true));
stack.extend(doc.children(node).iter().rev().map(|&c| (c, false)));
}
out
}
struct Marks {
slots: Vec<bool>,
}
impl Marks {
fn new(context: &[NodeId]) -> Self {
let len = context.iter().map(|id| id.index() + 1).max().unwrap_or(0);
let mut slots = alloc::vec![false; len];
for id in context {
slots[id.index()] = true;
}
Self { slots }
}
fn has(&self, id: NodeId) -> bool {
self.slots.get(id.index()).copied().unwrap_or(false)
}
fn attached_to(&self, doc: &Document, node: NodeId) -> usize {
doc.attribute_nodes(node)
.iter()
.chain(doc.namespace_nodes(node))
.filter(|&&id| self.has(id))
.count()
}
}
fn is_attached(doc: &Document, node: NodeId) -> bool {
matches!(
doc.kind(node),
Some(NodeKind::Attr(_) | NodeKind::Namespace { .. })
)
}
pub(crate) fn test_matches(
doc: &Document,
node: NodeId,
test: &NodeTest,
axis: Axis,
) -> bool {
match axis {
Axis::Attribute => attribute_test_matches(doc, node, test),
Axis::Namespace => namespace_test_matches(doc, node, test),
_ => element_test_matches(doc, node, test),
}
}
fn attribute_test_matches(
doc: &Document,
node: NodeId,
test: &NodeTest,
) -> bool {
let Some(NodeKind::Attr(attribute)) = doc.kind(node) else {
return false;
};
match test {
NodeTest::Wildcard | NodeTest::Any => true,
NodeTest::Name { namespace, local } => {
doc.name(attribute.name).is_some_and(|name| {
&name.local == local
&& name.namespace.as_deref() == namespace.as_deref()
})
}
NodeTest::AnyNamespace { local } => doc
.name(attribute.name)
.is_some_and(|name| &name.local == local),
_ => false,
}
}
fn namespace_test_matches(
doc: &Document,
node: NodeId,
test: &NodeTest,
) -> bool {
let Some(NodeKind::Namespace { prefix, .. }) = doc.kind(node) else {
return false;
};
match test {
NodeTest::Wildcard | NodeTest::Any => true,
NodeTest::Name { namespace, local } => {
namespace.is_none() && local == prefix
}
NodeTest::AnyNamespace { local } => local == prefix,
_ => false,
}
}
fn element_test_matches(doc: &Document, node: NodeId, test: &NodeTest) -> bool {
match test {
NodeTest::Any => true,
NodeTest::Wildcard => doc.is_element(node),
NodeTest::Name { namespace, local } => doc
.element_name(node)
.is_some_and(|e| e.local == *local && e.namespace == *namespace),
NodeTest::AnyNamespace { local } => {
doc.element_name(node).is_some_and(|e| e.local == *local)
}
kind => kind_test_matches(doc.kind(node), kind),
}
}
fn kind_test_matches(kind: Option<NodeKind<'_>>, test: &NodeTest) -> bool {
match (test, kind) {
(NodeTest::Text, Some(NodeKind::Text(_)))
| (NodeTest::Comment, Some(NodeKind::Comment(_))) => true,
(
NodeTest::ProcessingInstruction(want),
Some(NodeKind::ProcessingInstruction { target, .. }),
) => want.as_ref().is_none_or(|w| w == target),
_ => false,
}
}