1#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
4use std::collections::HashMap;
5use std::{
6 hash::{Hash, Hasher},
7 ops::Range,
8};
9
10use super::{ParseError, ParsingOptions, XmlBackend};
11
12const XML_NAMESPACE_URI: &str = "http://www.w3.org/XML/1998/namespace";
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct NodeId(u32);
17
18impl NodeId {
19 pub(crate) const fn get(self) -> u32 {
20 self.0
21 }
22 pub(crate) fn get_usize(self) -> usize {
23 self.index()
24 }
25 fn index(self) -> usize {
26 self.0 as usize
27 }
28}
29
30impl From<u32> for NodeId {
31 fn from(value: u32) -> Self {
32 Self(value)
33 }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38pub enum NodeType {
39 Root,
41 Element,
43 Text,
45 Comment,
47 PI,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub(super) struct NodeData {
53 pub(super) parent: Option<NodeId>,
54 pub(super) children: Vec<NodeId>,
55 pub(super) kind: NodeKind,
56 pub(super) range: Range<usize>,
57 pub(super) range_actionable: bool,
58 pub(super) subtree_end: u32,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub(super) enum NodeKind {
63 Root,
64 Element {
65 name: String,
66 namespace: Option<String>,
67 prefix: Option<String>,
68 attributes: Vec<AttributeData>,
69 namespaces: Vec<NamespaceData>,
70 },
71 Text(String),
72 Comment(String),
73 PI {
74 target: String,
75 value: Option<String>,
76 },
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub(super) struct AttributeData {
81 pub(super) name: String,
82 pub(super) namespace: Option<String>,
83 pub(super) prefix: Option<String>,
84 pub(super) value: String,
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Hash)]
88pub(super) struct NamespaceData {
89 pub(super) prefix: Option<String>,
90 pub(super) uri: String,
91}
92
93pub(super) struct TreeBuilder<'input> {
95 input: &'input str,
96 nodes: Vec<NodeData>,
97}
98
99impl<'input> TreeBuilder<'input> {
100 pub(super) fn new(input: &'input str, capacity: usize) -> Self {
101 Self {
102 input,
103 nodes: Vec::with_capacity(capacity),
104 }
105 }
106
107 #[cfg(feature = "xml-backend-xmloxide")]
108 pub(super) fn push(
109 &mut self,
110 parent: Option<NodeId>,
111 kind: NodeKind,
112 range: Range<usize>,
113 ) -> NodeId {
114 self.push_with_actionability(parent, kind, range, true)
115 }
116
117 pub(super) fn push_with_actionability(
118 &mut self,
119 parent: Option<NodeId>,
120 kind: NodeKind,
121 range: Range<usize>,
122 range_actionable: bool,
123 ) -> NodeId {
124 let id = NodeId(
125 u32::try_from(self.nodes.len()).expect("bounded XML node count must fit into u32"),
126 );
127 self.nodes.push(NodeData {
128 parent,
129 children: Vec::new(),
130 kind,
131 range,
132 range_actionable,
133 subtree_end: id.0 + 1,
134 });
135 if let Some(parent) = parent {
136 self.nodes[parent.index()].children.push(id);
137 }
138 id
139 }
140
141 pub(super) fn finish_subtree(&mut self, node: NodeId) {
142 self.nodes[node.index()].subtree_end =
143 u32::try_from(self.nodes.len()).expect("bounded XML node count must fit into u32");
144 }
145
146 #[cfg(feature = "xml-backend-xmloxide")]
147 pub(super) fn append_text(
148 &mut self,
149 parent: NodeId,
150 value: &str,
151 range: Range<usize>,
152 range_actionable: bool,
153 ) {
154 if let Some(last) = self.nodes[parent.index()].children.last().copied()
155 && let node = &mut self.nodes[last.index()]
156 && let NodeKind::Text(existing) = &mut node.kind
157 {
158 existing.push_str(value);
159 node.range.start = node.range.start.min(range.start);
163 node.range.end = node.range.end.max(range.end);
164 node.range_actionable &= range_actionable;
165 return;
166 }
167 self.push_with_actionability(
168 Some(parent),
169 NodeKind::Text(value.to_owned()),
170 range,
171 range_actionable,
172 );
173 }
174
175 #[cfg(feature = "xml-backend-xmloxide")]
176 pub(super) fn len(&self) -> usize {
177 self.nodes.len()
178 }
179
180 #[cfg(feature = "xml-backend-xmloxide")]
181 pub(super) fn namespaces(&self, node: NodeId) -> Option<&[NamespaceData]> {
182 match &self.nodes[node.index()].kind {
183 NodeKind::Element { namespaces, .. } => Some(namespaces),
184 _ => None,
185 }
186 }
187
188 pub(super) fn finish(self) -> Document<'input> {
189 Document {
190 input: self.input,
191 nodes: self.nodes,
192 }
193 }
194
195 #[cfg(feature = "xml-backend-roxmltree")]
196 pub(super) fn input(&self) -> &'input str {
197 self.input
198 }
199}
200
201#[derive(Debug)]
206pub struct Document<'input> {
207 input: &'input str,
208 nodes: Vec<NodeData>,
209}
210
211impl<'input> Document<'input> {
212 pub fn parse(input: &'input str) -> Result<Self, ParseError> {
214 Self::parse_with_options(input, ParsingOptions::default())
215 }
216
217 pub fn parse_with_options(
219 input: &'input str,
220 options: ParsingOptions,
221 ) -> Result<Self, ParseError> {
222 Self::parse_with_options_and_backend(input, options, XmlBackend::default())
223 }
224
225 pub fn parse_with_backend(input: &'input str, backend: XmlBackend) -> Result<Self, ParseError> {
227 Self::parse_with_options_and_backend(input, ParsingOptions::default(), backend)
228 }
229
230 pub fn parse_with_options_and_backend(
232 input: &'input str,
233 options: ParsingOptions,
234 backend: XmlBackend,
235 ) -> Result<Self, ParseError> {
236 let options = crate::document::preflight_dom_limits(input, options)?;
237 Self::parse_after_limit_preflight_with_backend(input, options, backend)
238 }
239
240 pub(crate) fn parse_after_limit_preflight_with_backend(
241 input: &'input str,
242 options: ParsingOptions,
243 backend: XmlBackend,
244 ) -> Result<Self, ParseError> {
245 let preflight = super::LexicalPreflight::scan(input, options.allow_dtd)?;
246 backend.parse(input, options, &preflight)
247 }
248
249 pub fn input_text(&self) -> &'input str {
251 self.input
252 }
253
254 pub fn root(&self) -> Node<'_, 'input> {
256 self.get_node(NodeId(0))
257 .expect("parsed document has a root")
258 }
259
260 pub fn root_element(&self) -> Node<'_, 'input> {
262 self.root()
263 .children()
264 .find(Node::is_element)
265 .expect("well-formed XML has a document element")
266 }
267
268 pub fn descendants(&self) -> Descendants<'_, 'input> {
270 self.root().descendants()
271 }
272
273 pub fn get_node(&self, id: NodeId) -> Option<Node<'_, 'input>> {
275 self.nodes
276 .get(id.index())
277 .map(|_| Node { document: self, id })
278 }
279
280 #[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
281 pub(super) fn ensure_semantically_equivalent(
282 &self,
283 other: &Self,
284 doctype: Option<&Range<usize>>,
285 ) -> Result<(), ParseError> {
286 if self.nodes.len() != other.nodes.len() {
287 let detail = self
288 .nodes
289 .iter()
290 .zip(&other.nodes)
291 .position(|(left, right)| {
292 node_kind_label(&left.kind) != node_kind_label(&right.kind)
293 })
294 .map_or_else(
295 || "one adapter retained trailing nodes".to_owned(),
296 |index| {
297 format!(
298 "node {index} is {} versus {}",
299 node_kind_label(&self.nodes[index].kind),
300 node_kind_label(&other.nodes[index].kind)
301 )
302 },
303 );
304 return Err(ParseError::BackendDivergence {
305 reason: format!(
306 "retained semantic node counts differ ({} versus {}): {detail}",
307 self.nodes.len(),
308 other.nodes.len()
309 ),
310 });
311 }
312 if let Some((index, (left, right))) = self
313 .nodes
314 .iter()
315 .zip(&other.nodes)
316 .enumerate()
317 .find(|(_, (left, right))| !nodes_semantically_equivalent(left, right, doctype))
318 {
319 return Err(ParseError::BackendDivergence {
320 reason: format!(
321 "retained semantic node {index} differs in {}",
322 node_difference(left, right, doctype)
323 ),
324 });
325 }
326 Ok(())
327 }
328}
329
330#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
331fn node_kind_label(kind: &NodeKind) -> &'static str {
332 match kind {
333 NodeKind::Root => "root",
334 NodeKind::Element { .. } => "element",
335 NodeKind::Text(_) => "text",
336 NodeKind::Comment(_) => "comment",
337 NodeKind::PI { .. } => "processing instruction",
338 }
339}
340
341#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
342fn nodes_semantically_equivalent(
343 left: &NodeData,
344 right: &NodeData,
345 doctype: Option<&Range<usize>>,
346) -> bool {
347 left.parent == right.parent
348 && left.children == right.children
349 && node_kinds_semantically_equivalent(&left.kind, &right.kind)
350 && source_ranges_equivalent(&left.range, &right.range, doctype)
351 && range_actionability_equivalent(left, right)
352 && left.subtree_end == right.subtree_end
353}
354
355#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
356fn node_difference(left: &NodeData, right: &NodeData, doctype: Option<&Range<usize>>) -> String {
357 if left.parent != right.parent {
358 "parent identity".to_owned()
359 } else if left.children != right.children {
360 "child identities".to_owned()
361 } else if !node_kinds_semantically_equivalent(&left.kind, &right.kind) {
362 node_kind_difference(&left.kind, &right.kind).to_owned()
363 } else if !source_ranges_equivalent(&left.range, &right.range, doctype) {
364 format!(
365 "source range ({}..{} versus {}..{})",
366 left.range.start, left.range.end, right.range.start, right.range.end
367 )
368 } else if !range_actionability_equivalent(left, right) {
369 "source-range actionability".to_owned()
370 } else {
371 "subtree boundary".to_owned()
372 }
373}
374
375#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
376fn range_actionability_equivalent(left: &NodeData, right: &NodeData) -> bool {
377 left.range_actionable == right.range_actionable
380}
381
382#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
383fn source_ranges_equivalent(
384 left: &Range<usize>,
385 right: &Range<usize>,
386 doctype: Option<&Range<usize>>,
387) -> bool {
388 if left == right {
389 return true;
390 }
391 let Some(doctype) = doctype else {
392 return false;
393 };
394 range_contains(doctype, left) != range_contains(doctype, right)
398}
399
400#[cfg(feature = "xml-backend-roxmltree")]
401pub(super) fn range_contains(container: &Range<usize>, candidate: &Range<usize>) -> bool {
402 candidate.start >= container.start && candidate.end <= container.end
403}
404
405#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
406fn node_kinds_semantically_equivalent(left: &NodeKind, right: &NodeKind) -> bool {
407 match (left, right) {
408 (
409 NodeKind::Element {
410 name: left_name,
411 namespace: left_namespace,
412 prefix: left_prefix,
413 attributes: left_attributes,
414 namespaces: left_namespaces,
415 },
416 NodeKind::Element {
417 name: right_name,
418 namespace: right_namespace,
419 prefix: right_prefix,
420 attributes: right_attributes,
421 namespaces: right_namespaces,
422 },
423 ) => {
424 left_name == right_name
425 && left_namespace == right_namespace
426 && left_prefix == right_prefix
427 && left_attributes == right_attributes
428 && namespace_axes_equivalent(left_namespaces, right_namespaces)
429 }
430 _ => left == right,
431 }
432}
433
434#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
435fn namespace_axes_equivalent(left: &[NamespaceData], right: &[NamespaceData]) -> bool {
436 if left.len() != right.len() {
437 return false;
438 }
439 let mut remaining = HashMap::with_capacity(left.len());
440 for item in left {
441 *remaining.entry(item).or_insert(0_usize) += 1;
442 }
443 right.iter().all(|item| {
444 remaining.get_mut(item).is_some_and(|count| {
445 if *count == 0 {
446 return false;
447 }
448 *count -= 1;
449 true
450 })
451 })
452}
453
454#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
455fn node_kind_difference(left: &NodeKind, right: &NodeKind) -> &'static str {
456 match (left, right) {
457 (
458 NodeKind::Element {
459 name: left_name,
460 namespace: left_namespace,
461 prefix: left_prefix,
462 attributes: left_attributes,
463 namespaces: left_namespaces,
464 },
465 NodeKind::Element {
466 name: right_name,
467 namespace: right_namespace,
468 prefix: right_prefix,
469 attributes: right_attributes,
470 namespaces: right_namespaces,
471 },
472 ) => {
473 if left_name != right_name {
474 "element local name"
475 } else if left_namespace != right_namespace {
476 match (left_namespace.as_deref(), right_namespace.as_deref()) {
477 (None, Some("")) => "element namespace URI (absent versus empty)",
478 (Some(""), None) => "element namespace URI (empty versus absent)",
479 (None, Some(_)) => "element namespace URI (absent versus non-empty)",
480 (Some(_), None) => "element namespace URI (non-empty versus absent)",
481 _ => "element namespace URI (different non-empty values)",
482 }
483 } else if left_prefix != right_prefix {
484 "element prefix"
485 } else if left_attributes != right_attributes {
486 "attribute axis"
487 } else if !namespace_axes_equivalent(left_namespaces, right_namespaces) {
488 "namespace axis"
489 } else {
490 "element semantics"
491 }
492 }
493 (NodeKind::Text(left), NodeKind::Text(right)) if left != right => "character data",
494 (NodeKind::Comment(left), NodeKind::Comment(right)) if left != right => "comment data",
495 (NodeKind::PI { .. }, NodeKind::PI { .. }) => "processing instruction data",
496 _ => "node kind",
497 }
498}
499
500#[derive(Clone, Copy, Debug)]
502pub struct Node<'a, 'input> {
503 document: &'a Document<'input>,
504 id: NodeId,
505}
506
507impl PartialEq for Node<'_, '_> {
508 fn eq(&self, other: &Self) -> bool {
509 std::ptr::eq(self.document, other.document) && self.id == other.id
510 }
511}
512impl Eq for Node<'_, '_> {}
513impl Hash for Node<'_, '_> {
514 fn hash<H: Hasher>(&self, state: &mut H) {
515 std::ptr::from_ref(self.document).hash(state);
516 self.id.hash(state);
517 }
518}
519
520impl<'a, 'input> Node<'a, 'input> {
521 fn data(self) -> &'a NodeData {
522 &self.document.nodes[self.id.index()]
523 }
524 pub fn id(self) -> NodeId {
526 self.id
527 }
528 pub fn document(self) -> &'a Document<'input> {
530 self.document
531 }
532 pub fn node_type(self) -> NodeType {
534 match self.data().kind {
535 NodeKind::Root => NodeType::Root,
536 NodeKind::Element { .. } => NodeType::Element,
537 NodeKind::Text(_) => NodeType::Text,
538 NodeKind::Comment(_) => NodeType::Comment,
539 NodeKind::PI { .. } => NodeType::PI,
540 }
541 }
542 pub fn is_root(&self) -> bool {
544 self.node_type() == NodeType::Root
545 }
546 pub fn is_element(&self) -> bool {
548 self.node_type() == NodeType::Element
549 }
550 pub fn is_text(&self) -> bool {
552 self.node_type() == NodeType::Text
553 }
554 pub fn is_comment(&self) -> bool {
556 self.node_type() == NodeType::Comment
557 }
558 pub fn is_pi(&self) -> bool {
560 self.node_type() == NodeType::PI
561 }
562 pub fn range(self) -> Range<usize> {
564 self.data().range.clone()
565 }
566 pub(crate) fn has_actionable_range(self) -> bool {
567 self.data().range_actionable
568 }
569 pub fn parent(self) -> Option<Self> {
571 self.data().parent.and_then(|id| self.document.get_node(id))
572 }
573 pub fn parent_element(self) -> Option<Self> {
575 self.parent()
576 .and_then(|node| node.is_element().then_some(node))
577 }
578 pub fn children(self) -> Children<'a, 'input> {
580 Children {
581 document: self.document,
582 ids: self.data().children.iter(),
583 }
584 }
585 pub fn first_child(self) -> Option<Self> {
587 self.children().next()
588 }
589 pub fn first_element_child(self) -> Option<Self> {
591 self.children().find(Node::is_element)
592 }
593 pub fn last_element_child(self) -> Option<Self> {
595 self.children().rev().find(Node::is_element)
596 }
597 pub fn descendants(self) -> Descendants<'a, 'input> {
599 Descendants {
600 document: self.document,
601 ids: self.id.0..self.data().subtree_end,
602 }
603 }
604 pub fn ancestors(self) -> Ancestors<'a, 'input> {
606 Ancestors { next: Some(self) }
607 }
608 pub fn prev_sibling(self) -> Option<Self> {
610 let parent = self.parent()?;
611 let position = parent
612 .data()
613 .children
614 .iter()
615 .position(|id| *id == self.id)?;
616 position
617 .checked_sub(1)
618 .and_then(|index| self.document.get_node(parent.data().children[index]))
619 }
620 pub fn next_sibling(self) -> Option<Self> {
622 let parent = self.parent()?;
623 let position = parent
624 .data()
625 .children
626 .iter()
627 .position(|id| *id == self.id)?;
628 parent
629 .data()
630 .children
631 .get(position + 1)
632 .and_then(|id| self.document.get_node(*id))
633 }
634 pub fn next_sibling_element(self) -> Option<Self> {
636 let mut current = self.next_sibling();
637 while let Some(node) = current {
638 if node.is_element() {
639 return Some(node);
640 }
641 current = node.next_sibling();
642 }
643 None
644 }
645 pub fn tag_name(self) -> ExpandedName<'a> {
647 match &self.data().kind {
648 NodeKind::Element {
649 name, namespace, ..
650 } => ExpandedName {
651 name,
652 namespace: namespace.as_deref(),
653 match_namespace: true,
654 },
655 _ => ExpandedName {
656 name: "",
657 namespace: None,
658 match_namespace: true,
659 },
660 }
661 }
662 pub fn has_tag_name<'n, N>(self, name: N) -> bool
668 where
669 N: Into<ExpandedName<'n>>,
670 {
671 if !self.is_element() {
672 return false;
673 }
674 let name = name.into();
675 self.tag_name().name() == name.name()
676 && (!name.match_namespace || self.tag_name().namespace() == name.namespace())
677 }
678 pub fn prefix(self) -> Option<&'a str> {
680 match &self.data().kind {
681 NodeKind::Element { prefix, .. } => prefix.as_deref(),
682 _ => None,
683 }
684 }
685 pub fn attributes(self) -> Attributes<'a> {
687 let values: &[AttributeData] = match &self.data().kind {
688 NodeKind::Element { attributes, .. } => attributes.as_slice(),
689 _ => &[],
690 };
691 Attributes {
692 values: values.iter(),
693 }
694 }
695 pub fn attribute<'n, N>(self, name: N) -> Option<&'a str>
697 where
698 N: Into<ExpandedName<'n>>,
699 {
700 let name = name.into();
701 self.attributes()
702 .find(|attr| attr.name() == name.name() && attr.namespace() == name.namespace())
703 .map(Attribute::value)
704 }
705 pub fn namespaces(self) -> Namespaces<'a> {
707 let values: &[NamespaceData] = match &self.data().kind {
708 NodeKind::Element { namespaces, .. } => namespaces.as_slice(),
709 _ => &[],
710 };
711 Namespaces {
712 values: values.iter(),
713 }
714 }
715 pub fn lookup_namespace_uri(self, prefix: Option<&str>) -> Option<&'a str> {
717 if prefix == Some("xml") {
718 return Some(XML_NAMESPACE_URI);
719 }
720 self.namespaces()
721 .find(|ns| ns.name() == prefix)
722 .map(Namespace::uri)
723 }
724 pub fn lookup_prefix(self, uri: &str) -> Option<&'a str> {
726 if uri == XML_NAMESPACE_URI {
727 return Some("xml");
728 }
729 self.namespaces()
730 .filter(|namespace| namespace.uri() == uri)
731 .find_map(Namespace::name)
732 }
733 pub fn text(self) -> Option<&'a str> {
735 match &self.data().kind {
736 NodeKind::Text(value) | NodeKind::Comment(value) => Some(value),
737 NodeKind::Element { .. } => {
738 self.first_child()
739 .and_then(|child| match &child.data().kind {
740 NodeKind::Text(value) => Some(value.as_str()),
741 _ => None,
742 })
743 }
744 NodeKind::PI { .. } => None,
745 NodeKind::Root => None,
746 }
747 }
748 pub fn pi(self) -> Option<PI<'a>> {
750 match &self.data().kind {
751 NodeKind::PI { target, value } => Some(PI {
752 target,
753 value: value.as_deref(),
754 }),
755 _ => None,
756 }
757 }
758}
759
760#[derive(Clone, Copy, Debug)]
762pub struct ExpandedName<'a> {
763 name: &'a str,
764 namespace: Option<&'a str>,
765 match_namespace: bool,
768}
769impl<'a> ExpandedName<'a> {
770 pub fn name(self) -> &'a str {
772 self.name
773 }
774 pub fn namespace(self) -> Option<&'a str> {
776 self.namespace
777 }
778}
779impl PartialEq for ExpandedName<'_> {
780 fn eq(&self, other: &Self) -> bool {
781 self.name == other.name && self.namespace == other.namespace
782 }
783}
784impl Eq for ExpandedName<'_> {}
785impl<'a> From<&'a str> for ExpandedName<'a> {
786 fn from(name: &'a str) -> Self {
787 Self {
788 name,
789 namespace: None,
790 match_namespace: false,
791 }
792 }
793}
794impl<'a> From<(Option<&'a str>, &'a str)> for ExpandedName<'a> {
795 fn from((namespace, name): (Option<&'a str>, &'a str)) -> Self {
796 Self {
797 name,
798 namespace,
799 match_namespace: true,
800 }
801 }
802}
803impl<'a> From<(&'a str, &'a str)> for ExpandedName<'a> {
804 fn from((namespace, name): (&'a str, &'a str)) -> Self {
805 Self {
806 name,
807 namespace: Some(namespace),
808 match_namespace: true,
809 }
810 }
811}
812
813#[derive(Clone, Copy)]
815pub struct Attribute<'a> {
816 data: &'a AttributeData,
817}
818impl<'a> Attribute<'a> {
819 pub fn name(self) -> &'a str {
821 &self.data.name
822 }
823 pub fn namespace(self) -> Option<&'a str> {
825 self.data.namespace.as_deref()
826 }
827 pub fn value(self) -> &'a str {
829 &self.data.value
830 }
831 pub fn prefix(self) -> Option<&'a str> {
833 self.data.prefix.as_deref()
834 }
835}
836
837pub struct Attributes<'a> {
839 values: std::slice::Iter<'a, AttributeData>,
840}
841impl<'a> Iterator for Attributes<'a> {
842 type Item = Attribute<'a>;
843 fn next(&mut self) -> Option<Self::Item> {
844 self.values.next().map(|data| Attribute { data })
845 }
846 fn size_hint(&self) -> (usize, Option<usize>) {
847 self.values.size_hint()
848 }
849}
850impl ExactSizeIterator for Attributes<'_> {}
851
852#[derive(Clone, Copy)]
854pub struct Namespace<'a> {
855 data: &'a NamespaceData,
856}
857impl<'a> Namespace<'a> {
858 pub fn name(self) -> Option<&'a str> {
860 self.data.prefix.as_deref()
861 }
862 pub fn uri(self) -> &'a str {
864 &self.data.uri
865 }
866}
867
868pub struct Namespaces<'a> {
870 values: std::slice::Iter<'a, NamespaceData>,
871}
872impl<'a> Iterator for Namespaces<'a> {
873 type Item = Namespace<'a>;
874 fn next(&mut self) -> Option<Self::Item> {
875 self.values.next().map(|data| Namespace { data })
876 }
877 fn size_hint(&self) -> (usize, Option<usize>) {
878 self.values.size_hint()
879 }
880}
881impl ExactSizeIterator for Namespaces<'_> {}
882
883#[derive(Clone, Copy)]
885pub struct PI<'a> {
886 pub target: &'a str,
888 pub value: Option<&'a str>,
890}
891
892pub struct Children<'a, 'input> {
894 document: &'a Document<'input>,
895 ids: std::slice::Iter<'a, NodeId>,
896}
897impl<'a, 'input> Iterator for Children<'a, 'input> {
898 type Item = Node<'a, 'input>;
899 fn next(&mut self) -> Option<Self::Item> {
900 self.ids.next().and_then(|id| self.document.get_node(*id))
901 }
902 fn size_hint(&self) -> (usize, Option<usize>) {
903 self.ids.size_hint()
904 }
905}
906impl ExactSizeIterator for Children<'_, '_> {}
907impl<'a, 'input> DoubleEndedIterator for Children<'a, 'input> {
908 fn next_back(&mut self) -> Option<Self::Item> {
909 self.ids
910 .next_back()
911 .and_then(|id| self.document.get_node(*id))
912 }
913}
914
915pub struct Descendants<'a, 'input> {
917 document: &'a Document<'input>,
918 ids: Range<u32>,
919}
920impl<'a, 'input> Iterator for Descendants<'a, 'input> {
921 type Item = Node<'a, 'input>;
922 fn next(&mut self) -> Option<Self::Item> {
923 self.ids
924 .next()
925 .and_then(|id| self.document.get_node(NodeId(id)))
926 }
927 fn size_hint(&self) -> (usize, Option<usize>) {
928 self.ids.size_hint()
929 }
930}
931impl DoubleEndedIterator for Descendants<'_, '_> {
932 fn next_back(&mut self) -> Option<Self::Item> {
933 self.ids
934 .next_back()
935 .and_then(|id| self.document.get_node(NodeId(id)))
936 }
937}
938impl ExactSizeIterator for Descendants<'_, '_> {}
939
940pub struct Ancestors<'a, 'input> {
942 next: Option<Node<'a, 'input>>,
943}
944impl<'a, 'input> Iterator for Ancestors<'a, 'input> {
945 type Item = Node<'a, 'input>;
946 fn next(&mut self) -> Option<Self::Item> {
947 let current = self.next?;
948 self.next = current.parent();
949 Some(current)
950 }
951}
952
953#[cfg(test)]
954mod tests {
955 use super::Document;
956 #[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
957 use super::{NamespaceData, namespace_axes_equivalent};
958 use crate::xml::dom::{ParseError, ParsingOptions, XmlBackend};
959
960 #[test]
961 fn runtime_selection_never_falls_back_to_a_compiled_backend() {
962 for backend in [
965 XmlBackend::Xmloxide,
966 XmlBackend::Roxmltree,
967 XmlBackend::Differential,
968 ] {
969 let result = Document::parse_with_backend("<r/>", backend);
970 if backend.is_available() {
971 assert!(result.is_ok(), "{backend:?} should be available");
972 } else {
973 assert_eq!(
974 result.expect_err("an unavailable backend must not fall back"),
975 ParseError::BackendUnavailable { backend }
976 );
977 }
978 }
979 }
980
981 #[cfg(feature = "xml-backend-differential")]
982 #[test]
983 fn compatibility_feature_selects_differential_by_default() {
984 assert_eq!(XmlBackend::default(), XmlBackend::Differential);
985 }
986
987 #[cfg(all(
988 feature = "xml-backend-xmloxide",
989 feature = "xml-backend-roxmltree",
990 not(feature = "xml-backend-differential")
991 ))]
992 #[test]
993 fn fat_build_defaults_to_xmloxide_without_implicit_double_parsing() {
994 assert_eq!(XmlBackend::default(), XmlBackend::Xmloxide);
995 }
996
997 #[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
998 #[test]
999 fn fat_build_selects_each_semantically_equivalent_runtime_mode() {
1000 let xml = r#"<r xmlns:p="urn:p"><p:a x="1">value</p:a></r>"#;
1003 let snapshot = |backend| {
1004 let document = Document::parse_with_backend(xml, backend)
1005 .expect("every compiled runtime mode must parse the fixture");
1006 document
1007 .descendants()
1008 .map(|node| {
1009 (
1010 node.node_type(),
1011 node.range(),
1012 node.text().map(str::to_owned),
1013 )
1014 })
1015 .collect::<Vec<_>>()
1016 };
1017
1018 let xmloxide = snapshot(XmlBackend::Xmloxide);
1019 assert_eq!(snapshot(XmlBackend::Roxmltree), xmloxide);
1020 assert_eq!(snapshot(XmlBackend::Differential), xmloxide);
1021 }
1022
1023 #[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
1024 #[test]
1025 fn differential_namespace_axis_comparison_preserves_multiplicity() {
1026 let binding = |prefix: &str, uri: &str| NamespaceData {
1029 prefix: Some(prefix.to_owned()),
1030 uri: uri.to_owned(),
1031 };
1032 let a = binding("a", "urn:a");
1033 let b = binding("b", "urn:b");
1034
1035 assert!(!namespace_axes_equivalent(
1036 &[a.clone(), a.clone()],
1037 &[a.clone(), b.clone()]
1038 ));
1039 assert!(!namespace_axes_equivalent(
1040 &[a.clone(), b],
1041 &[a.clone(), a.clone()]
1042 ));
1043 assert!(namespace_axes_equivalent(
1044 &[a.clone(), a.clone()],
1045 &[a.clone(), a]
1046 ));
1047 }
1048
1049 #[test]
1050 fn selected_backend_preserves_element_source_ranges() {
1051 let xml = "<?p before?><root><empty/><child>value</child></root><!--after-->";
1053 let document = Document::parse(xml).expect("fixture must parse");
1054 let root = document.root_element();
1055 let child = root
1056 .children()
1057 .find(|node| node.has_tag_name("child"))
1058 .expect("fixture must contain the child element");
1059 assert_eq!(
1060 &xml[root.range()],
1061 "<root><empty/><child>value</child></root>"
1062 );
1063 assert_eq!(&xml[child.range()], "<child>value</child>");
1064 }
1065
1066 #[test]
1067 fn selected_backend_materializes_shadowed_namespace_axis() {
1068 let document = Document::parse(
1070 r#"<r xmlns="urn:outer" xmlns:p="urn:p1"><p:a xmlns:p="urn:p2" xmlns=""><p:b/></p:a></r>"#,
1071 ).expect("fixture must parse");
1072 let child = document
1073 .descendants()
1074 .find(|node| node.has_tag_name(("urn:p2", "b")))
1075 .expect("fixture must contain the namespaced descendant");
1076 assert_eq!(child.lookup_namespace_uri(Some("p")), Some("urn:p2"));
1077 assert_eq!(child.lookup_namespace_uri(None), Some(""));
1078 assert_eq!(child.namespaces().count(), 2);
1079 }
1080
1081 #[test]
1082 fn tag_name_matcher_distinguishes_local_from_unqualified_names() {
1083 let document = Document::parse(r#"<r xmlns:p="urn:p"><item/><p:item/></r>"#)
1086 .expect("namespace matching fixture must parse");
1087 let mut items = document
1088 .root_element()
1089 .children()
1090 .filter(|node| node.is_element());
1091 let unqualified = items.next().expect("unqualified item must exist");
1092 let namespaced = items.next().expect("namespaced item must exist");
1093
1094 assert!(unqualified.has_tag_name("item"));
1095 assert!(namespaced.has_tag_name("item"));
1096 assert!(unqualified.has_tag_name((None, "item")));
1097 assert!(!namespaced.has_tag_name((None, "item")));
1098 assert!(namespaced.has_tag_name((Some("urn:p"), "item")));
1099 }
1100
1101 #[test]
1102 fn namespace_lookup_includes_the_predefined_xml_binding() {
1103 let document = Document::parse("<root><child xml:lang=\"en\"/></root>")
1106 .expect("predefined namespace fixture must parse");
1107 let child = document
1108 .root_element()
1109 .first_element_child()
1110 .expect("fixture must contain a child");
1111
1112 assert_eq!(
1113 child.lookup_namespace_uri(Some("xml")),
1114 Some("http://www.w3.org/XML/1998/namespace")
1115 );
1116 assert_eq!(
1117 child.lookup_prefix("http://www.w3.org/XML/1998/namespace"),
1118 Some("xml")
1119 );
1120 }
1121
1122 #[test]
1123 fn selected_backend_folds_cdata_and_entities_into_one_text_node() {
1124 let document = Document::parse_with_options(
1126 "<!DOCTYPE r [<!ENTITY value 'two'>]><r>one<![CDATA[+]]>&value;three</r>",
1127 ParsingOptions {
1128 allow_dtd: true,
1129 nodes_limit: 8,
1130 },
1131 )
1132 .expect("bounded internal entity fixture must parse");
1133 let text = document
1134 .root_element()
1135 .children()
1136 .filter(|node| node.is_text())
1137 .collect::<Vec<_>>();
1138 assert_eq!(text.len(), 1);
1139 assert_eq!(text[0].text(), Some("one+twothree"));
1140 }
1141
1142 #[test]
1143 fn selected_backend_maps_builtin_references_into_the_surrounding_text_range() {
1144 let xml = "<r>left&right</r>";
1148 let document = Document::parse(xml).expect("fixture must parse");
1149 let text = document
1150 .root_element()
1151 .first_child()
1152 .expect("text child must exist");
1153
1154 assert_eq!(text.text(), Some("left&right"));
1155 assert_eq!(&xml[text.range()], "left&right");
1156 }
1157
1158 #[test]
1159 fn selected_backend_rejects_dtd_before_building_a_tree() {
1160 assert_eq!(
1162 Document::parse("<!DOCTYPE r><r/>")
1163 .expect_err("DTD-disabled parsing must reject a document type"),
1164 ParseError::DtdDetected,
1165 );
1166 }
1167
1168 #[test]
1169 fn selected_backend_enforces_semantic_node_limit_after_text_folding() {
1170 let error = Document::parse_with_options(
1172 "<r><a/><b/></r>",
1173 ParsingOptions {
1174 allow_dtd: false,
1175 nodes_limit: 3,
1176 },
1177 )
1178 .expect_err("the semantic node limit must reject the fourth node");
1179 assert_eq!(error, ParseError::NodesLimitReached);
1180 }
1181
1182 #[test]
1183 fn selected_backend_enforces_the_absolute_node_ceiling_before_dom_allocation() {
1184 let count = crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize;
1188 let xml = format!("<r>{}</r>", "<n/>".repeat(count));
1189
1190 assert_eq!(
1191 Document::parse_with_options(
1192 &xml,
1193 ParsingOptions {
1194 allow_dtd: false,
1195 nodes_limit: u32::MAX,
1196 },
1197 )
1198 .expect_err("the absolute retained-node ceiling must remain effective"),
1199 ParseError::NodesLimitReached,
1200 );
1201 }
1202
1203 #[test]
1204 fn selected_backend_enforces_the_absolute_byte_ceiling_before_dom_allocation() {
1205 let maximum = crate::hard_limits::XML_DOCUMENT_BYTE_CEILING;
1208 let xml = format!("<r>{}</r>", "x".repeat(maximum));
1209 let expected = ParseError::ByteLimitReached {
1210 maximum,
1211 actual: xml.len(),
1212 };
1213
1214 assert_eq!(
1215 Document::parse(&xml)
1216 .expect_err("default direct parsing must enforce the byte ceiling"),
1217 expected,
1218 );
1219 assert_eq!(
1220 Document::parse_with_options(
1221 &xml,
1222 ParsingOptions {
1223 allow_dtd: false,
1224 nodes_limit: u32::MAX,
1225 },
1226 )
1227 .expect_err("custom direct parsing must retain the absolute byte ceiling"),
1228 expected,
1229 );
1230 }
1231
1232 #[test]
1233 fn selected_backend_bounds_direct_entity_expansion_count() {
1234 let references =
1237 "&value;".repeat(crate::hard_limits::XML_ENTITY_EXPANSION_CEILING as usize + 1);
1238 let xml = format!("<!DOCTYPE r [<!ENTITY value 'x'>]><r>{references}</r>");
1239
1240 assert!(matches!(
1241 Document::parse_with_options(
1242 &xml,
1243 ParsingOptions {
1244 allow_dtd: true,
1245 nodes_limit: u32::MAX,
1246 },
1247 ),
1248 Err(ParseError::EntityExpansionLimitReached { maximum, actual })
1249 if maximum == crate::hard_limits::XML_ENTITY_EXPANSION_CEILING
1250 && actual == maximum + 1
1251 ));
1252 }
1253
1254 #[test]
1255 fn selected_backend_bounds_source_positions_after_semantic_text_folding() {
1256 let segments = crate::hard_limits::XML_SOURCE_POSITION_CEILING + 1;
1260 let xml = format!("<r>{}</r>", "<![CDATA[x]]>".repeat(segments));
1261
1262 assert!(matches!(
1263 Document::parse_with_options(
1264 &xml,
1265 ParsingOptions {
1266 allow_dtd: false,
1267 nodes_limit: 3,
1268 },
1269 ),
1270 Err(ParseError::SourcePositionLimitReached { maximum, actual })
1271 if maximum == crate::hard_limits::XML_SOURCE_POSITION_CEILING
1272 && actual == maximum + 1
1273 ));
1274 }
1275
1276 #[test]
1277 fn unqualified_attribute_lookup_requires_no_namespace() {
1278 let document = Document::parse(
1281 r#"<r xmlns:evil="urn:evil" evil:Algorithm="extension" Algorithm="schema"/>"#,
1282 )
1283 .expect("fixture must parse");
1284 let root = document.root_element();
1285
1286 assert_eq!(root.attribute("Algorithm"), Some("schema"));
1287 assert_eq!(root.attribute(("urn:evil", "Algorithm")), Some("extension"));
1288
1289 let extension_only =
1290 Document::parse(r#"<r xmlns:evil="urn:evil" evil:Algorithm="extension"/>"#)
1291 .expect("fixture must parse");
1292 assert_eq!(extension_only.root_element().attribute("Algorithm"), None);
1293 }
1294
1295 #[test]
1296 fn node_text_preserves_character_data_contract() {
1297 let document = Document::parse("<r><?p hidden?>visible</r>").expect("fixture must parse");
1300 let root = document.root_element();
1301 let pi = root.first_child().expect("PI must exist");
1302
1303 assert_eq!(pi.text(), None);
1304 assert_eq!(pi.pi().and_then(|value| value.value), Some("hidden"));
1305 assert_eq!(root.text(), None);
1306 assert_eq!(
1307 pi.next_sibling().and_then(|node| node.text()),
1308 Some("visible")
1309 );
1310
1311 let nested =
1312 Document::parse("<r><child>nested</child>later</r>").expect("fixture must parse");
1313 assert_eq!(nested.root_element().text(), None);
1314 }
1315
1316 #[test]
1317 fn selected_backend_rejects_deep_documents_before_dom_parsing() {
1318 const DEPTH: usize = 20_000;
1321 let mut xml = String::with_capacity(DEPTH * 7);
1322 xml.push_str(&"<n>".repeat(DEPTH));
1323 xml.push_str(&"</n>".repeat(DEPTH));
1324
1325 assert!(matches!(
1326 Document::parse(&xml),
1327 Err(ParseError::DepthLimitReached { maximum, actual })
1328 if maximum == crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING
1329 && actual == maximum + 1
1330 ));
1331 }
1332
1333 #[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
1334 #[test]
1335 fn differential_comparison_fails_closed_on_semantic_divergence() {
1336 let left = Document::parse("<r><a/></r>").expect("fixture must parse");
1339 let mut right = Document::parse("<r><a/></r>").expect("fixture must parse");
1340 right.nodes[2].range.end -= 1;
1341
1342 assert!(matches!(
1343 left.ensure_semantically_equivalent(&right, None),
1344 Err(ParseError::BackendDivergence { .. })
1345 ));
1346 }
1347
1348 #[test]
1349 fn dtd_internal_comments_are_not_document_nodes() {
1350 let document = Document::parse_with_options(
1353 "<!--before--><!DOCTYPE r [<!-- ] > --><!ENTITY value 'ok'>]><r>&value;</r><!--after-->",
1354 ParsingOptions {
1355 allow_dtd: true,
1356 ..ParsingOptions::default()
1357 },
1358 )
1359 .expect("DTD fixture must parse");
1360 let root = document.root();
1361 let comments = root
1362 .descendants()
1363 .filter(|node| node.is_comment())
1364 .filter_map(|node| node.text())
1365 .collect::<Vec<_>>();
1366
1367 assert_eq!(comments, ["before", "after"]);
1368 assert_eq!(
1369 root.descendants()
1370 .find(|node| node.is_text())
1371 .and_then(|node| node.text()),
1372 Some("ok")
1373 );
1374 }
1375
1376 #[test]
1377 fn dtd_range_ignores_doctype_text_in_prolog_nodes() {
1378 let document = Document::parse_with_options(
1381 "<!-- <!DOCTYPE fake> --><?probe <!DOCTYPE fake> ?><!DOCTYPE r [<!--hidden-->]><r/>",
1382 ParsingOptions {
1383 allow_dtd: true,
1384 ..ParsingOptions::default()
1385 },
1386 )
1387 .expect("DTD fixture must parse");
1388 let root = document.root();
1389 let comments = root
1390 .children()
1391 .filter(|node| node.is_comment())
1392 .filter_map(|node| node.text())
1393 .collect::<Vec<_>>();
1394 let processing_instructions = root
1395 .children()
1396 .filter(|node| node.is_pi())
1397 .filter_map(|node| node.pi())
1398 .collect::<Vec<_>>();
1399
1400 assert_eq!(comments, [" <!DOCTYPE fake> "]);
1401 assert_eq!(processing_instructions.len(), 1);
1402 assert_eq!(processing_instructions[0].target, "probe");
1403 }
1404
1405 #[cfg(feature = "xml-backend-roxmltree")]
1406 #[test]
1407 fn dtd_range_scanning_accepts_unicode_names() {
1408 let input = "<!DOCTYPE r [<!ENTITY café 'ok'>]><r/>";
1411 let end = input.find("><r/>").expect("fixture has document root") + 1;
1412
1413 let preflight = super::super::LexicalPreflight::scan(input, true)
1414 .expect("Unicode DTD fixture must pass lexical preflight");
1415 assert_eq!(preflight.doctype_range(), Some(&(0..end)));
1416 }
1417}