use crate::document::Document;
use crate::error::Error;
use crate::node::{Arena, Node, NodeId};
pub fn append_child(doc: Document, parent: NodeId, child: NodeId) -> Result<Document, Error> {
let parent_node = doc
.get(parent)
.cloned()
.ok_or(Error::NodeNotFound { id: parent.raw() })?;
let child_node = doc
.get(child)
.cloned()
.ok_or(Error::NodeNotFound { id: child.raw() })?;
if child_node.parent().is_some() {
return Err(Error::AlreadyParented { child: child.raw() });
}
if !matches!(parent_node, Node::Element(_) | Node::Document(_)) {
return Err(Error::NotAnElement { id: parent.raw() });
}
let new_children: Vec<NodeId> = parent_node
.children()
.iter()
.copied()
.chain(std::iter::once(child))
.collect();
let updated_parent = parent_node.with_children(new_children);
let updated_child = child_node.with_parent(Some(parent));
let arena = doc.arena().clone();
let arena = store_or_fail(arena, parent, updated_parent)?;
let arena = store_or_fail(arena, child, updated_child)?;
Ok(doc.with_arena(arena))
}
pub fn remove_child(doc: Document, parent: NodeId, child: NodeId) -> Result<Document, Error> {
let parent_node = doc
.get(parent)
.cloned()
.ok_or(Error::NodeNotFound { id: parent.raw() })?;
let new_children: Vec<NodeId> = parent_node
.children()
.iter()
.copied()
.filter(|id| *id != child)
.collect();
let updated_parent = parent_node.with_children(new_children);
let arena = doc.arena().clone();
let arena = store_or_fail(arena, parent, updated_parent)?;
let arena = remove_subtree(arena, child);
Ok(doc.with_arena(arena))
}
fn remove_subtree(arena: Arena, root: NodeId) -> Arena {
let descendants = arena
.get(root)
.map_or(Vec::new(), |n| n.children().to_vec());
let arena = descendants.into_iter().fold(arena, remove_subtree);
arena.remove(root).unwrap_or_else(|a| a)
}
pub fn set_attribute(
doc: Document,
element_id: NodeId,
name: &str,
value: &str,
) -> Result<Document, Error> {
let element_data = element_data_of(&doc, element_id)?;
let updated_attrs: Vec<(String, String)> = element_data
.attributes()
.iter()
.filter(|(k, _)| !k.eq_ignore_ascii_case(name))
.cloned()
.chain(std::iter::once((name.to_owned(), value.to_owned())))
.collect();
let updated = Node::Element(element_data.with_attributes(updated_attrs));
let arena = store_or_fail(doc.arena().clone(), element_id, updated)?;
Ok(doc.with_arena(arena))
}
pub fn remove_attribute(doc: Document, element_id: NodeId, name: &str) -> Result<Document, Error> {
let element_data = element_data_of(&doc, element_id)?;
let updated_attrs: Vec<(String, String)> = element_data
.attributes()
.iter()
.filter(|(k, _)| !k.eq_ignore_ascii_case(name))
.cloned()
.collect();
let updated = Node::Element(element_data.with_attributes(updated_attrs));
let arena = store_or_fail(doc.arena().clone(), element_id, updated)?;
Ok(doc.with_arena(arena))
}
pub fn replace_text(doc: Document, text_id: NodeId, content: &str) -> Result<Document, Error> {
let node = doc
.get(text_id)
.cloned()
.ok_or(Error::NodeNotFound { id: text_id.raw() })?;
match node {
Node::Text(data) => {
let updated = Node::Text(data.with_content(content.to_owned()));
let arena = store_or_fail(doc.arena().clone(), text_id, updated)?;
Ok(doc.with_arena(arena))
}
Node::Document(_) | Node::Element(_) | Node::Comment(_) => {
Err(Error::NotAnElement { id: text_id.raw() })
}
}
}
fn element_data_of(doc: &Document, id: NodeId) -> Result<crate::node::ElementData, Error> {
match doc.get(id) {
Some(Node::Element(e)) => Ok(e.clone()),
Some(_other) => Err(Error::NotAnElement { id: id.raw() }),
None => Err(Error::NodeNotFound { id: id.raw() }),
}
}
fn store_or_fail(arena: Arena, id: NodeId, node: Node) -> Result<Arena, Error> {
arena
.store(id, node)
.map_err(|_| Error::NodeNotFound { id: id.raw() })
}