use alloc::vec::Vec;
use super::blocks::BlockEdit;
use super::names::{check_comment, check_pi, check_text};
use super::{Document, NodeData, NodeError, NodeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Arena {
Children,
Attributes,
Namespaces,
}
impl Document {
#[must_use]
pub fn empty() -> Self {
Self::with_capacity(0)
}
pub(super) fn element(&self, id: NodeId) -> Result<(), NodeError> {
match self.resolve(id).map(|n| &n.data) {
None => Err(NodeError::Stale),
Some(NodeData::Element { .. }) => Ok(()),
Some(_) => Err(NodeError::NotAnElement),
}
}
pub(super) fn container(&self, id: NodeId) -> Result<(), NodeError> {
match self.resolve(id).map(|n| &n.data) {
None => Err(NodeError::Stale),
Some(NodeData::Root | NodeData::Element { .. }) => Ok(()),
Some(_) => Err(NodeError::NotAContainer),
}
}
pub(super) fn child_kind(&self, id: NodeId) -> Result<(), NodeError> {
match self.resolve(id).map(|n| &n.data) {
None => Err(NodeError::Stale),
Some(NodeData::Root) => Err(NodeError::NotRemovable),
Some(NodeData::Attr { .. } | NodeData::Namespace { .. }) => {
Err(NodeError::NotAChild)
}
Some(_) => Ok(()),
}
}
pub(super) fn refuse_second_root(
&self,
parent: NodeId,
is_element: bool,
except: Option<NodeId>,
) -> Result<(), NodeError> {
let taken = parent == self.root()
&& is_element
&& self.root_element().is_some_and(|r| Some(r) != except);
if taken {
Err(NodeError::RootElementExists)
} else {
Ok(())
}
}
pub(super) fn check_no_cycle(
&self,
id: NodeId,
new_parent: NodeId,
) -> Result<(), NodeError> {
let mut cursor = Some(new_parent);
while let Some(node) = cursor {
if node == id {
return Err(NodeError::WouldCycle);
}
cursor = self.parent(node);
}
Ok(())
}
fn check_text_at(
&self,
parent: Option<NodeId>,
text: &str,
) -> Result<(), NodeError> {
check_text(text)?;
if parent == Some(self.root())
&& text.chars().any(|c| !matches!(c, ' ' | '\t' | '\n'))
{
return Err(NodeError::InvalidText);
}
Ok(())
}
pub fn append_element(
&mut self,
parent: NodeId,
namespace: Option<&str>,
local: &str,
) -> Result<NodeId, NodeError> {
self.container(parent)?;
self.refuse_second_root(parent, true, None)?;
let name = self.intern(namespace, local)?;
let data = NodeData::Element {
name,
attributes: (0, 0),
namespaces: (0, 0),
};
Ok(self.append_node(parent, data))
}
pub fn append_text(
&mut self,
parent: NodeId,
text: &str,
) -> Result<NodeId, NodeError> {
self.container(parent)?;
self.check_text_at(Some(parent), text)?;
let chars = self.store_text(text);
Ok(self.append_node(parent, NodeData::Text(chars)))
}
pub fn append_comment(
&mut self,
parent: NodeId,
text: &str,
) -> Result<NodeId, NodeError> {
self.container(parent)?;
check_comment(text)?;
let chars = self.store_text(text);
Ok(self.append_node(parent, NodeData::Comment(chars)))
}
pub fn append_pi(
&mut self,
parent: NodeId,
target: &str,
data: &str,
) -> Result<NodeId, NodeError> {
self.container(parent)?;
check_pi(target, data)?;
let target = self.store_text(target);
let data = self.store_text(data);
Ok(self.append_node(
parent,
NodeData::ProcessingInstruction { target, data },
))
}
pub fn set_text(
&mut self,
id: NodeId,
text: &str,
) -> Result<(), NodeError> {
let old = match self.resolve(id).map(|n| &n.data) {
None => return Err(NodeError::Stale),
Some(NodeData::Text(c)) => *c,
Some(_) => return Err(NodeError::NotText),
};
self.check_text_at(self.parent(id), text)?;
let chars = self.replace_text(old, text);
if let Some(n) = self.resolve_mut(id) {
n.data = NodeData::Text(chars);
}
Ok(())
}
pub(super) fn free_slot(&mut self, id: NodeId) {
let index = id.index as usize;
let Some(g) = self.generations.get_mut(index) else {
return;
};
if *g % 2 == 1 {
return;
}
*g = g.wrapping_add(1);
self.free_slots.push(id.index);
self.order_dirty = true;
self.release_node_text(index);
}
pub(super) fn free_subtree(&mut self, id: NodeId) {
let mut stack = Vec::from([id]);
while let Some(current) = stack.pop() {
stack.extend_from_slice(self.children(current));
stack.extend_from_slice(self.attribute_nodes(current));
stack.extend_from_slice(self.namespace_nodes(current));
self.free_slot(current);
}
}
pub fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
let owned_by = match self.resolve(id).map(|n| &n.data) {
None => return Err(NodeError::Stale),
Some(NodeData::Root) => return Err(NodeError::NotRemovable),
Some(NodeData::Attr { .. }) => Some(Arena::Attributes),
Some(NodeData::Namespace { .. }) => Some(Arena::Namespaces),
Some(_) => None,
};
match (owned_by, self.parent(id)) {
(Some(arena), Some(owner)) => {
let _ = self.edit_block(owner, arena, BlockEdit::Without(id));
}
(None, _) => self.unlink_from_parent(id),
(Some(_), None) => {}
}
self.free_subtree(id);
Ok(())
}
pub fn detach(&mut self, id: NodeId) -> Result<(), NodeError> {
self.child_kind(id)?;
self.unlink_from_parent(id);
if let Some(n) = self.resolve_mut(id) {
n.parent = None;
}
self.order_dirty = true;
Ok(())
}
pub fn reparent(
&mut self,
id: NodeId,
new_parent: NodeId,
) -> Result<(), NodeError> {
self.move_node(id, new_parent, None)
}
pub fn insert_at(
&mut self,
parent: NodeId,
index: usize,
child: NodeId,
) -> Result<(), NodeError> {
self.move_node(child, parent, Some(index))
}
pub(super) fn move_node(
&mut self,
id: NodeId,
new_parent: NodeId,
at: Option<usize>,
) -> Result<(), NodeError> {
self.child_kind(id)?;
self.container(new_parent)?;
self.check_no_cycle(id, new_parent)?;
self.refuse_second_root(new_parent, self.is_element(id), Some(id))?;
if let Some(super::NodeKind::Text(text)) = self.kind(id) {
self.check_text_at(Some(new_parent), text)?;
}
self.unlink_from_parent(id);
if let Some(n) = self.resolve_mut(id) {
n.parent = Some(new_parent);
}
let edit =
at.map_or(BlockEdit::Append(id), |i| BlockEdit::InsertAt(i, id));
let _ = self.edit_block(new_parent, Arena::Children, edit);
Ok(())
}
}