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)]
42pub mod exports {
43    #[cfg(all(feature = "minidom", feature = "macros"))]
44    pub use minidom;
45    #[cfg(feature = "macros")]
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    #[cfg(feature = "macros")]
69    pub type CoreBool = bool;
70
71    /// The built-in `u8` type.
72    ///
73    /// This is re-exported for use by macros in cases where we cannot rely on
74    /// people not having done `type u8 = str` or some similar shenanigans.
75    #[cfg(feature = "macros")]
76    pub type CoreU8 = u8;
77
78    /// Compile-time comparison of two strings.
79    ///
80    /// Used by macro-generated code.
81    ///
82    /// This is necessary because `<str as PartialEq>::eq` is not `const`.
83    #[cfg(feature = "macros")]
84    pub const fn const_str_eq(a: &'static str, b: &'static str) -> bool {
85        let a = a.as_bytes();
86        let b = b.as_bytes();
87        if a.len() != b.len() {
88            return false;
89        }
90
91        let mut i = 0;
92        while i < a.len() {
93            if a[i] != b[i] {
94                return false;
95            }
96            i += 1;
97        }
98
99        true
100    }
101}
102
103use alloc::{borrow::Cow, string::String, vec::Vec};
104
105#[doc(inline)]
106pub use fromxml::Context;
107
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`.
138#[diagnostic::on_unimplemented(message = "`{Self}` cannot be serialised as XML")]
139pub trait AsXml {
140    /// The iterator type.
141    ///
142    /// **Important:** Changing this type is considered a non-breaking change
143    /// for any given implementation of this trait. Always refer to a type's
144    /// iterator type using fully-qualified notation, for example:
145    /// `<T as xso::AsXml>::ItemIter`.
146    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
147    where
148        Self: 'x;
149
150    /// Return an iterator which emits the contents of the struct or enum as
151    /// serialisable [`Item`] items.
152    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
153}
154
155/// Trait for a temporary object allowing to construct a struct from
156/// [`rxml::Event`] items.
157///
158/// Objects of this type are generally constructed through
159/// [`FromXml::from_events`] and are used to build Rust structs or enums from
160/// XML data. The XML data must be fed as `rxml::Event` to the
161/// [`feed`][`Self::feed`] method.
162pub trait FromEventsBuilder {
163    /// The type which will be constructed by this builder.
164    type Output;
165
166    /// Feed another [`rxml::Event`] into the element construction
167    /// process.
168    ///
169    /// Once the construction process completes, `Ok(Some(_))` is returned.
170    /// When valid data has been fed but more events are needed to fully
171    /// construct the resulting struct, `Ok(None)` is returned.
172    ///
173    /// If the construction fails, `Err(_)` is returned. Errors are generally
174    /// fatal and the builder should be assumed to be broken at that point.
175    /// Feeding more events after an error may result in panics, errors or
176    /// inconsistent result data, though it may never result in unsound or
177    /// unsafe behaviour.
178    fn feed(
179        &mut self,
180        ev: rxml::Event,
181        ctx: &Context<'_>,
182    ) -> Result<Option<Self::Output>, self::error::Error>;
183}
184
185/// Trait allowing to construct a struct from a stream of
186/// [`rxml::Event`] items.
187///
188/// To use this, first call [`FromXml::from_events`] with the qualified
189/// name and the attributes of the corresponding
190/// [`rxml::Event::StartElement`] event. If the call succeeds, the
191/// returned builder object must be fed with the events representing the
192/// contents of the element, and then with the `EndElement` event.
193///
194/// The `StartElement` passed to `from_events` must not be passed to `feed`.
195///
196/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
197/// is considered a non-breaking change for any given implementation of this
198/// trait. Always refer to a type's builder type using fully-qualified
199/// notation, for example: `<T as xso::FromXml>::Builder`.
200#[diagnostic::on_unimplemented(message = "`{Self}` cannot be parsed from XML")]
201pub trait FromXml {
202    /// A builder type used to construct the element.
203    ///
204    /// **Important:** Changing this type is considered a non-breaking change
205    /// for any given implementation of this trait. Always refer to a type's
206    /// builder type using fully-qualified notation, for example:
207    /// `<T as xso::FromXml>::Builder`.
208    type Builder: FromEventsBuilder<Output = Self>;
209
210    /// Attempt to initiate the streamed construction of this struct from XML.
211    ///
212    /// If the passed qualified `name` and `attrs` match the element's type,
213    /// the [`Self::Builder`] is returned and should be fed with XML events
214    /// by the caller.
215    ///
216    /// Otherwise, an appropriate error is returned.
217    fn from_events(
218        name: rxml::QName,
219        attrs: rxml::AttrMap,
220        ctx: &Context<'_>,
221    ) -> Result<Self::Builder, self::error::FromEventsError>;
222}
223
224/// Trait allowing to convert XML text to a value.
225///
226/// This trait is similar to [`FromStr`][`core::str::FromStr`], however, to
227/// allow specialisation for XML<->Text conversion, a separate trait is
228/// introduced. Unlike `FromStr`, this trait allows taking ownership of the
229/// original text data, potentially saving allocations.
230///
231/// **Important:** See the [`text`][`crate::text`] module's documentation
232/// for notes regarding implementations for types from third-party crates.
233#[diagnostic::on_unimplemented(
234    message = "`{Self}` cannot be parsed from XML text",
235    note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
236)]
237pub trait FromXmlText: Sized {
238    /// Convert the given XML text to a value.
239    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
240}
241
242/// Trait to convert a value to an XML text string.
243///
244/// Implementing this trait for a type allows it to be used both for XML
245/// character data within elements and for XML attributes. For XML attributes,
246/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
247/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
248/// [`AsXmlText`] automatically provides an implementation of
249/// [`AsOptionalXmlText`].
250///
251/// If your type should only be used in XML attributes and has no correct
252/// serialisation in XML text, you should *only* implement
253/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
254///
255/// **Important:** See the [`text`][`crate::text`] module's documentation
256/// for notes regarding implementations for types from third-party crates.
257#[diagnostic::on_unimplemented(
258    message = "`{Self}` cannot be serialised to XML text",
259    note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
260)]
261pub trait AsXmlText {
262    /// Convert the value to an XML string in a context where an absent value
263    /// cannot be represented.
264    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
265
266    /// Convert the value to an XML string in a context where an absent value
267    /// can be represented.
268    ///
269    /// The provided implementation will always return the result of
270    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
271    /// this method, implementors can customize the behaviour for certain
272    /// values.
273    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
274        Ok(Some(self.as_xml_text()?))
275    }
276}
277
278/// Specialized variant of [`AsXmlText`].
279///
280/// Normally, it should not be necessary to implement this trait as it is
281/// automatically implemented for all types implementing [`AsXmlText`].
282/// However, if your type can only be serialised as an XML attribute (for
283/// example because an absent value has a particular meaning), it is correct
284/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
285///
286/// If your type can be serialised as both (text and attribute) but needs
287/// special handling in attributes, implement [`AsXmlText`] but provide a
288/// custom implementation of [`AsXmlText::as_optional_xml_text`].
289///
290/// **Important:** See the [`text`][`crate::text`] module's documentation
291/// for notes regarding implementations for types from third-party crates.
292#[diagnostic::on_unimplemented(
293    message = "`{Self}` cannot be serialised as XML attribute",
294    note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
295)]
296pub trait AsOptionalXmlText {
297    /// Convert the value to an XML string in a context where an absent value
298    /// can be represented.
299    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
300}
301
302/// # Control how unknown attributes are handled
303///
304/// The variants of this enum are referenced in the
305/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
306/// enum variants. The specified variant controls how attributes, which are
307/// not handled by any member of the compound, are handled during parsing.
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
309pub enum UnknownAttributePolicy {
310    /// All unknown attributes are discarded.
311    ///
312    /// This is the default policy if the crate is built with the
313    /// `non-pedantic` feature.
314    #[cfg_attr(feature = "non-pedantic", default)]
315    Discard,
316
317    /// The first unknown attribute which is encountered generates a fatal
318    /// parsing error.
319    ///
320    /// This is the default policy if the crate is built **without** the
321    /// `non-pedantic` feature.
322    #[cfg_attr(not(feature = "non-pedantic"), default)]
323    Fail,
324}
325
326impl UnknownAttributePolicy {
327    #[doc(hidden)]
328    /// Implementation of the policy.
329    ///
330    /// This is an internal API and not subject to semver versioning.
331    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
332        match self {
333            Self::Fail => Err(self::error::Error::Other(msg)),
334            Self::Discard => Ok(()),
335        }
336    }
337}
338
339/// # Control how unknown child elements are handled
340///
341/// The variants of this enum are referenced in the
342/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
343/// enum variants. The specified variant controls how children, which are not
344/// handled by any member of the compound, are handled during parsing.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
346pub enum UnknownChildPolicy {
347    /// All unknown children are discarded.
348    ///
349    /// This is the default policy if the crate is built with the
350    /// `non-pedantic` feature.
351    #[cfg_attr(feature = "non-pedantic", default)]
352    Discard,
353
354    /// The first unknown child which is encountered generates a fatal
355    /// parsing error.
356    ///
357    /// This is the default policy if the crate is built **without** the
358    /// `non-pedantic` feature.
359    #[cfg_attr(not(feature = "non-pedantic"), default)]
360    Fail,
361}
362
363impl UnknownChildPolicy {
364    #[doc(hidden)]
365    /// Implementation of the policy.
366    ///
367    /// This is an internal API and not subject to semver versioning.
368    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
369        match self {
370            Self::Fail => Err(self::error::Error::Other(msg)),
371            Self::Discard => Ok(()),
372        }
373    }
374}
375
376/// # Transform a value into another value via XML
377///
378/// This function takes `from`, converts it into XML using its [`AsXml`]
379/// implementation and builds a `T` from it (without buffering the tree in
380/// memory).
381///
382/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
383/// particular, if `T` expects a different element header than the header
384/// provided by `from`,
385/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
386///
387/// ## Example
388///
389#[cfg_attr(
390    not(all(feature = "std", feature = "macros")),
391    doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
392)]
393#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
394/// # use xso::{AsXml, FromXml, transform};
395/// #[derive(AsXml)]
396/// #[xml(namespace = "urn:example", name = "foo")]
397/// struct Source {
398///     #[xml(attribute = "xml:lang")]
399///     lang: &'static str,
400/// }
401///
402/// #[derive(FromXml, PartialEq, Debug)]
403/// #[xml(namespace = "urn:example", name = "foo")]
404/// struct Dest {
405///     #[xml(lang)]
406///     lang: Option<String>,
407/// }
408///
409/// assert_eq!(
410///     Dest { lang: Some("en".to_owned()) },
411///     transform(&Source { lang: "en" }).unwrap(),
412/// );
413/// ```
414pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
415    let mut languages = rxml::xml_lang::XmlLangStack::new();
416    let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
417    let (qname, attrs) = match iter.next() {
418        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
419        Some(Err(e)) => return Err(e),
420        _ => panic!("into_event_iter did not start with StartElement event!"),
421    };
422    languages.push_from_attrs(&attrs);
423    let mut sink = match T::from_events(
424        qname,
425        attrs,
426        &Context::empty().with_language(languages.current()),
427    ) {
428        Ok(v) => v,
429        Err(self::error::FromEventsError::Mismatch { .. }) => {
430            return Err(self::error::Error::TypeMismatch)
431        }
432        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
433    };
434    for event in iter {
435        let event = event?;
436        languages.handle_event(&event);
437        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
438            return Ok(v);
439        }
440    }
441    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
442}
443
444/// Attempt to convert a [`minidom::Element`] into a type implementing
445/// [`FromXml`], fallably.
446///
447/// Unlike [`transform`] (which can also be used with an element), this
448/// function will return the element unharmed if its element header does not
449/// match the expectations of `T`.
450#[cfg(feature = "minidom")]
451#[deprecated(
452    since = "0.1.3",
453    note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
454)]
455pub fn try_from_element<T: FromXml>(
456    from: minidom::Element,
457) -> Result<T, self::error::FromElementError> {
458    let mut languages = rxml::xml_lang::XmlLangStack::new();
459    #[allow(deprecated)]
460    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
461
462    languages.push_from_attrs(&attrs);
463    let mut sink = match T::from_events(
464        qname,
465        attrs,
466        &Context::empty().with_language(languages.current()),
467    ) {
468        Ok(v) => v,
469        Err(self::error::FromEventsError::Mismatch { .. }) => {
470            return Err(self::error::FromElementError::Mismatch(from))
471        }
472        Err(self::error::FromEventsError::Invalid(e)) => {
473            return Err(self::error::FromElementError::Invalid(e))
474        }
475    };
476
477    let mut iter = from.as_xml_iter()?;
478    // consume the element header
479    for item in &mut iter {
480        let item = item?;
481        match item {
482            // discard the element header
483            Item::XmlDeclaration(..) => (),
484            Item::ElementHeadStart(..) => (),
485            Item::Attribute(..) => (),
486            Item::ElementHeadEnd => {
487                // now that the element header is over, we break out
488                break;
489            }
490            Item::Text(..) => panic!("text before end of element header"),
491            Item::ElementFoot => panic!("element foot before end of element header"),
492        }
493    }
494    let iter = self::rxml_util::ItemToEvent::new(iter);
495    for event in iter {
496        let event = event?;
497        languages.handle_event(&event);
498        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
499            return Ok(v);
500        }
501    }
502    // unreachable! instead of error here, because minidom::Element always
503    // produces the complete event sequence of a single element, and FromXml
504    // implementations must be constructible from that.
505    unreachable!("minidom::Element did not produce enough events to complete element")
506}
507
508/// # Parse a value from a byte slice containing XML data
509///
510/// This function parses the XML found in `buf`, assuming it contains a
511/// complete XML document (with optional XML declaration) and builds a `T`
512/// from it (without buffering the tree in memory).
513///
514/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
515/// particular, if `T` expects a different element header than the element
516/// header at the root of the document in `bytes`,
517/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
518///
519/// ## Example
520///
521#[cfg_attr(
522    not(feature = "macros"),
523    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
524)]
525#[cfg_attr(feature = "macros", doc = "\n```\n")]
526/// # use xso::{AsXml, FromXml, from_bytes};
527/// #[derive(FromXml, PartialEq, Debug)]
528/// #[xml(namespace = "urn:example", name = "foo")]
529/// struct Foo {
530///     #[xml(attribute)]
531///     a: String,
532/// }
533///
534/// assert_eq!(
535///     Foo { a: "some-value".to_owned() },
536///     from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
537/// );
538/// ```
539pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
540    use rxml::{error::EndOrError, Parse};
541
542    let mut languages = rxml::xml_lang::XmlLangStack::new();
543    let mut parser = rxml::Parser::new();
544    let (name, attrs) = loop {
545        match parser.parse(&mut buf, true) {
546            Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
547            Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
548            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
549            Ok(None) | Err(EndOrError::NeedMoreData) => {
550                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
551                    rxml::error::ErrorContext::DocumentBegin,
552                ))))
553            }
554            Ok(Some(_)) => {
555                return Err(self::error::Error::Other(
556                    "Unexpected event at start of document",
557                ))
558            }
559        }
560    };
561    languages.push_from_attrs(&attrs);
562    let mut builder = match T::from_events(
563        name,
564        attrs,
565        &Context::empty().with_language(languages.current()),
566    ) {
567        Ok(v) => v,
568        Err(self::error::FromEventsError::Mismatch { .. }) => {
569            return Err(self::error::Error::TypeMismatch);
570        }
571        Err(self::error::FromEventsError::Invalid(e)) => {
572            return Err(e);
573        }
574    };
575
576    loop {
577        match parser.parse(&mut buf, true) {
578            Ok(Some(ev)) => {
579                languages.handle_event(&ev);
580                if let Some(v) =
581                    builder.feed(ev, &Context::empty().with_language(languages.current()))?
582                {
583                    return Ok(v);
584                }
585            }
586            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
587            Ok(None) | Err(EndOrError::NeedMoreData) => {
588                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
589            }
590        }
591    }
592}
593
594#[cfg(feature = "std")]
595fn read_start_event_io(
596    r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
597) -> io::Result<(rxml::QName, rxml::AttrMap)> {
598    for ev in r {
599        match ev? {
600            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
601            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
602            _ => {
603                return Err(io::Error::new(
604                    io::ErrorKind::InvalidData,
605                    self::error::Error::Other("Unexpected event at start of document"),
606                ))
607            }
608        }
609    }
610    Err(io::Error::new(
611        io::ErrorKind::InvalidData,
612        self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
613            rxml::error::ErrorContext::DocumentBegin,
614        ))),
615    ))
616}
617
618/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
619///
620/// This function parses the XML found in `r`, assuming it contains a
621/// complete XML document (with optional XML declaration) and builds a `T`
622/// from it (without buffering the tree in memory).
623///
624/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
625/// particular, if `T` expects a different element header than the element
626/// header at the root of the document in `r`,
627/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
628///
629/// ## Example
630///
631#[cfg_attr(
632    not(feature = "macros"),
633    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
634)]
635#[cfg_attr(feature = "macros", doc = "\n```\n")]
636/// # use xso::{AsXml, FromXml, from_reader};
637/// # use std::io::BufReader;
638/// #[derive(FromXml, PartialEq, Debug)]
639/// #[xml(namespace = "urn:example", name = "foo")]
640/// struct Foo {
641///     #[xml(attribute)]
642///     a: String,
643/// }
644///
645/// // let file = ..
646/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
647/// assert_eq!(
648///     Foo { a: "some-value".to_owned() },
649///     from_reader(BufReader::new(file)).unwrap(),
650/// );
651/// ```
652#[cfg(feature = "std")]
653pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
654    let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
655    let (name, attrs) = read_start_event_io(&mut reader)?;
656    let mut builder = match T::from_events(
657        name,
658        attrs,
659        &Context::empty().with_language(reader.language()),
660    ) {
661        Ok(v) => v,
662        Err(self::error::FromEventsError::Mismatch { .. }) => {
663            return Err(self::error::Error::TypeMismatch)
664                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
665        }
666        Err(self::error::FromEventsError::Invalid(e)) => {
667            return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
668        }
669    };
670    while let Some(ev) = reader.next() {
671        if let Some(v) = builder
672            .feed(ev?, &Context::empty().with_language(reader.language()))
673            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
674        {
675            return Ok(v);
676        }
677    }
678    Err(io::Error::new(
679        io::ErrorKind::UnexpectedEof,
680        self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
681    ))
682}
683
684/// # Serialize a value to UTF-8-encoded XML
685///
686/// This function takes `xso`, converts it into XML using its [`AsXml`]
687/// implementation and serialises the resulting XML events into a `Vec<u8>`.
688///
689/// If serialisation fails, an error is returned instead.
690///
691/// ## Example
692///
693#[cfg_attr(
694    not(feature = "macros"),
695    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
696)]
697#[cfg_attr(feature = "macros", doc = "\n```\n")]
698/// # use xso::{AsXml, FromXml, to_vec};
699/// # use std::io::BufReader;
700/// #[derive(AsXml, PartialEq, Debug)]
701/// #[xml(namespace = "urn:example", name = "foo")]
702/// struct Foo {
703///     #[xml(attribute)]
704///     a: String,
705/// }
706///
707/// assert_eq!(
708///     b"<foo xmlns='urn:example' a='some-value'></foo>",
709///     &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
710/// );
711/// ```
712pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
713    let iter = xso.as_xml_iter()?;
714    let mut writer = rxml::writer::Encoder::new();
715    let mut buf = Vec::new();
716    for item in iter {
717        let item = item?;
718        writer.encode(item.as_rxml_item(), &mut buf)?;
719    }
720    Ok(buf)
721}
722
723/// # Test if a string contains exclusively XML whitespace
724///
725/// This function returns true if `s` contains only XML whitespace. XML
726/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
727/// and U+000d (carriage return), so this test is implemented on bytes instead
728/// of codepoints for efficiency.
729///
730/// # Example
731///
732/// ```
733/// # use xso::is_xml_whitespace;
734/// assert!(is_xml_whitespace(" \t\r\n  "));
735/// assert!(!is_xml_whitespace("  hello  "));
736/// ```
737pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
738    s.as_ref()
739        .iter()
740        .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
741}