1use crate::unescape::{unescape, validate_references};
7use std::borrow::Cow;
8use std::collections::HashSet;
9use std::error::Error;
10use std::fmt::{Display, Formatter};
11use xmlparser::{ElementEnd, Token, Tokenizer};
12
13pub type Depth = usize;
14
15#[derive(Debug)]
19enum XmlDecodeErrorKind {
20 InvalidXml(xmlparser::Error),
21 InvalidEscape { esc: String },
22 Custom(Cow<'static, str>),
23 Unhandled(Box<dyn std::error::Error + Send + Sync + 'static>),
24}
25
26#[derive(Debug)]
27pub struct XmlDecodeError {
28 kind: XmlDecodeErrorKind,
29}
30
31impl Display for XmlDecodeError {
32 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
33 match &self.kind {
34 XmlDecodeErrorKind::InvalidXml(_) => write!(f, "XML parse error"),
35 XmlDecodeErrorKind::InvalidEscape { esc } => write!(f, "invalid XML escape: {esc}"),
36 XmlDecodeErrorKind::Custom(msg) => write!(f, "error parsing XML: {msg}"),
37 XmlDecodeErrorKind::Unhandled(_) => write!(f, "error parsing XML"),
38 }
39 }
40}
41
42impl Error for XmlDecodeError {
43 fn source(&self) -> Option<&(dyn Error + 'static)> {
44 match &self.kind {
45 XmlDecodeErrorKind::InvalidXml(source) => Some(source as _),
46 XmlDecodeErrorKind::Unhandled(source) => Some(source.as_ref() as _),
47 XmlDecodeErrorKind::InvalidEscape { .. } | XmlDecodeErrorKind::Custom(..) => None,
48 }
49 }
50}
51
52impl XmlDecodeError {
53 pub(crate) fn invalid_xml(error: xmlparser::Error) -> Self {
54 Self {
55 kind: XmlDecodeErrorKind::InvalidXml(error),
56 }
57 }
58
59 pub(crate) fn invalid_escape(esc: impl Into<String>) -> Self {
60 Self {
61 kind: XmlDecodeErrorKind::InvalidEscape { esc: esc.into() },
62 }
63 }
64
65 pub fn custom(msg: impl Into<Cow<'static, str>>) -> Self {
66 Self {
67 kind: XmlDecodeErrorKind::Custom(msg.into()),
68 }
69 }
70
71 pub fn unhandled(error: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
72 Self {
73 kind: XmlDecodeErrorKind::Unhandled(error.into()),
74 }
75 }
76}
77
78#[derive(PartialEq, Debug)]
79pub struct Name<'a> {
80 pub prefix: &'a str,
81 pub local: &'a str,
82}
83
84impl Name<'_> {
85 pub fn matches(&self, tag_name: &str) -> bool {
87 let split = tag_name.find(':');
88 match split {
89 None => tag_name == self.local,
90 Some(idx) => {
91 let (prefix, local) = tag_name.split_at(idx);
92 let local = &local[1..];
93 self.local == local && self.prefix == prefix
94 }
95 }
96 }
97}
98
99#[derive(Debug, PartialEq)]
100pub struct Attr<'a> {
101 name: Name<'a>,
102 value: Cow<'a, str>,
104}
105
106#[derive(Debug, PartialEq)]
107pub struct StartEl<'a> {
108 name: Name<'a>,
109 attributes: Vec<Attr<'a>>,
110 closed: bool,
111 depth: Depth,
112}
113
114impl<'a> StartEl<'a> {
122 pub fn depth(&self) -> Depth {
123 self.depth
124 }
125
126 fn new(local: &'a str, prefix: &'a str, depth: Depth) -> Self {
127 Self {
128 name: Name { prefix, local },
129 attributes: vec![],
130 closed: false,
131 depth,
132 }
133 }
134
135 pub fn attr<'b>(&'b self, key: &'b str) -> Option<&'b str> {
139 self.attributes
140 .iter()
141 .find(|attr| attr.name.matches(key))
142 .map(|attr| attr.value.as_ref())
143 }
144
145 pub fn matches(&self, pat: &str) -> bool {
148 self.name.matches(pat)
149 }
150
151 pub fn local(&self) -> &str {
158 self.name.local
159 }
160
161 pub fn prefix(&self) -> &str {
167 self.name.prefix
168 }
169
170 fn end_el(&self, el: ElementEnd<'_>, depth: Depth) -> bool {
172 if depth != self.depth {
173 return false;
174 }
175 match el {
176 ElementEnd::Open => false,
177 ElementEnd::Close(prefix, local) => {
178 prefix.as_str() == self.name.prefix && local.as_str() == self.name.local
179 }
180 ElementEnd::Empty => false,
181 }
182 }
183}
184
185pub struct Document<'a> {
190 tokenizer: Tokenizer<'a>,
191 depth: Depth,
192 open_elements: Option<Vec<(&'a str, &'a str)>>,
195 attribute_names: Option<HashSet<(&'a str, &'a str)>>,
197 malformed: Option<String>,
199}
200
201impl<'a> TryFrom<&'a [u8]> for Document<'a> {
202 type Error = XmlDecodeError;
203
204 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
205 Ok(Document::new(
206 std::str::from_utf8(value).map_err(XmlDecodeError::unhandled)?,
207 ))
208 }
209}
210
211impl<'inp> Document<'inp> {
212 pub fn new(doc: &'inp str) -> Self {
213 Document {
214 tokenizer: Tokenizer::from(doc),
215 depth: 0,
216 open_elements: None,
217 attribute_names: None,
218 malformed: None,
219 }
220 }
221
222 pub(crate) fn check_well_formedness(&mut self) {
231 self.open_elements = Some(Vec::new());
232 self.attribute_names = Some(HashSet::new());
233 }
234
235 pub(crate) fn finish_well_formed(&mut self) -> Result<(), XmlDecodeError> {
238 for _ in self.by_ref() {}
239 match self.malformed.take() {
240 Some(msg) => Err(XmlDecodeError::custom(msg)),
241 None => Ok(()),
242 }
243 }
244
245 #[cold]
248 #[inline(never)]
249 fn record_malformed(&mut self, what: &str, prefix: &str, local: &str) -> XmlDecodeError {
250 let msg = if prefix.is_empty() {
251 format!("{what} `{local}`")
252 } else {
253 format!("{what} `{prefix}:{local}`")
254 };
255 let err = XmlDecodeError::custom(msg.clone());
256 self.malformed = Some(msg);
257 err
258 }
259
260 pub fn next_start_element<'a>(&'a mut self) -> Option<StartEl<'inp>> {
276 next_start_element(self)
277 }
278
279 pub fn root_element<'a>(&'a mut self) -> Result<ScopedDecoder<'inp, 'a>, XmlDecodeError> {
281 let start_el = self
282 .next_start_element()
283 .ok_or_else(|| XmlDecodeError::custom("no root element"))?;
284 Ok(ScopedDecoder {
285 doc: self,
286 start_el,
287 terminated: false,
288 })
289 }
290
291 pub fn scoped_to<'a>(&'a mut self, start_el: StartEl<'inp>) -> ScopedDecoder<'inp, 'a> {
297 ScopedDecoder {
298 doc: self,
299 start_el,
300 terminated: false,
301 }
302 }
303
304 fn offset(&self) -> usize {
306 self.tokenizer.stream().pos()
307 }
308}
309
310#[derive(Debug)]
313pub struct XmlToken<'inp>(Token<'inp>);
314
315impl<'inp> Iterator for Document<'inp> {
326 type Item = Result<(XmlToken<'inp>, Depth), XmlDecodeError>;
327 fn next<'a>(&'a mut self) -> Option<Result<(XmlToken<'inp>, Depth), XmlDecodeError>> {
328 if self.malformed.is_some() {
329 return None;
330 }
331 let tok = match self.tokenizer.next() {
332 Some(tok) => tok,
333 None => {
334 let unclosed = self.open_elements.as_ref().and_then(|o| o.last()).copied();
335 return unclosed.map(|(prefix, local)| {
336 Err(self.record_malformed("unclosed element", prefix, local))
337 });
338 }
339 };
340 let tok = match tok {
341 Err(e) => {
342 if self.open_elements.is_some() {
343 self.malformed = Some(e.to_string());
344 }
345 return Some(Err(XmlDecodeError::invalid_xml(e)));
346 }
347 Ok(tok) => tok,
348 };
349 if let Some(attributes) = self.attribute_names.as_mut() {
350 let references = match tok {
351 Token::Attribute {
352 prefix,
353 local,
354 value,
355 ..
356 } => {
357 let name = (prefix.as_str(), local.as_str());
358 if !attributes.insert(name) {
359 return Some(Err(self.record_malformed(
360 "duplicate attribute",
361 name.0,
362 name.1,
363 )));
364 }
365 Some(value.as_str())
366 }
367 Token::Text { text } => Some(text.as_str()),
368 _ => None,
369 };
370 if let Some(text) = references {
371 if let Err(error) = validate_references(text) {
372 self.malformed = Some(error.to_string());
373 return Some(Err(error));
374 }
375 }
376 }
377 match tok {
379 Token::ElementEnd {
380 end: ElementEnd::Close(prefix, local),
381 ..
382 } => {
383 if let Some(open) = self.open_elements.as_mut() {
384 let (prefix, local) = (prefix.as_str(), local.as_str());
385 let matches = |(p, l): (&str, &str)| {
390 l == local && ((p.is_empty() && prefix.is_empty()) || p == prefix)
391 };
392 if !open.pop().is_some_and(matches) {
393 return Some(Err(self.record_malformed(
394 "mismatched end tag",
395 prefix,
396 local,
397 )));
398 }
399 }
400 self.depth -= 1;
401 }
402 Token::ElementEnd {
403 end: ElementEnd::Empty,
404 ..
405 } => {
406 if let Some(open) = self.open_elements.as_mut() {
407 open.pop();
408 }
409 self.depth -= 1
410 }
411 t @ Token::ElementStart { prefix, local, .. } => {
412 if let Some(open) = self.open_elements.as_mut() {
413 open.push((prefix.as_str(), local.as_str()));
414 }
415 if let Some(attributes) = self.attribute_names.as_mut() {
416 attributes.clear();
417 }
418 self.depth += 1;
419 return Some(Ok((XmlToken(t), self.depth - 1)));
422 }
423 _ => {}
424 }
425 Some(Ok((XmlToken(tok), self.depth)))
426 }
427}
428
429pub struct ScopedDecoder<'inp, 'a> {
434 doc: &'a mut Document<'inp>,
435 start_el: StartEl<'inp>,
436 terminated: bool,
437}
438
439impl Drop for ScopedDecoder<'_, '_> {
442 fn drop(&mut self) {
443 for _ in self {}
444 }
445}
446
447impl<'inp> ScopedDecoder<'inp, '_> {
448 pub fn start_el<'a>(&'a self) -> &'a StartEl<'inp> {
450 &self.start_el
451 }
452
453 pub(crate) fn end_offset(mut self) -> usize {
460 for _ in &mut self {}
461 self.doc.offset()
462 }
463
464 pub fn next_tag<'a>(&'a mut self) -> Option<ScopedDecoder<'inp, 'a>> {
479 let next_tag = next_start_element(self)?;
480 Some(self.nested_decoder(next_tag))
481 }
482
483 fn nested_decoder<'a>(&'a mut self, start_el: StartEl<'inp>) -> ScopedDecoder<'inp, 'a> {
484 ScopedDecoder {
485 doc: self.doc,
486 start_el,
487 terminated: false,
488 }
489 }
490}
491
492impl<'inp> Iterator for ScopedDecoder<'inp, '_> {
493 type Item = Result<(XmlToken<'inp>, Depth), XmlDecodeError>;
494
495 fn next(&mut self) -> Option<Self::Item> {
496 if self.start_el.closed {
497 self.terminated = true;
498 }
499 if self.terminated {
500 return None;
501 }
502 let (tok, depth) = match self.doc.next() {
503 Some(Ok((tok, depth))) => (tok, depth),
504 other => return other,
505 };
506
507 match tok.0 {
508 Token::ElementEnd { end, .. } if self.start_el.end_el(end, depth) => {
509 self.terminated = true;
510 return None;
511 }
512 _ => {}
513 }
514 Some(Ok((tok, depth)))
515 }
516}
517
518fn next_start_element<'a, 'inp>(
520 tokens: &'a mut impl Iterator<Item = Result<(XmlToken<'inp>, Depth), XmlDecodeError>>,
521) -> Option<StartEl<'inp>> {
522 let mut out = StartEl::new("", "", 0);
523 loop {
524 match tokens.next()? {
525 Ok((XmlToken(Token::ElementStart { local, prefix, .. }), depth)) => {
526 out.name.local = local.as_str();
527 out.name.prefix = prefix.as_str();
528 out.depth = depth;
529 }
530 Ok((
531 XmlToken(Token::Attribute {
532 prefix,
533 local,
534 value,
535 ..
536 }),
537 _,
538 )) => out.attributes.push(Attr {
539 name: Name {
540 local: local.as_str(),
541 prefix: prefix.as_str(),
542 },
543 value: unescape(value.as_str()).ok()?,
544 }),
545 Ok((
546 XmlToken(Token::ElementEnd {
547 end: ElementEnd::Open,
548 ..
549 }),
550 _,
551 )) => break,
552 Ok((
553 XmlToken(Token::ElementEnd {
554 end: ElementEnd::Empty,
555 ..
556 }),
557 _,
558 )) => {
559 out.closed = true;
560 break;
561 }
562 _ => {}
563 }
564 }
565 Some(out)
566}
567
568pub fn try_data<'a, 'inp>(
573 tokens: &'a mut impl Iterator<Item = Result<(XmlToken<'inp>, Depth), XmlDecodeError>>,
574) -> Result<Cow<'inp, str>, XmlDecodeError> {
575 loop {
576 match tokens.next().map(|opt| opt.map(|opt| opt.0)) {
577 None => return Ok(Cow::Borrowed("")),
578 Some(Ok(XmlToken(Token::Text { text }))) => return unescape(text.as_str()),
579 Some(Ok(e @ XmlToken(Token::ElementStart { .. }))) => {
580 return Err(XmlDecodeError::custom(format!(
581 "looking for a data element, found: {e:?}"
582 )))
583 }
584 Some(Err(e)) => return Err(e),
585 _ => {}
586 }
587 }
588}
589
590#[cfg(test)]
591mod test {
592 use crate::decode::{try_data, Attr, Depth, Document, Name, StartEl};
593
594 fn closed<'a>(local: &'a str, prefix: &'a str, depth: Depth) -> StartEl<'a> {
596 let mut s = StartEl::new(local, prefix, depth);
597 s.closed = true;
598 s
599 }
600
601 #[test]
602 fn scoped_tokens() {
603 let xml = r#"<Response><A></A></Response>"#;
604 let mut doc = Document::new(xml);
605 let mut root = doc.root_element().expect("valid document");
606 assert_eq!(root.start_el().local(), "Response");
607 assert_eq!(root.next_tag().expect("tag exists").start_el().local(), "A");
608 assert!(root.next_tag().is_none());
609 }
610
611 #[test]
612 fn handle_depth_properly() {
613 let xml = r#"<Response><Response></Response><A/></Response>"#;
614 let mut doc = Document::new(xml);
615 let mut scoped = doc.root_element().expect("valid document");
616 assert_eq!(
617 scoped.next_tag().unwrap().start_el(),
618 &StartEl::new("Response", "", 1)
619 );
620 let closed_a = closed("A", "", 1);
621 assert_eq!(scoped.next_tag().unwrap().start_el(), &closed_a);
622 assert!(scoped.next_tag().is_none())
623 }
624
625 #[test]
626 fn self_closing() {
627 let xml = r#"<Response/>"#;
628 let mut doc = Document::new(xml);
629 let mut scoped = doc.root_element().expect("valid doc");
630 assert!(scoped.start_el.closed);
631 assert!(scoped.next_tag().is_none())
632 }
633
634 #[test]
635 fn terminate_scope() {
636 let xml = r#"<Response><Struct><A></A><Also/></Struct><More/></Response>"#;
637 let mut doc = Document::new(xml);
638 let mut response_iter = doc.root_element().expect("valid doc");
639 let mut struct_iter = response_iter.next_tag().unwrap();
640 assert_eq!(
641 struct_iter.next_tag().as_ref().map(|t| t.start_el()),
642 Some(&StartEl::new("A", "", 2))
643 );
644 drop(struct_iter);
647 assert_eq!(
648 response_iter.next_tag().unwrap().start_el(),
649 &closed("More", "", 1)
650 );
651 }
652
653 #[test]
654 fn read_data_invalid() {
655 let xml = r#"<Response><A></A></Response>"#;
656 let mut doc = Document::new(xml);
657 let mut resp = doc.root_element().unwrap();
658 try_data(&mut resp).expect_err("no data");
659 }
660
661 #[test]
662 fn read_data() {
663 let xml = r#"<Response>hello</Response>"#;
664 let mut doc = Document::new(xml);
665 let mut scoped = doc.root_element().unwrap();
666 assert_eq!(try_data(&mut scoped).unwrap(), "hello");
667 }
668
669 #[test]
671 fn read_data_whitespace() {
672 let xml = r#"<Response> hello </Response>"#;
673 let mut doc = Document::new(xml);
674 let mut scoped = doc.root_element().unwrap();
675 assert_eq!(try_data(&mut scoped).unwrap(), " hello ");
676 }
677
678 #[test]
679 fn ignore_insignificant_whitespace() {
680 let xml = r#"<Response> <A> </A> </Response>"#;
681 let mut doc = Document::new(xml);
682 let mut resp = doc.root_element().unwrap();
683 let mut a = resp.next_tag().expect("should be a");
684 let data = try_data(&mut a).expect("valid");
685 assert_eq!(data, " ");
686 }
687
688 #[test]
689 fn read_attributes() {
690 let xml = r#"<Response xsi:type="CanonicalUser">hello</Response>"#;
691 let mut tokenizer = Document::new(xml);
692 let root = tokenizer.root_element().unwrap();
693
694 assert_eq!(
695 root.start_el().attributes,
696 vec![Attr {
697 name: Name {
698 prefix: "xsi",
699 local: "type"
700 },
701 value: "CanonicalUser".into()
702 }]
703 )
704 }
705
706 #[test]
707 fn unescape_data() {
708 let xml = r#"<Response key=""hey">">></Response>"#;
709 let mut doc = Document::new(xml);
710 let mut root = doc.root_element().unwrap();
711 assert_eq!(try_data(&mut root).unwrap(), ">");
712 assert_eq!(root.start_el().attr("key"), Some("\"hey\">"));
713 }
714
715 #[test]
716 fn nested_self_closer() {
717 let xml = r#"<XmlListsInputOutput>
718 <stringList/>
719 <stringSet></stringSet>
720 </XmlListsInputOutput>"#;
721 let mut doc = Document::new(xml);
722 let mut root = doc.root_element().unwrap();
723 let mut string_list = root.next_tag().unwrap();
724 assert_eq!(string_list.start_el(), &closed("stringList", "", 1));
725 assert!(string_list.next_tag().is_none());
726 drop(string_list);
727 assert_eq!(
728 root.next_tag().unwrap().start_el(),
729 &StartEl::new("stringSet", "", 1)
730 );
731 }
732
733 #[test]
734 fn confusing_nested_same_name_tag() {
735 let root_tags = &["a", "b", "c", "d"];
738 let xml = r#"<XmlListsInputOutput>
739 <a/>
740 <b>
741 <c/>
742 <b></b>
743 <here/>
744 </b>
745 <c></c>
746 <d>more</d>
747 </XmlListsInputOutput>"#;
748 let mut doc = Document::new(xml);
749 let mut root = doc.root_element().unwrap();
750 let mut cmp = vec![];
751 while let Some(tag) = root.next_tag() {
752 cmp.push(tag.start_el().local().to_owned());
753 }
754 assert_eq!(root_tags, cmp.as_slice());
755 }
756}