use super::node::{NodeData, NodeId, NodeKind, QName};
#[derive(Debug, Clone)]
pub struct Document {
pub(crate) nodes: Vec<NodeData>,
pub(crate) root: NodeId,
pub(crate) base_uri: Option<String>,
pub(crate) extra_roots: Vec<NodeId>,
}
impl Document {
#[must_use]
pub(crate) fn empty() -> Self {
let root = NodeData::new(NodeKind::Root, None, 0);
Self {
nodes: vec![root],
root: NodeId(0),
base_uri: None,
extra_roots: Vec::new(),
}
}
#[must_use]
pub const fn root(&self) -> NodeId {
self.root
}
#[must_use]
pub fn root_of(&self, node: NodeId) -> NodeId {
if self.extra_roots.is_empty() {
return self.root;
}
let mut current = node;
while let Some(parent) = self.parent(current) {
current = parent;
}
current
}
#[must_use]
pub fn roots(&self) -> Vec<NodeId> {
let mut roots = vec![self.root];
roots.extend_from_slice(&self.extra_roots);
roots
}
pub(crate) fn append_document(&mut self, other: &Document) -> NodeId {
let mut order = self.nodes.iter().map(|node| node.order).max().unwrap_or(0);
let root = self.copy_from(other, other.root(), None, &mut order);
self.extra_roots.push(root);
self.finalize_subtree(root);
root
}
fn copy_from(
&mut self,
source: &Document,
node: NodeId,
parent: Option<NodeId>,
order: &mut usize,
) -> NodeId {
*order += 1;
let id = NodeId(self.nodes.len());
let mut data = NodeData::new(source.kind(node), parent, *order);
data.name = source.name(node).cloned();
data.value = source.value(node).to_string();
self.nodes.push(data);
if let Some(parent) = parent {
self.nodes[parent.0].children.push(id);
}
for &namespace in source.namespaces(node) {
*order += 1;
let copy = NodeId(self.nodes.len());
let mut data = NodeData::new(NodeKind::Namespace, Some(id), *order);
data.name = source.name(namespace).cloned();
data.value = source.value(namespace).to_string();
self.nodes.push(data);
self.nodes[id.0].namespaces.push(copy);
}
for &attribute in source.attributes(node) {
*order += 1;
let copy = NodeId(self.nodes.len());
let mut data = NodeData::new(NodeKind::Attribute, Some(id), *order);
data.name = source.name(attribute).cloned();
data.value = source.value(attribute).to_string();
self.nodes.push(data);
self.nodes[id.0].attributes.push(copy);
}
for &child in source.children(node) {
self.copy_from(source, child, Some(id), order);
}
id
}
#[must_use]
pub fn document_element(&self) -> Option<NodeId> {
self.children(self.root)
.iter()
.copied()
.find(|&n| self.kind(n) == NodeKind::Element)
}
#[must_use]
pub fn base_uri(&self) -> Option<&str> {
self.base_uri.as_deref()
}
pub fn set_base_uri(&mut self, uri: impl Into<String>) {
self.base_uri = Some(uri.into());
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() <= 1
}
pub(crate) fn data(&self, id: NodeId) -> &NodeData {
&self.nodes[id.0]
}
#[must_use]
pub fn kind(&self, id: NodeId) -> NodeKind {
self.data(id).kind
}
#[must_use]
pub fn name(&self, id: NodeId) -> Option<&QName> {
self.data(id).name.as_ref()
}
#[must_use]
pub fn parent(&self, id: NodeId) -> Option<NodeId> {
self.data(id).parent
}
#[must_use]
pub fn children(&self, id: NodeId) -> &[NodeId] {
&self.data(id).children
}
#[must_use]
pub fn attributes(&self, id: NodeId) -> &[NodeId] {
&self.data(id).attributes
}
#[must_use]
pub fn namespaces(&self, id: NodeId) -> &[NodeId] {
&self.data(id).namespaces
}
#[must_use]
pub fn order(&self, id: NodeId) -> usize {
self.data(id).order
}
#[must_use]
pub fn value(&self, id: NodeId) -> &str {
&self.data(id).value
}
#[must_use]
pub fn string_value(&self, id: NodeId) -> String {
match self.kind(id) {
NodeKind::Root | NodeKind::Element => {
let mut out = String::new();
self.collect_text(id, &mut out);
out
}
_ => self.data(id).value.clone(),
}
}
fn collect_text(&self, id: NodeId, out: &mut String) {
for &child in self.children(id) {
match self.kind(child) {
NodeKind::Text => out.push_str(&self.data(child).value),
NodeKind::Element => self.collect_text(child, out),
_ => {}
}
}
}
#[must_use]
pub fn ancestors(&self, id: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
let mut current = self.parent(id);
while let Some(node) = current {
out.push(node);
current = self.parent(node);
}
out
}
#[must_use]
pub fn descendants_or_self(&self, id: NodeId) -> Vec<NodeId> {
let mut out = Vec::with_capacity(self.subtree_capacity(id));
self.push_descendants_or_self(id, &mut out);
out
}
#[must_use]
pub fn descendants(&self, id: NodeId) -> Vec<NodeId> {
let mut out = Vec::with_capacity(self.subtree_capacity(id));
for &child in self.children(id) {
self.push_descendants_or_self(child, &mut out);
}
out
}
fn subtree_capacity(&self, id: NodeId) -> usize {
self.subtree_end(id)
.saturating_sub(self.order(id))
.saturating_add(1)
}
fn push_descendants_or_self(&self, id: NodeId, out: &mut Vec<NodeId>) {
out.push(id);
for &child in self.children(id) {
self.push_descendants_or_self(child, out);
}
}
#[must_use]
pub fn all_nodes_in_document_order(&self) -> Vec<NodeId> {
let mut out = Vec::with_capacity(self.nodes.len());
self.push_all(self.root, &mut out);
out
}
fn push_all(&self, id: NodeId, out: &mut Vec<NodeId>) {
out.push(id);
out.extend_from_slice(self.namespaces(id));
out.extend_from_slice(self.attributes(id));
for &child in self.children(id) {
self.push_all(child, out);
}
}
#[must_use]
pub fn subtree_end(&self, id: NodeId) -> usize {
self.data(id).subtree_end
}
#[must_use]
pub fn is_descendant_of(&self, descendant: NodeId, ancestor: NodeId) -> bool {
let order = self.order(descendant);
order > self.order(ancestor) && order <= self.subtree_end(ancestor)
}
fn position_among_siblings(&self, id: NodeId) -> usize {
self.data(id).sibling_position
}
pub(crate) fn finalize(&mut self) {
for root in self.roots() {
self.finalize_subtree(root);
}
}
fn finalize_subtree(&mut self, id: NodeId) -> usize {
let mut end = self.data(id).order;
for &node in self
.data(id)
.namespaces
.iter()
.chain(self.data(id).attributes.iter())
{
end = end.max(self.data(node).order);
}
let mut counts: std::collections::HashMap<(NodeKind, Option<String>, String), usize> =
std::collections::HashMap::new();
let children = self.data(id).children.clone();
for child in children {
let key = {
let data = self.data(child);
let (uri, local) = data
.name
.as_ref()
.map_or((None, String::new()), |n| (n.uri.clone(), n.local.clone()));
(data.kind, uri, local)
};
let counter = counts.entry(key).or_insert(0);
*counter += 1;
self.nodes[child.0].sibling_position = *counter;
end = end.max(self.finalize_subtree(child));
}
self.nodes[id.0].subtree_end = end;
end
}
#[must_use]
pub fn location(&self, id: NodeId) -> String {
if self.kind(id) == NodeKind::Root {
return "/".to_string();
}
let mut chain = Vec::new();
let mut current = Some(id);
while let Some(node) = current {
if self.kind(node) == NodeKind::Root {
break;
}
chain.push(node);
current = self.parent(node);
}
let mut out = String::new();
for &node in chain.iter().rev() {
out.push('/');
self.push_location_step(node, &mut out);
}
out
}
fn push_location_step(&self, id: NodeId, out: &mut String) {
match self.kind(id) {
NodeKind::Root => out.push('/'),
NodeKind::Attribute => {
out.push('@');
match self.name(id) {
Some(name) => match name.uri.as_deref() {
Some(uri) => {
out.push_str("*[");
out.push_str(&name_predicate(&name.local, uri));
out.push(']');
}
None => out.push_str(&name.local),
},
None => out.push('*'),
}
}
NodeKind::Namespace => {
out.push_str("namespace::");
if let Some(name) = self.name(id) {
out.push_str(&name.local);
}
}
kind => {
match kind {
NodeKind::Element => match self.name(id) {
Some(name) => match name.uri.as_deref() {
Some(uri) => {
out.push_str("*[");
out.push_str(&name_predicate(&name.local, uri));
out.push(']');
}
None => out.push_str(&name.local),
},
None => out.push('*'),
},
NodeKind::Text => out.push_str("text()"),
NodeKind::Comment => out.push_str("comment()"),
NodeKind::ProcessingInstruction => out.push_str("processing-instruction()"),
NodeKind::Root | NodeKind::Attribute | NodeKind::Namespace => {
unreachable!("handled by the outer match arms")
}
}
out.push('[');
out.push_str(itoa(self.position_among_siblings(id)).as_str());
out.push(']');
}
}
}
}
fn itoa(value: usize) -> String {
let mut buffer = [0u8; 20];
let mut index = buffer.len();
let mut value = value;
loop {
index -= 1;
buffer[index] = b'0' + u8::try_from(value % 10).expect("a digit");
value /= 10;
if value == 0 {
break;
}
}
String::from_utf8_lossy(&buffer[index..]).into_owned()
}
fn name_predicate(local: &str, uri: &str) -> String {
format!(
"local-name()={} and namespace-uri()={}",
xpath_literal(local),
xpath_literal(uri)
)
}
fn xpath_literal(value: &str) -> String {
if value.contains('\'') {
format!("\"{value}\"")
} else {
format!("'{value}'")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn doc(source: &str) -> Document {
Document::from_str(source).unwrap()
}
#[test]
fn root_is_not_document_element() {
let d = doc("<a/>");
assert_ne!(d.root(), d.document_element().unwrap());
assert_eq!(d.kind(d.root()), NodeKind::Root);
}
#[test]
fn string_value_concatenates_descendant_text() {
let d = doc("<a>one<b>two</b>three</a>");
assert_eq!(d.string_value(d.document_element().unwrap()), "onetwothree");
}
#[test]
fn string_value_of_attribute_is_its_value() {
let d = doc("<a x='1'/>");
let a = d.document_element().unwrap();
let attr = d.attributes(a)[0];
assert_eq!(d.string_value(attr), "1");
}
#[test]
fn attributes_are_not_children() {
let d = doc("<a x='1'><b/></a>");
let a = d.document_element().unwrap();
assert_eq!(d.children(a).len(), 1);
assert_eq!(d.attributes(a).len(), 1);
}
#[test]
fn document_order_puts_attributes_before_children() {
let d = doc("<a x='1'><b/></a>");
let a = d.document_element().unwrap();
let attr = d.attributes(a)[0];
let b = d.children(a)[0];
assert!(d.order(attr) < d.order(b));
}
#[test]
fn location_uses_positional_predicates() {
let d = doc("<a><b/><b/></a>");
let a = d.document_element().unwrap();
assert_eq!(d.location(d.children(a)[1]), "/a[1]/b[2]");
}
#[test]
fn appending_a_document_keeps_the_two_trees_separate() {
let mut primary = doc("<a><b/></a>");
let before = primary.all_nodes_in_document_order().len();
let root = primary.append_document(&doc("<x><y/></x>"));
assert_eq!(primary.kind(root), NodeKind::Root);
assert_eq!(primary.parent(root), None);
assert_eq!(primary.roots().len(), 2);
let a = primary.document_element().unwrap();
assert_eq!(primary.name(a).unwrap().local, "a");
assert_eq!(primary.all_nodes_in_document_order().len(), before);
}
#[test]
fn root_of_finds_the_document_a_node_belongs_to() {
let mut primary = doc("<a><b/></a>");
let a = primary.document_element().unwrap();
let b = primary.children(a)[0];
let root = primary.append_document(&doc("<x><y/></x>"));
let x = primary.children(root)[0];
let y = primary.children(x)[0];
assert_eq!(primary.root_of(b), primary.root());
assert_eq!(primary.root_of(y), root);
assert_ne!(primary.root_of(y), primary.root_of(b));
}
#[test]
fn appended_documents_keep_document_order_increasing() {
let mut primary = doc("<a/>");
let highest = primary
.all_nodes_in_document_order()
.iter()
.map(|&n| primary.order(n))
.max()
.unwrap();
let root = primary.append_document(&doc("<x/>"));
assert!(primary.order(root) > highest);
}
#[test]
fn appended_documents_are_finalized() {
let mut primary = doc("<a/>");
let root = primary.append_document(&doc("<x><y/><y/></x>"));
let x = primary.children(root)[0];
let second = primary.children(x)[1];
assert_eq!(primary.location(second), "/x[1]/y[2]");
assert!(primary.is_descendant_of(second, root));
assert!(!primary.is_descendant_of(second, primary.root()));
}
#[test]
fn appending_copies_attributes_and_namespaces() {
let mut primary = doc("<a/>");
let root = primary.append_document(&doc(r#"<x xmlns:p="urn:p" p:q="1"/>"#));
let x = primary.children(root)[0];
assert_eq!(primary.attributes(x).len(), 1);
let attribute = primary.attributes(x)[0];
assert_eq!(primary.value(attribute), "1");
assert_eq!(primary.name(attribute).unwrap().uri.as_deref(), Some("urn:p"));
assert!(!primary.namespaces(x).is_empty());
}
#[test]
fn location_of_attribute() {
let d = doc("<a><b q='2'/></a>");
let a = d.document_element().unwrap();
let b = d.children(a)[0];
let attr = d.attributes(b)[0];
assert_eq!(d.location(attr), "/a[1]/b[1]/@q");
}
}