xso/
fromxml.rs

1//! # Generic builder type implementations
2//!
3//! This module contains [`FromEventsBuilder`] implementations for types from
4//! foreign libraries (such as the standard library).
5//!
6//! In order to not clutter the `xso` crate's main namespace, they are
7//! stashed away in a separate module.
8
9// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
10//
11// This Source Code Form is subject to the terms of the Mozilla Public
12// License, v. 2.0. If a copy of the MPL was not distributed with this
13// file, You can obtain one at http://mozilla.org/MPL/2.0/.
14
15use alloc::boxed::Box;
16
17use crate::error::{Error, FromEventsError};
18use crate::{FromEventsBuilder, FromXml};
19
20/// Match an XML element qualified name.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum XmlNameMatcher<'x> {
23    /// Match any XML element
24    Any,
25
26    /// Match any XML element in the given namespace.
27    InNamespace(&'x str),
28
29    /// Match any XML element with the exact namespace/name combination.
30    Specific(&'x str, &'x str),
31}
32
33impl<'x> XmlNameMatcher<'x> {
34    /// Return the superset of two `XmlNameMatcher` instances.
35    pub const fn superset(self, other: Self) -> Self {
36        match self {
37            Self::Any => Self::Any,
38            Self::InNamespace(my_namespace) => match other {
39                Self::Any => Self::Any,
40                Self::InNamespace(other_namespace) | Self::Specific(other_namespace, _) => {
41                    if crate::util::const_str_eq(my_namespace, other_namespace) {
42                        Self::InNamespace(my_namespace)
43                    } else {
44                        Self::Any
45                    }
46                }
47            },
48            Self::Specific(my_namespace, my_name) => match other {
49                Self::Any => Self::Any,
50                Self::InNamespace(other_namespace) => {
51                    if crate::util::const_str_eq(my_namespace, other_namespace) {
52                        Self::InNamespace(my_namespace)
53                    } else {
54                        Self::Any
55                    }
56                }
57                Self::Specific(other_namespace, other_name) => {
58                    if crate::util::const_str_eq(my_namespace, other_namespace) {
59                        if crate::util::const_str_eq(my_name, other_name) {
60                            Self::Specific(my_name, other_name)
61                        } else {
62                            Self::InNamespace(my_namespace)
63                        }
64                    } else {
65                        Self::Any
66                    }
67                }
68            },
69        }
70    }
71
72    /// Return true if the given `qname` matches this matcher.
73    pub fn matches(&self, qname: &rxml::QName) -> bool {
74        match self {
75            Self::Any => true,
76            Self::InNamespace(ns) => qname.0.as_str() == *ns,
77            Self::Specific(ns, name) => qname.0.as_str() == *ns && qname.1.as_str() == *name,
78        }
79    }
80}
81
82/// # Parsing context for [`FromEventsBuilder`]
83///
84/// For the most part, [`FromEventsBuilder`] implementations can work with
85/// only the information inside the [`rxml::Event`] which is delivered to
86/// them (and any information they may have stored from previous events).
87///
88/// However, there is (currently) one special case: the `xml:lang` attribute.
89/// That attribute is inherited across the entire document tree hierarchy. If
90/// the parsed element is not the top-level element, there may be an implicit
91/// value for `xml:lang`.
92#[derive(Debug)]
93#[doc(hidden)]
94pub struct Context<'x> {
95    language: Option<&'x str>,
96}
97
98impl<'x> Context<'x> {
99    /// A context suitable for the beginning of the document.
100    ///
101    /// `xml:lang` is assumed to be unset.
102    pub fn empty() -> Self {
103        Self { language: None }
104    }
105
106    /// Set the effective `xml:lang` value on the context and return it.
107    pub fn with_language(mut self, language: Option<&'x str>) -> Self {
108        self.language = language;
109        self
110    }
111
112    /// Return the `xml:lang` value in effect at the end of the event which
113    /// is currently being processed.
114    pub fn language(&self) -> Option<&str> {
115        self.language.as_deref()
116    }
117}
118
119/// Helper struct to construct an `Option<T>` from XML events.
120pub struct OptionBuilder<T: FromEventsBuilder>(T);
121
122impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
123    type Output = Option<T::Output>;
124
125    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
126        self.0.feed(ev, ctx).map(|ok| ok.map(Some))
127    }
128}
129
130/// Parsers `T` into `Some(.)`.
131///
132/// Note that this never generates `None`: The main use case is to allow
133/// external (i.e. without calling `from_events`) defaulting to `None` and
134/// for optional serialisation (the [`AsXml`][`crate::AsXml`] implementation
135/// on `Option<T>` emits nothing for `None`).
136impl<T: FromXml> FromXml for Option<T> {
137    type Builder = OptionBuilder<T::Builder>;
138
139    fn from_events(
140        name: rxml::QName,
141        attrs: rxml::AttrMap,
142        ctx: &Context<'_>,
143    ) -> Result<Self::Builder, FromEventsError> {
144        Ok(OptionBuilder(T::from_events(name, attrs, ctx)?))
145    }
146}
147
148/// Helper struct to construct an `Box<T>` from XML events.
149pub struct BoxBuilder<T: FromEventsBuilder + ?Sized>(Box<T>);
150
151impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for BoxBuilder<T> {
152    type Output = Box<T::Output>;
153
154    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
155        self.0.feed(ev, ctx).map(|ok| ok.map(Box::new))
156    }
157}
158
159/// Parses `T` into a `Box`.
160impl<T: FromXml + ?Sized> FromXml for Box<T> {
161    type Builder = BoxBuilder<T::Builder>;
162
163    fn from_events(
164        name: rxml::QName,
165        attrs: rxml::AttrMap,
166        ctx: &Context<'_>,
167    ) -> Result<Self::Builder, FromEventsError> {
168        Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?)))
169    }
170}
171
172impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for Box<T> {
173    type Output = T::Output;
174
175    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
176        (**self).feed(ev, ctx)
177    }
178}
179
180#[derive(Debug)]
181enum FallibleBuilderInner<T: FromEventsBuilder, E> {
182    Processing { depth: usize, builder: T },
183    Failed { depth: usize, err: Option<E> },
184    Done,
185}
186
187/// Build a `Result<T, E>` from XML.
188///
189/// This builder, invoked generally via the [`FromXml`] implementation on
190/// `Result<T, E> where T: FromXml, E: From<Error>`, allows to fallably parse
191/// an XSO from XML.
192///
193/// If an error occurs while parsing the XSO, the remaining events which
194/// belong to that XSO are discarded. Once all events have been seen, the
195/// error is returned as `Err(.)` value.
196///
197/// If parsing succeeds, the parsed XSO is returned as `Ok(.)` value.
198#[derive(Debug)]
199pub struct FallibleBuilder<T: FromEventsBuilder, E>(FallibleBuilderInner<T, E>);
200
201impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
202    type Output = Result<T::Output, E>;
203
204    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
205        match self.0 {
206            FallibleBuilderInner::Processing {
207                ref mut depth,
208                ref mut builder,
209            } => {
210                let new_depth = match ev {
211                    rxml::Event::StartElement(..) => match depth.checked_add(1) {
212                        // I *think* it is OK to return an err here
213                        // instead of panicking. The reason is that anyone
214                        // who intends to resume processing at the level
215                        // of where we started to parse this thing in case
216                        // of an error either has to:
217                        // - Use this fallible implementation and rely on
218                        //   it capturing the error (which we don't in
219                        //   this case).
220                        // - Or count the depth themselves, which will
221                        //   either fail in the same way, or they use a
222                        //   wider type (in which case it's ok).
223                        None => {
224                            self.0 = FallibleBuilderInner::Done;
225                            return Err(Error::Other("maximum XML nesting depth exceeded"));
226                        }
227                        Some(v) => Some(v),
228                    },
229                    // In case of an element end, underflow means that we
230                    // have reached the end of the XSO we wanted to process.
231                    // We handle that case at the end of the outer match's
232                    // body: Either we have returned a value then (good), or,
233                    // if we reach the end there with a new_depth == None,
234                    // something went horribly wrong (and we panic).
235                    rxml::Event::EndElement(..) => depth.checked_sub(1),
236
237                    // Text and XML declarations have no influence on parsing
238                    // depth.
239                    rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
240                };
241
242                match builder.feed(ev, ctx) {
243                    Ok(Some(v)) => {
244                        self.0 = FallibleBuilderInner::Done;
245                        return Ok(Some(Ok(v)));
246                    }
247                    Ok(None) => {
248                        // continue processing in the next round.
249                    }
250                    Err(e) => {
251                        // We are now officially failed ..
252                        match new_depth {
253                            // .. but we are not done yet, so enter the
254                            // failure backtracking state.
255                            Some(depth) => {
256                                self.0 = FallibleBuilderInner::Failed {
257                                    depth,
258                                    err: Some(e.into()),
259                                };
260                                return Ok(None);
261                            }
262                            // .. and we are done with parsing, so we return
263                            // the error as value.
264                            None => {
265                                self.0 = FallibleBuilderInner::Done;
266                                return Ok(Some(Err(e.into())));
267                            }
268                        }
269                    }
270                };
271
272                *depth = match new_depth {
273                    Some(v) => v,
274                    None => unreachable!("fallible parsing continued beyond end of element"),
275                };
276
277                // Need more events.
278                Ok(None)
279            }
280            FallibleBuilderInner::Failed {
281                ref mut depth,
282                ref mut err,
283            } => {
284                *depth = match ev {
285                    rxml::Event::StartElement(..) => match depth.checked_add(1) {
286                        // See above for error return rationale.
287                        None => {
288                            self.0 = FallibleBuilderInner::Done;
289                            return Err(Error::Other("maximum XML nesting depth exceeded"));
290                        }
291                        Some(v) => v,
292                    },
293                    rxml::Event::EndElement(..) => match depth.checked_sub(1) {
294                        Some(v) => v,
295                        None => {
296                            // We are officially done, return a value, switch
297                            // states, and be done with it.
298                            let err = err.take().expect("fallible parsing somehow lost its error");
299                            self.0 = FallibleBuilderInner::Done;
300                            return Ok(Some(Err(err)));
301                        }
302                    },
303
304                    // Text and XML declarations have no influence on parsing
305                    // depth.
306                    rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => *depth,
307                };
308
309                // Need more events
310                Ok(None)
311            }
312            FallibleBuilderInner::Done => {
313                panic!("FromEventsBuilder called after it returned a value")
314            }
315        }
316    }
317}
318
319/// Parsers `T` fallibly. See [`FallibleBuilder`] for details.
320impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
321    type Builder = FallibleBuilder<T::Builder, E>;
322
323    fn from_events(
324        name: rxml::QName,
325        attrs: rxml::AttrMap,
326        ctx: &Context<'_>,
327    ) -> Result<Self::Builder, FromEventsError> {
328        match T::from_events(name, attrs, ctx) {
329            Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
330                depth: 0,
331                builder,
332            })),
333            Err(FromEventsError::Mismatch { name, attrs }) => {
334                Err(FromEventsError::Mismatch { name, attrs })
335            }
336            Err(FromEventsError::Invalid(e)) => Ok(FallibleBuilder(FallibleBuilderInner::Failed {
337                depth: 0,
338                err: Some(e.into()),
339            })),
340        }
341    }
342}
343
344/// Builder which discards an entire child tree without inspecting the
345/// contents.
346#[derive(Debug, Default)]
347pub struct Discard {
348    depth: usize,
349}
350
351impl Discard {
352    /// Create a new discarding builder.
353    pub fn new() -> Self {
354        Self::default()
355    }
356}
357
358impl FromEventsBuilder for Discard {
359    type Output = ();
360
361    fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
362        match ev {
363            rxml::Event::StartElement(..) => {
364                self.depth = match self.depth.checked_add(1) {
365                    Some(v) => v,
366                    None => return Err(Error::Other("maximum XML nesting depth exceeded")),
367                };
368                Ok(None)
369            }
370            rxml::Event::EndElement(..) => match self.depth.checked_sub(1) {
371                None => Ok(Some(())),
372                Some(v) => {
373                    self.depth = v;
374                    Ok(None)
375                }
376            },
377            _ => Ok(None),
378        }
379    }
380}
381
382/// Builder which discards the contents (or raises on unexpected contents).
383///
384/// This builder is only to be used from within the proc macros and is not
385/// stable, public API.
386#[doc(hidden)]
387#[cfg(feature = "macros")]
388pub struct EmptyBuilder {
389    childerr: &'static str,
390    texterr: &'static str,
391}
392
393#[cfg(feature = "macros")]
394impl FromEventsBuilder for EmptyBuilder {
395    type Output = ();
396
397    fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
398        match ev {
399            rxml::Event::EndElement(..) => Ok(Some(())),
400            rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
401            rxml::Event::Text(..) => Err(Error::Other(self.texterr)),
402            _ => Err(Error::Other(
403                "unexpected content in supposed-to-be-empty element",
404            )),
405        }
406    }
407}
408
409/// Precursor struct for [`EmptyBuilder`].
410///
411/// This struct is only to be used from within the proc macros and is not
412/// stable, public API.
413#[doc(hidden)]
414#[cfg(feature = "macros")]
415pub struct Empty {
416    pub attributeerr: &'static str,
417    pub childerr: &'static str,
418    pub texterr: &'static str,
419}
420
421#[cfg(feature = "macros")]
422impl Empty {
423    pub fn start(self, attr: rxml::AttrMap) -> Result<EmptyBuilder, Error> {
424        if !attr.is_empty() {
425            return Err(Error::Other(self.attributeerr));
426        }
427        Ok(EmptyBuilder {
428            childerr: self.childerr,
429            texterr: self.texterr,
430        })
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    use alloc::borrow::ToOwned;
439    use rxml::{parser::EventMetrics, Event, Namespace, NcName};
440
441    macro_rules! null_builder {
442        ($name:ident for $output:ident) => {
443            #[derive(Debug)]
444            enum $name {}
445
446            impl FromEventsBuilder for $name {
447                type Output = $output;
448
449                fn feed(
450                    &mut self,
451                    _: Event,
452                    _: &Context<'_>,
453                ) -> Result<Option<Self::Output>, Error> {
454                    unreachable!();
455                }
456            }
457        };
458    }
459
460    null_builder!(AlwaysMismatchBuilder for AlwaysMismatch);
461    null_builder!(InitialErrorBuilder for InitialError);
462
463    #[derive(Debug)]
464    struct AlwaysMismatch;
465
466    impl FromXml for AlwaysMismatch {
467        type Builder = AlwaysMismatchBuilder;
468
469        fn from_events(
470            name: rxml::QName,
471            attrs: rxml::AttrMap,
472            _ctx: &Context<'_>,
473        ) -> Result<Self::Builder, FromEventsError> {
474            Err(FromEventsError::Mismatch { name, attrs })
475        }
476    }
477
478    #[derive(Debug)]
479    struct InitialError;
480
481    impl FromXml for InitialError {
482        type Builder = InitialErrorBuilder;
483
484        fn from_events(
485            _: rxml::QName,
486            _: rxml::AttrMap,
487            _: &Context<'_>,
488        ) -> Result<Self::Builder, FromEventsError> {
489            Err(FromEventsError::Invalid(Error::Other("some error")))
490        }
491    }
492
493    #[derive(Debug)]
494    struct FailOnContentBuilder;
495
496    impl FromEventsBuilder for FailOnContentBuilder {
497        type Output = FailOnContent;
498
499        fn feed(&mut self, _: Event, _: &Context<'_>) -> Result<Option<Self::Output>, Error> {
500            Err(Error::Other("content error"))
501        }
502    }
503
504    #[derive(Debug)]
505    struct FailOnContent;
506
507    impl FromXml for FailOnContent {
508        type Builder = FailOnContentBuilder;
509
510        fn from_events(
511            _: rxml::QName,
512            _: rxml::AttrMap,
513            _: &Context<'_>,
514        ) -> Result<Self::Builder, FromEventsError> {
515            Ok(FailOnContentBuilder)
516        }
517    }
518
519    fn qname() -> rxml::QName {
520        (Namespace::NONE, NcName::try_from("test").unwrap())
521    }
522
523    fn attrs() -> rxml::AttrMap {
524        rxml::AttrMap::new()
525    }
526
527    #[test]
528    fn fallible_builder_mismatch_passthrough() {
529        match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs(), &Context::empty()) {
530            Err(FromEventsError::Mismatch { .. }) => (),
531            other => panic!("unexpected result: {:?}", other),
532        }
533    }
534
535    #[test]
536    fn fallible_builder_initial_error_capture() {
537        let ctx = Context::empty();
538        let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
539            Ok(v) => v,
540            other => panic!("unexpected result: {:?}", other),
541        };
542        match builder.feed(
543            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
544            &ctx,
545        ) {
546            Ok(None) => (),
547            other => panic!("unexpected result: {:?}", other),
548        };
549        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
550            Ok(Some(Err(Error::Other("some error")))) => (),
551            other => panic!("unexpected result: {:?}", other),
552        };
553    }
554
555    #[test]
556    fn fallible_builder_initial_error_capture_allows_nested_stuff() {
557        let ctx = Context::empty();
558        let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
559            Ok(v) => v,
560            other => panic!("unexpected result: {:?}", other),
561        };
562        match builder.feed(
563            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
564            &ctx,
565        ) {
566            Ok(None) => (),
567            other => panic!("unexpected result: {:?}", other),
568        };
569        match builder.feed(
570            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
571            &ctx,
572        ) {
573            Ok(None) => (),
574            other => panic!("unexpected result: {:?}", other),
575        };
576        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
577            Ok(None) => (),
578            other => panic!("unexpected result: {:?}", other),
579        };
580        match builder.feed(
581            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
582            &ctx,
583        ) {
584            Ok(None) => (),
585            other => panic!("unexpected result: {:?}", other),
586        };
587        match builder.feed(
588            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
589            &ctx,
590        ) {
591            Ok(None) => (),
592            other => panic!("unexpected result: {:?}", other),
593        };
594        match builder.feed(
595            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
596            &ctx,
597        ) {
598            Ok(None) => (),
599            other => panic!("unexpected result: {:?}", other),
600        };
601        match builder.feed(
602            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
603            &ctx,
604        ) {
605            Ok(None) => (),
606            other => panic!("unexpected result: {:?}", other),
607        };
608        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
609            Ok(None) => (),
610            other => panic!("unexpected result: {:?}", other),
611        };
612        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
613            Ok(None) => (),
614            other => panic!("unexpected result: {:?}", other),
615        };
616        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
617            Ok(Some(Err(Error::Other("some error")))) => (),
618            other => panic!("unexpected result: {:?}", other),
619        };
620    }
621
622    #[test]
623    fn fallible_builder_content_error_capture() {
624        let ctx = Context::empty();
625        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
626        {
627            Ok(v) => v,
628            other => panic!("unexpected result: {:?}", other),
629        };
630        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
631            Ok(Some(Err(Error::Other("content error")))) => (),
632            other => panic!("unexpected result: {:?}", other),
633        };
634    }
635
636    #[test]
637    fn fallible_builder_content_error_capture_with_more_content() {
638        let ctx = Context::empty();
639        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
640        {
641            Ok(v) => v,
642            other => panic!("unexpected result: {:?}", other),
643        };
644        match builder.feed(
645            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
646            &ctx,
647        ) {
648            Ok(None) => (),
649            other => panic!("unexpected result: {:?}", other),
650        };
651        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
652            Ok(Some(Err(Error::Other("content error")))) => (),
653            other => panic!("unexpected result: {:?}", other),
654        };
655    }
656
657    #[test]
658    fn fallible_builder_content_error_capture_with_nested_content() {
659        let ctx = Context::empty();
660        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
661        {
662            Ok(v) => v,
663            other => panic!("unexpected result: {:?}", other),
664        };
665        match builder.feed(
666            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
667            &ctx,
668        ) {
669            Ok(None) => (),
670            other => panic!("unexpected result: {:?}", other),
671        };
672        match builder.feed(
673            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
674            &ctx,
675        ) {
676            Ok(None) => (),
677            other => panic!("unexpected result: {:?}", other),
678        };
679        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
680            Ok(None) => (),
681            other => panic!("unexpected result: {:?}", other),
682        };
683        match builder.feed(
684            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
685            &ctx,
686        ) {
687            Ok(None) => (),
688            other => panic!("unexpected result: {:?}", other),
689        };
690        match builder.feed(
691            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
692            &ctx,
693        ) {
694            Ok(None) => (),
695            other => panic!("unexpected result: {:?}", other),
696        };
697        match builder.feed(
698            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
699            &ctx,
700        ) {
701            Ok(None) => (),
702            other => panic!("unexpected result: {:?}", other),
703        };
704        match builder.feed(
705            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
706            &ctx,
707        ) {
708            Ok(None) => (),
709            other => panic!("unexpected result: {:?}", other),
710        };
711        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
712            Ok(None) => (),
713            other => panic!("unexpected result: {:?}", other),
714        };
715        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
716            Ok(None) => (),
717            other => panic!("unexpected result: {:?}", other),
718        };
719        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
720            Ok(Some(Err(Error::Other("content error")))) => (),
721            other => panic!("unexpected result: {:?}", other),
722        };
723    }
724}