1use crate::xml::Namespace;
4use fop_types::{FopError, Location, Result};
5use quick_xml::events::{BytesStart, Event};
6use quick_xml::Reader;
7use std::collections::HashMap;
8use std::io::BufRead;
9
10#[derive(Debug, Clone)]
12pub struct EntityResolver {
13 entities: HashMap<String, String>,
14}
15
16impl EntityResolver {
17 pub fn new() -> Self {
19 let mut entities = HashMap::new();
20
21 entities.insert("amp".to_string(), "&".to_string());
23 entities.insert("lt".to_string(), "<".to_string());
24 entities.insert("gt".to_string(), ">".to_string());
25 entities.insert("quot".to_string(), "\"".to_string());
26 entities.insert("apos".to_string(), "'".to_string());
27
28 Self { entities }
29 }
30
31 pub fn add_entity(&mut self, name: String, value: String) {
33 self.entities.insert(name, value);
34 }
35
36 pub fn resolve(&self, entity: &str, location: Location) -> Result<String> {
38 if let Some(hex_str) = entity
40 .strip_prefix("#x")
41 .or_else(|| entity.strip_prefix("#X"))
42 {
43 if let Ok(code) = u32::from_str_radix(hex_str, 16) {
45 if let Some(ch) = char::from_u32(code) {
46 return Ok(ch.to_string());
47 }
48 }
49 return Err(FopError::EntityError {
50 message: format!("Invalid hexadecimal character reference: {}", entity),
51 location,
52 });
53 } else if let Some(dec_str) = entity.strip_prefix('#') {
54 if let Ok(code) = dec_str.parse::<u32>() {
56 if let Some(ch) = char::from_u32(code) {
57 return Ok(ch.to_string());
58 }
59 }
60 return Err(FopError::EntityError {
61 message: format!("Invalid decimal character reference: {}", entity),
62 location,
63 });
64 }
65
66 self.entities
68 .get(entity)
69 .cloned()
70 .ok_or_else(|| FopError::EntityError {
71 message: format!("Unknown entity: &{};", entity),
72 location,
73 })
74 }
75
76 pub fn resolve_entities(&self, text: &str, location: Location) -> Result<String> {
78 let mut result = String::new();
79 let mut chars = text.chars().peekable();
80
81 while let Some(ch) = chars.next() {
82 if ch == '&' {
83 let mut entity_name = String::new();
85 let mut found_semicolon = false;
86
87 while let Some(&next_ch) = chars.peek() {
88 if next_ch == ';' {
89 chars.next(); found_semicolon = true;
91 break;
92 }
93 entity_name.push(next_ch);
94 chars.next();
95 }
96
97 if !found_semicolon {
98 return Err(FopError::EntityError {
99 message: format!("Unterminated entity reference: &{}", entity_name),
100 location,
101 });
102 }
103
104 let resolved = self.resolve(&entity_name, location)?;
105 result.push_str(&resolved);
106 } else {
107 result.push(ch);
108 }
109 }
110
111 Ok(result)
112 }
113}
114
115impl Default for EntityResolver {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub struct ProcessingInstruction {
124 pub target: String,
125 pub data: Option<String>,
126}
127
128impl ProcessingInstruction {
129 pub fn new(target: String, data: Option<String>) -> Self {
130 Self { target, data }
131 }
132}
133
134#[derive(Debug, Default)]
136struct NamespaceScope {
137 decls: Vec<(String, String)>,
139}
140
141pub struct XmlParser<R: BufRead> {
143 reader: Reader<R>,
144 buf: Vec<u8>,
145 namespace_stack: Vec<NamespaceScope>,
147 entity_resolver: EntityResolver,
149 processing_instructions: Vec<ProcessingInstruction>,
151}
152
153impl<R: BufRead> XmlParser<R> {
154 pub fn new(reader: R) -> Self {
156 let mut xml_reader = Reader::from_reader(reader);
157 xml_reader.config_mut().trim_text(true);
158 xml_reader.config_mut().expand_empty_elements = true;
159
160 Self {
161 reader: xml_reader,
162 buf: Vec::new(),
163 namespace_stack: Vec::new(),
164 entity_resolver: EntityResolver::new(),
165 processing_instructions: Vec::new(),
166 }
167 }
168
169 pub fn reader(&self) -> &Reader<R> {
171 &self.reader
172 }
173
174 pub fn reader_mut(&mut self) -> &mut Reader<R> {
176 &mut self.reader
177 }
178
179 pub fn entity_resolver(&self) -> &EntityResolver {
181 &self.entity_resolver
182 }
183
184 pub fn entity_resolver_mut(&mut self) -> &mut EntityResolver {
186 &mut self.entity_resolver
187 }
188
189 pub fn processing_instructions(&self) -> &[ProcessingInstruction] {
191 &self.processing_instructions
192 }
193
194 pub fn location(&self) -> Location {
196 let pos = self.reader.buffer_position();
197 Location::new(pos as usize, 0)
199 }
200
201 pub fn read_event(&mut self) -> Result<Event<'static>> {
203 self.buf.clear();
204 let event = self
205 .reader
206 .read_event_into(&mut self.buf)
207 .map(|e| e.into_owned())
208 .map_err(|e| {
209 let location = self.location();
210 FopError::XmlErrorWithLocation {
211 message: format!("XML parsing error: {}", e),
212 location,
213 suggestion: None,
214 }
215 })?;
216
217 if let Event::PI(ref pi) = event {
219 if let Ok(target) = std::str::from_utf8(pi.as_ref()) {
220 let parts: Vec<&str> = target.splitn(2, ' ').collect();
222 let pi_target = parts[0].to_string();
223 let pi_data = parts.get(1).map(|s| s.to_string());
224
225 self.processing_instructions
226 .push(ProcessingInstruction::new(pi_target, pi_data));
227 }
228 }
229
230 Ok(event)
231 }
232
233 pub fn push_namespace_scope(&mut self, start: &BytesStart<'_>) {
236 let mut scope = NamespaceScope::default();
237 for attr in start.attributes().with_checks(false).flatten() {
238 let key = match std::str::from_utf8(attr.key.as_ref()) {
239 Ok(k) => k,
240 Err(_) => continue,
241 };
242 if key == "xmlns" {
243 if let Ok(uri) = attr.decode_and_unescape_value(self.reader.decoder()) {
244 scope.decls.push((String::new(), uri.into_owned()));
245 }
246 } else if let Some(suffix) = key.strip_prefix("xmlns:") {
247 if let Ok(uri) = attr.decode_and_unescape_value(self.reader.decoder()) {
248 scope.decls.push((suffix.to_string(), uri.into_owned()));
249 }
250 }
251 }
252 self.namespace_stack.push(scope);
253 }
254
255 pub fn pop_namespace_scope(&mut self) {
257 self.namespace_stack.pop();
258 }
259
260 pub fn resolve_prefix<'a>(&'a self, prefix: &str) -> Option<&'a str> {
263 for scope in self.namespace_stack.iter().rev() {
264 for (p, uri) in &scope.decls {
265 if p == prefix {
266 return Some(uri.as_str());
267 }
268 }
269 }
270 None
271 }
272
273 pub fn snapshot_in_scope(&self) -> Vec<(String, String)> {
277 let mut map: HashMap<String, String> = HashMap::new();
278 for scope in self.namespace_stack.iter() {
280 for (prefix, uri) in &scope.decls {
281 map.insert(prefix.clone(), uri.clone());
282 }
283 }
284 let mut result: Vec<(String, String)> = map.into_iter().collect();
285 result.sort_by(|a, b| a.0.cmp(&b.0));
286 result
287 }
288
289 pub fn extract_name(&self, start: &BytesStart) -> Result<(String, Namespace)> {
291 let location = self.location();
292 let name = start.name();
293
294 let (ns_prefix, local_name) = if let Some(pos) =
296 name.as_ref().iter().position(|&b| b == b':')
297 {
298 let prefix = std::str::from_utf8(&name.as_ref()[..pos]).map_err(|e| {
299 FopError::XmlErrorWithLocation {
300 message: format!("Invalid UTF-8 in prefix: {}", e),
301 location,
302 suggestion: None,
303 }
304 })?;
305 let local = std::str::from_utf8(&name.as_ref()[pos + 1..]).map_err(|e| {
306 FopError::XmlErrorWithLocation {
307 message: format!("Invalid UTF-8 in local name: {}", e),
308 location,
309 suggestion: None,
310 }
311 })?;
312 (Some(prefix.to_string()), local)
313 } else {
314 let local =
315 std::str::from_utf8(name.as_ref()).map_err(|e| FopError::XmlErrorWithLocation {
316 message: format!("Invalid UTF-8 in element name: {}", e),
317 location,
318 suggestion: None,
319 })?;
320 (None, local)
321 };
322
323 let ns_uri = if let Some(ref prefix) = ns_prefix {
325 self.resolve_prefix(prefix)
326 .map(str::to_string)
327 .unwrap_or_default()
328 } else {
329 self.resolve_prefix("")
330 .map(str::to_string)
331 .unwrap_or_default()
332 };
333
334 let namespace = Namespace::from_uri(&ns_uri);
335
336 Ok((local_name.to_string(), namespace))
337 }
338
339 pub fn extract_attributes(&self, start: &BytesStart) -> Result<Vec<(String, String)>> {
341 let location = self.location();
342 let mut attrs = Vec::new();
343
344 for attr_result in start.attributes() {
345 let attr = attr_result.map_err(|e| FopError::XmlErrorWithLocation {
346 message: format!("Attribute parsing error: {}", e),
347 location,
348 suggestion: None,
349 })?;
350
351 let key = std::str::from_utf8(attr.key.as_ref())
352 .map_err(|e| FopError::XmlErrorWithLocation {
353 message: format!("Invalid UTF-8 in attribute name: {}", e),
354 location,
355 suggestion: None,
356 })?
357 .to_string();
358
359 if key.starts_with("xmlns") {
361 continue;
362 }
363
364 let value = attr
366 .decode_and_unescape_value(self.reader.decoder())
367 .map_err(|e| FopError::XmlErrorWithLocation {
368 message: format!("Attribute value decode error: {}", e),
369 location,
370 suggestion: None,
371 })?
372 .to_string();
373
374 attrs.push((key, value));
375 }
376
377 Ok(attrs)
378 }
379
380 pub fn extract_text(&self, text: &[u8]) -> Result<String> {
382 let location = self.location();
383 let text_str = std::str::from_utf8(text).map_err(|e| FopError::XmlErrorWithLocation {
384 message: format!("Invalid UTF-8 in text: {}", e),
385 location,
386 suggestion: None,
387 })?;
388
389 self.entity_resolver.resolve_entities(text_str, location)
391 }
392
393 pub fn extract_cdata(&self, cdata: &[u8]) -> Result<String> {
395 let location = self.location();
396 std::str::from_utf8(cdata)
397 .map(|s| s.to_string())
398 .map_err(|e| FopError::XmlErrorWithLocation {
399 message: format!("Invalid UTF-8 in CDATA: {}", e),
400 location,
401 suggestion: None,
402 })
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use std::io::Cursor;
410
411 #[test]
412 fn test_parse_simple_fo() {
413 let xml = r#"<?xml version="1.0"?>
414<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
415 <fo:layout-master-set>
416 <fo:simple-page-master master-name="A4">
417 </fo:simple-page-master>
418 </fo:layout-master-set>
419</fo:root>"#;
420
421 let cursor = Cursor::new(xml);
422 let mut parser = XmlParser::new(cursor);
423
424 let mut found_root = false;
425 let mut found_layout_master_set = false;
426
427 loop {
428 let event = parser.read_event();
429 match event {
430 Ok(Event::Start(ref start)) | Ok(Event::Empty(ref start)) => {
431 parser.push_namespace_scope(start);
432 let (name, ns) = parser.extract_name(start).expect("test: should succeed");
433
434 if name == "root" && ns.is_fo() {
435 found_root = true;
436 }
437 if name == "layout-master-set" && ns.is_fo() {
438 found_layout_master_set = true;
439 }
440 }
441 Ok(Event::Eof) => break,
442 Err(e) => panic!("Parse error: {}", e),
443 _ => {}
444 }
445 }
446
447 assert!(found_root);
448 assert!(found_layout_master_set);
449 }
450
451 #[test]
452 fn test_extract_attributes() {
453 let xml = r#"<?xml version="1.0"?>
454<fo:simple-page-master xmlns:fo="http://www.w3.org/1999/XSL/Format"
455 master-name="A4"
456 page-width="210mm"
457 page-height="297mm">
458</fo:simple-page-master>"#;
459
460 let cursor = Cursor::new(xml);
461 let mut parser = XmlParser::new(cursor);
462
463 loop {
464 let event = parser.read_event();
465 match event {
466 Ok(Event::Start(ref start)) | Ok(Event::Empty(ref start)) => {
467 parser.push_namespace_scope(start);
468 let attrs = parser
469 .extract_attributes(start)
470 .expect("test: should succeed");
471
472 let master_name = attrs
474 .iter()
475 .find(|(k, _)| k == "master-name")
476 .map(|(_, v)| v.as_str());
477
478 assert_eq!(master_name, Some("A4"));
479
480 let page_width = attrs
481 .iter()
482 .find(|(k, _)| k == "page-width")
483 .map(|(_, v)| v.as_str());
484
485 assert_eq!(page_width, Some("210mm"));
486
487 break;
488 }
489 Ok(Event::Eof) => break,
490 Err(e) => panic!("Parse error: {}", e),
491 _ => {}
492 }
493 }
494 }
495
496 #[test]
497 fn test_cdata_section() {
498 let xml = r#"<?xml version="1.0"?>
499<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format">
500 <![CDATA[<tag> & "quotes"]]>
501</fo:block>"#;
502
503 let cursor = Cursor::new(xml);
504 let mut parser = XmlParser::new(cursor);
505
506 let mut found_cdata = false;
507 let mut cdata_content = String::new();
508
509 loop {
510 match parser.read_event() {
511 Ok(Event::CData(ref cdata)) => {
512 found_cdata = true;
513 cdata_content = parser.extract_cdata(cdata).expect("test: should succeed");
514 }
515 Ok(Event::Eof) => break,
516 Ok(_) => {}
517 Err(e) => panic!("Parse error: {}", e),
518 }
519 }
520
521 assert!(found_cdata);
522 assert_eq!(cdata_content, r#"<tag> & "quotes""#);
523 }
524
525 #[test]
526 fn test_entity_resolution_builtin() {
527 let resolver = EntityResolver::new();
528 let location = Location::new(1, 1);
529
530 assert_eq!(
531 resolver
532 .resolve("amp", location)
533 .expect("test: should succeed"),
534 "&"
535 );
536 assert_eq!(
537 resolver
538 .resolve("lt", location)
539 .expect("test: should succeed"),
540 "<"
541 );
542 assert_eq!(
543 resolver
544 .resolve("gt", location)
545 .expect("test: should succeed"),
546 ">"
547 );
548 assert_eq!(
549 resolver
550 .resolve("quot", location)
551 .expect("test: should succeed"),
552 "\""
553 );
554 assert_eq!(
555 resolver
556 .resolve("apos", location)
557 .expect("test: should succeed"),
558 "'"
559 );
560 }
561
562 #[test]
563 fn test_entity_resolution_numeric_decimal() {
564 let resolver = EntityResolver::new();
565 let location = Location::new(1, 1);
566
567 assert_eq!(
569 resolver
570 .resolve("#65", location)
571 .expect("test: should succeed"),
572 "A"
573 );
574 assert_eq!(
576 resolver
577 .resolve("#36", location)
578 .expect("test: should succeed"),
579 "$"
580 );
581 }
582
583 #[test]
584 fn test_entity_resolution_numeric_hex() {
585 let resolver = EntityResolver::new();
586 let location = Location::new(1, 1);
587
588 assert_eq!(
590 resolver
591 .resolve("#x41", location)
592 .expect("test: should succeed"),
593 "A"
594 );
595 assert_eq!(
596 resolver
597 .resolve("#X41", location)
598 .expect("test: should succeed"),
599 "A"
600 );
601 assert_eq!(
603 resolver
604 .resolve("#xA9", location)
605 .expect("test: should succeed"),
606 "©"
607 );
608 }
609
610 #[test]
611 fn test_entity_resolution_custom() {
612 let mut resolver = EntityResolver::new();
613 resolver.add_entity("copy".to_string(), "©".to_string());
614
615 let location = Location::new(1, 1);
616 assert_eq!(
617 resolver
618 .resolve("copy", location)
619 .expect("test: should succeed"),
620 "©"
621 );
622 }
623
624 #[test]
625 fn test_entity_resolution_in_text() {
626 let resolver = EntityResolver::new();
627 let location = Location::new(1, 1);
628
629 let text = "Price: $100 & up";
630 let resolved = resolver
631 .resolve_entities(text, location)
632 .expect("test: should succeed");
633 assert_eq!(resolved, "Price: $100 & up");
634 }
635
636 #[test]
637 fn test_entity_resolution_unknown() {
638 let resolver = EntityResolver::new();
639 let location = Location::new(1, 1);
640
641 let result = resolver.resolve("unknown", location);
642 assert!(result.is_err());
643 }
644
645 #[test]
646 fn test_processing_instruction() {
647 let xml = r#"<?xml version="1.0"?>
648<?xml-stylesheet type="text/xsl" href="style.xsl"?>
649<?fop-renderer backend="pdf"?>
650<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
651</fo:root>"#;
652
653 let cursor = Cursor::new(xml);
654 let mut parser = XmlParser::new(cursor);
655
656 loop {
657 match parser.read_event() {
658 Ok(Event::Eof) => break,
659 Ok(_) => {}
660 Err(e) => panic!("Parse error: {}", e),
661 }
662 }
663
664 let pis = parser.processing_instructions();
665 assert_eq!(pis.len(), 2);
666
667 assert_eq!(pis[0].target, "xml-stylesheet");
668 assert!(pis[0].data.is_some());
669
670 assert_eq!(pis[1].target, "fop-renderer");
671 assert!(pis[1].data.is_some());
672 }
673
674 #[test]
675 fn test_entities_in_attributes() {
676 let xml = r#"<?xml version="1.0"?>
677<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" title="Test & More">
678</fo:block>"#;
679
680 let cursor = Cursor::new(xml);
681 let mut parser = XmlParser::new(cursor);
682
683 loop {
684 match parser.read_event() {
685 Ok(Event::Start(ref start)) | Ok(Event::Empty(ref start)) => {
686 parser.push_namespace_scope(start);
687 let attrs = parser
688 .extract_attributes(start)
689 .expect("test: should succeed");
690
691 let title = attrs
692 .iter()
693 .find(|(k, _)| k == "title")
694 .map(|(_, v)| v.as_str());
695
696 assert_eq!(title, Some("Test & More"));
697 break;
698 }
699 Ok(Event::Eof) => break,
700 Ok(_) => {}
701 Err(e) => panic!("Parse error: {}", e),
702 }
703 }
704 }
705
706 #[test]
707 fn test_cdata_preserves_content() {
708 let xml = r#"<?xml version="1.0"?>
709<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format">
710 <![CDATA[Code with <tags> & "special" & chars]]>
711</fo:block>"#;
712
713 let cursor = Cursor::new(xml);
714 let mut parser = XmlParser::new(cursor);
715
716 let mut cdata_content = String::new();
717
718 loop {
719 match parser.read_event() {
720 Ok(Event::CData(ref cdata)) => {
721 cdata_content = parser.extract_cdata(cdata).expect("test: should succeed");
722 }
723 Ok(Event::Eof) => break,
724 Ok(_) => {}
725 Err(e) => panic!("Parse error: {}", e),
726 }
727 }
728
729 assert_eq!(cdata_content, r#"Code with <tags> & "special" & chars"#);
731 }
732
733 #[test]
734 fn test_multiple_entities() {
735 let resolver = EntityResolver::new();
736 let location = Location::new(1, 1);
737
738 let text = "<tag> & "text"";
739 let resolved = resolver
740 .resolve_entities(text, location)
741 .expect("test: should succeed");
742 assert_eq!(resolved, r#"<tag> & "text""#);
743 }
744
745 #[test]
746 fn test_unterminated_entity() {
747 let resolver = EntityResolver::new();
748 let location = Location::new(1, 1);
749
750 let text = "& no semicolon";
751 let result = resolver.resolve_entities(text, location);
752 assert!(result.is_err());
753 }
754
755 #[test]
756 fn test_location_tracking() {
757 let xml = r#"<?xml version="1.0"?>
758<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
759</fo:root>"#;
760
761 let cursor = Cursor::new(xml);
762 let parser = XmlParser::new(cursor);
763
764 let _location = parser.location();
766 }
767
768 #[test]
769 fn test_error_with_location() {
770 let xml = r#"<?xml version="1.0"?>
771<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
772 <unclosed-tag>
773</fo:root>"#;
774
775 let cursor = Cursor::new(xml);
776 let mut parser = XmlParser::new(cursor);
777
778 let mut error_found = false;
779
780 loop {
781 match parser.read_event() {
782 Ok(Event::Eof) => break,
783 Ok(_) => {}
784 Err(e) => {
785 error_found = true;
786 let error_str = format!("{}", e);
788 assert!(error_str.contains("line") || error_str.contains("XML parsing error"));
789 break;
790 }
791 }
792 }
793
794 assert!(error_found);
795 }
796}
797
798#[cfg(test)]
801mod additional_tests {
802 use super::*;
803 use std::io::Cursor;
804
805 #[test]
808 fn test_entity_resolver_apos() {
809 let resolver = EntityResolver::new();
810 let location = Location::new(1, 1);
811 assert_eq!(
812 resolver
813 .resolve("apos", location)
814 .expect("test: should succeed"),
815 "'"
816 );
817 }
818
819 #[test]
820 fn test_entity_resolver_quot() {
821 let resolver = EntityResolver::new();
822 let location = Location::new(1, 1);
823 assert_eq!(
824 resolver
825 .resolve("quot", location)
826 .expect("test: should succeed"),
827 "\""
828 );
829 }
830
831 #[test]
832 fn test_entity_resolver_gt() {
833 let resolver = EntityResolver::new();
834 let location = Location::new(1, 1);
835 assert_eq!(
836 resolver
837 .resolve("gt", location)
838 .expect("test: should succeed"),
839 ">"
840 );
841 }
842
843 #[test]
844 fn test_entity_resolver_empty_text() {
845 let resolver = EntityResolver::new();
846 let location = Location::new(1, 1);
847 let result = resolver
848 .resolve_entities("", location)
849 .expect("test: should succeed");
850 assert_eq!(result, "");
851 }
852
853 #[test]
854 fn test_entity_resolver_text_without_entities() {
855 let resolver = EntityResolver::new();
856 let location = Location::new(1, 1);
857 let result = resolver
858 .resolve_entities("hello world", location)
859 .expect("test: should succeed");
860 assert_eq!(result, "hello world");
861 }
862
863 #[test]
864 fn test_entity_resolver_only_entity() {
865 let resolver = EntityResolver::new();
866 let location = Location::new(1, 1);
867 let result = resolver
868 .resolve_entities("&", location)
869 .expect("test: should succeed");
870 assert_eq!(result, "&");
871 }
872
873 #[test]
874 fn test_entity_resolver_hex_zero() {
875 let resolver = EntityResolver::new();
877 let location = Location::new(1, 1);
878 let result = resolver
879 .resolve("#x0041", location)
880 .expect("test: should succeed");
881 assert_eq!(result, "A");
882 }
883
884 #[test]
885 fn test_entity_resolver_decimal_newline() {
886 let resolver = EntityResolver::new();
888 let location = Location::new(1, 1);
889 let result = resolver
890 .resolve("#10", location)
891 .expect("test: should succeed");
892 assert_eq!(result, "\n");
893 }
894
895 #[test]
896 fn test_entity_resolver_decimal_tab() {
897 let resolver = EntityResolver::new();
899 let location = Location::new(1, 1);
900 let result = resolver
901 .resolve("#9", location)
902 .expect("test: should succeed");
903 assert_eq!(result, "\t");
904 }
905
906 #[test]
907 fn test_entity_resolver_unicode_multibyte() {
908 let resolver = EntityResolver::new();
910 let location = Location::new(1, 1);
911 let result = resolver
912 .resolve("#x4e2d", location)
913 .expect("test: should succeed");
914 assert_eq!(result, "中");
915 }
916
917 #[test]
918 fn test_entity_resolver_add_multiple_custom() {
919 let mut resolver = EntityResolver::new();
920 resolver.add_entity("euro".to_string(), "€".to_string());
921 resolver.add_entity("yen".to_string(), "¥".to_string());
922 resolver.add_entity("pound".to_string(), "£".to_string());
923
924 let location = Location::new(1, 1);
925 assert_eq!(
926 resolver
927 .resolve("euro", location)
928 .expect("test: should succeed"),
929 "€"
930 );
931 assert_eq!(
932 resolver
933 .resolve("yen", location)
934 .expect("test: should succeed"),
935 "¥"
936 );
937 assert_eq!(
938 resolver
939 .resolve("pound", location)
940 .expect("test: should succeed"),
941 "£"
942 );
943 }
944
945 #[test]
946 fn test_entity_resolver_override_custom() {
947 let mut resolver = EntityResolver::new();
948 resolver.add_entity("amp".to_string(), "AMPERSAND".to_string());
950
951 let location = Location::new(1, 1);
952 assert_eq!(
953 resolver
954 .resolve("amp", location)
955 .expect("test: should succeed"),
956 "AMPERSAND"
957 );
958 }
959
960 #[test]
961 fn test_entity_resolver_resolve_entities_multiple() {
962 let resolver = EntityResolver::new();
963 let location = Location::new(1, 1);
964 let text = "<>&"'";
965 let result = resolver
966 .resolve_entities(text, location)
967 .expect("test: should succeed");
968 assert_eq!(result, "<>&\"'");
969 }
970
971 #[test]
972 fn test_entity_resolver_numeric_in_text() {
973 let resolver = EntityResolver::new();
974 let location = Location::new(1, 1);
975 let text = "AABBC";
976 let result = resolver
977 .resolve_entities(text, location)
978 .expect("test: should succeed");
979 assert_eq!(result, "AABBC");
980 }
981
982 #[test]
983 fn test_entity_resolver_hex_uppercase() {
984 let resolver = EntityResolver::new();
986 let location = Location::new(1, 1);
987 let result = resolver
988 .resolve("#X41", location)
989 .expect("test: should succeed");
990 assert_eq!(result, "A");
991 }
992
993 #[test]
996 fn test_processing_instruction_new() {
997 let pi = ProcessingInstruction::new("target".to_string(), Some("data".to_string()));
998 assert_eq!(pi.target, "target");
999 assert_eq!(pi.data, Some("data".to_string()));
1000 }
1001
1002 #[test]
1003 fn test_processing_instruction_no_data() {
1004 let pi = ProcessingInstruction::new("target".to_string(), None);
1005 assert_eq!(pi.target, "target");
1006 assert!(pi.data.is_none());
1007 }
1008
1009 #[test]
1010 fn test_processing_instruction_equality() {
1011 let pi1 = ProcessingInstruction::new("foo".to_string(), Some("bar".to_string()));
1012 let pi2 = ProcessingInstruction::new("foo".to_string(), Some("bar".to_string()));
1013 assert_eq!(pi1, pi2);
1014 }
1015
1016 #[test]
1017 fn test_processing_instruction_inequality() {
1018 let pi1 = ProcessingInstruction::new("foo".to_string(), Some("bar".to_string()));
1019 let pi2 = ProcessingInstruction::new("baz".to_string(), Some("bar".to_string()));
1020 assert_ne!(pi1, pi2);
1021 }
1022
1023 #[test]
1026 fn test_nested_namespace_declarations() {
1027 let xml = r#"<?xml version="1.0"?>
1028<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
1029 xmlns:svg="http://www.w3.org/2000/svg">
1030 <fo:layout-master-set></fo:layout-master-set>
1031</fo:root>"#;
1032
1033 let cursor = Cursor::new(xml);
1034 let mut parser = XmlParser::new(cursor);
1035
1036 let mut found_root = false;
1037 loop {
1038 let event = parser.read_event();
1039 match event {
1040 Ok(Event::Start(ref start)) | Ok(Event::Empty(ref start)) => {
1041 parser.push_namespace_scope(start);
1042 let result = parser.extract_name(start);
1043 if let Ok((name, ns)) = result {
1044 if name == "root" && ns.is_fo() {
1045 found_root = true;
1046 }
1047 }
1048 }
1049 Ok(Event::Eof) => break,
1050 Err(e) => panic!("Parse error: {}", e),
1051 _ => {}
1052 }
1053 }
1054 assert!(found_root);
1055 }
1056
1057 #[test]
1058 fn test_fox_extension_namespace() {
1059 let xml = r#"<?xml version="1.0"?>
1060<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
1061 xmlns:fox="http://xmlgraphics.apache.org/fop/extensions">
1062 <fo:layout-master-set></fo:layout-master-set>
1063</fo:root>"#;
1064
1065 let cursor = Cursor::new(xml);
1066 let mut parser = XmlParser::new(cursor);
1067
1068 let mut found_root = false;
1069 loop {
1070 let event = parser.read_event();
1071 match event {
1072 Ok(Event::Start(ref start)) | Ok(Event::Empty(ref start)) => {
1073 parser.push_namespace_scope(start);
1074 if let Ok((name, ns)) = parser.extract_name(start) {
1075 if name == "root" && ns.is_fo() {
1076 found_root = true;
1077 }
1078 }
1079 }
1080 Ok(Event::Eof) => break,
1081 Err(e) => panic!("Parse error: {}", e),
1082 _ => {}
1083 }
1084 }
1085 assert!(found_root);
1086 }
1087
1088 #[test]
1091 fn test_empty_element_produces_start_end() {
1092 let xml = r#"<?xml version="1.0"?>
1094<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
1095 <fo:layout-master-set>
1096 <fo:simple-page-master master-name="A4">
1097 <fo:region-body/>
1098 </fo:simple-page-master>
1099 </fo:layout-master-set>
1100</fo:root>"#;
1101
1102 let cursor = Cursor::new(xml);
1103 let mut parser = XmlParser::new(cursor);
1104
1105 let mut element_count = 0;
1106 loop {
1107 match parser.read_event() {
1108 Ok(Event::Start(ref start)) => {
1109 parser.push_namespace_scope(start);
1110 element_count += 1;
1111 }
1112 Ok(Event::Eof) => break,
1113 Ok(_) => {}
1114 Err(e) => panic!("Parse error: {}", e),
1115 }
1116 }
1117 assert!(element_count >= 4);
1119 }
1120
1121 #[test]
1122 fn test_multiple_attributes_preserved_order() {
1123 let xml = r#"<?xml version="1.0"?>
1124<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format"
1125 font-size="12pt"
1126 font-family="Arial"
1127 color="black"
1128 margin-top="10pt">text</fo:block>"#;
1129
1130 let cursor = Cursor::new(xml);
1131 let mut parser = XmlParser::new(cursor);
1132
1133 loop {
1134 match parser.read_event() {
1135 Ok(Event::Start(ref start)) => {
1136 parser.push_namespace_scope(start);
1137 let attrs = parser
1138 .extract_attributes(start)
1139 .expect("test: should succeed");
1140 assert_eq!(attrs.len(), 4);
1142 break;
1143 }
1144 Ok(Event::Eof) => break,
1145 Ok(_) => {}
1146 Err(e) => panic!("Parse error: {}", e),
1147 }
1148 }
1149 }
1150
1151 #[test]
1152 fn test_text_with_special_chars_in_cdata() {
1153 let xml = r#"<?xml version="1.0"?>
1154<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format"><![CDATA[a < b && c > d]]></fo:block>"#;
1155
1156 let cursor = Cursor::new(xml);
1157 let mut parser = XmlParser::new(cursor);
1158
1159 let mut cdata_text = String::new();
1160 loop {
1161 match parser.read_event() {
1162 Ok(Event::CData(ref cdata)) => {
1163 cdata_text = parser.extract_cdata(cdata).expect("test: should succeed");
1164 }
1165 Ok(Event::Eof) => break,
1166 Ok(_) => {}
1167 Err(e) => panic!("Parse error: {}", e),
1168 }
1169 }
1170 assert_eq!(cdata_text, "a < b && c > d");
1171 }
1172
1173 #[test]
1174 fn test_extract_cdata_preserves_angle_brackets() {
1175 let xml = r#"<?xml version="1.0"?>
1176<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format"><![CDATA[<tag attr="val"/>]]></fo:block>"#;
1177
1178 let cursor = Cursor::new(xml);
1179 let mut parser = XmlParser::new(cursor);
1180
1181 let mut cdata_text = String::new();
1182 loop {
1183 match parser.read_event() {
1184 Ok(Event::CData(ref cdata)) => {
1185 cdata_text = parser.extract_cdata(cdata).expect("test: should succeed");
1186 }
1187 Ok(Event::Eof) => break,
1188 Ok(_) => {}
1189 Err(e) => panic!("Parse error: {}", e),
1190 }
1191 }
1192 assert_eq!(cdata_text, r#"<tag attr="val"/>"#);
1193 }
1194
1195 #[test]
1196 fn test_comment_does_not_produce_text_event() {
1197 let xml = r#"<?xml version="1.0"?>
1198<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"><!-- this is a comment --></fo:root>"#;
1199
1200 let cursor = Cursor::new(xml);
1201 let mut parser = XmlParser::new(cursor);
1202
1203 let mut text_events = 0;
1204 loop {
1205 match parser.read_event() {
1206 Ok(Event::Text(_)) => {
1207 text_events += 1;
1208 }
1209 Ok(Event::Eof) => break,
1210 Ok(_) => {}
1211 Err(e) => panic!("Parse error: {}", e),
1212 }
1213 }
1214 assert_eq!(text_events, 0);
1216 }
1217
1218 #[test]
1219 fn test_multiple_processing_instructions() {
1220 let xml = r#"<?xml version="1.0"?>
1221<?stylesheet type="text/css"?>
1222<?renderer backend="pdf"?>
1223<?custom-pi data="value"?>
1224<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"></fo:root>"#;
1225
1226 let cursor = Cursor::new(xml);
1227 let mut parser = XmlParser::new(cursor);
1228
1229 loop {
1230 match parser.read_event() {
1231 Ok(Event::Eof) => break,
1232 Ok(_) => {}
1233 Err(e) => panic!("Parse error: {}", e),
1234 }
1235 }
1236
1237 let pis = parser.processing_instructions();
1238 assert_eq!(pis.len(), 3);
1239 assert_eq!(pis[0].target, "stylesheet");
1240 assert_eq!(pis[1].target, "renderer");
1241 assert_eq!(pis[2].target, "custom-pi");
1242 }
1243
1244 #[test]
1245 fn test_no_processing_instructions_when_none_present() {
1246 let xml = r#"<?xml version="1.0"?>
1247<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"></fo:root>"#;
1248
1249 let cursor = Cursor::new(xml);
1250 let mut parser = XmlParser::new(cursor);
1251
1252 loop {
1253 match parser.read_event() {
1254 Ok(Event::Eof) => break,
1255 Ok(_) => {}
1256 Err(e) => panic!("Parse error: {}", e),
1257 }
1258 }
1259
1260 let pis = parser.processing_instructions();
1261 assert_eq!(pis.len(), 0);
1262 }
1263
1264 #[test]
1265 fn test_attributes_with_apos_entity() {
1266 let xml = r#"<?xml version="1.0"?>
1267<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" title="it's">text</fo:block>"#;
1268
1269 let cursor = Cursor::new(xml);
1270 let mut parser = XmlParser::new(cursor);
1271
1272 loop {
1273 match parser.read_event() {
1274 Ok(Event::Start(ref start)) => {
1275 parser.push_namespace_scope(start);
1276 let attrs = parser
1277 .extract_attributes(start)
1278 .expect("test: should succeed");
1279 let title = attrs
1280 .iter()
1281 .find(|(k, _)| k == "title")
1282 .map(|(_, v)| v.as_str());
1283 assert_eq!(title, Some("it's"));
1284 break;
1285 }
1286 Ok(Event::Eof) => break,
1287 Ok(_) => {}
1288 Err(e) => panic!("Parse error: {}", e),
1289 }
1290 }
1291 }
1292
1293 #[test]
1294 fn test_attributes_with_lt_entity() {
1295 let xml = r#"<?xml version="1.0"?>
1296<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" title="a < b">text</fo:block>"#;
1297
1298 let cursor = Cursor::new(xml);
1299 let mut parser = XmlParser::new(cursor);
1300
1301 loop {
1302 match parser.read_event() {
1303 Ok(Event::Start(ref start)) => {
1304 parser.push_namespace_scope(start);
1305 let attrs = parser
1306 .extract_attributes(start)
1307 .expect("test: should succeed");
1308 let title = attrs
1309 .iter()
1310 .find(|(k, _)| k == "title")
1311 .map(|(_, v)| v.as_str());
1312 assert_eq!(title, Some("a < b"));
1313 break;
1314 }
1315 Ok(Event::Eof) => break,
1316 Ok(_) => {}
1317 Err(e) => panic!("Parse error: {}", e),
1318 }
1319 }
1320 }
1321
1322 #[test]
1323 fn test_attribute_with_numeric_entity() {
1324 let xml = r#"<?xml version="1.0"?>
1325<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" title="ABC">text</fo:block>"#;
1326
1327 let cursor = Cursor::new(xml);
1328 let mut parser = XmlParser::new(cursor);
1329
1330 loop {
1331 match parser.read_event() {
1332 Ok(Event::Start(ref start)) => {
1333 parser.push_namespace_scope(start);
1334 let attrs = parser
1335 .extract_attributes(start)
1336 .expect("test: should succeed");
1337 let title = attrs
1338 .iter()
1339 .find(|(k, _)| k == "title")
1340 .map(|(_, v)| v.as_str());
1341 assert_eq!(title, Some("ABC"));
1342 break;
1343 }
1344 Ok(Event::Eof) => break,
1345 Ok(_) => {}
1346 Err(e) => panic!("Parse error: {}", e),
1347 }
1348 }
1349 }
1350
1351 #[test]
1352 fn test_xml_with_utf8_text() {
1353 let xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fo:root xmlns:fo=\"http://www.w3.org/1999/XSL/Format\"><fo:block>日本語テスト</fo:block></fo:root>";
1354
1355 let cursor = Cursor::new(xml);
1356 let mut parser = XmlParser::new(cursor);
1357
1358 let mut text_content = String::new();
1359 loop {
1360 match parser.read_event() {
1361 Ok(Event::Text(ref text)) => {
1362 text_content = parser.extract_text(text).expect("test: should succeed");
1363 }
1364 Ok(Event::Eof) => break,
1365 Ok(_) => {}
1366 Err(e) => panic!("Parse error: {}", e),
1367 }
1368 }
1369 assert_eq!(text_content, "日本語テスト");
1370 }
1371
1372 #[test]
1373 fn test_entity_resolver_clone() {
1374 let mut resolver = EntityResolver::new();
1375 resolver.add_entity("test".to_string(), "TEST_VALUE".to_string());
1376 let cloned = resolver.clone();
1377
1378 let location = Location::new(1, 1);
1379 assert_eq!(
1380 cloned
1381 .resolve("test", location)
1382 .expect("test: should succeed"),
1383 "TEST_VALUE"
1384 );
1385 assert_eq!(
1386 cloned
1387 .resolve("amp", location)
1388 .expect("test: should succeed"),
1389 "&"
1390 );
1391 }
1392
1393 #[test]
1394 fn test_entity_resolver_default() {
1395 let resolver = EntityResolver::default();
1396 let location = Location::new(1, 1);
1397 assert_eq!(
1399 resolver
1400 .resolve("amp", location)
1401 .expect("test: should succeed"),
1402 "&"
1403 );
1404 assert_eq!(
1405 resolver
1406 .resolve("lt", location)
1407 .expect("test: should succeed"),
1408 "<"
1409 );
1410 assert_eq!(
1411 resolver
1412 .resolve("gt", location)
1413 .expect("test: should succeed"),
1414 ">"
1415 );
1416 assert_eq!(
1417 resolver
1418 .resolve("quot", location)
1419 .expect("test: should succeed"),
1420 "\""
1421 );
1422 assert_eq!(
1423 resolver
1424 .resolve("apos", location)
1425 .expect("test: should succeed"),
1426 "'"
1427 );
1428 }
1429
1430 #[test]
1431 fn test_xml_deeply_nested_elements() {
1432 let xml = r#"<?xml version="1.0"?>
1434<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
1435 <fo:layout-master-set>
1436 <fo:simple-page-master master-name="p1">
1437 <fo:region-body/>
1438 </fo:simple-page-master>
1439 </fo:layout-master-set>
1440 <fo:page-sequence master-reference="p1">
1441 <fo:flow flow-name="xsl-region-body">
1442 <fo:block>
1443 <fo:inline>
1444 <fo:inline>
1445 <fo:inline>deep nesting</fo:inline>
1446 </fo:inline>
1447 </fo:inline>
1448 </fo:block>
1449 </fo:flow>
1450 </fo:page-sequence>
1451</fo:root>"#;
1452
1453 let cursor = Cursor::new(xml);
1454 let mut parser = XmlParser::new(cursor);
1455 let mut error = None;
1456
1457 loop {
1458 match parser.read_event() {
1459 Ok(Event::Eof) => break,
1460 Ok(_) => {}
1461 Err(e) => {
1462 error = Some(e);
1463 break;
1464 }
1465 }
1466 }
1467 assert!(error.is_none(), "Deep nesting should parse without error");
1468 }
1469
1470 #[test]
1471 fn test_xml_empty_text_nodes_trimmed() {
1472 let xml = r#"<?xml version="1.0"?>
1474<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
1475 <fo:layout-master-set>
1476 </fo:layout-master-set>
1477</fo:root>"#;
1478
1479 let cursor = Cursor::new(xml);
1480 let mut parser = XmlParser::new(cursor);
1481
1482 let mut non_empty_text = 0;
1483 loop {
1484 match parser.read_event() {
1485 Ok(Event::Text(ref text)) => {
1486 let content = parser.extract_text(text).unwrap_or_default();
1487 if !content.is_empty() {
1488 non_empty_text += 1;
1489 }
1490 }
1491 Ok(Event::Eof) => break,
1492 Ok(_) => {}
1493 Err(e) => panic!("Parse error: {}", e),
1494 }
1495 }
1496 assert_eq!(non_empty_text, 0);
1498 }
1499
1500 #[test]
1501 fn test_xml_pi_target_with_data() {
1502 let xml = r#"<?xml version="1.0"?>
1503<?fop-config key="value" other="data"?>
1504<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"></fo:root>"#;
1505
1506 let cursor = Cursor::new(xml);
1507 let mut parser = XmlParser::new(cursor);
1508
1509 loop {
1510 match parser.read_event() {
1511 Ok(Event::Eof) => break,
1512 Ok(_) => {}
1513 Err(e) => panic!("Parse error: {}", e),
1514 }
1515 }
1516
1517 let pis = parser.processing_instructions();
1518 assert_eq!(pis.len(), 1);
1519 assert_eq!(pis[0].target, "fop-config");
1520 assert!(pis[0].data.is_some());
1521 let data = pis[0].data.as_ref().expect("test: should succeed");
1522 assert!(data.contains("key"));
1523 }
1524
1525 #[test]
1526 fn test_entity_resolver_unknown_entity_has_name_in_error() {
1527 let resolver = EntityResolver::new();
1528 let location = Location::new(5, 10);
1529 let result = resolver.resolve("nonexistent", location);
1530 assert!(result.is_err());
1531 let err = result.unwrap_err();
1532 let err_str = format!("{}", err);
1533 assert!(err_str.contains("nonexistent"));
1534 }
1535
1536 #[test]
1537 fn test_entity_resolver_invalid_hex_ref() {
1538 let resolver = EntityResolver::new();
1539 let location = Location::new(1, 1);
1540 let result = resolver.resolve("#xZZZZ", location);
1542 assert!(result.is_err());
1543 }
1544
1545 #[test]
1546 fn test_entity_resolver_invalid_decimal_ref() {
1547 let resolver = EntityResolver::new();
1548 let location = Location::new(1, 1);
1549 let result = resolver.resolve("#abc", location);
1551 assert!(result.is_err());
1552 }
1553
1554 #[test]
1557 fn test_namespace_scope_pop_restores_outer() {
1558 let xml = r#"<root xmlns:x="uri:outer"></root>"#;
1559 let cursor = Cursor::new(xml);
1560 let mut parser = XmlParser::new(cursor);
1561
1562 let outer_start = BytesStart::from_content(r#"root xmlns:x="uri:outer""#, 4);
1564 let inner_start = BytesStart::from_content(r#"child xmlns:x="uri:inner""#, 5);
1565 parser.push_namespace_scope(&outer_start);
1566 parser.push_namespace_scope(&inner_start);
1567 assert_eq!(
1568 parser.resolve_prefix("x"),
1569 Some("uri:inner"),
1570 "inner scope should shadow outer"
1571 );
1572 parser.pop_namespace_scope();
1573 assert_eq!(
1574 parser.resolve_prefix("x"),
1575 Some("uri:outer"),
1576 "after pop, outer scope should be visible"
1577 );
1578 parser.pop_namespace_scope();
1579 assert_eq!(
1580 parser.resolve_prefix("x"),
1581 None,
1582 "after all pops, prefix should be unresolvable"
1583 );
1584 }
1585
1586 #[test]
1587 fn test_namespace_scope_sibling_rebind_does_not_leak() {
1588 let xml = r#"<root></root>"#;
1589 let cursor = Cursor::new(xml);
1590 let mut parser = XmlParser::new(cursor);
1591
1592 let sibling_a = BytesStart::from_content(r#"a xmlns:foo="uri:a""#, 1);
1593 parser.push_namespace_scope(&sibling_a);
1594 assert_eq!(parser.resolve_prefix("foo"), Some("uri:a"));
1595 parser.pop_namespace_scope();
1596
1597 let sibling_b = BytesStart::from_content(r#"b"#, 1);
1599 parser.push_namespace_scope(&sibling_b);
1600 assert_eq!(
1601 parser.resolve_prefix("foo"),
1602 None,
1603 "sibling's xmlns:foo must not be visible after pop"
1604 );
1605 parser.pop_namespace_scope();
1606 }
1607
1608 #[test]
1609 fn test_namespace_snapshot_in_scope_innermost_wins() {
1610 let xml = r#"<root></root>"#;
1611 let cursor = Cursor::new(xml);
1612 let mut parser = XmlParser::new(cursor);
1613
1614 let outer = BytesStart::from_content(r#"outer xmlns:x="outer:uri""#, 5);
1615 let inner = BytesStart::from_content(r#"inner xmlns:x="inner:uri""#, 5);
1616 parser.push_namespace_scope(&outer);
1617 parser.push_namespace_scope(&inner);
1618
1619 let snapshot = parser.snapshot_in_scope();
1620 let x_uri = snapshot
1621 .iter()
1622 .find(|(p, _)| p == "x")
1623 .map(|(_, u)| u.as_str());
1624 assert_eq!(
1625 x_uri,
1626 Some("inner:uri"),
1627 "innermost binding should win in snapshot"
1628 );
1629 }
1630
1631 #[test]
1632 fn test_namespace_scope_empty_prefix_default_namespace() {
1633 let xml = r#"<root></root>"#;
1634 let cursor = Cursor::new(xml);
1635 let mut parser = XmlParser::new(cursor);
1636
1637 let start = BytesStart::from_content(r#"root xmlns="http://example.com/""#, 4);
1638 parser.push_namespace_scope(&start);
1639 assert_eq!(
1640 parser.resolve_prefix(""),
1641 Some("http://example.com/"),
1642 "default namespace should be resolvable via empty-string prefix"
1643 );
1644 parser.pop_namespace_scope();
1645 }
1646
1647 #[test]
1648 fn test_namespace_resolve_prefix_returns_none_on_empty_stack() {
1649 let xml = r#"<root></root>"#;
1650 let cursor = Cursor::new(xml);
1651 let parser = XmlParser::new(cursor);
1652 assert_eq!(parser.resolve_prefix("fo"), None);
1653 assert_eq!(parser.resolve_prefix(""), None);
1654 }
1655}