1use rustc_hash::{FxHashMap, FxHashSet};
53
54use crate::error::Result;
55use crate::ns_helpers::{
56 ns_err, validate_qname, validate_xmlns_decl, XML_NS_URI, XMLNS_NS_URI,
57};
58use crate::options::ParseOptions;
59use crate::reader::{Attr, EventInto, XmlReader};
60use sup_xml_tree::dom::{Document, DocumentBuilder, Namespace, Node};
61
62pub struct StreamParser<'src> {
66 reader: XmlReader<'src>,
67 attr_buf: Vec<Attr<'src>>,
68 emit: EmitMode,
69 state: State,
70}
71
72pub type EmitPredicate = Box<dyn Fn(&[String]) -> bool + Send + Sync + 'static>;
78
79enum EmitMode {
81 None,
83 Depth(u32),
86 Path(Vec<String>),
88 When(EmitPredicate),
95}
96
97struct State {
98 sax_depth: u32,
100 name_stack: Vec<String>,
102 ns_bindings: Vec<(Option<String>, String)>,
107 ns_frames: Vec<usize>,
110 current: Option<Emission>,
112}
113
114impl Default for State {
115 fn default() -> Self {
116 Self {
117 sax_depth: 0,
118 name_stack: Vec::new(),
119 ns_bindings: vec![
120 (Some("xml".to_string()), XML_NS_URI.to_string()),
121 (Some("xmlns".to_string()), XMLNS_NS_URI.to_string()),
122 ],
123 ns_frames: Vec::new(),
124 current: None,
125 }
126 }
127}
128
129struct Emission {
131 builder: DocumentBuilder,
132 start_depth: u32,
134 stack: Vec<*const ()>,
137 ns_cache: FxHashMap<(Option<String>, String), *const ()>,
141}
142
143#[inline] fn erase(node: &Node<'_>) -> *const () {
148 node as *const Node<'_> as *const ()
149}
150
151#[inline] unsafe fn unerase<'a>(p: *const ()) -> &'a Node<'a> {
156 unsafe { &*(p as *const Node<'a>) }
157}
158
159impl<'src> StreamParser<'src> {
162 pub fn from_str(input: &'src str) -> Self {
163 Self {
164 reader: XmlReader::from_str(input),
165 attr_buf: Vec::new(),
166 emit: EmitMode::None,
167 state: State::default(),
168 }
169 }
170
171 pub fn from_bytes(input: &'src [u8]) -> Result<Self> {
172 Ok(Self {
173 reader: XmlReader::from_bytes(input)?,
174 attr_buf: Vec::new(),
175 emit: EmitMode::None,
176 state: State::default(),
177 })
178 }
179
180 pub fn with_options(mut self, opts: &ParseOptions) -> Self {
183 self.reader = self.reader.with_options(opts.clone());
184 self
185 }
186
187 pub fn emit_at_depth(mut self, depth: u32) -> Self {
190 self.emit = EmitMode::Depth(depth);
191 self
192 }
193
194 pub fn emit_at_path(mut self, path: &[&str]) -> Self {
198 self.emit = EmitMode::Path(path.iter().map(|s| (*s).to_owned()).collect());
199 self
200 }
201
202 pub fn emit_when<F>(mut self, predicate: F) -> Self
229 where
230 F: Fn(&[String]) -> bool + Send + Sync + 'static,
231 {
232 self.emit = EmitMode::When(Box::new(predicate));
233 self
234 }
235
236 #[allow(clippy::should_implement_trait)]
239 pub fn next(&mut self) -> Result<Option<Document>> {
240 loop {
241 let event = self.reader.next_into(&mut self.attr_buf)?;
242 match event {
243 EventInto::StartElement { name } => {
244 let name = name.into_owned();
245 self.state.sax_depth += 1;
246 self.state.name_stack.push(name.clone());
247 validate_qname(&name, "element")?;
248
249 self.state.ns_frames.push(self.state.ns_bindings.len());
251 let attrs: Vec<(String, String)> = self.attr_buf.drain(..)
255 .map(|a| (a.name.to_owned(), a.value.into_owned()))
256 .collect();
257 for (an, av) in &attrs {
258 if an == "xmlns" {
259 self.state.ns_bindings.push((None, av.clone()));
260 } else if let Some(local) = an.strip_prefix("xmlns:") {
261 validate_xmlns_decl(local, av)?;
262 if local == "xml" { continue; } self.state.ns_bindings.push((Some(local.to_string()), av.clone()));
264 }
265 }
266
267 let starts_emission = self.state.current.is_none() && self.element_matches();
269
270 if starts_emission {
271 let mut emis = Emission {
273 builder: DocumentBuilder::new(),
274 start_depth: self.state.sax_depth,
275 stack: Vec::new(),
276 ns_cache: FxHashMap::default(),
277 };
278 let el_ptr = build_element_with_ns(
279 &mut emis, &name, &attrs, &self.state.ns_bindings,
280 )?;
281 emis.stack.push(el_ptr);
282 self.state.current = Some(emis);
283 } else if let Some(emis) = self.state.current.as_mut() {
284 let el_ptr = build_element_with_ns(
285 emis, &name, &attrs, &self.state.ns_bindings,
286 )?;
287 let parent: &Node<'_> = unsafe { unerase(*emis.stack.last().unwrap()) };
289 let el: &Node<'_> = unsafe { unerase(el_ptr) };
290 emis.builder.append_child(parent, el);
291 emis.stack.push(el_ptr);
292 } else {
293 validate_element_ns(&name, &attrs, &self.state.ns_bindings)?;
296 }
297 }
298
299 EventInto::EndElement { name: _ } => {
300 self.state.name_stack.pop();
301 if let Some(frame_start) = self.state.ns_frames.pop() {
303 self.state.ns_bindings.truncate(frame_start);
304 }
305 let closed_depth = self.state.sax_depth;
306 self.state.sax_depth -= 1;
307
308 if let Some(emis) = self.state.current.as_mut() {
309 if emis.start_depth == closed_depth {
310 let emis = self.state.current.take().unwrap();
312 let root: &Node<'_> = unsafe { unerase(emis.stack[0]) };
314 emis.builder.set_root(root);
315 return Ok(Some(emis.builder.build()));
316 } else {
317 emis.stack.pop();
318 }
319 }
320 }
321
322 EventInto::Text(t) => {
323 if let Some(emis) = self.state.current.as_mut() {
324 let parent_ptr = *emis.stack.last().unwrap();
325 let s = emis.builder.alloc_str(&t);
326 let node = emis.builder.new_text(s);
327 let parent: &Node<'_> = unsafe { unerase(parent_ptr) };
329 emis.builder.append_child(parent, node);
330 }
331 }
332 EventInto::CData(t) => {
333 if let Some(emis) = self.state.current.as_mut() {
334 let parent_ptr = *emis.stack.last().unwrap();
335 let s = emis.builder.alloc_str(&t);
336 let node = emis.builder.new_cdata(s);
337 let parent: &Node<'_> = unsafe { unerase(parent_ptr) };
338 emis.builder.append_child(parent, node);
339 }
340 }
341 EventInto::Comment(t) => {
342 if let Some(emis) = self.state.current.as_mut() {
343 let parent_ptr = *emis.stack.last().unwrap();
344 let s = emis.builder.alloc_str(&t);
345 let node = emis.builder.new_comment(s);
346 let parent: &Node<'_> = unsafe { unerase(parent_ptr) };
347 emis.builder.append_child(parent, node);
348 }
349 }
350 EventInto::Pi { target, content } => {
351 if let Some(emis) = self.state.current.as_mut() {
352 let parent_ptr = *emis.stack.last().unwrap();
353 let t = emis.builder.alloc_str(&target);
354 let c = if content.is_empty() { None } else { Some(&*emis.builder.alloc_str(&content)) };
355 let node = emis.builder.new_pi(t, c);
356 let parent: &Node<'_> = unsafe { unerase(parent_ptr) };
357 emis.builder.append_child(parent, node);
358 }
359 }
360 EventInto::EntityRef { name } => {
361 if let Some(emis) = self.state.current.as_mut() {
362 let parent_ptr = *emis.stack.last().unwrap();
363 let n = emis.builder.alloc_str(&name);
364 let lit = format!("&{name};");
367 let c = emis.builder.alloc_str(&lit);
368 let node = emis.builder.new_entity_ref(n, c);
369 let parent: &Node<'_> = unsafe { unerase(parent_ptr) };
370 emis.builder.append_child(parent, node);
371 }
372 }
373
374 EventInto::Eof => return Ok(None),
375 }
376 }
377 }
378
379 fn element_matches(&self) -> bool {
382 match &self.emit {
383 EmitMode::None => false,
384 EmitMode::Depth(d) => self.state.sax_depth.saturating_sub(1) == *d,
389 EmitMode::Path(path) => path.len() == self.state.name_stack.len()
390 && self.state.name_stack.iter().zip(path.iter())
391 .all(|(a, b)| a == b),
392 EmitMode::When(pred) => pred(&self.state.name_stack),
393 }
394 }
395}
396
397fn build_element_with_ns(
405 emis: &mut Emission,
406 name: &str,
407 attrs: &[(String, String)],
408 global_ns_bindings: &[(Option<String>, String)],
409) -> Result<*const ()> {
410 let Emission { builder, ns_cache, .. } = emis;
415
416 let aname = builder.alloc_str(name);
417 let el = builder.new_element(aname);
418
419 if let Some((p, h)) = resolve_qname(name, global_ns_bindings, false)? {
421 let ns_ptr = lookup_or_alloc_ns(builder, ns_cache, p, h);
422 let ns: &Namespace<'_> = unsafe { &*(ns_ptr as *const Namespace<'_>) };
424 el.namespace.set(Some(ns));
425 }
426
427 let mut seen: FxHashSet<(String, String)> = FxHashSet::default();
429 for (an, av) in attrs {
430 let aname = builder.alloc_str(an);
431 let avalue = builder.alloc_str(av);
432 let attr = builder.new_attribute(aname, avalue);
433 builder.append_attribute(el, attr);
434
435 if an == "xmlns" || an.starts_with("xmlns:") {
436 continue;
437 }
438 validate_qname(an, "attribute")?;
439 if let Some((p, h)) = resolve_qname(an, global_ns_bindings, true)? {
440 let ns_ptr = lookup_or_alloc_ns(builder, ns_cache, p, h);
441 let ns: &Namespace<'_> = unsafe { &*(ns_ptr as *const Namespace<'_>) };
442 attr.namespace.set(Some(ns));
443 }
444 let ns_uri = attr.namespace.get().map(|n| n.href()).unwrap_or("");
446 let local = an.rfind(':').map(|i| &an[i+1..]).unwrap_or(an);
447 if !seen.insert((ns_uri.to_string(), local.to_string())) {
448 return Err(ns_err(format!(
449 "duplicate attribute '{local}' in namespace '{ns_uri}' after namespace expansion"
450 )));
451 }
452 }
453
454 Ok(erase(el))
455}
456
457fn lookup_or_alloc_ns(
462 builder: &DocumentBuilder,
463 ns_cache: &mut FxHashMap<(Option<String>, String), *const ()>,
464 prefix: Option<&str>,
465 href: &str,
466) -> *const () {
467 let key = (prefix.map(str::to_owned), href.to_owned());
468 if let Some(&ptr) = ns_cache.get(&key) {
469 return ptr;
470 }
471 let pref_arena = prefix.map(|p| builder.alloc_str(p));
472 let href_arena = builder.alloc_str(href);
473 let ns = builder.new_namespace(pref_arena, href_arena);
474 let ptr = ns as *const Namespace<'_> as *const ();
475 ns_cache.insert(key, ptr);
476 ptr
477}
478
479fn resolve_qname<'a>(
484 qname: &'a str,
485 bindings: &'a [(Option<String>, String)],
486 is_attribute: bool,
487) -> Result<Option<(Option<&'a str>, &'a str)>> {
488 if let Some(colon) = qname.find(':') {
489 let prefix = &qname[..colon];
490 match lookup_prefix(Some(prefix), bindings) {
491 Some(href) => Ok(Some((Some(prefix), href))),
492 None => Err(ns_err(format!("undeclared namespace prefix '{prefix}' in '{qname}'"))),
493 }
494 } else if is_attribute {
495 Ok(None)
496 } else {
497 match lookup_prefix(None, bindings) {
498 Some(href) if !href.is_empty() => Ok(Some((None, href))),
499 _ => Ok(None),
500 }
501 }
502}
503
504fn lookup_prefix<'a>(
506 prefix: Option<&str>,
507 bindings: &'a [(Option<String>, String)],
508) -> Option<&'a str> {
509 for (p, h) in bindings.iter().rev() {
510 if p.as_deref() == prefix {
511 return Some(h);
512 }
513 }
514 None
515}
516
517fn validate_element_ns(
521 name: &str,
522 attrs: &[(String, String)],
523 bindings: &[(Option<String>, String)],
524) -> Result<()> {
525 validate_qname(name, "element")?;
526 let _ = resolve_qname(name, bindings, false)?;
527 for (an, _av) in attrs {
528 if an == "xmlns" || an.starts_with("xmlns:") { continue; }
529 validate_qname(an, "attribute")?;
530 let _ = resolve_qname(an, bindings, true)?;
531 }
532 Ok(())
533}
534
535#[cfg(test)]
538mod tests {
539 use super::*;
540 use sup_xml_tree::dom::NodeKind;
541
542 fn collect_names(mut sp: StreamParser) -> Vec<String> {
543 let mut out = Vec::new();
544 while let Some(doc) = sp.next().unwrap() {
545 out.push(doc.root().name().to_string());
546 }
547 out
548 }
549
550 #[test]
553 fn depth_one_matches_children_of_root() {
554 let xml = "<r><a/><b/><c/></r>";
555 let sp = StreamParser::from_str(xml).emit_at_depth(1);
556 assert_eq!(collect_names(sp), vec!["a", "b", "c"]);
557 }
558
559 #[test]
560 fn depth_zero_emits_root() {
561 let xml = "<root><a/><b/></root>";
562 let mut sp = StreamParser::from_str(xml).emit_at_depth(0);
563 let doc = sp.next().unwrap().expect("should yield root");
564 assert_eq!(doc.root().name(), "root");
565 assert_eq!(doc.root().children().count(), 2);
566 assert!(sp.next().unwrap().is_none());
567 }
568
569 #[test]
570 fn depth_two_matches_grandchildren() {
571 let xml = "<r><a><b/><b/></a><a><b/></a></r>";
572 let sp = StreamParser::from_str(xml).emit_at_depth(2);
573 assert_eq!(collect_names(sp), vec!["b", "b", "b"]);
574 }
575
576 #[test]
577 fn no_emit_mode_yields_nothing() {
578 let mut sp = StreamParser::from_str("<r><a/></r>");
579 assert!(sp.next().unwrap().is_none());
580 }
581
582 #[test]
585 fn path_root_anchored() {
586 let xml = "<rss><channel>\
589 <item><id>1</id></item>\
590 <item><id>2</id><item><id>2a</id></item></item>\
591 </channel></rss>";
592 let sp = StreamParser::from_str(xml)
593 .emit_at_path(&["rss", "channel", "item"]);
594 assert_eq!(collect_names(sp), vec!["item", "item"]);
595 }
596
597 #[test]
598 fn path_wrong_root_yields_nothing() {
599 let xml = "<feed><channel><item/></channel></feed>";
600 let mut sp = StreamParser::from_str(xml)
601 .emit_at_path(&["rss", "channel", "item"]);
602 assert!(sp.next().unwrap().is_none());
603 }
604
605 #[test]
606 fn path_single_element_emits_root() {
607 let xml = "<root><a/></root>";
608 let mut sp = StreamParser::from_str(xml).emit_at_path(&["root"]);
609 let doc = sp.next().unwrap().unwrap();
610 assert_eq!(doc.root().name(), "root");
611 assert!(sp.next().unwrap().is_none());
612 }
613
614 #[test]
621 fn predicate_matches_by_name_across_nesting_levels() {
622 let xml = r#"<root>
623 <section><item>one</item></section>
624 <wrapper><inner><item>two</item></inner></wrapper>
625 <thing/>
626 </root>"#;
627 let sp = StreamParser::from_str(xml)
628 .emit_when(|chain| chain.last().map(String::as_str) == Some("item"));
629 let names: Vec<String> = {
630 let mut sp = sp;
631 let mut out = Vec::new();
632 while let Some(d) = sp.next().unwrap() {
633 out.push(d.root().text_content().unwrap_or_default().to_string());
634 }
635 out
636 };
637 assert_eq!(names, vec!["one", "two"],
638 "predicate should pick both <item>s irrespective of depth");
639 }
640
641 #[test]
645 fn predicate_can_inspect_ancestor_chain() {
646 let xml = r#"<archive>
647 <library><book>L1</book></library>
648 <library><book>L2</book></library>
649 <library><book>L3<book>nested</book></book></library>
650 </archive>"#;
651 let sp = StreamParser::from_str(xml).emit_when(|chain| {
652 chain.len() >= 2
653 && chain.last().map(String::as_str) == Some("book")
654 && chain.get(chain.len() - 2).map(String::as_str) == Some("library")
655 });
656 let texts: Vec<String> = {
657 let mut sp = sp;
658 let mut out = Vec::new();
659 while let Some(d) = sp.next().unwrap() {
660 let t = d.root().text_content().unwrap_or_default().to_string();
661 out.push(t);
662 }
663 out
664 };
665 assert_eq!(texts.len(), 3,
669 "expected exactly the three immediate-child <book>s, got {texts:?}");
670 }
671
672 #[test]
676 fn predicate_emission_preserves_namespace_resolution() {
677 let xml = r#"<feed xmlns="http://www.w3.org/2005/Atom">
678 <entry><title>t1</title></entry>
679 <entry><title>t2</title></entry>
680 </feed>"#;
681 let mut sp = StreamParser::from_str(xml)
682 .emit_when(|chain| chain.last().map(String::as_str) == Some("entry"));
683 let entry = sp.next().unwrap().expect("should yield entry");
684 let ns = entry.root().namespace.get()
685 .expect("entry must inherit feed's default namespace");
686 assert_eq!(ns.href(), "http://www.w3.org/2005/Atom");
687 }
688
689 #[test]
692 fn emitted_subtree_contains_children() {
693 let xml = "<r><item><title>X</title><body>hello</body></item></r>";
694 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
695 let item = sp.next().unwrap().unwrap();
696 let root = item.root();
697 assert_eq!(root.name(), "item");
698 let title = root.find_child("title").unwrap();
699 assert_eq!(title.text_content(), Some("X"));
700 let body = root.find_child("body").unwrap();
701 assert_eq!(body.text_content(), Some("hello"));
702 }
703
704 #[test]
705 fn emitted_subtree_contains_attributes() {
706 let xml = r#"<r><page id="1" lang="en">x</page></r>"#;
707 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
708 let page = sp.next().unwrap().unwrap();
709 let pairs: Vec<(&str, &str)> = page.root().attributes()
710 .map(|a| (a.name(), a.value())).collect();
711 assert_eq!(pairs, vec![("id", "1"), ("lang", "en")]);
712 }
713
714 #[test]
715 fn mixed_content_preserved() {
716 let xml = "<r><item>before<!-- c --><b>x</b><![CDATA[raw]]>after</item></r>";
717 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
718 let item = sp.next().unwrap().unwrap();
719 let kinds: Vec<NodeKind> = item.root().children().map(|c| c.kind).collect();
720 assert_eq!(kinds, vec![
721 NodeKind::Text, NodeKind::Comment, NodeKind::Element,
722 NodeKind::CData, NodeKind::Text,
723 ]);
724 }
725
726 #[test]
727 fn entity_in_attr_and_text_expanded() {
728 let xml = r#"<r><x v="a&b">c<d</x></r>"#;
729 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
730 let x = sp.next().unwrap().unwrap();
731 assert_eq!(x.root().attributes().next().unwrap().value(), "a&b");
732 assert_eq!(x.root().text_content(), Some("c<d"));
733 }
734
735 #[test]
738 fn many_emissions_independent_arenas() {
739 let mut xml = String::from("<r>");
742 for i in 0..50 { xml.push_str(&format!("<i>{i}</i>")); }
743 xml.push_str("</r>");
744
745 let mut sp = StreamParser::from_str(&xml).emit_at_depth(1);
746 let mut docs = Vec::new();
747 while let Some(d) = sp.next().unwrap() { docs.push(d); }
748 assert_eq!(docs.len(), 50);
749 for d in &docs {
751 assert!(d.memory_bytes() > 0);
752 assert!(d.memory_bytes() < 4096, "single-item arena should be small");
753 }
754 let removed = docs.remove(0);
756 drop(removed);
757 assert_eq!(docs[0].root().name(), "i");
758 }
759
760 #[test]
761 fn eof_then_none_idempotent() {
762 let mut sp = StreamParser::from_str("<r><a/></r>").emit_at_depth(1);
763 assert!(sp.next().unwrap().is_some());
764 assert!(sp.next().unwrap().is_none());
765 assert!(sp.next().unwrap().is_none());
766 }
767
768 #[test]
769 fn errors_propagate() {
770 let mut sp = StreamParser::from_str("<r><a></b></r>").emit_at_depth(1);
771 assert!(sp.next().is_err());
772 }
773
774 #[test]
775 fn prolog_events_are_silently_discarded() {
776 let xml = "<?xml version='1.0'?><!-- pre --><?stylesheet?><r><a/></r>";
777 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
778 let a = sp.next().unwrap().unwrap();
779 assert_eq!(a.root().name(), "a");
780 assert!(sp.next().unwrap().is_none());
781 }
782
783 #[test]
786 fn ns_inherited_from_ancestor_above_emit_boundary() {
787 let xml = r#"<feed xmlns="http://www.w3.org/2005/Atom"><entry><title>X</title></entry></feed>"#;
789 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
790 let entry = sp.next().unwrap().unwrap();
791 let ns = entry.root().namespace.get().expect("entry must inherit feed's default ns");
792 assert!(ns.prefix.is_none());
793 assert_eq!(ns.href(), "http://www.w3.org/2005/Atom");
794 let title = entry.root().find_child("title").unwrap();
796 assert_eq!(title.namespace.get().unwrap().href(), "http://www.w3.org/2005/Atom");
797 }
798
799 #[test]
800 fn ns_prefix_inherited_from_ancestor() {
801 let xml = r#"<rss xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><dc:title>X</dc:title></channel></rss>"#;
802 let mut sp = StreamParser::from_str(xml)
803 .emit_at_path(&["rss", "channel"]);
804 let channel = sp.next().unwrap().unwrap();
805 let title = channel.root().find_child("dc:title").unwrap();
806 let ns = title.namespace.get().unwrap();
807 assert_eq!(ns.prefix(), Some("dc"));
808 assert_eq!(ns.href(), "http://purl.org/dc/elements/1.1/");
809 }
810
811 #[test]
812 fn ns_declared_inside_emission() {
813 let xml = r#"<feed><entry xmlns:atom="http://www.w3.org/2005/Atom" atom:rel="self"/></feed>"#;
815 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
816 let entry = sp.next().unwrap().unwrap();
817 let rel = entry.root().attributes().find(|a| a.name() == "atom:rel").unwrap();
818 let ns = rel.namespace.get().unwrap();
819 assert_eq!(ns.href(), "http://www.w3.org/2005/Atom");
820 }
821
822 #[test]
823 fn ns_xml_prefix_resolved_inside_emission() {
824 let xml = r#"<r><item xml:lang="en"/></r>"#;
825 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
826 let item = sp.next().unwrap().unwrap();
827 let lang = item.root().attributes().next().unwrap();
828 assert_eq!(lang.namespace.get().unwrap().href(),
829 "http://www.w3.org/XML/1998/namespace");
830 }
831
832 #[test]
833 fn ns_undeclared_prefix_above_emit_boundary_errors() {
834 let xml = r#"<r><foo:bad/><item/></r>"#;
837 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
838 assert!(sp.next().is_err());
840 }
841
842 #[test]
843 fn ns_undeclared_prefix_inside_emission_errors() {
844 let xml = r#"<r><item><bad:thing/></item></r>"#;
845 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
846 assert!(sp.next().is_err());
847 }
848
849 #[test]
850 fn ns_cache_dedups_repeated_prefix_uses() {
851 let single = r#"<r><item><a><x:c xmlns:x="http://x.com/"/></a></item></r>"#;
856 let many = r#"<r><item><a xmlns:x="http://x.com/"><x:c/><x:c/><x:c/><x:c/><x:c/><x:c/><x:c/><x:c/></a></item></r>"#;
857
858 let mut sp1 = StreamParser::from_str(single).emit_at_depth(1);
859 let mut sp2 = StreamParser::from_str(many).emit_at_depth(1);
860 let d1 = sp1.next().unwrap().unwrap();
861 let d2 = sp2.next().unwrap().unwrap();
862 let ratio = d2.memory_bytes() as f64 / d1.memory_bytes() as f64;
866 assert!(ratio < 4.0, "8× uses shouldn't bloat 8× — got ratio {ratio:.2}");
867 }
868
869 #[test]
870 fn ns_override_in_emitted_subtree() {
871 let xml = r#"<root xmlns="http://outer.com/"><item><inner xmlns="http://inner.com/"/></item></root>"#;
873 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
874 let item = sp.next().unwrap().unwrap();
875 assert_eq!(item.root().namespace.get().unwrap().href(), "http://outer.com/");
876 let inner = item.root().find_child("inner").unwrap();
877 assert_eq!(inner.namespace.get().unwrap().href(), "http://inner.com/");
878 }
879
880 #[test]
881 fn ns_unprefixed_attr_not_in_default_ns() {
882 let xml = r#"<r xmlns="http://ex.com/"><item id="1"/></r>"#;
884 let mut sp = StreamParser::from_str(xml).emit_at_depth(1);
885 let item = sp.next().unwrap().unwrap();
886 assert!(item.root().namespace.get().is_some());
887 let id = item.root().attributes().next().unwrap();
888 assert!(id.namespace.get().is_none(), "default ns must not apply to unprefixed attrs");
889 }
890}