use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
mod attributes;
mod blocks;
mod builder;
mod compact;
mod error;
mod mutate;
#[cfg(test)]
mod mutation_tests;
mod names;
pub use builder::Builder;
pub use error::NodeError;
use blocks::Caps;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId {
pub(crate) index: u32,
pub(crate) generation: u32,
}
impl NodeId {
pub(crate) const UNUSED: Self = Self {
index: u32::MAX,
generation: 1,
};
#[must_use]
pub const fn index(self) -> usize {
self.index as usize
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExpandedName {
pub namespace: Option<String>,
pub local: String,
}
impl ExpandedName {
#[must_use]
pub fn local(local: impl Into<String>) -> Self {
Self {
namespace: None,
local: local.into(),
}
}
#[must_use]
pub fn qualified(
namespace: impl Into<String>,
local: impl Into<String>,
) -> Self {
Self {
namespace: Some(namespace.into()),
local: local.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute<'a> {
pub name: NameId,
pub value: &'a str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeKind<'a> {
Root,
Element {
name: NameId,
attributes: (u32, u32),
namespaces: (u32, u32),
},
Attr(Attribute<'a>),
Text(&'a str),
Comment(&'a str),
Namespace {
prefix: &'a str,
uri: &'a str,
},
ProcessingInstruction {
target: &'a str,
data: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Chars {
Span(u32, u32),
Expanded(u32),
}
impl Chars {
pub(crate) const EMPTY: Self = Self::Span(0, 0);
}
#[derive(Debug, Clone)]
pub(crate) enum NodeData {
Root,
Element {
name: NameId,
attributes: (u32, u32),
namespaces: (u32, u32),
},
Attr {
name: NameId,
value: Chars,
},
Text(Chars),
Comment(Chars),
Namespace {
prefix: Chars,
uri: Chars,
},
ProcessingInstruction {
target: Chars,
data: Chars,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NameId(pub(crate) u32);
#[derive(Debug, Clone)]
pub(crate) struct Node {
pub(crate) data: NodeData,
pub(crate) parent: Option<NodeId>,
pub(crate) children: (u32, u32),
}
pub(crate) const NO_SPAN: (u32, u32) = (u32::MAX, u32::MAX);
#[derive(Debug, Clone)]
pub struct Document {
pub(crate) input: String,
pub(crate) expanded: Vec<String>,
pub(crate) free_expanded: Vec<u32>,
pub(crate) nodes: Vec<Node>,
pub(crate) generations: Vec<u32>,
pub(crate) spans: Vec<(u32, u32)>,
pub(crate) free_slots: Vec<u32>,
pub(crate) names: Vec<ExpandedName>,
pub(crate) name_prefixes: Vec<Option<String>>,
pub(crate) attr_ids: Vec<NodeId>,
pub(crate) child_ids: Vec<NodeId>,
pub(crate) ns_ids: Vec<NodeId>,
pub(crate) child_caps: Caps,
pub(crate) attr_caps: Caps,
pub(crate) ns_caps: Caps,
pub(crate) scratch: Vec<NodeId>,
pub(crate) ids: BTreeMap<String, NodeId>,
pub(crate) order: Vec<u32>,
pub(crate) order_dirty: bool,
}
impl Document {
pub(crate) const fn placeholder() -> Self {
Self {
input: String::new(),
expanded: Vec::new(),
free_expanded: Vec::new(),
generations: Vec::new(),
spans: Vec::new(),
nodes: Vec::new(),
free_slots: Vec::new(),
names: Vec::new(),
name_prefixes: Vec::new(),
attr_ids: Vec::new(),
child_ids: Vec::new(),
ns_ids: Vec::new(),
child_caps: Caps::new(),
attr_caps: Caps::new(),
ns_caps: Caps::new(),
scratch: Vec::new(),
ids: BTreeMap::new(),
order: Vec::new(),
order_dirty: false,
}
}
pub(crate) fn with_capacity(nodes: usize) -> Self {
let mut v = Vec::with_capacity(nodes.saturating_add(1));
v.push(Node {
data: NodeData::Root,
parent: None,
children: (0, 0),
});
let mut generations = Vec::with_capacity(nodes.saturating_add(1));
generations.push(0);
let mut spans = Vec::with_capacity(nodes.saturating_add(1));
spans.push(NO_SPAN);
Self {
generations,
spans,
nodes: v,
child_ids: Vec::with_capacity(nodes),
..Self::placeholder()
}
}
#[must_use]
pub const fn root(&self) -> NodeId {
NodeId {
index: 0,
generation: 0,
}
}
pub(crate) fn push(&mut self, data: NodeData, parent: NodeId) -> NodeId {
let id = self.mint();
self.nodes.push(Node {
data,
parent: Some(parent),
children: (0, 0),
});
self.scratch.push(id);
id
}
pub(crate) fn scratch_mark(&self) -> usize {
self.scratch.len()
}
pub(crate) fn finish_children(&mut self, parent: NodeId, mark: usize) {
let start = self.child_ids.len();
self.child_ids.extend_from_slice(&self.scratch[mark..]);
self.scratch.truncate(mark);
let range = (
u32::try_from(start).unwrap_or(u32::MAX),
u32::try_from(self.child_ids.len() - start).unwrap_or(0),
);
if let Some(n) = self.resolve_mut(parent) {
n.children = range;
}
}
pub(crate) fn push_detached(
&mut self,
data: NodeData,
parent: NodeId,
) -> NodeId {
let id = self.mint();
self.nodes.push(Node {
data,
parent: Some(parent),
children: (0, 0),
});
id
}
#[must_use]
pub fn range(&self, id: NodeId) -> Option<core::ops::Range<usize>> {
let _live = self.resolve(id)?;
let (start, end) = *self.spans.get(id.index as usize)?;
if (start, end) == NO_SPAN {
return None;
}
Some(start as usize..end as usize)
}
pub(crate) fn set_span(&mut self, id: NodeId, start: usize, end: usize) {
let (Ok(s), Ok(e)) = (u32::try_from(start), u32::try_from(end)) else {
return;
};
if let Some(slot) = self.spans.get_mut(id.index as usize) {
*slot = (s, e);
}
}
pub(crate) fn set_span_end(&mut self, id: NodeId, end: usize) {
let Ok(e) = u32::try_from(end) else { return };
if let Some(slot) = self.spans.get_mut(id.index as usize)
&& *slot != NO_SPAN
{
slot.1 = e;
}
}
pub(crate) fn mint(&mut self) -> NodeId {
let index = u32::try_from(self.nodes.len())
.expect("document exceeds u32::MAX nodes");
self.generations.push(0);
self.spans.push(NO_SPAN);
NodeId {
index,
generation: 0,
}
}
pub(crate) fn alloc_node(
&mut self,
data: NodeData,
parent: Option<NodeId>,
) -> NodeId {
self.order_dirty = true;
let Some(index) = self.free_slots.pop() else {
let id = self.mint();
self.nodes.push(Node {
data,
parent,
children: (0, 0),
});
return id;
};
let slot = index as usize;
self.spans[slot] = NO_SPAN;
let slot_gen = &mut self.generations[slot];
*slot_gen = slot_gen.wrapping_add(1);
debug_assert_eq!(*slot_gen % 2, 0, "live slots have even generations");
self.nodes[slot] = Node {
data,
parent,
children: (0, 0),
};
NodeId {
index,
generation: *slot_gen,
}
}
pub(crate) fn resolve(&self, id: NodeId) -> Option<&Node> {
let index = id.index as usize;
let generation = *self.generations.get(index)?;
if generation % 2 == 1 || generation != id.generation {
return None;
}
self.nodes.get(index)
}
pub(crate) fn resolve_mut(&mut self, id: NodeId) -> Option<&mut Node> {
let index = id.index as usize;
let generation = *self.generations.get(index)?;
if generation % 2 == 1 || generation != id.generation {
return None;
}
self.nodes.get_mut(index)
}
pub(crate) fn chars(&self, c: Chars) -> &str {
match c {
Chars::Span(start, len) => {
let (start, len) = (start as usize, len as usize);
self.input.get(start..start + len).unwrap_or_default()
}
Chars::Expanded(i) => self
.expanded
.get(i as usize)
.map_or("", alloc::string::String::as_str),
}
}
#[must_use]
pub fn kind(&self, id: NodeId) -> Option<NodeKind<'_>> {
let node = self.resolve(id)?;
Some(match &node.data {
NodeData::Root => NodeKind::Root,
NodeData::Element {
name,
attributes,
namespaces,
} => NodeKind::Element {
name: *name,
attributes: *attributes,
namespaces: *namespaces,
},
NodeData::Attr { name, value } => NodeKind::Attr(Attribute {
name: *name,
value: self.chars(*value),
}),
NodeData::Text(c) => NodeKind::Text(self.chars(*c)),
NodeData::Comment(c) => NodeKind::Comment(self.chars(*c)),
NodeData::Namespace { prefix, uri } => NodeKind::Namespace {
prefix: self.chars(*prefix),
uri: self.chars(*uri),
},
NodeData::ProcessingInstruction { target, data } => {
NodeKind::ProcessingInstruction {
target: self.chars(*target),
data: self.chars(*data),
}
}
})
}
pub(crate) fn push_expanded(&mut self, text: &str) -> Chars {
let Ok(i) = u32::try_from(self.expanded.len()) else {
return Chars::EMPTY;
};
self.expanded.push(text.into());
Chars::Expanded(i)
}
pub(crate) fn data_mut(&mut self, id: NodeId) -> Option<&mut NodeData> {
self.resolve_mut(id).map(|n| &mut n.data)
}
#[must_use]
pub fn parent(&self, id: NodeId) -> Option<NodeId> {
self.resolve(id).and_then(|n| n.parent)
}
#[must_use]
pub fn children(&self, id: NodeId) -> &[NodeId] {
self.resolve(id).map_or(&[], |n| {
let (start, len) = (n.children.0 as usize, n.children.1 as usize);
self.child_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
})
}
#[must_use]
pub fn root_element(&self) -> Option<NodeId> {
self.children(self.root())
.iter()
.copied()
.find(|id| self.is_element(*id))
}
#[must_use]
pub fn is_element(&self, id: NodeId) -> bool {
matches!(self.kind(id), Some(NodeKind::Element { .. }))
}
#[must_use]
pub fn element_name(&self, id: NodeId) -> Option<&ExpandedName> {
match self.kind(id) {
Some(NodeKind::Element { name, .. }) => self.name(name),
_ => None,
}
}
#[must_use]
pub fn name(&self, id: NameId) -> Option<&ExpandedName> {
self.names.get(id.0 as usize)
}
#[must_use]
pub fn element_by_id(&self, value: &str) -> Option<NodeId> {
self.ids.get(value).copied()
}
#[must_use]
pub fn prefix(&self, id: NameId) -> Option<&str> {
self.name_prefixes.get(id.0 as usize)?.as_deref()
}
#[must_use]
pub fn attribute_nodes(&self, id: NodeId) -> &[NodeId] {
match self.kind(id) {
Some(NodeKind::Element { attributes, .. }) => {
let (start, len) =
(attributes.0 as usize, attributes.1 as usize);
self.attr_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
}
_ => &[],
}
}
#[must_use]
pub fn namespace_nodes(&self, id: NodeId) -> &[NodeId] {
match self.kind(id) {
Some(NodeKind::Element { namespaces, .. }) => {
let (start, len) =
(namespaces.0 as usize, namespaces.1 as usize);
self.ns_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
}
_ => &[],
}
}
#[must_use]
pub fn attributes(&self, id: NodeId) -> Vec<Attribute<'_>> {
self.attribute_nodes(id)
.iter()
.filter_map(|a| match self.kind(*a) {
Some(NodeKind::Attr(at)) => Some(at),
_ => None,
})
.collect()
}
#[must_use]
pub fn attribute(&self, id: NodeId, local: &str) -> Option<&str> {
self.attributes(id)
.into_iter()
.find(|a| self.name(a.name).is_some_and(|n| n.local == local))
.map(|a| a.value)
}
#[must_use]
pub fn attribute_ns(
&self,
id: NodeId,
namespace: Option<&str>,
local: &str,
) -> Option<&str> {
let attr = self.find_attribute(id, namespace, local)?;
match self.kind(attr) {
Some(NodeKind::Attr(a)) => Some(a.value),
_ => None,
}
}
#[must_use]
pub fn text(&self, id: NodeId) -> String {
let mut out = String::new();
self.collect_text(id, &mut out);
out
}
fn collect_text(&self, id: NodeId, out: &mut String) {
match self.kind(id) {
Some(NodeKind::Attr(a)) => out.push_str(a.value),
Some(NodeKind::Namespace { uri, .. }) => out.push_str(uri),
Some(NodeKind::Text(t)) => out.push_str(t),
Some(NodeKind::Root | NodeKind::Element { .. }) => {
for child in self.children(id) {
self.collect_text(*child, out);
}
}
_ => {}
}
}
#[must_use]
pub fn text_content(&self, id: NodeId) -> Option<&str> {
match self.kind(id)? {
NodeKind::Text(t) => Some(t),
NodeKind::Root | NodeKind::Element { .. } => {
self.single_text_child(id)
}
_ => None,
}
}
fn single_text_child(&self, id: NodeId) -> Option<&str> {
match self.children(id) {
[] => Some(""),
[only] => match self.kind(*only) {
Some(NodeKind::Text(t)) => Some(t),
_ => None,
},
_ => None,
}
}
pub fn nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
(0..self.nodes.len())
.filter(|i| self.generations.get(*i).is_none_or(|g| g % 2 == 0))
.map(|i| NodeId {
index: u32::try_from(i).unwrap_or(u32::MAX),
generation: self.generations.get(i).copied().unwrap_or(0),
})
}
pub fn descendants(&self) -> impl Iterator<Item = NodeId> + '_ {
self.nodes()
}
#[must_use]
pub fn descendants_of(&self, id: NodeId) -> Descendants<'_> {
let mut stack = Vec::new();
stack.extend(self.children(id).iter().rev());
Descendants { doc: self, stack }
}
pub fn document_order(&mut self) -> &[u32] {
if self.order_dirty || self.order.len() != self.nodes.len() {
self.refresh_order();
}
&self.order
}
#[must_use]
pub fn order_key(&self, id: NodeId) -> Option<u32> {
let _ = self.resolve(id)?;
if self.order.is_empty() && !self.order_dirty {
return Some(id.index);
}
self.order
.get(id.index())
.copied()
.filter(|key| *key != u32::MAX)
}
#[must_use]
pub const fn is_order_dirty(&self) -> bool {
self.order_dirty
}
pub(crate) fn refresh_order(&mut self) {
let mut order = Vec::new();
order.resize(self.nodes.len(), u32::MAX);
let mut next = 0u32;
let mut stack = Vec::from([self.root()]);
while let Some(id) = stack.pop() {
place(&mut order, id, &mut next);
for &ns in self.namespace_nodes(id) {
place(&mut order, ns, &mut next);
}
for &attr in self.attribute_nodes(id) {
place(&mut order, attr, &mut next);
}
stack.extend(self.children(id).iter().rev());
}
self.order = order;
self.order_dirty = false;
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len().saturating_sub(self.free_slots.len())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() <= 1
}
}
fn place(order: &mut [u32], id: NodeId, next: &mut u32) {
if let Some(slot) = order.get_mut(id.index()) {
*slot = *next;
*next = next.saturating_add(1);
}
}
#[derive(Debug, Clone)]
pub struct Descendants<'a> {
doc: &'a Document,
stack: Vec<NodeId>,
}
impl Iterator for Descendants<'_> {
type Item = NodeId;
fn next(&mut self) -> Option<NodeId> {
let id = self.stack.pop()?;
self.stack.extend(self.doc.children(id).iter().rev());
Some(id)
}
}
#[cfg(test)]
mod generation_tests {
use super::*;
#[test]
fn node_id_stays_eight_bytes() {
assert_eq!(core::mem::size_of::<NodeId>(), 8);
}
#[test]
fn generations_stay_in_lockstep_with_nodes() {
let doc = crate::parse("<a><b/><c>text</c></a>").expect("well-formed");
assert_eq!(doc.generations.len(), doc.nodes.len());
}
#[test]
fn a_fresh_document_resolves_every_id_it_hands_out() {
let doc = crate::parse("<a><b/><c>text</c></a>").expect("well-formed");
for id in doc.nodes() {
assert!(
doc.resolve(id).is_some(),
"nodes() handed out an id it cannot resolve: {id:?}"
);
}
}
#[test]
fn an_id_from_another_generation_does_not_resolve() {
let doc = crate::parse("<a/>").expect("well-formed");
let live = doc.root();
let stale = NodeId {
index: live.index,
generation: live.generation + 1,
};
assert!(doc.resolve(live).is_some());
assert!(
doc.resolve(stale).is_none(),
"a mismatched generation must not resolve to the live node"
);
}
#[test]
fn descendants_of_is_pre_order() {
let doc = crate::parse("<a><b><c/></b><d/></a>").expect("well-formed");
let names: Vec<&str> = doc
.descendants_of(doc.root())
.filter_map(|n| doc.element_name(n).map(|e| e.local.as_str()))
.collect();
assert_eq!(names, ["a", "b", "c", "d"]);
let a = doc.root_element().expect("root element");
assert_eq!(doc.descendants_of(a).count(), 3, "not including a");
}
#[test]
fn document_order_puts_namespaces_and_attributes_before_children() {
let mut doc =
crate::parse("<a xmlns:p=\"u\" k=\"v\"><b/></a>").expect("valid");
let order = doc.document_order().to_vec();
let a = doc.root_element().expect("root element");
let ns = doc.namespace_nodes(a)[0];
let attr = doc.attribute_nodes(a)[0];
let b = doc.children(a)[0];
assert_eq!(order[doc.root().index()], 0);
assert!(order[a.index()] < order[ns.index()]);
assert!(order[ns.index()] < order[attr.index()]);
assert!(order[attr.index()] < order[b.index()]);
assert_eq!(doc.order_key(b), Some(order[b.index()]));
}
}