use std::fmt;
use super::{
validate_xml_name, ErrorKind, NamespaceDeclaration, NamespaceTable, QName, XmlError, XmlResult,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(usize);
impl NodeId {
pub fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Document {
root: Option<NodeId>,
nodes: Vec<Node>,
namespaces: NamespaceTable,
}
impl Document {
pub fn new() -> Self {
Self {
root: None,
nodes: Vec::new(),
namespaces: NamespaceTable::new(),
}
}
pub fn root(&self) -> Option<NodeId> {
self.root
}
pub fn namespaces(&self) -> &NamespaceTable {
&self.namespaces
}
pub fn namespaces_mut(&mut self) -> &mut NamespaceTable {
&mut self.namespaces
}
pub fn node(&self, id: NodeId) -> XmlResult<&Node> {
self.nodes
.get(id.index())
.filter(|node| node.id == id)
.ok_or_else(|| invalid_node_id(id))
}
pub fn parent(&self, id: NodeId) -> XmlResult<Option<NodeId>> {
Ok(self.node(id)?.parent)
}
pub fn children(&self, id: NodeId) -> XmlResult<&[NodeId]> {
match &self.node(id)?.kind {
NodeKind::Element(element) => Ok(&element.children),
_ => Err(XmlError::new(
ErrorKind::InvalidOperation,
"only element nodes can have children",
)),
}
}
pub fn add_root_element(&mut self, name: QName) -> XmlResult<NodeId> {
if self.root.is_some() {
return Err(XmlError::new(
ErrorKind::InvalidOperation,
"document already has a root element",
));
}
let root = self.push_node(None, NodeKind::Element(ElementData::new(name)));
self.root = Some(root);
Ok(root)
}
pub fn add_element(&mut self, parent: NodeId, name: QName) -> XmlResult<NodeId> {
self.add_child(parent, NodeKind::Element(ElementData::new(name)))
}
pub fn add_text(&mut self, parent: NodeId, text: impl Into<String>) -> XmlResult<NodeId> {
self.add_child(parent, NodeKind::Text(text.into()))
}
pub fn add_comment(&mut self, parent: NodeId, text: impl Into<String>) -> XmlResult<NodeId> {
self.add_child(parent, NodeKind::Comment(text.into()))
}
pub fn add_cdata(&mut self, parent: NodeId, text: impl Into<String>) -> XmlResult<NodeId> {
self.add_child(parent, NodeKind::CData(text.into()))
}
pub fn add_processing_instruction(
&mut self,
parent: NodeId,
target: impl Into<String>,
data: Option<impl Into<String>>,
) -> XmlResult<NodeId> {
let target = target.into();
validate_xml_name(&target)?;
self.add_child(
parent,
NodeKind::ProcessingInstruction {
target,
data: data.map(Into::into),
},
)
}
pub fn add_attribute(&mut self, element: NodeId, attribute: Attribute) -> XmlResult<()> {
self.element_mut(element)?.attributes.push(attribute);
Ok(())
}
pub fn add_namespace_declaration(
&mut self,
element: NodeId,
declaration: NamespaceDeclaration,
) -> XmlResult<()> {
self.element_mut(element)?
.namespace_declarations
.push(declaration);
Ok(())
}
pub fn path(&self, id: NodeId) -> XmlResult<XmlPath> {
self.node(id)?;
let mut segments = Vec::new();
let mut current = Some(id);
while let Some(current_id) = current {
let node = self.node(current_id)?;
segments.push(path_segment(node));
current = node.parent;
}
segments.reverse();
Ok(XmlPath::new(segments))
}
fn add_child(&mut self, parent: NodeId, kind: NodeKind) -> XmlResult<NodeId> {
self.node(parent)?;
if !matches!(self.node(parent)?.kind, NodeKind::Element(_)) {
return Err(XmlError::new(
ErrorKind::InvalidOperation,
"only element nodes can have children",
));
}
let child = self.push_node(Some(parent), kind);
self.element_mut(parent)?.children.push(child);
Ok(child)
}
fn push_node(&mut self, parent: Option<NodeId>, kind: NodeKind) -> NodeId {
let id = NodeId(self.nodes.len());
self.nodes.push(Node { id, parent, kind });
id
}
fn element_mut(&mut self, id: NodeId) -> XmlResult<&mut ElementData> {
match &mut self
.nodes
.get_mut(id.index())
.filter(|node| node.id == id)
.ok_or_else(|| invalid_node_id(id))?
.kind
{
NodeKind::Element(element) => Ok(element),
_ => Err(XmlError::new(
ErrorKind::InvalidOperation,
"operation requires an element node",
)),
}
}
}
impl Default for Document {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
id: NodeId,
parent: Option<NodeId>,
kind: NodeKind,
}
impl Node {
pub fn id(&self) -> NodeId {
self.id
}
pub fn parent(&self) -> Option<NodeId> {
self.parent
}
pub fn kind(&self) -> &NodeKind {
&self.kind
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeKind {
Element(ElementData),
Text(String),
Comment(String),
CData(String),
ProcessingInstruction {
target: String,
data: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElementData {
name: QName,
attributes: Vec<Attribute>,
children: Vec<NodeId>,
namespace_declarations: Vec<NamespaceDeclaration>,
}
impl ElementData {
pub fn new(name: QName) -> Self {
Self {
name,
attributes: Vec::new(),
children: Vec::new(),
namespace_declarations: Vec::new(),
}
}
pub fn name(&self) -> &QName {
&self.name
}
pub fn attributes(&self) -> &[Attribute] {
&self.attributes
}
pub fn children(&self) -> &[NodeId] {
&self.children
}
pub fn namespace_declarations(&self) -> &[NamespaceDeclaration] {
&self.namespace_declarations
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
name: QName,
value: String,
}
impl Attribute {
pub fn new(name: QName, value: impl Into<String>) -> Self {
Self {
name,
value: value.into(),
}
}
pub fn name(&self) -> &QName {
&self.name
}
pub fn value(&self) -> &str {
&self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XmlPath {
segments: Vec<String>,
}
impl XmlPath {
pub fn new(segments: Vec<String>) -> Self {
Self { segments }
}
pub fn segments(&self) -> &[String] {
&self.segments
}
}
impl fmt::Display for XmlPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.segments.is_empty() {
return f.write_str("/");
}
write!(f, "/{}", self.segments.join("/"))
}
}
fn invalid_node_id(id: NodeId) -> XmlError {
XmlError::new(
ErrorKind::NotFound,
format!("node id {} does not exist in this document", id.index()),
)
}
fn path_segment(node: &Node) -> String {
match &node.kind {
NodeKind::Element(element) => element.name().lexical_name(),
NodeKind::Text(_) => "text()".to_owned(),
NodeKind::Comment(_) => "comment()".to_owned(),
NodeKind::CData(_) => "cdata()".to_owned(),
NodeKind::ProcessingInstruction { target, .. } => {
format!("processing-instruction({target})")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{NamespacePrefix, NamespaceUri};
fn qname(local: &str) -> QName {
QName::new(local).expect("valid name")
}
#[test]
fn document_new_creates_empty_document() {
let document = Document::new();
assert_eq!(document.root(), None);
assert!(document.namespaces().is_empty());
}
#[test]
fn document_tree_creates_three_levels() {
let mut document = Document::new();
let root = document
.add_root_element(qname("Root"))
.expect("root element");
let child = document
.add_element(root, qname("Child"))
.expect("child element");
let grandchild = document
.add_element(child, qname("Grandchild"))
.expect("grandchild element");
let text = document.add_text(grandchild, "value").expect("text node");
assert_eq!(document.root(), Some(root));
assert_eq!(document.parent(child).expect("parent"), Some(root));
assert_eq!(document.parent(grandchild).expect("parent"), Some(child));
assert_eq!(document.parent(text).expect("parent"), Some(grandchild));
}
#[test]
fn navigation_lists_children_and_parent() {
let mut document = Document::new();
let root = document
.add_root_element(qname("Root"))
.expect("root element");
let first = document.add_element(root, qname("First")).expect("child");
let second = document.add_element(root, qname("Second")).expect("child");
assert_eq!(document.children(root).expect("children"), &[first, second]);
assert_eq!(document.parent(first).expect("parent"), Some(root));
assert_eq!(document.parent(root).expect("parent"), None);
}
#[test]
fn node_kinds_support_text_comment_cdata_and_processing_instruction() {
let mut document = Document::new();
let root = document
.add_root_element(qname("Root"))
.expect("root element");
let text = document.add_text(root, "text").expect("text");
let comment = document.add_comment(root, "comment").expect("comment");
let cdata = document.add_cdata(root, "raw <xml>").expect("cdata");
let instruction = document
.add_processing_instruction(root, "xml-stylesheet", Some("href=\"style.xsl\""))
.expect("processing instruction");
assert!(
matches!(document.node(text).expect("node").kind(), NodeKind::Text(value) if value == "text")
);
assert!(
matches!(document.node(comment).expect("node").kind(), NodeKind::Comment(value) if value == "comment")
);
assert!(
matches!(document.node(cdata).expect("node").kind(), NodeKind::CData(value) if value == "raw <xml>")
);
assert!(matches!(
document.node(instruction).expect("node").kind(),
NodeKind::ProcessingInstruction { target, data }
if target == "xml-stylesheet" && data.as_deref() == Some("href=\"style.xsl\"")
));
}
#[test]
fn document_adds_attributes_and_namespaces_to_elements() {
let mut document = Document::new();
let root = document
.add_root_element(QName::qualified("doc", "Root", "urn:doc").expect("valid qname"))
.expect("root element");
document
.add_attribute(root, Attribute::new(qname("id"), "123"))
.expect("attribute");
document
.add_namespace_declaration(
root,
NamespaceDeclaration::prefixed("doc", "urn:doc").expect("namespace"),
)
.expect("namespace declaration");
let root_node = document.node(root).expect("root node");
let NodeKind::Element(element) = root_node.kind() else {
panic!("root must be element");
};
assert_eq!(element.attributes()[0].name().local(), "id");
assert_eq!(element.attributes()[0].value(), "123");
assert_eq!(
element.namespace_declarations()[0]
.prefix()
.map(NamespacePrefix::as_str),
Some("doc")
);
assert_eq!(
element.namespace_declarations()[0].uri().as_str(),
"urn:doc"
);
}
#[test]
fn document_rejects_second_root() {
let mut document = Document::new();
document
.add_root_element(qname("Root"))
.expect("root element");
let error = document
.add_root_element(qname("OtherRoot"))
.expect_err("second root must fail");
assert_eq!(error.kind(), &ErrorKind::InvalidOperation);
}
#[test]
fn document_rejects_invalid_node_id() {
let document = Document::new();
let error = document
.node(NodeId(99))
.expect_err("invalid node id must fail");
assert_eq!(error.kind(), &ErrorKind::NotFound);
}
#[test]
fn node_rejects_children_on_non_element_nodes() {
let mut document = Document::new();
let root = document
.add_root_element(qname("Root"))
.expect("root element");
let text = document.add_text(root, "text").expect("text node");
let error = document
.add_text(text, "nested")
.expect_err("text cannot have child nodes");
assert_eq!(error.kind(), &ErrorKind::InvalidOperation);
}
#[test]
fn xml_path_identifies_logical_location() {
let mut document = Document::new();
let root = document
.add_root_element(qname("Root"))
.expect("root element");
let child = document.add_element(root, qname("Child")).expect("child");
let text = document.add_text(child, "value").expect("text");
let path = document.path(text).expect("path");
assert_eq!(
path.segments(),
&["Root".to_owned(), "Child".to_owned(), "text()".to_owned()]
);
assert_eq!(path.to_string(), "/Root/Child/text()");
}
#[test]
fn document_namespace_table_is_available() {
let mut document = Document::new();
document
.namespaces_mut()
.declare_default("urn:default")
.expect("default namespace");
assert_eq!(
document
.namespaces()
.default_namespace()
.map(NamespaceUri::as_str),
Some("urn:default")
);
}
}