use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub(crate) usize);
impl NodeId {
#[must_use]
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "node#{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeKind {
Root,
Element,
Attribute,
Namespace,
Text,
Comment,
ProcessingInstruction,
}
impl NodeKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
NodeKind::Root => "root",
NodeKind::Element => "element",
NodeKind::Attribute => "attribute",
NodeKind::Namespace => "namespace",
NodeKind::Text => "text",
NodeKind::Comment => "comment",
NodeKind::ProcessingInstruction => "processing-instruction",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QName {
pub prefix: Option<String>,
pub local: String,
pub uri: Option<String>,
}
impl QName {
pub fn new(
prefix: Option<impl Into<String>>,
local: impl Into<String>,
uri: Option<impl Into<String>>,
) -> Self {
Self {
prefix: prefix.map(Into::into),
local: local.into(),
uri: uri.map(Into::into),
}
}
pub fn local(local: impl Into<String>) -> Self {
Self {
prefix: None,
local: local.into(),
uri: None,
}
}
#[must_use]
pub fn matches(&self, other: &QName) -> bool {
self.local == other.local && self.uri == other.uri
}
#[must_use]
pub fn matches_parts(&self, uri: Option<&str>, local: &str) -> bool {
self.local == local && self.uri.as_deref() == uri
}
#[must_use]
pub fn display_name(&self) -> String {
match &self.prefix {
Some(p) if !p.is_empty() => format!("{p}:{}", self.local),
_ => self.local.clone(),
}
}
}
impl fmt::Display for QName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.display_name())
}
}
#[derive(Debug, Clone)]
pub(crate) struct NodeData {
pub(crate) kind: NodeKind,
pub(crate) parent: Option<NodeId>,
pub(crate) children: Vec<NodeId>,
pub(crate) attributes: Vec<NodeId>,
pub(crate) namespaces: Vec<NodeId>,
pub(crate) name: Option<QName>,
pub(crate) value: String,
pub(crate) order: usize,
pub(crate) subtree_end: usize,
pub(crate) sibling_position: usize,
}
impl NodeData {
pub(crate) fn new(kind: NodeKind, parent: Option<NodeId>, order: usize) -> Self {
Self {
kind,
parent,
children: Vec::new(),
attributes: Vec::new(),
namespaces: Vec::new(),
name: None,
value: String::new(),
order,
subtree_end: order,
sibling_position: 1,
}
}
}
pub const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
pub const XMLNS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns/";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn qname_matches_ignores_prefix() {
let a = QName::new(Some("p"), "x", Some("urn:n"));
let b = QName::new(Some("q"), "x", Some("urn:n"));
assert!(a.matches(&b));
}
#[test]
fn qname_matches_distinguishes_namespace() {
let a = QName::new(Some("p"), "x", Some("urn:n"));
let b = QName::local("x");
assert!(!a.matches(&b));
}
#[test]
fn qname_display_name_uses_prefix() {
assert_eq!(QName::new(Some("p"), "x", Some("urn:n")).display_name(), "p:x");
assert_eq!(QName::local("x").display_name(), "x");
}
#[test]
fn node_kind_as_str() {
assert_eq!(NodeKind::Element.as_str(), "element");
assert_eq!(NodeKind::ProcessingInstruction.as_str(), "processing-instruction");
}
}