xso/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![forbid(unsafe_code)]
4#![warn(missing_docs)]
5/*!
6# XML Streamed Objects -- serde-like parsing for XML
7
8This crate provides the traits for parsing XML data into Rust structs, and
9vice versa.
10
11While it is in 0.0.x versions, many features still need to be developed, but
12rest assured that there is a solid plan to get it fully usable for even
13advanced XML scenarios.
14
15XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
16use of this library in parsing XML streams like specified in RFC 6120.
17*/
18
19// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
20//
21// This Source Code Form is subject to the terms of the Mozilla Public
22// License, v. 2.0. If a copy of the MPL was not distributed with this
23// file, You can obtain one at http://mozilla.org/MPL/2.0/.
24
25#![no_std]
26
27extern crate alloc;
28#[cfg(feature = "std")]
29extern crate std;
30#[cfg(feature = "std")]
31use std::io;
32
33pub mod asxml;
34pub mod error;
35pub mod fromxml;
36#[cfg(feature = "minidom")]
37pub mod minidom_compat;
38mod rxml_util;
39pub mod text;
40
41#[doc(hidden)]
42#[cfg(feature = "macros")]
43pub mod exports {
44    #[cfg(feature = "minidom")]
45    pub use minidom;
46    pub use rxml;
47
48    // These re-exports are necessary to support both std and no_std in code
49    // generated by the macros.
50    //
51    // If we attempted to use ::alloc directly from macros, std builds would
52    // not work because alloc is not generally present in builds using std.
53    // If we used ::std, no_std builds would obviously not work. By exporting
54    // std as alloc in std builds, we can safely use the alloc types from
55    // there.
56    //
57    // Obviously, we have to be careful in xso-proc to not refer to types
58    // which are not in alloc.
59    #[cfg(not(feature = "std"))]
60    pub extern crate alloc;
61    #[cfg(feature = "std")]
62    pub extern crate std as alloc;
63
64    /// The built-in `bool` type.
65    ///
66    /// This is re-exported for use by macros in cases where we cannot rely on
67    /// people not having done `type bool = str` or some similar shenanigans.
68    pub type CoreBool = bool;
69
70    /// The built-in `u8` type.
71    ///
72    /// This is re-exported for use by macros in cases where we cannot rely on
73    /// people not having done `type u8 = str` or some similar shenanigans.
74    pub type CoreU8 = u8;
75
76    /// Compile-time comparison of two strings.
77    ///
78    /// Used by macro-generated code.
79    ///
80    /// This is necessary because `<str as PartialEq>::eq` is not `const`.
81    pub const fn const_str_eq(a: &'static str, b: &'static str) -> bool {
82        let a = a.as_bytes();
83        let b = b.as_bytes();
84        if a.len() != b.len() {
85            return false;
86        }
87
88        let mut i = 0;
89        while i < a.len() {
90            if a[i] != b[i] {
91                return false;
92            }
93            i += 1;
94        }
95
96        true
97    }
98}
99
100use alloc::{
101    borrow::{Cow, ToOwned},
102    boxed::Box,
103    string::String,
104    vec::Vec,
105};
106
107#[doc(inline)]
108pub use text::TextCodec;
109
110#[doc(inline)]
111pub use rxml_util::Item;
112
113pub use asxml::PrintRawXml;
114
115#[doc = include_str!("from_xml_doc.md")]
116#[doc(inline)]
117#[cfg(feature = "macros")]
118pub use xso_proc::FromXml;
119
120/// # Make a struct or enum serialisable to XML
121///
122/// This derives the [`AsXml`] trait on a struct or enum. It is the
123/// counterpart to [`macro@FromXml`].
124///
125/// The attributes necessary and available for the derivation to work are
126/// documented on [`macro@FromXml`].
127#[doc(inline)]
128#[cfg(feature = "macros")]
129pub use xso_proc::AsXml;
130
131/// Trait allowing to iterate a struct's contents as serialisable
132/// [`Item`]s.
133///
134/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
135/// type is considered a non-breaking change for any given implementation of
136/// this trait. Always refer to a type's iterator type using fully-qualified
137/// notation, for example: `<T as xso::AsXml>::ItemIter`.
138pub trait AsXml {
139    /// The iterator type.
140    ///
141    /// **Important:** Changing this type is considered a non-breaking change
142    /// for any given implementation of this trait. Always refer to a type's
143    /// iterator type using fully-qualified notation, for example:
144    /// `<T as xso::AsXml>::ItemIter`.
145    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
146    where
147        Self: 'x;
148
149    /// Return an iterator which emits the contents of the struct or enum as
150    /// serialisable [`Item`] items.
151    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
152}
153
154/// Additional parsing context supplied to [`FromEventsBuilder`]
155/// implementations.
156pub struct Context<'x> {
157    language: Option<&'x str>,
158}
159
160impl<'x> Context<'x> {
161    /// A context suitable for the beginning of the document.
162    pub fn empty() -> Self {
163        Self { language: None }
164    }
165
166    /// Create a new context.
167    ///
168    /// - `language` must be the effective value of the `xml:lang` value at
169    ///   the end of the current event.
170    pub fn new(language: Option<&'x str>) -> Self {
171        Self { language }
172    }
173
174    /// Return the `xml:lang` value in effect at the end of the event which
175    /// is currently being processed.
176    pub fn language(&self) -> Option<&str> {
177        self.language.as_deref()
178    }
179}
180
181/// Trait for a temporary object allowing to construct a struct from
182/// [`rxml::Event`] items.
183///
184/// Objects of this type are generally constructed through
185/// [`FromXml::from_events`] and are used to build Rust structs or enums from
186/// XML data. The XML data must be fed as `rxml::Event` to the
187/// [`feed`][`Self::feed`] method.
188pub trait FromEventsBuilder {
189    /// The type which will be constructed by this builder.
190    type Output;
191
192    /// Feed another [`rxml::Event`] into the element construction
193    /// process.
194    ///
195    /// Once the construction process completes, `Ok(Some(_))` is returned.
196    /// When valid data has been fed but more events are needed to fully
197    /// construct the resulting struct, `Ok(None)` is returned.
198    ///
199    /// If the construction fails, `Err(_)` is returned. Errors are generally
200    /// fatal and the builder should be assumed to be broken at that point.
201    /// Feeding more events after an error may result in panics, errors or
202    /// inconsistent result data, though it may never result in unsound or
203    /// unsafe behaviour.
204    fn feed(
205        &mut self,
206        ev: rxml::Event,
207        ctx: &Context<'_>,
208    ) -> Result<Option<Self::Output>, self::error::Error>;
209}
210
211/// Trait allowing to construct a struct from a stream of
212/// [`rxml::Event`] items.
213///
214/// To use this, first call [`FromXml::from_events`] with the qualified
215/// name and the attributes of the corresponding
216/// [`rxml::Event::StartElement`] event. If the call succeeds, the
217/// returned builder object must be fed with the events representing the
218/// contents of the element, and then with the `EndElement` event.
219///
220/// The `StartElement` passed to `from_events` must not be passed to `feed`.
221///
222/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
223/// is considered a non-breaking change for any given implementation of this
224/// trait. Always refer to a type's builder type using fully-qualified
225/// notation, for example: `<T as xso::FromXml>::Builder`.
226pub trait FromXml {
227    /// A builder type used to construct the element.
228    ///
229    /// **Important:** Changing this type is considered a non-breaking change
230    /// for any given implementation of this trait. Always refer to a type's
231    /// builder type using fully-qualified notation, for example:
232    /// `<T as xso::FromXml>::Builder`.
233    type Builder: FromEventsBuilder<Output = Self>;
234
235    /// Attempt to initiate the streamed construction of this struct from XML.
236    ///
237    /// If the passed qualified `name` and `attrs` match the element's type,
238    /// the [`Self::Builder`] is returned and should be fed with XML events
239    /// by the caller.
240    ///
241    /// Otherwise, an appropriate error is returned.
242    fn from_events(
243        name: rxml::QName,
244        attrs: rxml::AttrMap,
245        ctx: &Context<'_>,
246    ) -> Result<Self::Builder, self::error::FromEventsError>;
247}
248
249/// Trait allowing to convert XML text to a value.
250///
251/// This trait is similar to [`core::str::FromStr`], however, due to
252/// restrictions imposed by the orphan rule, a separate trait is needed.
253/// Implementations for many standard library types are available. In
254/// addition, the following feature flags can enable more implementations:
255///
256/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
257/// - `uuid`: `uuid::Uuid`
258///
259/// Because of this unfortunate situation, we are **extremely liberal** with
260/// accepting optional dependencies for this purpose. You are very welcome to
261/// make merge requests against this crate adding support for parsing
262/// third-party crates.
263pub trait FromXmlText: Sized {
264    /// Convert the given XML text to a value.
265    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
266}
267
268impl FromXmlText for String {
269    /// Return the string unchanged.
270    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
271        Ok(data)
272    }
273}
274
275impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
276    /// Return a [`Cow::Owned`] containing the parsed value.
277    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
278        Ok(Cow::Owned(T::from_xml_text(data)?))
279    }
280}
281
282impl<T: FromXmlText> FromXmlText for Option<T> {
283    /// Return a [`Some`] containing the parsed value.
284    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
285        Ok(Some(T::from_xml_text(data)?))
286    }
287}
288
289impl<T: FromXmlText> FromXmlText for Box<T> {
290    /// Return a [`Box`] containing the parsed value.
291    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
292        Ok(Box::new(T::from_xml_text(data)?))
293    }
294}
295
296/// Trait to convert a value to an XML text string.
297///
298/// Implementing this trait for a type allows it to be used both for XML
299/// character data within elements and for XML attributes. For XML attributes,
300/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
301/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
302/// [`AsXmlText`] automatically provides an implementation of
303/// [`AsOptionalXmlText`].
304///
305/// If your type should only be used in XML attributes and has no correct
306/// serialisation in XML text, you should *only* implement
307/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
308///
309/// This trait is implemented for many standard library types implementing
310/// [`core::fmt::Display`]. In addition, the following feature flags can enable
311/// more implementations:
312///
313/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
314/// - `uuid`: `uuid::Uuid`
315///
316/// Because of the unfortunate situation as described in [`FromXmlText`], we
317/// are **extremely liberal** with accepting optional dependencies for this
318/// purpose. You are very welcome to make merge requests against this crate
319/// adding support for parsing third-party crates.
320pub trait AsXmlText {
321    /// Convert the value to an XML string in a context where an absent value
322    /// cannot be represented.
323    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
324
325    /// Convert the value to an XML string in a context where an absent value
326    /// can be represented.
327    ///
328    /// The provided implementation will always return the result of
329    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
330    /// this method, implementors can customize the behaviour for certain
331    /// values.
332    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
333        Ok(Some(self.as_xml_text()?))
334    }
335}
336
337impl AsXmlText for String {
338    /// Return the borrowed string contents.
339    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
340        Ok(Cow::Borrowed(self))
341    }
342}
343
344impl AsXmlText for str {
345    /// Return the borrowed string contents.
346    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
347        Ok(Cow::Borrowed(self))
348    }
349}
350
351impl<T: AsXmlText> AsXmlText for Box<T> {
352    /// Return the borrowed [`Box`] contents.
353    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
354        T::as_xml_text(self)
355    }
356}
357
358impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
359    /// Return the borrowed [`Cow`] contents.
360    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
361        B::as_xml_text(self)
362    }
363}
364
365impl<T: AsXmlText> AsXmlText for &T {
366    /// Delegate to the `AsXmlText` implementation on `T`.
367    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
368        T::as_xml_text(*self)
369    }
370}
371
372/// Specialized variant of [`AsXmlText`].
373///
374/// Normally, it should not be necessary to implement this trait as it is
375/// automatically implemented for all types implementing [`AsXmlText`].
376/// However, if your type can only be serialised as an XML attribute (for
377/// example because an absent value has a particular meaning), it is correct
378/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
379///
380/// If your type can be serialised as both (text and attribute) but needs
381/// special handling in attributes, implement [`AsXmlText`] but provide a
382/// custom implementation of [`AsXmlText::as_optional_xml_text`].
383pub trait AsOptionalXmlText {
384    /// Convert the value to an XML string in a context where an absent value
385    /// can be represented.
386    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
387}
388
389impl<T: AsXmlText> AsOptionalXmlText for T {
390    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
391        <Self as AsXmlText>::as_optional_xml_text(self)
392    }
393}
394
395impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
396    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
397        self.as_ref()
398            .map(T::as_optional_xml_text)
399            .transpose()
400            .map(Option::flatten)
401    }
402}
403
404/// Control how unknown attributes are handled.
405///
406/// The variants of this enum are referenced in the
407/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
408/// enum variants. The specified variant controls how attributes, which are
409/// not handled by any member of the compound, are handled during parsing.
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
411pub enum UnknownAttributePolicy {
412    /// All unknown attributes are discarded.
413    ///
414    /// This is the default policy if the crate is built with the
415    /// `non-pedantic` feature.
416    #[cfg_attr(feature = "non-pedantic", default)]
417    Discard,
418
419    /// The first unknown attribute which is encountered generates a fatal
420    /// parsing error.
421    ///
422    /// This is the default policy if the crate is built **without** the
423    /// `non-pedantic` feature.
424    #[cfg_attr(not(feature = "non-pedantic"), default)]
425    Fail,
426}
427
428impl UnknownAttributePolicy {
429    #[doc(hidden)]
430    /// Implementation of the policy.
431    ///
432    /// This is an internal API and not subject to semver versioning.
433    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
434        match self {
435            Self::Fail => Err(self::error::Error::Other(msg)),
436            Self::Discard => Ok(()),
437        }
438    }
439}
440
441/// Control how unknown children are handled.
442///
443/// The variants of this enum are referenced in the
444/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
445/// enum variants. The specified variant controls how children, which are not
446/// handled by any member of the compound, are handled during parsing.
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
448pub enum UnknownChildPolicy {
449    /// All unknown children are discarded.
450    ///
451    /// This is the default policy if the crate is built with the
452    /// `non-pedantic` feature.
453    #[cfg_attr(feature = "non-pedantic", default)]
454    Discard,
455
456    /// The first unknown child which is encountered generates a fatal
457    /// parsing error.
458    ///
459    /// This is the default policy if the crate is built **without** the
460    /// `non-pedantic` feature.
461    #[cfg_attr(not(feature = "non-pedantic"), default)]
462    Fail,
463}
464
465impl UnknownChildPolicy {
466    #[doc(hidden)]
467    /// Implementation of the policy.
468    ///
469    /// This is an internal API and not subject to semver versioning.
470    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
471        match self {
472            Self::Fail => Err(self::error::Error::Other(msg)),
473            Self::Discard => Ok(()),
474        }
475    }
476}
477
478/// Attempt to transform a type implementing [`AsXml`] into another
479/// type which implements [`FromXml`].
480pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
481    let mut languages = rxml::xml_lang::XmlLangStack::new();
482    let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
483    let (qname, attrs) = match iter.next() {
484        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
485        Some(Err(e)) => return Err(e),
486        _ => panic!("into_event_iter did not start with StartElement event!"),
487    };
488    languages.push_from_attrs(&attrs);
489    let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) {
490        Ok(v) => v,
491        Err(self::error::FromEventsError::Mismatch { .. }) => {
492            return Err(self::error::Error::TypeMismatch)
493        }
494        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
495    };
496    for event in iter {
497        let event = event?;
498        languages.handle_event(&event);
499        if let Some(v) = sink.feed(event, &Context::new(languages.current()))? {
500            return Ok(v);
501        }
502    }
503    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
504}
505
506/// Attempt to convert a [`minidom::Element`] into a type implementing
507/// [`FromXml`], fallably.
508///
509/// Unlike [`transform`] (which can also be used with an element), this
510/// function will return the element unharmed if its element header does not
511/// match the expectations of `T`.
512#[cfg(feature = "minidom")]
513pub fn try_from_element<T: FromXml>(
514    from: minidom::Element,
515) -> Result<T, self::error::FromElementError> {
516    let mut languages = rxml::xml_lang::XmlLangStack::new();
517    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
518
519    languages.push_from_attrs(&attrs);
520    let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) {
521        Ok(v) => v,
522        Err(self::error::FromEventsError::Mismatch { .. }) => {
523            return Err(self::error::FromElementError::Mismatch(from))
524        }
525        Err(self::error::FromEventsError::Invalid(e)) => {
526            return Err(self::error::FromElementError::Invalid(e))
527        }
528    };
529
530    let mut iter = from.as_xml_iter()?;
531    // consume the element header
532    for item in &mut iter {
533        let item = item?;
534        match item {
535            // discard the element header
536            Item::XmlDeclaration(..) => (),
537            Item::ElementHeadStart(..) => (),
538            Item::Attribute(..) => (),
539            Item::ElementHeadEnd => {
540                // now that the element header is over, we break out
541                break;
542            }
543            Item::Text(..) => panic!("text before end of element header"),
544            Item::ElementFoot => panic!("element foot before end of element header"),
545        }
546    }
547    let iter = self::rxml_util::ItemToEvent::new(iter);
548    for event in iter {
549        let event = event?;
550        languages.handle_event(&event);
551        if let Some(v) = sink.feed(event, &Context::new(languages.current()))? {
552            return Ok(v);
553        }
554    }
555    // unreachable! instead of error here, because minidom::Element always
556    // produces the complete event sequence of a single element, and FromXml
557    // implementations must be constructible from that.
558    unreachable!("minidom::Element did not produce enough events to complete element")
559}
560
561#[cfg(feature = "std")]
562fn map_nonio_error<T>(r: Result<T, io::Error>) -> Result<T, self::error::Error> {
563    match r {
564        Ok(v) => Ok(v),
565        Err(e) => match e.downcast::<rxml::Error>() {
566            Ok(e) => Err(e.into()),
567            Err(_) => unreachable!("I/O error cannot be caused by &[]"),
568        },
569    }
570}
571
572#[cfg(feature = "std")]
573fn read_start_event(
574    r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
575) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
576    for ev in r {
577        match map_nonio_error(ev)? {
578            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
579            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
580            _ => {
581                return Err(self::error::Error::Other(
582                    "Unexpected event at start of document",
583                ))
584            }
585        }
586    }
587    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
588        rxml::error::ErrorContext::DocumentBegin,
589    ))))
590}
591
592/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
593/// containing XML data.
594#[cfg(feature = "std")]
595pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
596    let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(&mut buf));
597    let (name, attrs) = read_start_event(&mut reader)?;
598    let mut builder = match T::from_events(name, attrs, &Context::new(reader.language())) {
599        Ok(v) => v,
600        Err(self::error::FromEventsError::Mismatch { .. }) => {
601            return Err(self::error::Error::TypeMismatch)
602        }
603        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
604    };
605    while let Some(ev) = reader.next() {
606        if let Some(v) = builder.feed(map_nonio_error(ev)?, &Context::new(reader.language()))? {
607            return Ok(v);
608        }
609    }
610    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
611}
612
613#[cfg(feature = "std")]
614fn read_start_event_io(
615    r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
616) -> io::Result<(rxml::QName, rxml::AttrMap)> {
617    for ev in r {
618        match ev? {
619            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
620            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
621            _ => {
622                return Err(io::Error::new(
623                    io::ErrorKind::InvalidData,
624                    self::error::Error::Other("Unexpected event at start of document"),
625                ))
626            }
627        }
628    }
629    Err(io::Error::new(
630        io::ErrorKind::InvalidData,
631        self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
632            rxml::error::ErrorContext::DocumentBegin,
633        ))),
634    ))
635}
636
637/// Attempt to parse a type implementing [`FromXml`] from a reader.
638#[cfg(feature = "std")]
639pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
640    let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
641    let (name, attrs) = read_start_event_io(&mut reader)?;
642    let mut builder = match T::from_events(name, attrs, &Context::new(reader.language())) {
643        Ok(v) => v,
644        Err(self::error::FromEventsError::Mismatch { .. }) => {
645            return Err(self::error::Error::TypeMismatch)
646                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
647        }
648        Err(self::error::FromEventsError::Invalid(e)) => {
649            return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
650        }
651    };
652    while let Some(ev) = reader.next() {
653        if let Some(v) = builder
654            .feed(ev?, &Context::new(reader.language()))
655            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
656        {
657            return Ok(v);
658        }
659    }
660    Err(io::Error::new(
661        io::ErrorKind::UnexpectedEof,
662        self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
663    ))
664}
665
666/// Attempt to serialise a type implementing [`AsXml`] to a vector of bytes.
667pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
668    let iter = xso.as_xml_iter()?;
669    let mut writer = rxml::writer::Encoder::new();
670    let mut buf = Vec::new();
671    for item in iter {
672        let item = item?;
673        writer.encode(item.as_rxml_item(), &mut buf)?;
674    }
675    Ok(buf)
676}
677
678/// Return true if the string contains exclusively XML whitespace.
679///
680/// XML whitespace is defined as U+0020 (space), U+0009 (tab), U+000a
681/// (newline) and U+000d (carriage return).
682pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
683    s.as_ref()
684        .iter()
685        .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
686}