use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId {
pub(crate) index: u32,
pub(crate) generation: u32,
}
impl NodeId {
#[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),
}
#[derive(Debug, Clone)]
pub struct Document {
pub(crate) input: String,
pub(crate) expanded: Vec<String>,
pub(crate) nodes: Vec<Node>,
pub(crate) generations: Vec<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) scratch: Vec<NodeId>,
pub(crate) ids: BTreeMap<String, NodeId>,
pub(crate) ns_ids: Vec<NodeId>,
}
impl Document {
pub(crate) const fn placeholder() -> Self {
Self {
input: String::new(),
expanded: Vec::new(),
generations: Vec::new(),
nodes: Vec::new(),
free_slots: Vec::new(),
names: Vec::new(),
name_prefixes: Vec::new(),
attr_ids: Vec::new(),
child_ids: Vec::new(),
scratch: Vec::new(),
ids: BTreeMap::new(),
ns_ids: Vec::new(),
}
}
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),
});
Self {
input: String::new(),
expanded: Vec::new(),
generations: {
let mut g = Vec::with_capacity(nodes.saturating_add(1));
g.push(0);
g
},
nodes: v,
free_slots: Vec::new(),
names: Vec::new(),
name_prefixes: Vec::new(),
attr_ids: Vec::new(),
child_ids: Vec::with_capacity(nodes),
scratch: Vec::new(),
ids: BTreeMap::new(),
ns_ids: Vec::new(),
}
}
#[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
}
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);
NodeId {
index,
generation: 0,
}
}
pub(crate) fn alloc_node(
&mut self,
data: NodeData,
parent: Option<NodeId>,
) -> NodeId {
if let Some(index) = self.free_slots.pop() {
let slot = index as usize;
let slot_gen = &mut self.generations[slot];
*slot_gen = slot_gen.wrapping_add(1);
debug_assert_eq!(
*slot_gen % 2,
0,
"Live slots must have even generation"
);
self.nodes[slot] = Node {
data,
parent,
children: (0, 0),
};
NodeId {
index,
generation: *slot_gen,
}
} else {
let id = self.mint();
self.nodes.push(Node {
data,
parent,
children: (0, 0),
});
id
}
}
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 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);
}
}
_ => {}
}
}
pub fn descendants(&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),
})
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() <= 1
}
}
#[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.descendants() {
assert!(
doc.resolve(id).is_some(),
"descendants() 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"
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeError {
Stale,
NotAnElement,
RootElementExists,
WouldCycle,
}
impl core::fmt::Display for NodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Stale => f.write_str("the node has been removed"),
Self::NotAnElement => f.write_str("the node is not an element"),
Self::RootElementExists => {
f.write_str("the document already has a root element")
}
Self::WouldCycle => f.write_str(
"the move would place a node inside its own subtree",
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for NodeError {}
impl Document {
#[must_use]
pub fn empty() -> Self {
Self::with_capacity(0)
}
fn intern(&mut self, namespace: Option<&str>, local: &str) -> NameId {
let wanted = ExpandedName {
namespace: namespace.map(String::from),
local: String::from(local),
};
if let Some(i) = self.names.iter().position(|n| *n == wanted) {
return NameId(u32::try_from(i).unwrap_or(u32::MAX));
}
let id = u32::try_from(self.names.len())
.expect("document exceeds u32::MAX names");
self.names.push(wanted);
self.name_prefixes.push(None);
NameId(id)
}
fn intern_text(&mut self, text: &str) -> Chars {
if text.is_empty() {
return Chars::EMPTY;
}
let id = u32::try_from(self.expanded.len())
.expect("document exceeds u32::MAX texts");
self.expanded.push(String::from(text));
Chars::Expanded(id)
}
fn push_child(&mut self, parent: NodeId, child: NodeId) {
let Some(node) = self.resolve(parent) else {
return;
};
let (start, len) = (node.children.0 as usize, node.children.1 as usize);
let new_start = self.child_ids.len();
self.child_ids
.extend_from_within(start..start.saturating_add(len));
self.child_ids.push(child);
let range = (
u32::try_from(new_start).unwrap_or(u32::MAX),
u32::try_from(len + 1).unwrap_or(u32::MAX),
);
if let Some(n) = self.resolve_mut(parent) {
n.children = range;
}
}
pub fn append_element(
&mut self,
parent: NodeId,
namespace: Option<&str>,
local: &str,
) -> Result<NodeId, NodeError> {
if self.resolve(parent).is_none() {
return Err(NodeError::Stale);
}
if parent == self.root() && self.root_element().is_some() {
return Err(NodeError::RootElementExists);
}
let name = self.intern(namespace, local);
let id = self.alloc_node(
NodeData::Element {
name,
attributes: (0, 0),
namespaces: (0, 0),
},
Some(parent),
);
self.push_child(parent, id);
Ok(id)
}
pub fn append_text(
&mut self,
parent: NodeId,
text: &str,
) -> Result<NodeId, NodeError> {
if self.resolve(parent).is_none() {
return Err(NodeError::Stale);
}
let chars = self.intern_text(text);
let id = self.alloc_node(NodeData::Text(chars), Some(parent));
self.push_child(parent, id);
Ok(id)
}
pub fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
if self.resolve(id).is_none() {
return Err(NodeError::Stale);
}
if let Some(parent) = self.resolve(id).and_then(|n| n.parent) {
if let Some(p) = self.resolve(parent) {
let (start, len) =
(p.children.0 as usize, p.children.1 as usize);
let kept: Vec<NodeId> = self
.child_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
.iter()
.copied()
.filter(|c| *c != id)
.collect();
let new_start = self.child_ids.len();
self.child_ids.extend_from_slice(&kept);
let range = (
u32::try_from(new_start).unwrap_or(u32::MAX),
u32::try_from(kept.len()).unwrap_or(u32::MAX),
);
if let Some(p) = self.resolve_mut(parent) {
p.children = range;
}
}
}
let mut doomed = Vec::new();
let mut stack = alloc::vec::Vec::from([id]);
while let Some(current) = stack.pop() {
doomed.push(current);
stack.extend_from_slice(self.children(current));
for &attr in self.attribute_nodes(current) {
doomed.push(attr);
}
}
for node in doomed {
if let Some(g) = self.generations.get_mut(node.index as usize) {
*g = g.wrapping_add(1);
}
self.free_slots.push(node.index);
}
Ok(())
}
}
#[cfg(test)]
mod mutation_tests {
use super::*;
#[test]
fn a_document_can_be_built_from_nothing() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("root is live");
let b = doc.append_element(a, None, "b").expect("a is live");
let _ = doc.append_text(b, "hello").expect("b is live");
assert_eq!(doc.children(root), [a]);
assert_eq!(doc.children(a), [b]);
assert_eq!(doc.text(root), "hello");
}
#[test]
fn children_stay_contiguous_and_ordered_as_they_are_appended() {
let mut doc = Document::empty();
let root = doc.root();
let parent = doc.append_element(root, None, "p").expect("live");
let kids: Vec<NodeId> = (0..8)
.map(|i| {
if i % 2 == 0 {
doc.append_element(parent, None, "e").expect("live")
} else {
doc.append_text(parent, "t").expect("live")
}
})
.collect();
assert_eq!(doc.children(parent), kids.as_slice());
}
#[test]
fn a_removed_node_leaves_its_identifier_stale() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
assert!(doc.resolve(a).is_some());
doc.remove(a).expect("a is live");
assert!(doc.resolve(a).is_none(), "the identifier must not resolve");
assert_eq!(
doc.remove(a),
Err(NodeError::Stale),
"and removing twice is an error"
);
assert!(
doc.children(root).is_empty(),
"and the parent must not still list it"
);
}
#[test]
fn a_walk_does_not_visit_removed_nodes() {
let mut doc = Document::empty();
let root = doc.root();
let top = doc.append_element(root, None, "r").expect("live");
let a = doc.append_element(top, None, "a").expect("live");
let _deep = doc.append_element(a, None, "b").expect("live");
assert_eq!(doc.descendants().count(), 4);
doc.remove(a).expect("live");
assert_eq!(doc.descendants().count(), 2, "root and r only");
assert!(
!doc.descendants().any(|d| d.index() == a.index()),
"a removed node must not be walked"
);
for id in doc.descendants() {
for child in doc.children(id) {
assert_eq!(doc.parent(*child), Some(id), "links disagree");
}
}
}
#[test]
fn removing_a_subtree_invalidates_every_descendant() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let b = doc.append_element(a, None, "b").expect("live");
let t = doc.append_text(b, "deep").expect("live");
doc.remove(a).expect("live");
for (name, id) in [("a", a), ("b", b), ("text", t)] {
assert!(doc.resolve(id).is_none(), "{name} should be stale");
}
}
#[test]
fn removing_one_child_leaves_its_siblings_alone() {
let mut doc = Document::empty();
let root = doc.root();
let p = doc.append_element(root, None, "p").expect("live");
let one = doc.append_element(p, None, "one").expect("live");
let two = doc.append_element(p, None, "two").expect("live");
let three = doc.append_element(p, None, "three").expect("live");
doc.remove(two).expect("live");
assert_eq!(doc.children(p), [one, three]);
assert!(doc.resolve(one).is_some());
assert!(doc.resolve(three).is_some());
}
#[test]
fn appending_to_a_removed_parent_is_an_error_not_a_panic() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
doc.remove(a).expect("live");
assert_eq!(doc.append_element(a, None, "b"), Err(NodeError::Stale));
assert_eq!(doc.append_text(a, "t"), Err(NodeError::Stale));
}
#[test]
fn a_built_document_serialises_and_reparses_to_the_same_tree() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let b = doc.append_element(a, None, "b").expect("live");
let _ = doc.append_text(b, "p & q").expect("live");
let once = doc.to_xml();
let reparsed = crate::parse(&once).expect("what we built must parse");
assert_eq!(reparsed.to_xml(), once, "built: {once}");
assert_eq!(reparsed.text(reparsed.root()), "p & q");
}
#[test]
fn text_is_stored_verbatim_and_escaped_only_on_the_way_out() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let _ = doc.append_text(a, "x & y < z").expect("live");
assert_eq!(doc.text(root), "x & y < z");
let out = doc.to_xml();
assert!(out.contains("&"), "{out}");
assert!(out.contains("<"), "{out}");
assert_eq!(
crate::parse(&out).expect("reparses").text(doc.root()),
"x & y < z"
);
}
#[test]
fn names_are_interned_rather_than_repeated() {
let mut doc = Document::empty();
let root = doc.root();
let holder = doc.append_element(root, None, "holder").expect("live");
let before = doc.names.len();
for _ in 0..10 {
let _ = doc.append_element(holder, None, "same").expect("live");
}
assert_eq!(doc.names.len(), before + 1, "one name, ten elements");
}
}
impl Document {
pub fn set_attribute(
&mut self,
element: NodeId,
namespace: Option<&str>,
local: &str,
value: &str,
) -> Result<(), NodeError> {
let Some(node) = self.resolve(element) else {
return Err(NodeError::Stale);
};
let NodeData::Element { attributes, .. } = node.data else {
return Err(NodeError::NotAnElement);
};
let name = self.intern(namespace, local);
let chars = self.intern_text(value);
let (start, len) = (attributes.0 as usize, attributes.1 as usize);
let existing: Option<NodeId> = self
.attr_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
.iter()
.copied()
.find(|a| {
matches!(
self.resolve(*a).map(|n| &n.data),
Some(NodeData::Attr { name: n, .. }) if *n == name
)
});
if let Some(attr) = existing {
if let Some(n) = self.resolve_mut(attr) {
n.data = NodeData::Attr { name, value: chars };
}
return Ok(());
}
let attr = self
.alloc_node(NodeData::Attr { name, value: chars }, Some(element));
let new_start = self.attr_ids.len();
self.attr_ids
.extend_from_within(start..start.saturating_add(len));
self.attr_ids.push(attr);
let range = (
u32::try_from(new_start).unwrap_or(u32::MAX),
u32::try_from(len + 1).unwrap_or(u32::MAX),
);
if let Some(n) = self.resolve_mut(element) {
if let NodeData::Element { attributes, .. } = &mut n.data {
*attributes = range;
}
}
Ok(())
}
pub fn remove_attribute(
&mut self,
element: NodeId,
namespace: Option<&str>,
local: &str,
) -> Result<bool, NodeError> {
let Some(node) = self.resolve(element) else {
return Err(NodeError::Stale);
};
let NodeData::Element { attributes, .. } = node.data else {
return Err(NodeError::NotAnElement);
};
let wanted = ExpandedName {
namespace: namespace.map(String::from),
local: String::from(local),
};
let Some(name) = self
.names
.iter()
.position(|n| *n == wanted)
.and_then(|i| u32::try_from(i).ok())
.map(NameId)
else {
return Ok(false);
};
let (start, len) = (attributes.0 as usize, attributes.1 as usize);
let mut removed_attr = None;
let kept: Vec<NodeId> = self
.attr_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
.iter()
.copied()
.filter(|a| {
let matches_name = matches!(
self.resolve(*a).map(|n| &n.data),
Some(NodeData::Attr { name: n, .. }) if *n == name
);
if matches_name {
removed_attr = Some(*a);
false
} else {
true
}
})
.collect();
if kept.len() == len {
return Ok(false);
}
if let Some(attr_id) = removed_attr {
if let Some(g) = self.generations.get_mut(attr_id.index as usize) {
*g = g.wrapping_add(1);
}
self.free_slots.push(attr_id.index);
}
let new_start = self.attr_ids.len();
self.attr_ids.extend_from_slice(&kept);
let range = (
u32::try_from(new_start).unwrap_or(u32::MAX),
u32::try_from(kept.len()).unwrap_or(u32::MAX),
);
if let Some(n) = self.resolve_mut(element) {
if let NodeData::Element { attributes, .. } = &mut n.data {
*attributes = range;
}
}
Ok(true)
}
pub fn reparent(
&mut self,
id: NodeId,
new_parent: NodeId,
) -> Result<(), NodeError> {
if self.resolve(id).is_none() || self.resolve(new_parent).is_none() {
return Err(NodeError::Stale);
}
if id == new_parent {
return Err(NodeError::WouldCycle);
}
let mut cursor = self.resolve(new_parent).and_then(|n| n.parent);
while let Some(node) = cursor {
if node == id {
return Err(NodeError::WouldCycle);
}
cursor = self.resolve(node).and_then(|n| n.parent);
}
self.unlink_from_parent(id);
if let Some(n) = self.resolve_mut(id) {
n.parent = Some(new_parent);
}
self.push_child(new_parent, id);
Ok(())
}
fn unlink_from_parent(&mut self, id: NodeId) {
let Some(parent) = self.resolve(id).and_then(|n| n.parent) else {
return;
};
let Some(p) = self.resolve(parent) else {
return;
};
let (start, len) = (p.children.0 as usize, p.children.1 as usize);
let kept: Vec<NodeId> = self
.child_ids
.get(start..start.saturating_add(len))
.unwrap_or(&[])
.iter()
.copied()
.filter(|c| *c != id)
.collect();
let new_start = self.child_ids.len();
self.child_ids.extend_from_slice(&kept);
let range = (
u32::try_from(new_start).unwrap_or(u32::MAX),
u32::try_from(kept.len()).unwrap_or(u32::MAX),
);
if let Some(p) = self.resolve_mut(parent) {
p.children = range;
}
}
}
#[cfg(test)]
mod attribute_and_move_tests {
use super::*;
fn built() -> (Document, NodeId, NodeId) {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
(doc, root, a)
}
#[test]
fn an_attribute_can_be_set_and_read_back() {
let (mut doc, _root, a) = built();
doc.set_attribute(a, None, "id", "one").expect("live");
assert_eq!(doc.attribute(a, "id"), Some("one"));
}
#[test]
fn setting_the_same_name_twice_replaces_rather_than_duplicates() {
let (mut doc, _root, a) = built();
doc.set_attribute(a, None, "id", "one").expect("live");
doc.set_attribute(a, None, "id", "two").expect("live");
assert_eq!(doc.attribute(a, "id"), Some("two"));
assert_eq!(doc.attribute_nodes(a).len(), 1, "one attribute, not two");
let xml = doc.to_xml();
assert!(crate::parse(&xml).is_ok(), "must stay well-formed: {xml}");
}
#[test]
fn several_attributes_survive_the_arena_relocation() {
let (mut doc, _root, a) = built();
for (n, v) in [("one", "1"), ("two", "2"), ("three", "3")] {
doc.set_attribute(a, None, n, v).expect("live");
}
assert_eq!(doc.attribute_nodes(a).len(), 3);
assert_eq!(doc.attribute(a, "one"), Some("1"));
assert_eq!(doc.attribute(a, "three"), Some("3"));
}
#[test]
fn removing_an_attribute_reports_whether_there_was_one() {
let (mut doc, _root, a) = built();
doc.set_attribute(a, None, "id", "one").expect("live");
assert_eq!(doc.remove_attribute(a, None, "id"), Ok(true));
assert_eq!(doc.attribute(a, "id"), None);
assert_eq!(
doc.remove_attribute(a, None, "id"),
Ok(false),
"removing it again is not an error, just nothing to do"
);
assert_eq!(
doc.remove_attribute(a, None, "never-existed"),
Ok(false),
"a name the document has never interned is not an error either"
);
}
#[test]
fn attributes_need_an_element() {
let (mut doc, root, a) = built();
let text = doc.append_text(a, "t").expect("live");
assert_eq!(
doc.set_attribute(text, None, "id", "x"),
Err(NodeError::NotAnElement)
);
assert_eq!(
doc.set_attribute(root, None, "id", "x"),
Err(NodeError::NotAnElement),
"the document root is not an element"
);
}
#[test]
fn attributes_of_a_removed_element_are_stale() {
let (mut doc, _root, a) = built();
doc.remove(a).expect("live");
assert_eq!(
doc.set_attribute(a, None, "id", "x"),
Err(NodeError::Stale)
);
assert_eq!(doc.remove_attribute(a, None, "id"), Err(NodeError::Stale));
}
#[test]
fn a_subtree_can_be_moved_between_parents() {
let mut doc = Document::empty();
let root = doc.root();
let d = doc.append_element(root, None, "d").expect("live");
let from = doc.append_element(d, None, "from").expect("live");
let to = doc.append_element(d, None, "to").expect("live");
let moved = doc.append_element(from, None, "moved").expect("live");
let _ = doc.append_text(moved, "payload").expect("live");
doc.reparent(moved, to).expect("both live, no cycle");
assert!(doc.children(from).is_empty(), "left the old parent");
assert_eq!(doc.children(to), [moved], "arrived at the new one");
assert_eq!(doc.parent(moved), Some(to), "and knows its parent");
assert_eq!(doc.text(to), "payload", "with its subtree intact");
}
#[test]
fn a_node_cannot_be_moved_into_its_own_subtree() {
let mut doc = Document::empty();
let root = doc.root();
let outer = doc.append_element(root, None, "outer").expect("live");
let inner = doc.append_element(outer, None, "inner").expect("live");
let deeper = doc.append_element(inner, None, "deeper").expect("live");
assert_eq!(doc.reparent(outer, inner), Err(NodeError::WouldCycle));
assert_eq!(doc.reparent(outer, deeper), Err(NodeError::WouldCycle));
assert_eq!(doc.reparent(outer, outer), Err(NodeError::WouldCycle));
assert_eq!(doc.children(root), [outer]);
assert_eq!(doc.children(outer), [inner]);
}
#[test]
fn a_refused_move_leaves_the_document_walkable() {
let mut doc = Document::empty();
let root = doc.root();
let outer = doc.append_element(root, None, "outer").expect("live");
let inner = doc.append_element(outer, None, "inner").expect("live");
let _ = doc.reparent(outer, inner);
assert_eq!(doc.descendants().count(), doc.len());
assert!(doc.to_xml().contains("<outer>"), "{}", doc.to_xml());
}
#[test]
fn moving_to_a_removed_parent_is_stale() {
let mut doc = Document::empty();
let root = doc.root();
let d = doc.append_element(root, None, "d").expect("live");
let a = doc.append_element(d, None, "a").expect("live");
let b = doc.append_element(d, None, "b").expect("live");
doc.remove(b).expect("live");
assert_eq!(doc.reparent(a, b), Err(NodeError::Stale));
}
#[test]
fn a_second_root_element_is_refused() {
let mut doc = Document::empty();
let root = doc.root();
let first = doc.append_element(root, None, "one").expect("live");
assert_eq!(
doc.append_element(root, None, "two"),
Err(NodeError::RootElementExists)
);
let inner = doc.append_element(first, None, "inner");
assert!(inner.is_ok(), "{inner:?}");
assert!(doc.append_text(root, " ").is_ok(), "text at document level");
let xml = doc.to_xml();
assert!(crate::parse(&xml).is_ok(), "must reparse: {xml}");
}
#[test]
fn a_moved_document_still_round_trips() {
let mut doc = Document::empty();
let root = doc.root();
let doc_el = doc.append_element(root, None, "doc").expect("live");
let from = doc.append_element(doc_el, None, "from").expect("live");
let to = doc.append_element(doc_el, None, "to").expect("live");
let moved = doc.append_element(from, None, "moved").expect("live");
doc.set_attribute(moved, None, "k", "v & w").expect("live");
doc.reparent(moved, to).expect("live");
let once = doc.to_xml();
let reparsed = crate::parse(&once).expect("must parse: {once}");
assert_eq!(reparsed.to_xml(), once);
assert!(once.contains("&"), "attribute escaped: {once}");
}
}
#[derive(Debug)]
pub struct Builder<'a> {
doc: &'a mut Document,
id: NodeId,
error: Option<NodeError>,
}
impl Builder<'_> {
pub fn attr(&mut self, local: &str, value: &str) -> &mut Self {
if self.error.is_none() {
if let Err(e) = self.doc.set_attribute(self.id, None, local, value)
{
self.error = Some(e);
}
}
self
}
pub fn attr_ns(
&mut self,
namespace: &str,
local: &str,
value: &str,
) -> &mut Self {
if self.error.is_none() {
if let Err(e) =
self.doc
.set_attribute(self.id, Some(namespace), local, value)
{
self.error = Some(e);
}
}
self
}
pub fn text(&mut self, text: &str) -> &mut Self {
if self.error.is_none() {
if let Err(e) = self.doc.append_text(self.id, text) {
self.error = Some(e);
}
}
self
}
pub fn child(
&mut self,
local: &str,
f: impl FnOnce(&mut Builder<'_>),
) -> &mut Self {
if self.error.is_some() {
return self;
}
match self.doc.append_element(self.id, None, local) {
Ok(child) => {
let mut inner = Builder {
doc: &mut *self.doc,
id: child,
error: None,
};
f(&mut inner);
self.error = inner.error;
}
Err(e) => self.error = Some(e),
}
self
}
pub fn finish(&mut self) -> Result<NodeId, NodeError> {
match self.error {
Some(e) => Err(e),
None => Ok(self.id),
}
}
}
impl Document {
pub fn build<'a>(&'a mut self, parent: NodeId, local: &str) -> Builder<'a> {
match self.append_element(parent, None, local) {
Ok(id) => Builder {
doc: self,
id,
error: None,
},
Err(e) => Builder {
doc: self,
id: parent,
error: Some(e),
},
}
}
}
#[cfg(test)]
mod builder_tests {
use super::*;
#[test]
fn a_nested_subtree_is_built_in_one_expression() {
let mut doc = Document::empty();
let root = doc.root();
let cat = doc
.build(root, "catalogue")
.attr("version", "1.0")
.child("book", |b| {
let _ = b.attr("year", "1965").child("title", |t| {
let _ = t.text("Dune");
});
})
.child("book", |b| {
let _ = b.attr("year", "1961").child("title", |t| {
let _ = t.text("Solaris");
});
})
.finish()
.expect("root is live");
assert_eq!(doc.children(cat).len(), 2);
assert_eq!(doc.attribute(cat, "version"), Some("1.0"));
assert_eq!(doc.text(root), "DuneSolaris");
}
#[test]
fn the_builder_produces_the_same_tree_as_the_primitives() {
let mut built = Document::empty();
let root = built.root();
let _ = built
.build(root, "a")
.attr("k", "v")
.child("b", |b| {
let _ = b.text("x");
})
.finish()
.expect("live");
let mut manual = Document::empty();
let mroot = manual.root();
let a = manual.append_element(mroot, None, "a").expect("live");
manual.set_attribute(a, None, "k", "v").expect("live");
let b = manual.append_element(a, None, "b").expect("live");
let _ = manual.append_text(b, "x").expect("live");
assert_eq!(built.to_xml(), manual.to_xml());
}
#[test]
fn the_first_error_is_reported_and_later_steps_do_nothing() {
let mut doc = Document::empty();
let root = doc.root();
let _first = doc.append_element(root, None, "one").expect("live");
let before = doc.len();
let result = doc
.build(root, "two")
.attr("k", "v")
.child("c", |b| {
let _ = b.text("x");
})
.finish();
assert_eq!(result, Err(NodeError::RootElementExists));
assert_eq!(doc.len(), before, "nothing was added after the failure");
}
#[test]
fn a_closure_builds_into_the_same_document() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc
.build(root, "a")
.child("b", |b| {
let _ = b.text("x").child("c", |c| {
let _ = c.text("y");
});
})
.finish()
.expect("live");
assert_eq!(doc.text(a), "xy", "both closures wrote to one document");
assert_eq!(doc.children(a).len(), 1);
}
#[test]
fn building_on_a_stale_parent_reports_it_rather_than_panicking() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let b = doc.append_element(a, None, "b").expect("live");
doc.remove(a).expect("live");
let result = doc.build(b, "c").attr("k", "v").finish();
assert_eq!(result, Err(NodeError::Stale));
}
#[test]
fn a_built_document_round_trips() {
let mut doc = Document::empty();
let root = doc.root();
let _ = doc
.build(root, "r")
.attr("note", "a & b < c")
.child("child", |c| {
let _ = c.text("text & more");
})
.finish()
.expect("live");
let xml = doc.to_xml();
let reparsed = crate::parse(&xml).expect("must parse");
assert_eq!(reparsed.to_xml(), xml);
assert_eq!(reparsed.text(reparsed.root()), "text & more");
}
#[test]
fn recycled_slots_do_not_grow_arena_and_reject_stale_handles() {
let mut doc = Document::empty();
let root = doc.root();
let first = doc.append_element(root, None, "item").expect("live");
let first_index = first.index();
let initial_len = doc.len();
doc.remove(first).expect("live");
assert!(
doc.resolve(first).is_none(),
"removed node is not resolvable"
);
let second = doc.append_element(root, None, "item2").expect("live");
assert_eq!(second.index(), first_index, "reused recycled slot");
assert_ne!(second.generation, first.generation, "generation advanced");
assert_eq!(doc.len(), initial_len, "arena size did not grow");
assert!(
doc.resolve(first).is_none(),
"stale generation 0 does not resolve"
);
assert_eq!(
doc.element_name(second).map(|n| n.local.as_str()),
Some("item2")
);
}
}