#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::num::NonZeroU32;
use core::ops::Range;
use crate::xml::dom::storage::{Native, Storage, Values};
use alloc::vec::Vec;
mod parse;
mod tokenizer;
#[cfg(test)]
mod tokenizer_tests;
#[cfg(test)]
mod documentation_tests;
pub use crate::parser_roxmltree::parse::*;
pub const NS_XML_URI: &str = "http://www.w3.org/XML/1998/namespace";
const NS_XML_PREFIX: &str = "xml";
pub const NS_XMLNS_URI: &str = "http://www.w3.org/2000/xmlns/";
const XMLNS: &str = "xmlns";
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct TextPos {
pub row: u32,
pub col: u32,
}
impl TextPos {
pub fn new(row: u32, col: u32) -> TextPos {
TextPos { row, col }
}
}
impl fmt::Display for TextPos {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.row, self.col)
}
}
pub struct Document<'input, S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native>
{
text: &'input str,
nodes: S::Values<NodeData<'input, S>>,
attributes: S::Values<AttributeData<'input, S>>,
namespaces: Namespaces<'input, S>,
}
impl<'input, S: crate::xml::dom::storage::Storage> Document<'input, S> {
#[inline]
pub fn root<'a>(&'a self) -> Node<'a, 'input, S> {
Node {
id: NodeId::new(0),
d: &self.nodes[0],
doc: self,
}
}
#[inline]
pub fn get_node<'a>(&'a self, id: NodeId) -> Option<Node<'a, 'input, S>> {
self.nodes.get(id.get_usize()).map(|data| Node {
id,
d: data,
doc: self,
})
}
#[inline]
pub fn root_element<'a>(&'a self) -> Node<'a, 'input, S> {
self.root()
.first_element_child()
.expect("XML documents must contain a root element")
}
#[inline]
pub fn descendants(&self) -> Descendants<'_, 'input, S> {
self.root().descendants()
}
#[inline]
pub fn text_pos_at(&self, pos: usize) -> TextPos {
tokenizer::Stream::new(self.text).gen_text_pos_from(pos)
}
#[inline]
pub fn input_text(&self) -> &'input str {
self.text
}
}
impl<'input, S: crate::xml::dom::storage::Storage> fmt::Debug for Document<'input, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if !self.root().has_children() {
return write!(f, "Document []");
}
macro_rules! writeln_indented {
($depth:expr, $f:expr, $fmt:expr) => {
for _ in 0..$depth { write!($f, " ")?; }
writeln!($f, $fmt)?;
};
($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => {
for _ in 0..$depth { write!($f, " ")?; }
writeln!($f, $fmt, $($arg)*)?;
};
}
fn print_into_iter<
T: fmt::Debug,
E: ExactSizeIterator<Item = T>,
I: IntoIterator<Item = T, IntoIter = E>,
>(
prefix: &str,
data: I,
depth: usize,
f: &mut fmt::Formatter,
) -> Result<(), fmt::Error> {
let data = data.into_iter();
if data.len() == 0 {
return Ok(());
}
writeln_indented!(depth, f, "{}: [", prefix);
for v in data {
writeln_indented!(depth + 1, f, "{:?}", v);
}
writeln_indented!(depth, f, "]");
Ok(())
}
fn print_children<S: crate::xml::dom::storage::Storage>(
parent: Node<'_, '_, S>,
depth: usize,
f: &mut fmt::Formatter,
) -> Result<(), fmt::Error> {
for child in parent.children() {
if child.is_element() {
writeln_indented!(depth, f, "Element {{");
writeln_indented!(depth, f, " tag_name: {:?}", child.tag_name());
print_into_iter("attributes", child.attributes(), depth + 1, f)?;
print_into_iter("namespaces", child.namespaces(), depth + 1, f)?;
if child.has_children() {
writeln_indented!(depth, f, " children: [");
print_children(child, depth + 2, f)?;
writeln_indented!(depth, f, " ]");
}
writeln_indented!(depth, f, "}}");
} else {
writeln_indented!(depth, f, "{:?}", child);
}
}
Ok(())
}
writeln!(f, "Document [")?;
print_children(self.root(), 1, f)?;
writeln!(f, "]")?;
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NodeType {
Root,
Element,
PI,
Comment,
Text,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(missing_docs)]
pub struct PI<'input> {
pub target: &'input str,
pub value: Option<&'input str>,
}
#[derive(Clone, Copy, Debug)]
struct ShortRange {
start: u32,
end: u32,
}
impl From<Range<usize>> for ShortRange {
#[inline]
fn from(range: Range<usize>) -> Self {
debug_assert!(range.start <= u32::MAX as usize);
debug_assert!(range.end <= u32::MAX as usize);
ShortRange::new(range.start as u32, range.end as u32)
}
}
impl ShortRange {
#[inline]
fn new(start: u32, end: u32) -> Self {
ShortRange { start, end }
}
#[inline]
fn to_urange(self) -> Range<usize> {
self.start as usize..self.end as usize
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct NodeId(NonZeroU32);
impl NodeId {
#[inline]
pub fn new(id: u32) -> Self {
debug_assert!(id < u32::MAX);
NodeId(NonZeroU32::new(id + 1).expect("node IDs are below the u32 arena limit"))
}
#[inline]
pub fn get(self) -> u32 {
self.0.get() - 1
}
#[inline]
pub fn get_usize(self) -> usize {
self.get() as usize
}
}
impl From<u32> for NodeId {
#[inline]
fn from(id: u32) -> Self {
NodeId::new(id)
}
}
impl From<usize> for NodeId {
#[inline]
fn from(id: usize) -> Self {
debug_assert!(id <= u32::MAX as usize);
NodeId::new(id as u32)
}
}
#[derive(Debug)]
enum NodeKind<'input, S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native> {
Root,
Element {
tag_name: ExpandedNameIndexed<'input>,
attributes: ShortRange,
namespaces: ShortRange,
},
PI(PI<'input>),
Comment(StringStorage<'input, S>),
Text(StringStorage<'input, S>),
}
#[derive(Debug)]
struct NodeData<'input, S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native> {
parent: Option<NodeId>,
prev_sibling: Option<NodeId>,
next_subtree: Option<NodeId>,
last_child: Option<NodeId>,
kind: NodeKind<'input, S>,
#[cfg(feature = "xml-backend-roxmltree")]
range: Range<usize>,
}
#[derive(Debug)]
pub enum StringStorage<
'input,
S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native,
> {
Borrowed(&'input str),
Owned(S::Text),
}
impl<S: crate::xml::dom::storage::Storage> StringStorage<'_, S> {
fn from_owned(s: S::Text) -> Self {
Self::Owned(s)
}
pub fn as_str(&self) -> &str {
match self {
StringStorage::Borrowed(s) => s,
StringStorage::Owned(s) => s,
}
}
}
impl<S: crate::xml::dom::storage::Storage> PartialEq for StringStorage<'_, S> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<S: crate::xml::dom::storage::Storage> Eq for StringStorage<'_, S> {}
impl<S: crate::xml::dom::storage::Storage> core::fmt::Display for StringStorage<'_, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<S: crate::xml::dom::storage::Storage> core::ops::Deref for StringStorage<'_, S> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
#[cfg(test)]
mod owned_text_tests {
use super::*;
#[cfg(feature = "xmldsig")]
#[test]
fn scoped_parser_retains_real_tree_storage_and_releases_failures() {
use crate::xml::dom::storage::Accounted;
use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
let source = "<r xmlns:p='urn:p' p:a='one two'>left<![CDATA[middle]]>right</r>";
for capacity in [0, 1, 127, 1024, 1 << 20] {
let memory = Memory::new(std::sync::Arc::new(MemoryAccount::new(capacity, 1 << 20)));
let parsed =
Document::parse_with_storage(source, ParsingOptions::default(), Accounted(&memory));
match parsed {
Ok(document) => {
assert_eq!(document.root_element().text(), Some("leftmiddleright"));
assert_eq!(
document.root_element().attribute(("urn:p", "a")),
Some("one two")
);
assert!(memory.used() > 0);
drop(document);
}
Err(Error::SharedExecution(MemoryError::LimitExceeded)) => {
assert!(capacity < 1 << 20);
}
Err(error) => panic!("unexpected parser error: {error}"),
}
assert_eq!(memory.used(), 0);
}
}
#[cfg(feature = "xmldsig")]
#[test]
fn scoped_parser_keeps_dynamic_error_labels_in_the_current_account() {
use crate::xml::dom::storage::Accounted;
use structured_asn1::memory::{Memory, MemoryAccount};
let memory = Memory::new(std::sync::Arc::new(MemoryAccount::new(1 << 20, 1 << 20)));
let error = Document::parse_with_storage(
"<unbound:root/>",
ParsingOptions::default(),
Accounted(&memory),
)
.unwrap_err();
assert!(matches!(&error, Error::UnknownNamespace(name, _) if &**name == "unbound"));
assert!(memory.used() > 0);
drop(error);
assert_eq!(memory.used(), 0);
}
#[cfg(feature = "xmldsig")]
#[test]
fn scoped_parser_cannot_erase_the_current_query_work_ceiling() {
use crate::xml::dom::storage::Accounted;
use structured_asn1::{
Encoding, Limits, Parser,
memory::{Memory, MemoryAccount, MemoryError},
};
let memory = Memory::new(std::sync::Arc::new(MemoryAccount::new(1 << 20, 1 << 20)));
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: 0,
..Default::default()
},
memory.clone(),
);
let reader = parser.read(&[], 0).unwrap();
for source in ["<r/>", "<unbound:root/>"] {
assert!(matches!(
Document::parse_with_storage(
source,
ParsingOptions::default(),
Accounted(&reader.execution())
),
Err(Error::SharedExecution(MemoryError::WorkExceeded))
));
}
assert_eq!(memory.used(), 0);
assert_eq!(memory.work_used(), 0);
}
#[test]
fn normalization_buffer_is_adopted_without_another_allocation() {
let source = alloc::string::String::from("normalized text");
let pointer = source.as_ptr();
let stored: StringStorage<'_> = StringStorage::from_owned(source);
assert_eq!(stored.as_str(), "normalized text");
assert_eq!(stored.as_str().as_ptr(), pointer);
}
#[test]
fn moving_text_buffers_preserves_normalization_and_coalescing() {
for (xml, expected) in [
(
"<root>left<![CDATA[middle]]>right</root>",
"leftmiddleright",
),
("<root>left right</root>", "left right"),
("<root>line\r\nnext</root>", "line\nnext"),
("<root><![CDATA[]]>after</root>", "after"),
(
"<!DOCTYPE root [<!ENTITY e 'entity'>]><root>before&e;after</root>",
"beforeentityafter",
),
] {
let document = Document::parse_with_options(
xml,
ParsingOptions {
allow_dtd: true,
..ParsingOptions::default()
},
)
.expect("fixed normalization fixture");
assert_eq!(document.root_element().text(), Some(expected));
assert_eq!(document.root_element().children().count(), 1);
}
let document = Document::parse("<root a='line\r\nnext
end'/>")
.expect("fixed attribute normalization fixture");
assert_eq!(
document.root_element().attribute("a"),
Some("line next\nend")
);
}
}
#[derive(Debug)]
struct AttributeData<
'input,
S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native,
> {
name: ExpandedNameIndexed<'input>,
value: StringStorage<'input, S>,
#[cfg(feature = "xml-backend-roxmltree")]
range: Range<usize>,
#[cfg(feature = "xml-backend-roxmltree")]
qname_len: u16,
#[cfg(feature = "xml-backend-roxmltree")]
eq_len: u8, }
pub struct Attribute<
'a,
'input: 'a,
S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native,
> {
doc: &'a Document<'input, S>,
data: &'a AttributeData<'input, S>,
}
impl<S: crate::xml::dom::storage::Storage> Copy for Attribute<'_, '_, S> {}
impl<S: crate::xml::dom::storage::Storage> Clone for Attribute<'_, '_, S> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, 'input, S: crate::xml::dom::storage::Storage> Attribute<'a, 'input, S> {
#[inline]
pub fn namespace(&self) -> Option<&'a str> {
self.data.name.namespace(self.doc).map(Namespace::uri)
}
#[inline]
pub fn name(&self) -> &'input str {
self.data.name.local_name
}
#[inline]
pub fn value(&self) -> &'a str {
&self.data.value
}
#[inline]
pub fn value_storage(&self) -> &StringStorage<'input, S> {
&self.data.value
}
#[deprecated(note = "replaced by `range`")]
#[cfg(feature = "xml-backend-roxmltree")]
#[inline]
pub fn position(&self) -> usize {
self.data.range.start
}
#[cfg(feature = "xml-backend-roxmltree")]
#[inline]
pub fn range(&self) -> Range<usize> {
self.data.range.clone()
}
#[cfg(feature = "xml-backend-roxmltree")]
#[inline]
pub fn range_qname(&self) -> Range<usize> {
let end = self.data.range.start + usize::from(self.data.qname_len);
self.data.range.start..end
}
#[cfg(feature = "xml-backend-roxmltree")]
#[inline]
pub fn range_value(&self) -> Range<usize> {
let start = self.data.range.start
+ usize::from(self.data.qname_len)
+ usize::from(self.data.eq_len)
+ 1;
let end = self.data.range.end - 1;
start..end
}
}
impl<S: crate::xml::dom::storage::Storage> PartialEq for Attribute<'_, '_, S> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.data.name.as_expanded_name(self.doc) == other.data.name.as_expanded_name(other.doc)
&& self.data.value == other.data.value
}
}
impl<S: crate::xml::dom::storage::Storage> fmt::Debug for Attribute<'_, '_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Attribute {{ name: {:?}, value: {:?} }}",
self.data.name.as_expanded_name(self.doc),
self.data.value
)
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct Namespace<
'input,
S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native,
> {
name: Option<&'input str>,
uri: StringStorage<'input, S>,
}
impl<'input, S: crate::xml::dom::storage::Storage> Namespace<'input, S> {
#[inline]
pub fn name(&self) -> Option<&'input str> {
self.name
}
#[inline]
pub fn uri(&self) -> &str {
self.uri.as_ref()
}
}
struct Namespaces<'input, S: crate::xml::dom::storage::Storage = crate::xml::dom::storage::Native> {
values: S::Values<Namespace<'input, S>>,
tree_order: S::Values<NamespaceIdx>,
sorted_order: S::Values<NamespaceIdx>,
storage: S,
}
impl<'input, S: crate::xml::dom::storage::Storage> Namespaces<'input, S> {
fn new(storage: S) -> Result<Self, Error<S::Text>> {
Ok(Self {
values: storage.values(0)?,
tree_order: storage.values(0)?,
sorted_order: storage.values(0)?,
storage,
})
}
fn push_ns(
&mut self,
name: Option<&'input str>,
uri: StringStorage<'input, S>,
) -> Result<(), Error<S::Text>> {
debug_assert_ne!(name, Some(""));
let mut failure = None;
let search = self.sorted_order.binary_search_by(|idx| {
if failure.is_some() {
return Ordering::Equal;
}
if let Err(error) = self
.storage
.charge(1 + name.map_or(0, str::len) + uri.len())
{
failure = Some(error);
return Ordering::Equal;
}
let value = &self.values[idx.0 as usize];
(value.name, value.uri.as_ref()).cmp(&(name, uri.as_str()))
});
if let Some(error) = failure {
return Err(error.into());
}
let idx = match search {
Ok(sorted_idx) => self.sorted_order[sorted_idx],
Err(sorted_idx) => {
if self.values.len() > u16::MAX as usize {
return Err(Error::NamespacesLimitReached);
}
let idx = NamespaceIdx(self.values.len() as u16);
self.values.push(Namespace { name, uri })?;
self.storage.charge(
(self.sorted_order.len() - sorted_idx) * core::mem::size_of::<NamespaceIdx>(),
)?;
self.sorted_order.insert_copy(sorted_idx, idx)?;
idx
}
};
self.tree_order.push(idx)?;
Ok(())
}
#[inline]
fn push_ref(&mut self, tree_idx: usize) -> Result<(), Error<S::Text>> {
let idx = self.tree_order[tree_idx];
self.tree_order.push(idx)?;
Ok(())
}
#[inline]
fn exists(&self, start: usize, prefix: Option<&str>) -> Result<bool, Error<S::Text>> {
for idx in &self.tree_order[start..] {
self.storage.charge(1 + prefix.map_or(0, str::len))?;
if self.values[idx.0 as usize].name == prefix {
return Ok(true);
}
}
Ok(false)
}
fn shrink_to_fit(&mut self) -> Result<(), Error<S::Text>> {
self.storage.finish_values(&mut self.values);
self.storage.finish_values(&mut self.tree_order);
self.sorted_order = self.storage.values(0)?;
Ok(())
}
#[inline]
fn get(&self, idx: NamespaceIdx) -> &Namespace<'input, S> {
&self.values[idx.0 as usize]
}
}
impl Default for Namespaces<'_> {
fn default() -> Self {
Self::new(Native).expect("empty native namespace storage cannot allocate")
}
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
struct NamespaceIdx(u16);
#[derive(Clone, Copy, Debug)]
struct ExpandedNameIndexed<'input> {
namespace_idx: Option<NamespaceIdx>,
local_name: &'input str,
}
impl<'input> ExpandedNameIndexed<'input> {
#[inline]
fn namespace<'a, S: crate::xml::dom::storage::Storage>(
&self,
doc: &'a Document<'input, S>,
) -> Option<&'a Namespace<'input, S>> {
self.namespace_idx.map(|idx| doc.namespaces.get(idx))
}
#[inline]
fn as_expanded_name<'a, S: crate::xml::dom::storage::Storage>(
&self,
doc: &'a Document<'input, S>,
) -> ExpandedName<'a, 'input> {
ExpandedName {
uri: self.namespace(doc).map(Namespace::uri),
name: self.local_name,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ExpandedName<'a, 'b> {
uri: Option<&'a str>,
name: &'b str,
}
impl<'a, 'b> ExpandedName<'a, 'b> {
#[inline]
pub fn namespace(&self) -> Option<&'a str> {
self.uri
}
#[inline]
pub fn name(&self) -> &'b str {
self.name
}
}
impl ExpandedName<'static, 'static> {
pub const fn from_static(uri: &'static str, name: &'static str) -> Self {
Self {
uri: Some(uri),
name,
}
}
}
impl fmt::Debug for ExpandedName<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.namespace() {
Some(ns) => write!(f, "{{{}}}{}", ns, self.name),
None => write!(f, "{}", self.name),
}
}
}
impl<'a, 'b> From<&'b str> for ExpandedName<'a, 'b> {
#[inline]
fn from(v: &'b str) -> Self {
ExpandedName { uri: None, name: v }
}
}
impl<'a, 'b> From<(&'a str, &'b str)> for ExpandedName<'a, 'b> {
#[inline]
fn from(v: (&'a str, &'b str)) -> Self {
ExpandedName {
uri: Some(v.0),
name: v.1,
}
}
}
#[derive(Clone, Copy)]
pub struct Node<'a, 'input: 'a, S: Storage = Native> {
id: NodeId,
doc: &'a Document<'input, S>,
d: &'a NodeData<'input, S>,
}
impl<S: Storage> Eq for Node<'_, '_, S> {}
impl<S: Storage> PartialEq for Node<'_, '_, S> {
#[inline]
fn eq(&self, other: &Self) -> bool {
(self.id, self.doc as *const _) == (other.id, other.doc as *const _)
}
}
impl<S: Storage> PartialOrd for Node<'_, '_, S> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<S: Storage> Ord for Node<'_, '_, S> {
fn cmp(&self, other: &Self) -> Ordering {
(self.id.0, self.doc as *const _).cmp(&(other.id.0, other.doc as *const _))
}
}
impl<S: Storage> Hash for Node<'_, '_, S> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.0.hash(state);
core::ptr::from_ref(self.doc).hash(state);
core::ptr::from_ref(self.d).hash(state);
}
}
impl<'a, 'input: 'a, S: Storage> Node<'a, 'input, S> {
#[inline]
pub fn node_type(&self) -> NodeType {
match self.d.kind {
NodeKind::Root => NodeType::Root,
NodeKind::Element { .. } => NodeType::Element,
NodeKind::PI { .. } => NodeType::PI,
NodeKind::Comment(_) => NodeType::Comment,
NodeKind::Text(_) => NodeType::Text,
}
}
#[inline]
pub fn is_root(&self) -> bool {
self.node_type() == NodeType::Root
}
#[inline]
pub fn is_element(&self) -> bool {
self.node_type() == NodeType::Element
}
#[inline]
pub fn is_pi(&self) -> bool {
self.node_type() == NodeType::PI
}
#[inline]
pub fn is_comment(&self) -> bool {
self.node_type() == NodeType::Comment
}
#[inline]
pub fn is_text(&self) -> bool {
self.node_type() == NodeType::Text
}
#[inline]
pub fn document(&self) -> &'a Document<'input, S> {
self.doc
}
#[inline]
pub fn tag_name(&self) -> ExpandedName<'a, 'input> {
match self.d.kind {
NodeKind::Element { ref tag_name, .. } => tag_name.as_expanded_name(self.doc),
_ => "".into(),
}
}
pub fn has_tag_name<'n, 'm, N>(&self, name: N) -> bool
where
N: Into<ExpandedName<'n, 'm>>,
{
let name = name.into();
match self.d.kind {
NodeKind::Element { ref tag_name, .. } => match name.namespace() {
Some(_) => tag_name.as_expanded_name(self.doc) == name,
None => tag_name.local_name == name.name,
},
_ => false,
}
}
pub fn default_namespace(&self) -> Option<&'a str> {
self.namespaces()
.find(|ns| ns.name.is_none())
.map(|v| v.uri.as_ref())
}
pub fn lookup_prefix(&self, uri: &str) -> Option<&'input str> {
if uri == NS_XML_URI {
return Some(NS_XML_PREFIX);
}
self.namespaces()
.find(|ns| &*ns.uri == uri)
.map(|v| v.name)
.unwrap_or(None)
}
pub fn lookup_namespace_uri(&self, prefix: Option<&str>) -> Option<&'a str> {
self.namespaces()
.find(|ns| ns.name == prefix)
.map(|v| v.uri.as_ref())
}
pub fn attribute<'n, 'm, N>(&self, name: N) -> Option<&'a str>
where
N: Into<ExpandedName<'n, 'm>>,
{
self.attribute_node(name).map(|a| a.value())
}
pub fn attribute_node<'n, 'm, N>(&self, name: N) -> Option<Attribute<'a, 'input, S>>
where
N: Into<ExpandedName<'n, 'm>>,
{
let name = name.into();
match name.namespace() {
Some(_) => self
.attributes()
.find(|a| a.data.name.as_expanded_name(self.doc) == name),
None => self
.attributes()
.find(|a| a.data.name.local_name == name.name),
}
}
pub fn has_attribute<'n, 'm, N>(&self, name: N) -> bool
where
N: Into<ExpandedName<'n, 'm>>,
{
self.attribute_node(name).is_some()
}
#[inline]
pub fn attributes(&self) -> Attributes<'a, 'input, S> {
Attributes::new(self)
}
#[inline]
pub fn namespaces(&self) -> NamespaceIter<'a, 'input, S> {
let namespaces = match self.d.kind {
NodeKind::Element { ref namespaces, .. } => {
&self.doc.namespaces.tree_order[namespaces.to_urange()]
}
_ => &[],
};
NamespaceIter {
doc: self.doc,
namespaces: namespaces.iter(),
}
}
#[inline]
pub fn text(&self) -> Option<&'a str> {
self.text_storage().map(|s| s.as_str())
}
pub fn text_storage(&self) -> Option<&'a StringStorage<'input, S>> {
match self.d.kind {
NodeKind::Element { .. } => match self.first_child() {
Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind {
NodeKind::Text(ref text) => Some(text),
_ => None,
},
_ => None,
},
NodeKind::Comment(ref text) => Some(text),
NodeKind::Text(ref text) => Some(text),
_ => None,
}
}
#[inline]
pub fn tail(&self) -> Option<&'a str> {
self.tail_storage().map(|s| s.as_str())
}
pub fn tail_storage(&self) -> Option<&'a StringStorage<'input, S>> {
if !self.is_element() {
return None;
}
match self.next_sibling().map(|n| n.id) {
Some(id) => match self.doc.nodes[id.get_usize()].kind {
NodeKind::Text(ref text) => Some(text),
_ => None,
},
None => None,
}
}
#[inline]
pub fn pi(&self) -> Option<PI<'input>> {
match self.d.kind {
NodeKind::PI(pi) => Some(pi),
_ => None,
}
}
#[inline]
pub fn parent(&self) -> Option<Self> {
self.d
.parent
.map(|id| self.doc.get_node(id).expect("parent belongs to this arena"))
}
pub fn parent_element(&self) -> Option<Self> {
self.ancestors().skip(1).find(|n| n.is_element())
}
#[inline]
pub fn prev_sibling(&self) -> Option<Self> {
self.d.prev_sibling.map(|id| {
self.doc
.get_node(id)
.expect("previous sibling belongs to this arena")
})
}
pub fn prev_sibling_element(&self) -> Option<Self> {
self.prev_siblings().skip(1).find(|n| n.is_element())
}
#[inline]
pub fn next_sibling(&self) -> Option<Self> {
self.d
.next_subtree
.map(|id| {
self.doc
.get_node(id)
.expect("next subtree belongs to this arena")
})
.and_then(|node| {
let possibly_self = node
.d
.prev_sibling
.expect("next_subtree will always have a previous sibling");
if possibly_self == self.id {
Some(node)
} else {
None
}
})
}
pub fn next_sibling_element(&self) -> Option<Self> {
self.next_siblings().skip(1).find(|n| n.is_element())
}
#[inline]
pub fn first_child(&self) -> Option<Self> {
self.d.last_child.map(|_| {
self.doc
.get_node(NodeId::new(self.id.get() + 1))
.expect("a nonempty subtree starts at the next arena entry")
})
}
pub fn first_element_child(&self) -> Option<Self> {
self.children().find(|n| n.is_element())
}
#[inline]
pub fn last_child(&self) -> Option<Self> {
self.d.last_child.map(|id| {
self.doc
.get_node(id)
.expect("last child belongs to this arena")
})
}
pub fn last_element_child(&self) -> Option<Self> {
self.children().rfind(|child| child.is_element())
}
#[inline]
pub fn has_siblings(&self) -> bool {
self.d.prev_sibling.is_some() || self.next_sibling().is_some()
}
#[inline]
pub fn has_children(&self) -> bool {
self.d.last_child.is_some()
}
#[inline]
pub fn ancestors(&self) -> AxisIter<'a, 'input, S> {
AxisIter {
node: Some(*self),
next: Node::parent,
}
}
#[inline]
pub fn prev_siblings(&self) -> AxisIter<'a, 'input, S> {
AxisIter {
node: Some(*self),
next: Node::prev_sibling,
}
}
#[inline]
pub fn next_siblings(&self) -> AxisIter<'a, 'input, S> {
AxisIter {
node: Some(*self),
next: Node::next_sibling,
}
}
#[inline]
pub fn first_children(&self) -> AxisIter<'a, 'input, S> {
AxisIter {
node: Some(*self),
next: Node::first_child,
}
}
#[inline]
pub fn last_children(&self) -> AxisIter<'a, 'input, S> {
AxisIter {
node: Some(*self),
next: Node::last_child,
}
}
#[inline]
pub fn children(&self) -> Children<'a, 'input, S> {
Children {
front: self.first_child(),
back: self.last_child(),
}
}
#[inline]
pub fn descendants(&self) -> Descendants<'a, 'input, S> {
Descendants::new(*self)
}
#[cfg(feature = "xml-backend-roxmltree")]
#[inline]
pub fn range(&self) -> Range<usize> {
self.d.range.clone()
}
#[inline]
pub fn id(&self) -> NodeId {
self.id
}
}
impl<'a, 'input: 'a, S: Storage> fmt::Debug for Node<'a, 'input, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.d.kind {
NodeKind::Root => write!(f, "Root"),
NodeKind::Element { .. } => {
write!(
f,
"Element {{ tag_name: {:?}, attributes: {:?}, namespaces: {:?} }}",
self.tag_name(),
self.attributes(),
self.namespaces()
)
}
NodeKind::PI(pi) => {
write!(f, "PI {{ target: {:?}, value: {:?} }}", pi.target, pi.value)
}
NodeKind::Comment(ref text) => write!(f, "Comment({:?})", text.as_str()),
NodeKind::Text(ref text) => write!(f, "Text({:?})", text.as_str()),
}
}
}
#[derive(Clone)]
pub struct Attributes<'a, 'input, S: Storage = Native> {
doc: &'a Document<'input, S>,
attrs: core::slice::Iter<'a, AttributeData<'input, S>>,
}
impl<'a, 'input, S: Storage> Attributes<'a, 'input, S> {
#[inline]
fn new(node: &Node<'a, 'input, S>) -> Attributes<'a, 'input, S> {
let attrs = match node.d.kind {
NodeKind::Element { ref attributes, .. } => {
&node.doc.attributes[attributes.to_urange()]
}
_ => &[],
};
Attributes {
doc: node.doc,
attrs: attrs.iter(),
}
}
}
impl<'a, 'input, S: Storage> Iterator for Attributes<'a, 'input, S> {
type Item = Attribute<'a, 'input, S>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.attrs.next().map(|attr| Attribute {
doc: self.doc,
data: attr,
})
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.attrs.nth(n).map(|attr| Attribute {
doc: self.doc,
data: attr,
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.attrs.size_hint()
}
}
impl<'a, 'input, S: Storage> DoubleEndedIterator for Attributes<'a, 'input, S> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.attrs.next_back().map(|attr| Attribute {
doc: self.doc,
data: attr,
})
}
}
impl<S: Storage> ExactSizeIterator for Attributes<'_, '_, S> {}
impl<S: Storage> fmt::Debug for Attributes<'_, '_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("Attributes")
.field("attrs", &self.attrs)
.finish()
}
}
#[derive(Clone)]
pub struct AxisIter<'a, 'input: 'a, S: Storage = Native> {
node: Option<Node<'a, 'input, S>>,
next: fn(&Node<'a, 'input, S>) -> Option<Node<'a, 'input, S>>,
}
impl<'a, 'input: 'a, S: Storage> Iterator for AxisIter<'a, 'input, S> {
type Item = Node<'a, 'input, S>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let node = self.node.take();
self.node = node.as_ref().and_then(self.next);
node
}
}
impl<S: Storage> fmt::Debug for AxisIter<'_, '_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("AxisIter")
.field("node", &self.node)
.field("next", &"fn()")
.finish()
}
}
#[derive(Clone, Debug)]
pub struct Children<'a, 'input: 'a, S: Storage = Native> {
front: Option<Node<'a, 'input, S>>,
back: Option<Node<'a, 'input, S>>,
}
impl<'a, 'input: 'a, S: Storage> Iterator for Children<'a, 'input, S> {
type Item = Node<'a, 'input, S>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.front == self.back {
let node = self.front.take();
self.back = None;
node
} else {
let node = self.front.take();
self.front = node.as_ref().and_then(Node::next_sibling);
node
}
}
}
impl<'a, 'input: 'a, S: Storage> DoubleEndedIterator for Children<'a, 'input, S> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.back == self.front {
let node = self.back.take();
self.front = None;
node
} else {
let node = self.back.take();
self.back = node.as_ref().and_then(Node::prev_sibling);
node
}
}
}
#[derive(Clone)]
pub struct Descendants<'a, 'input, S: Storage = Native> {
doc: &'a Document<'input, S>,
nodes: core::iter::Enumerate<core::slice::Iter<'a, NodeData<'input, S>>>,
from: usize,
}
impl<'a, 'input, S: Storage> Descendants<'a, 'input, S> {
#[inline]
fn new(start: Node<'a, 'input, S>) -> Self {
let from = start.id.get_usize();
let until = start
.d
.next_subtree
.map(NodeId::get_usize)
.unwrap_or(start.doc.nodes.len());
let nodes = start.doc.nodes[from..until].iter().enumerate();
Self {
doc: start.doc,
nodes,
from,
}
}
}
impl<'a, 'input, S: Storage> Iterator for Descendants<'a, 'input, S> {
type Item = Node<'a, 'input, S>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.nodes.next().map(|(idx, data)| Node {
id: NodeId::from(self.from + idx),
d: data,
doc: self.doc,
})
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.nodes.nth(n).map(|(idx, data)| Node {
id: NodeId::from(self.from + idx),
d: data,
doc: self.doc,
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
}
impl<'a, 'input, S: Storage> DoubleEndedIterator for Descendants<'a, 'input, S> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.nodes.next_back().map(|(idx, data)| Node {
id: NodeId::from(self.from + idx),
d: data,
doc: self.doc,
})
}
}
impl<S: Storage> ExactSizeIterator for Descendants<'_, '_, S> {}
impl<S: Storage> fmt::Debug for Descendants<'_, '_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("Descendants")
.field("nodes", &self.nodes)
.field("from", &self.from)
.finish()
}
}
#[derive(Clone)]
pub struct NamespaceIter<'a, 'input, S: Storage = Native> {
doc: &'a Document<'input, S>,
namespaces: core::slice::Iter<'a, NamespaceIdx>,
}
impl<'a, 'input, S: Storage> Iterator for NamespaceIter<'a, 'input, S> {
type Item = &'a Namespace<'input, S>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.namespaces
.next()
.map(|idx| self.doc.namespaces.get(*idx))
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.namespaces
.nth(n)
.map(|idx| self.doc.namespaces.get(*idx))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.namespaces.size_hint()
}
}
impl<'a, 'input, S: Storage> DoubleEndedIterator for NamespaceIter<'a, 'input, S> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.namespaces
.next()
.map(|idx| self.doc.namespaces.get(*idx))
}
}
impl<S: Storage> ExactSizeIterator for NamespaceIter<'_, '_, S> {}
impl<S: Storage> fmt::Debug for NamespaceIter<'_, '_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("NamespaceIter")
.field("namespaces", &self.namespaces)
.finish()
}
}