Skip to main content

aws_smithy_xml/
decode.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use 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// in general, these errors are just for reporting what happened, there isn't
16// much value in lots of different match variants
17
18#[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    /// Check if a given name matches a tag name composed of `prefix:local` or just `local`
86    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    // attribute values can be escaped (e.g. with double quotes, so we need a Cow)
103    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
114/// Xml Start Element
115///
116/// ```xml
117/// <a:b   c="d">
118///  ^^^   ^^^^^
119///  name  attributes
120/// ```
121impl<'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    /// Retrieve an attribute with a given key
136    ///
137    /// key `prefix:local` combined as a str, joined by a `:`
138    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    /// Returns whether this `StartEl` matches a given name
146    /// in `prefix:local` form.
147    pub fn matches(&self, pat: &str) -> bool {
148        self.name.matches(pat)
149    }
150
151    /// Local component of this element's name
152    ///
153    /// ```xml
154    /// <foo:bar>
155    ///      ^^^
156    /// ```
157    pub fn local(&self) -> &str {
158        self.name.local
159    }
160
161    /// Prefix component of this elements name (or empty string)
162    /// ```xml
163    /// <foo:bar>
164    ///  ^^^
165    /// ```
166    pub fn prefix(&self) -> &str {
167        self.name.prefix
168    }
169
170    /// Returns true of `el` at `depth` is a match for this `start_el`
171    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
185/// Xml Document abstraction
186///
187/// This document wraps a lazy tokenizer with depth tracking.
188/// Constructing a document is essentially free.
189pub struct Document<'a> {
190    tokenizer: Tokenizer<'a>,
191    depth: Depth,
192    /// `Some` once [`check_well_formedness`](Self::check_well_formedness) is enabled: the
193    /// `(prefix, local)` names of the currently open elements.
194    open_elements: Option<Vec<(&'a str, &'a str)>>,
195    /// Attribute names on the current start tag, populated only in checking mode.
196    attribute_names: Option<HashSet<(&'a str, &'a str)>>,
197    /// First well-formedness violation found while checking. Once set, iteration stops.
198    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    /// Track open elements so that ill-formed input is detected: a close tag whose name does
223    /// not match the open element, an element left unclosed at the end of input, or a tokenizer
224    /// error. Also reject duplicate attributes and invalid references, including in skipped
225    /// content. Without this, the decoder recovers from such input silently, which is the legacy
226    /// behavior. Must be called before any token is read.
227    ///
228    /// The first violation stops iteration (as a tokenizer error does). Call
229    /// [`finish_well_formed`](Self::finish_well_formed) after decoding to surface it.
230    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    /// Read the rest of the document and return the first well-formedness violation, if any.
236    /// Only meaningful after [`check_well_formedness`](Self::check_well_formedness).
237    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    /// Record a well-formedness violation (checking mode only) and return it as an error token.
246    /// Cold and out of line so the error formatting stays off `next()`'s hot path.
247    #[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    /// "Depth first" iterator
261    ///
262    /// Unlike [`next_tag()`](ScopedDecoder::next_tag), this method returns the next
263    /// start element regardless of depth. This is useful to give a pointer into the middle
264    /// of a document to start reading.
265    ///
266    /// ```xml
267    /// <Response> <-- first call returns this:
268    ///    <A> <-- next call
269    ///      <Nested /> <-- next call returns this
270    ///      <MoreNested>hello</MoreNested> <-- then this:
271    ///    </A>
272    ///    <B/> <-- second call to next_tag returns this
273    /// </Response>
274    /// ```
275    pub fn next_start_element<'a>(&'a mut self) -> Option<StartEl<'inp>> {
276        next_start_element(self)
277    }
278
279    /// A scoped reader for the entire document
280    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    /// A scoped reader for a specific tag
292    ///
293    /// This method is necessary for when you need to return a ScopedDecoder from a function
294    /// since normally the stacked-ownership that `next_tag()` uses would prevent returning a reference
295    /// to a field owned by the current function
296    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    /// Byte offset into the document of the first byte the tokenizer has not yet consumed.
305    fn offset(&self) -> usize {
306        self.tokenizer.stream().pos()
307    }
308}
309
310/// A new-type wrapper around `Token` to prevent the wrapped third party type from showing up in
311/// public API
312#[derive(Debug)]
313pub struct XmlToken<'inp>(Token<'inp>);
314
315/// Depth tracking iterator
316///
317/// ```xml
318/// <a> <- startel depth 0
319///   <b> <- startel depth 1
320///     <c> <- startel depth 2
321///     </c> <- endel depth 2
322///   </b> <- endel depth 1
323/// </a> <- endel depth 0
324/// ```
325impl<'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        // depth bookkeeping
378        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                    // Almost every tag is unprefixed. Short-circuiting the empty-prefix case
386                    // matters: comparing the two empty `prefix` slices directly more than
387                    // doubled the cost of a tokenizer pass on a benchmark payload, whereas
388                    // this form adds ~3%.
389                    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                // We want the startel and endel to have the same depth, but after the opener,
420                // the parser will be at depth 1. Return the previous depth:
421                return Some(Ok((XmlToken(t), self.depth - 1)));
422            }
423            _ => {}
424        }
425        Some(Ok((XmlToken(tok), self.depth)))
426    }
427}
428
429/// XmlTag Abstraction
430///
431/// ScopedDecoder represents a tag-scoped view into an XML document. Methods
432/// on `ScopedDecoder` return `None` when the current tag has been exhausted.
433pub struct ScopedDecoder<'inp, 'a> {
434    doc: &'a mut Document<'inp>,
435    start_el: StartEl<'inp>,
436    terminated: bool,
437}
438
439/// When a scoped decoder is dropped, its entire scope is consumed so that the
440/// next read begins at the next tag at the same depth.
441impl Drop for ScopedDecoder<'_, '_> {
442    fn drop(&mut self) {
443        for _ in self {}
444    }
445}
446
447impl<'inp> ScopedDecoder<'inp, '_> {
448    /// The start element for this scope
449    pub fn start_el<'a>(&'a self) -> &'a StartEl<'inp> {
450        &self.start_el
451    }
452
453    /// Consume the rest of this scope and return the byte offset just past the element's close
454    /// tag (or past `/>` for a self-closing element).
455    ///
456    /// The offset comes from the tokenizer itself, so comments, CDATA, processing instructions and
457    /// attribute values can never be mistaken for markup. If the document ends or fails to
458    /// tokenize before the close tag, this is where the tokenizer stopped.
459    pub(crate) fn end_offset(mut self) -> usize {
460        for _ in &mut self {}
461        self.doc.offset()
462    }
463
464    /// Returns the next top-level tag in this scope
465    /// The returned reader will fully read the tag during its lifetime. If it is dropped without
466    /// the data being read, the reader will be advanced until the matching close tag. If you read
467    /// an element with `next_tag()` and you want to ignore it, simply drop the resulting `ScopeDecoder`.
468    ///
469    /// ```xml
470    /// <Response> <-- scoped reader on this tag
471    ///    <A> <-- first call to next_tag returns this
472    ///      <Nested /> <-- to get inner data, call `next_tag` on the returned decoder for `A`
473    ///      <MoreNested>hello</MoreNested>
474    ///    </A>
475    ///    <B/> <-- second call to next_tag returns this
476    /// </Response>
477    /// ```
478    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
518/// Load the next start element out of a depth-tagged token iterator
519fn 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
568/// Returns the data element at the current position
569///
570/// If the current position is not a data element (and is instead a `<start-element>`) an error
571/// will be returned
572pub 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    // test helper to create a closed startel
595    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        // When the inner iter is dropped, it will read to the end of its scope
645        // prevent accidental behavior where we didn't read a full node
646        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    /// Whitespace within an element is preserved
670    #[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="&quot;hey&quot;>">&gt;</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        // an inner b which could be confused as closing the outer b if depth
736        // is not properly tracked:
737        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}