use std::num::NonZeroU32;
use std::sync::Arc;
use smallvec::SmallVec;
use crate::namespace::Namespace;
pub type NodeId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeType {
Document,
Element,
Text,
CData,
Comment,
ProcessingInstruction,
Attribute,
Namespace,
}
impl NodeType {
pub fn can_have_children(&self) -> bool {
matches!(self, NodeType::Document | NodeType::Element)
}
}
#[derive(Debug)]
pub(crate) struct NodeData {
pub node_type: NodeType,
pub name: Arc<str>,
pub prefix: Option<Arc<str>>,
pub namespace_uri: Option<Arc<str>>,
pub content: Option<String>,
pub extra: Option<Box<NodeExtra>>,
parent_plus1: Option<NonZeroU32>,
pub children: SmallVec<[u32; 4]>,
pub line: Option<NonZeroU32>,
pub column: Option<NonZeroU32>,
}
#[derive(Debug, Clone)]
pub(crate) struct Attr {
pub name: Arc<str>,
pub value: Box<str>,
pub prefix: Option<Arc<str>>,
pub ns_uri: Option<Arc<str>>,
}
#[derive(Debug, Default)]
pub(crate) struct NodeExtra {
pub attributes: SmallVec<[Attr; 1]>,
pub namespace_decls: Vec<Namespace>,
}
impl NodeData {
pub fn attrs(&self) -> &[Attr] {
self.extra
.as_deref()
.map(|e| e.attributes.as_slice())
.unwrap_or(&[])
}
pub fn attr(&self, name: &str) -> Option<&str> {
self.attrs()
.iter()
.find(|a| a.name.as_ref() == name)
.map(|a| a.value.as_ref())
}
pub fn set_attr(&mut self, attr: Attr) {
let attrs = &mut self.extra.get_or_insert_default().attributes;
match attrs.iter_mut().find(|a| a.name == attr.name) {
Some(existing) => *existing = attr,
None => attrs.push(attr),
}
}
pub fn remove_attr(&mut self, name: &str) -> Option<Box<str>> {
let attrs = &mut self.extra.as_deref_mut()?.attributes;
let pos = attrs.iter().position(|a| a.name.as_ref() == name)?;
Some(attrs.remove(pos).value)
}
pub fn attr_ns_info(&self, local_name: &str) -> Option<(&str, &str)> {
self.attrs()
.iter()
.find(|a| a.name.as_ref() == local_name)
.and_then(|a| Some((a.prefix.as_deref()?, a.ns_uri.as_deref()?)))
}
pub fn ns_decls(&self) -> &[Namespace] {
self.extra
.as_deref()
.map(|e| e.namespace_decls.as_slice())
.unwrap_or(&[])
}
pub fn ns_decls_mut(&mut self) -> &mut Vec<Namespace> {
&mut self.extra.get_or_insert_default().namespace_decls
}
pub fn parent(&self) -> Option<NodeId> {
self.parent_plus1.map(|p| (p.get() - 1) as NodeId)
}
pub fn set_parent(&mut self, id: Option<NodeId>) {
self.parent_plus1 = id.map(|i| {
NonZeroU32::new(u32::try_from(i + 1).expect("document exceeds u32::MAX nodes"))
.expect("id + 1 is nonzero")
});
}
pub fn push_child(&mut self, id: NodeId) {
self.children
.push(u32::try_from(id).expect("document exceeds u32::MAX nodes"));
}
pub fn child_ids(&self) -> impl DoubleEndedIterator<Item = NodeId> + ExactSizeIterator + '_ {
self.children.iter().map(|&c| c as NodeId)
}
pub fn document() -> Self {
Self {
node_type: NodeType::Document,
name: Arc::from(""),
prefix: None,
namespace_uri: None,
content: None,
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn element(
name: Arc<str>,
prefix: Option<Arc<str>>,
namespace_uri: Option<Arc<str>>,
) -> Self {
Self {
node_type: NodeType::Element,
name,
prefix,
namespace_uri,
content: None,
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn text(content: String) -> Self {
Self {
node_type: NodeType::Text,
name: Arc::from(""),
prefix: None,
namespace_uri: None,
content: Some(content),
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn cdata(content: String) -> Self {
Self {
node_type: NodeType::CData,
name: Arc::from(""),
prefix: None,
namespace_uri: None,
content: Some(content),
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn comment(content: String) -> Self {
Self {
node_type: NodeType::Comment,
name: Arc::from(""),
prefix: None,
namespace_uri: None,
content: Some(content),
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn processing_instruction(target: String, content: Option<String>) -> Self {
Self {
node_type: NodeType::ProcessingInstruction,
name: Arc::from(target.as_str()),
prefix: None,
namespace_uri: None,
content,
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn attribute(
name: String,
value: String,
prefix: Option<String>,
namespace_uri: Option<String>,
) -> Self {
Self {
node_type: NodeType::Attribute,
name: Arc::from(name.as_str()),
prefix: prefix.map(|p| Arc::from(p.as_str())),
namespace_uri: namespace_uri.map(|n| Arc::from(n.as_str())),
content: Some(value),
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn namespace_node(prefix: String, uri: String) -> Self {
Self {
node_type: NodeType::Namespace,
name: Arc::from(prefix.as_str()),
prefix: None,
namespace_uri: None,
content: Some(uri),
extra: None,
parent_plus1: None,
children: SmallVec::new(),
line: None,
column: None,
}
}
pub fn qname(&self) -> String {
match &self.prefix {
Some(p) if !p.is_empty() => format!("{}:{}", p, self.name),
_ => self.name.to_string(),
}
}
}
#[cfg(test)]
mod size_tests {
#[test]
fn node_data_stays_compact() {
assert!(
std::mem::size_of::<super::NodeData>() <= 128,
"NodeData grew to {} bytes; keep rarely-used fields in NodeExtra",
std::mem::size_of::<super::NodeData>()
);
println!("NodeData: {} bytes", std::mem::size_of::<super::NodeData>());
}
}