use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub u32);
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeKind {
Element,
Text,
}
pub trait Node<'a>: Copy + Eq + 'a {
fn id(self) -> NodeId;
fn kind(self) -> NodeKind;
fn parent(self) -> Option<Self>;
fn children(self) -> impl Iterator<Item = Self> + 'a;
fn local_name(self) -> &'a str;
fn attributes(self) -> impl Iterator<Item = (&'a str, &'a str)> + 'a;
fn text(self) -> &'a str;
fn attr(self, name: &str) -> Option<&'a str> {
self.attributes().find(|(k, _)| *k == name).map(|(_, v)| v)
}
fn has_attr(self, name: &str) -> bool {
self.attr(name).is_some()
}
fn is_element(self, local_name: &str) -> bool {
self.kind() == NodeKind::Element && self.local_name() == local_name
}
fn document_order(self, other: Self) -> Ordering {
self.id().cmp(&other.id())
}
}
pub trait Document {
type N<'a>: Node<'a>
where
Self: 'a;
fn root(&self) -> Self::N<'_>;
fn node_count(&self) -> Option<usize> {
None
}
}
pub fn descendants<'a, N: Node<'a>>(node: N) -> impl Iterator<Item = N> + 'a {
let mut stack: Vec<N> = node.children().collect();
stack.reverse();
std::iter::from_fn(move || {
let n = stack.pop()?;
let mut kids: Vec<N> = n.children().collect();
kids.reverse();
stack.extend(kids);
Some(n)
})
}
pub fn self_and_descendants<'a, N: Node<'a>>(node: N) -> impl Iterator<Item = N> + 'a {
std::iter::once(node).chain(descendants(node))
}
pub fn ancestors<'a, N: Node<'a>>(node: N) -> impl Iterator<Item = N> + 'a {
let mut cur = node.parent();
std::iter::from_fn(move || {
let n = cur?;
cur = n.parent();
Some(n)
})
}
pub fn closest<'a, N: Node<'a>>(node: N, local_name: &str) -> Option<N> {
std::iter::once(node)
.chain(ancestors(node))
.find(|n| n.is_element(local_name))
}
pub fn subtree_text<'a, N: Node<'a>>(node: N) -> String {
let mut out = String::new();
for n in self_and_descendants(node) {
if n.kind() == NodeKind::Text {
out.push_str(n.text());
}
}
out
}
pub fn has_text<'a, N: Node<'a>>(node: N) -> bool {
self_and_descendants(node).any(|n| n.kind() == NodeKind::Text && !n.text().trim().is_empty())
}
pub fn elements<'a, D: Document>(doc: &'a D) -> impl Iterator<Item = D::N<'a>> + 'a {
self_and_descendants(doc.root()).filter(|n| n.kind() == NodeKind::Element)
}