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