Skip to main content

xso/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(docsrs, doc(auto_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 dynxso;
35pub mod error;
36pub mod fromxml;
37#[cfg(feature = "minidom")]
38pub mod minidom_compat;
39mod rxml_util;
40pub mod text;
41
42// This is a hack to not make `const_str_eq` publicly available, except
43// through the `exports` module if the `macros` feature is enabled, but have
44// it available internally in all cases.
45mod util {
46    /// Compile-time comparison of two strings.
47    ///
48    /// Used by macro-generated code.
49    ///
50    /// This is necessary because `<str as PartialEq>::eq` is not `const`.
51    pub const fn const_str_eq(a: &str, b: &str) -> bool {
52        let a = a.as_bytes();
53        let b = b.as_bytes();
54        if a.len() != b.len() {
55            return false;
56        }
57
58        let mut i = 0;
59        while i < a.len() {
60            if a[i] != b[i] {
61                return false;
62            }
63            i += 1;
64        }
65
66        true
67    }
68}
69
70#[doc(hidden)]
71pub mod exports {
72    #[cfg(all(feature = "minidom", feature = "macros"))]
73    pub use minidom;
74    #[cfg(any(feature = "macros", feature = "linktime"))]
75    pub use rxml;
76
77    // These re-exports are necessary to support both std and no_std in code
78    // generated by the macros.
79    //
80    // If we attempted to use ::alloc directly from macros, std builds would
81    // not work because alloc is not generally present in builds using std.
82    // If we used ::std, no_std builds would obviously not work. By exporting
83    // std as alloc in std builds, we can safely use the alloc types from
84    // there.
85    //
86    // Obviously, we have to be careful in xso-proc to not refer to types
87    // which are not in alloc.
88    #[cfg(not(feature = "std"))]
89    pub extern crate alloc;
90    #[cfg(feature = "std")]
91    pub extern crate std as alloc;
92
93    /// The built-in `bool` type.
94    ///
95    /// This is re-exported for use by macros in cases where we cannot rely on
96    /// people not having done `type bool = str` or some similar shenanigans.
97    #[cfg(feature = "macros")]
98    pub type CoreBool = bool;
99
100    /// The built-in `u8` type.
101    ///
102    /// This is re-exported for use by macros in cases where we cannot rely on
103    /// people not having done `type u8 = str` or some similar shenanigans.
104    #[cfg(feature = "macros")]
105    pub type CoreU8 = u8;
106
107    #[cfg(feature = "macros")]
108    pub use super::util::const_str_eq;
109
110    #[cfg(feature = "linktime")]
111    pub use paste;
112    #[cfg(feature = "linktime")]
113    pub use scattered_collect;
114}
115
116use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
117
118#[doc(inline)]
119pub use fromxml::Context;
120use fromxml::XmlNameMatcher;
121
122pub use text::TextCodec;
123
124#[doc(inline)]
125pub use rxml_util::Item;
126
127pub use asxml::PrintRawXml;
128
129#[doc = include_str!("from_xml_doc.md")]
130#[doc(inline)]
131#[cfg(feature = "macros")]
132pub use xso_proc::FromXml;
133
134/// # Make a struct or enum serialisable to XML
135///
136/// This derives the [`AsXml`] trait on a struct or enum. It is the
137/// counterpart to [`macro@FromXml`].
138///
139/// The attributes necessary and available for the derivation to work are
140/// documented on [`macro@FromXml`].
141#[doc(inline)]
142#[cfg(feature = "macros")]
143pub use xso_proc::AsXml;
144
145/// Trait allowing to iterate a struct's contents as serialisable
146/// [`Item`]s.
147///
148/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
149/// type is considered a non-breaking change for any given implementation of
150/// this trait. Always refer to a type's iterator type using fully-qualified
151/// notation, for example: `<T as xso::AsXml>::ItemIter`.
152#[diagnostic::on_unimplemented(message = "`{Self}` cannot be serialised as XML")]
153pub trait AsXml {
154    /// The iterator type.
155    ///
156    /// **Important:** Changing this type is considered a non-breaking change
157    /// for any given implementation of this trait. Always refer to a type's
158    /// iterator type using fully-qualified notation, for example:
159    /// `<T as xso::AsXml>::ItemIter`.
160    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
161    where
162        Self: 'x;
163
164    /// Return an iterator which emits the contents of the struct or enum as
165    /// serialisable [`Item`] items.
166    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
167
168    /// Return the same iterator as [`as_xml_iter`][`Self::as_xml_iter`], but
169    /// boxed to erase the concrete iterator type.
170    ///
171    /// The provided implementation uses a simple cast. In most cases, it does
172    /// not make sense to override the implementation. The only exception is
173    /// if [`Self::ItemIter`] is already a boxed type, in which case
174    /// overriding this method can avoid double-boxing the iterator.
175    fn as_xml_dyn_iter<'x>(
176        &'x self,
177    ) -> Result<
178        Box<dyn Iterator<Item = Result<Item<'x>, self::error::Error>> + 'x>,
179        self::error::Error,
180    > {
181        self.as_xml_iter().map(|x| Box::new(x) as Box<_>)
182    }
183}
184
185/// Trait for a temporary object allowing to construct a struct from
186/// [`rxml::Event`] items.
187///
188/// Objects of this type are generally constructed through
189/// [`FromXml::from_events`] and are used to build Rust structs or enums from
190/// XML data. The XML data must be fed as `rxml::Event` to the
191/// [`feed`][`Self::feed`] method.
192pub trait FromEventsBuilder {
193    /// The type which will be constructed by this builder.
194    type Output;
195
196    /// Feed another [`rxml::Event`] into the element construction
197    /// process.
198    ///
199    /// Once the construction process completes, `Ok(Some(_))` is returned.
200    /// When valid data has been fed but more events are needed to fully
201    /// construct the resulting struct, `Ok(None)` is returned.
202    ///
203    /// If the construction fails, `Err(_)` is returned. Errors are generally
204    /// fatal and the builder should be assumed to be broken at that point.
205    /// Feeding more events after an error may result in panics, errors or
206    /// inconsistent result data, though it may never result in unsound or
207    /// unsafe behaviour.
208    fn feed(
209        &mut self,
210        ev: rxml::Event,
211        ctx: &Context<'_>,
212    ) -> Result<Option<Self::Output>, self::error::Error>;
213}
214
215/// Trait allowing to construct a struct from a stream of
216/// [`rxml::Event`] items.
217///
218/// To use this, first call [`FromXml::from_events`] with the qualified
219/// name and the attributes of the corresponding
220/// [`rxml::Event::StartElement`] event. If the call succeeds, the
221/// returned builder object must be fed with the events representing the
222/// contents of the element, and then with the `EndElement` event.
223///
224/// The `StartElement` passed to `from_events` must not be passed to `feed`.
225///
226/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
227/// is considered a non-breaking change for any given implementation of this
228/// trait. Always refer to a type's builder type using fully-qualified
229/// notation, for example: `<T as xso::FromXml>::Builder`.
230#[diagnostic::on_unimplemented(message = "`{Self}` cannot be parsed from XML")]
231pub trait FromXml {
232    /// A builder type used to construct the element.
233    ///
234    /// **Important:** Changing this type is considered a non-breaking change
235    /// for any given implementation of this trait. Always refer to a type's
236    /// builder type using fully-qualified notation, for example:
237    /// `<T as xso::FromXml>::Builder`.
238    type Builder: FromEventsBuilder<Output = Self>;
239
240    /// A predicate which determines if `Self` *may* be parsed from
241    /// a given XML element.
242    ///
243    /// The returned matcher **must** match all elements from which `Self`
244    /// can be parsed, but it may also match elements from which `Self`
245    /// cannot be parsed.
246    ///
247    /// This is an optimisation utility for code locations which have to
248    /// disambiguate between many `FromXml` implementations. The provided
249    /// implementation returns a matcher which matches all elements, which is
250    /// correct, but also very inefficient.
251    const XML_NAME_MATCHER: XmlNameMatcher<'static> = XmlNameMatcher::Any;
252
253    /// Attempt to initiate the streamed construction of this struct from XML.
254    ///
255    /// If the passed qualified `name` and `attrs` match the element's type,
256    /// the [`Self::Builder`] is returned and should be fed with XML events
257    /// by the caller.
258    ///
259    /// Otherwise, an appropriate error is returned.
260    fn from_events(
261        name: rxml::QName,
262        attrs: rxml::AttrMap,
263        ctx: &Context<'_>,
264    ) -> Result<Self::Builder, self::error::FromEventsError>;
265}
266
267/// Trait allowing to convert XML text to a value.
268///
269/// This trait is similar to [`FromStr`][`core::str::FromStr`], however, to
270/// allow specialisation for XML<->Text conversion, a separate trait is
271/// introduced. Unlike `FromStr`, this trait allows taking ownership of the
272/// original text data, potentially saving allocations.
273///
274/// **Important:** See the [`text`][`crate::text`] module's documentation
275/// for notes regarding implementations for types from third-party crates.
276#[diagnostic::on_unimplemented(
277    message = "`{Self}` cannot be parsed from XML text",
278    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});`."
279)]
280pub trait FromXmlText: Sized {
281    /// Convert the given XML text to a value.
282    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
283}
284
285/// Trait to convert a value to an XML text string.
286///
287/// Implementing this trait for a type allows it to be used both for XML
288/// character data within elements and for XML attributes. For XML attributes,
289/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
290/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
291/// [`AsXmlText`] automatically provides an implementation of
292/// [`AsOptionalXmlText`].
293///
294/// If your type should only be used in XML attributes and has no correct
295/// serialisation in XML text, you should *only* implement
296/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
297///
298/// **Important:** See the [`text`][`crate::text`] module's documentation
299/// for notes regarding implementations for types from third-party crates.
300#[diagnostic::on_unimplemented(
301    message = "`{Self}` cannot be serialised to XML text",
302    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});`."
303)]
304pub trait AsXmlText {
305    /// Convert the value to an XML string in a context where an absent value
306    /// cannot be represented.
307    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
308
309    /// Convert the value to an XML string in a context where an absent value
310    /// can be represented.
311    ///
312    /// The provided implementation will always return the result of
313    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
314    /// this method, implementors can customize the behaviour for certain
315    /// values.
316    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
317        Ok(Some(self.as_xml_text()?))
318    }
319}
320
321/// Specialized variant of [`AsXmlText`].
322///
323/// Normally, it should not be necessary to implement this trait as it is
324/// automatically implemented for all types implementing [`AsXmlText`].
325/// However, if your type can only be serialised as an XML attribute (for
326/// example because an absent value has a particular meaning), it is correct
327/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
328///
329/// If your type can be serialised as both (text and attribute) but needs
330/// special handling in attributes, implement [`AsXmlText`] but provide a
331/// custom implementation of [`AsXmlText::as_optional_xml_text`].
332///
333/// **Important:** See the [`text`][`crate::text`] module's documentation
334/// for notes regarding implementations for types from third-party crates.
335#[diagnostic::on_unimplemented(
336    message = "`{Self}` cannot be serialised as XML attribute",
337    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});`."
338)]
339pub trait AsOptionalXmlText {
340    /// Convert the value to an XML string in a context where an absent value
341    /// can be represented.
342    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
343}
344
345/// # Control how unknown attributes are handled
346///
347/// The variants of this enum are referenced in the
348/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
349/// enum variants. The specified variant controls how attributes, which are
350/// not handled by any member of the compound, are handled during parsing.
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
352pub enum UnknownAttributePolicy {
353    /// All unknown attributes are discarded.
354    ///
355    /// This is the default policy if the crate is built without the
356    /// `pedantic` feature.
357    #[cfg_attr(not(feature = "pedantic"), default)]
358    Discard,
359
360    /// The first unknown attribute which is encountered generates a fatal
361    /// parsing error.
362    ///
363    /// This is the default policy if the crate is built with the
364    /// `pedantic` feature.
365    #[cfg_attr(feature = "pedantic", default)]
366    Fail,
367}
368
369impl UnknownAttributePolicy {
370    #[doc(hidden)]
371    /// Implementation of the policy.
372    ///
373    /// This is an internal API and not subject to semver versioning.
374    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
375        match self {
376            Self::Fail => Err(self::error::Error::Other(msg)),
377            Self::Discard => Ok(()),
378        }
379    }
380}
381
382/// # Control how unknown child elements are handled
383///
384/// The variants of this enum are referenced in the
385/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
386/// enum variants. The specified variant controls how children, which are not
387/// handled by any member of the compound, are handled during parsing.
388#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
389pub enum UnknownChildPolicy {
390    /// All unknown children are discarded.
391    ///
392    /// This is the default policy if the crate is built without the
393    /// `pedantic` feature.
394    #[cfg_attr(not(feature = "pedantic"), default)]
395    Discard,
396
397    /// The first unknown child which is encountered generates a fatal
398    /// parsing error.
399    ///
400    /// This is the default policy if the crate is built with the
401    /// `pedantic` feature.
402    #[cfg_attr(feature = "pedantic", default)]
403    Fail,
404}
405
406impl UnknownChildPolicy {
407    #[doc(hidden)]
408    /// Implementation of the policy.
409    ///
410    /// This is an internal API and not subject to semver versioning.
411    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
412        match self {
413            Self::Fail => Err(self::error::Error::Other(msg)),
414            Self::Discard => Ok(()),
415        }
416    }
417}
418
419/// # Transform a value into another value via XML
420///
421/// This function takes `from`, converts it into XML using its [`AsXml`]
422/// implementation and builds a `T` from it (without buffering the tree in
423/// memory).
424///
425/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
426/// particular, if `T` expects a different element header than the header
427/// provided by `from`,
428/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
429///
430/// ## Example
431///
432#[cfg_attr(
433    not(all(feature = "std", feature = "macros")),
434    doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
435)]
436#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
437/// # use xso::{AsXml, FromXml, transform};
438/// #[derive(AsXml)]
439/// #[xml(namespace = "urn:example", name = "foo")]
440/// struct Source {
441///     #[xml(attribute = "xml:lang")]
442///     lang: &'static str,
443/// }
444///
445/// #[derive(FromXml, PartialEq, Debug)]
446/// #[xml(namespace = "urn:example", name = "foo")]
447/// struct Dest {
448///     #[xml(lang)]
449///     lang: Option<String>,
450/// }
451///
452/// assert_eq!(
453///     Dest { lang: Some("en".to_owned()) },
454///     transform(&Source { lang: "en" }).unwrap(),
455/// );
456/// ```
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(
467        qname,
468        attrs,
469        &Context::empty().with_language(languages.current()),
470    ) {
471        Ok(v) => v,
472        Err(self::error::FromEventsError::Mismatch { .. }) => {
473            return Err(self::error::Error::TypeMismatch)
474        }
475        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
476    };
477    for event in iter {
478        let event = event?;
479        languages.handle_event(&event);
480        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
481            return Ok(v);
482        }
483    }
484    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
485}
486
487/// Attempt to convert a [`minidom::Element`] into a type implementing
488/// [`FromXml`], fallably.
489///
490/// Unlike [`transform`] (which can also be used with an element), this
491/// function will return the element unharmed if its element header does not
492/// match the expectations of `T`.
493#[cfg(feature = "minidom")]
494#[deprecated(
495    since = "0.1.3",
496    note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
497)]
498pub fn try_from_element<T: FromXml>(
499    from: minidom::Element,
500) -> Result<T, self::error::FromElementError> {
501    let mut languages = rxml::xml_lang::XmlLangStack::new();
502    #[allow(deprecated)]
503    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
504
505    languages.push_from_attrs(&attrs);
506    let mut sink = match T::from_events(
507        qname,
508        attrs,
509        &Context::empty().with_language(languages.current()),
510    ) {
511        Ok(v) => v,
512        Err(self::error::FromEventsError::Mismatch { .. }) => {
513            return Err(self::error::FromElementError::Mismatch(from))
514        }
515        Err(self::error::FromEventsError::Invalid(e)) => {
516            return Err(self::error::FromElementError::Invalid(e))
517        }
518    };
519
520    let mut iter = from.as_xml_iter()?;
521    // consume the element header
522    for item in &mut iter {
523        let item = item?;
524        match item {
525            // discard the element header
526            Item::XmlDeclaration(..) => (),
527            Item::ElementHeadStart(..) => (),
528            Item::Attribute(..) => (),
529            Item::ElementHeadEnd => {
530                // now that the element header is over, we break out
531                break;
532            }
533            Item::Text(..) => panic!("text before end of element header"),
534            Item::ElementFoot => panic!("element foot before end of element header"),
535        }
536    }
537    let iter = self::rxml_util::ItemToEvent::new(iter);
538    for event in iter {
539        let event = event?;
540        languages.handle_event(&event);
541        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
542            return Ok(v);
543        }
544    }
545    // unreachable! instead of error here, because minidom::Element always
546    // produces the complete event sequence of a single element, and FromXml
547    // implementations must be constructible from that.
548    unreachable!("minidom::Element did not produce enough events to complete element")
549}
550
551fn from_bytes_inner<T: FromXml>(
552    mut parser: rxml::Parser,
553    mut buf: &[u8],
554) -> Result<T, self::error::Error> {
555    use rxml::{error::EndOrError, Parse};
556
557    let mut languages = rxml::xml_lang::XmlLangStack::new();
558    let (name, attrs) = loop {
559        match parser.parse(&mut buf, true) {
560            Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
561            Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
562            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
563            Ok(None) | Err(EndOrError::NeedMoreData) => {
564                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
565                    rxml::error::ErrorContext::DocumentBegin,
566                ))))
567            }
568            Ok(Some(_)) => {
569                return Err(self::error::Error::Other(
570                    "Unexpected event at start of document",
571                ))
572            }
573        }
574    };
575    languages.push_from_attrs(&attrs);
576    let mut builder = match T::from_events(
577        name,
578        attrs,
579        &Context::empty().with_language(languages.current()),
580    ) {
581        Ok(v) => v,
582        Err(self::error::FromEventsError::Mismatch { .. }) => {
583            return Err(self::error::Error::TypeMismatch);
584        }
585        Err(self::error::FromEventsError::Invalid(e)) => {
586            return Err(e);
587        }
588    };
589
590    loop {
591        match parser.parse(&mut buf, true) {
592            Ok(Some(ev)) => {
593                languages.handle_event(&ev);
594                if let Some(v) =
595                    builder.feed(ev, &Context::empty().with_language(languages.current()))?
596                {
597                    return Ok(v);
598                }
599            }
600            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
601            Ok(None) | Err(EndOrError::NeedMoreData) => {
602                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
603            }
604        }
605    }
606}
607
608/// # Parse a value from a byte slice containing XML data
609///
610/// This function parses the XML found in `buf`, assuming it contains a
611/// complete XML document (with optional XML declaration) and builds a `T`
612/// from it (without buffering the tree in memory).
613///
614/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
615/// particular, if `T` expects a different element header than the element
616/// header at the root of the document in `bytes`,
617/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
618///
619/// ## Example
620///
621#[cfg_attr(
622    not(feature = "macros"),
623    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
624)]
625#[cfg_attr(feature = "macros", doc = "\n```\n")]
626/// # use xso::{AsXml, FromXml, from_bytes};
627/// #[derive(FromXml, PartialEq, Debug)]
628/// #[xml(namespace = "urn:example", name = "foo")]
629/// struct Foo {
630///     #[xml(attribute)]
631///     a: String,
632/// }
633///
634/// assert_eq!(
635///     Foo { a: "some-value".to_owned() },
636///     from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
637/// );
638/// ```
639pub fn from_bytes<T: FromXml>(buf: &[u8]) -> Result<T, self::error::Error> {
640    let parser = rxml::Parser::new();
641    from_bytes_inner(parser, buf)
642}
643
644/// # Parse a value from a byte slice with specific parser options.
645///
646/// This is the same as [`from_bytes`], except that the rxml parser
647/// [`Options`][`rxml::Options`] can be specified explicitly.
648///
649/// ## Example
650///
651#[cfg_attr(
652    not(feature = "macros"),
653    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
654)]
655#[cfg_attr(feature = "macros", doc = "\n```\n")]
656/// # use xso::{AsXml, FromXml, from_bytes_with_options};
657/// #[derive(FromXml, PartialEq, Debug)]
658/// #[xml(namespace = "urn:example", name = "foo")]
659/// struct Foo {
660///     #[xml(attribute)]
661///     a: String,
662/// }
663///
664/// let mut opts = rxml::Options::default();
665/// opts.comments = rxml::parser::CommentMode::Discard;
666///
667/// assert_eq!(
668///     Foo { a: "some-value".to_owned() },
669///     from_bytes_with_options(
670///         b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>",
671///         opts,
672///     ).unwrap(),
673/// );
674/// ```
675pub fn from_bytes_with_options<T: FromXml>(
676    buf: &[u8],
677    opts: rxml::Options,
678) -> Result<T, self::error::Error> {
679    use rxml::WithOptions;
680    let parser = rxml::Parser::with_options(opts);
681    from_bytes_inner(parser, buf)
682}
683
684#[cfg(feature = "std")]
685fn read_start_event_io(
686    r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
687) -> io::Result<(rxml::QName, rxml::AttrMap)> {
688    for ev in r {
689        match ev? {
690            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
691            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
692            _ => {
693                return Err(io::Error::new(
694                    io::ErrorKind::InvalidData,
695                    self::error::Error::Other("Unexpected event at start of document"),
696                ))
697            }
698        }
699    }
700    Err(io::Error::new(
701        io::ErrorKind::InvalidData,
702        self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
703            rxml::error::ErrorContext::DocumentBegin,
704        ))),
705    ))
706}
707
708fn from_reader_inner<T: FromXml, R: io::BufRead>(
709    mut reader: rxml::XmlLangTracker<rxml::Reader<R>>,
710) -> io::Result<T> {
711    let (name, attrs) = read_start_event_io(&mut reader)?;
712    let mut builder = match T::from_events(
713        name,
714        attrs,
715        &Context::empty().with_language(reader.language()),
716    ) {
717        Ok(v) => v,
718        Err(self::error::FromEventsError::Mismatch { .. }) => {
719            return Err(self::error::Error::TypeMismatch)
720                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
721        }
722        Err(self::error::FromEventsError::Invalid(e)) => {
723            return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
724        }
725    };
726    while let Some(ev) = reader.next() {
727        if let Some(v) = builder
728            .feed(ev?, &Context::empty().with_language(reader.language()))
729            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
730        {
731            return Ok(v);
732        }
733    }
734    Err(io::Error::new(
735        io::ErrorKind::UnexpectedEof,
736        self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
737    ))
738}
739
740/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
741///
742/// This function parses the XML found in `r`, assuming it contains a
743/// complete XML document (with optional XML declaration) and builds a `T`
744/// from it (without buffering the tree in memory).
745///
746/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
747/// particular, if `T` expects a different element header than the element
748/// header at the root of the document in `r`,
749/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
750///
751/// ## Example
752///
753#[cfg_attr(
754    not(feature = "macros"),
755    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
756)]
757#[cfg_attr(feature = "macros", doc = "\n```\n")]
758/// # use xso::{AsXml, FromXml, from_reader};
759/// # use std::io::BufReader;
760/// #[derive(FromXml, PartialEq, Debug)]
761/// #[xml(namespace = "urn:example", name = "foo")]
762/// struct Foo {
763///     #[xml(attribute)]
764///     a: String,
765/// }
766///
767/// // let file = ..
768/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
769/// assert_eq!(
770///     Foo { a: "some-value".to_owned() },
771///     from_reader(BufReader::new(file)).unwrap(),
772/// );
773/// ```
774#[cfg(feature = "std")]
775pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
776    from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::new(r)))
777}
778
779/// # Parse a value using a specific parser config
780///
781/// This is the same as [`from_reader`], except that the rxml parser
782/// [`Options`][`rxml::Options`] can be specified explicitly.
783///
784/// ## Example
785///
786#[cfg_attr(
787    not(feature = "macros"),
788    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
789)]
790#[cfg_attr(feature = "macros", doc = "\n```\n")]
791/// # use xso::{AsXml, FromXml, from_reader_with_options};
792/// # use std::io::BufReader;
793/// #[derive(FromXml, PartialEq, Debug)]
794/// #[xml(namespace = "urn:example", name = "foo")]
795/// struct Foo {
796///     #[xml(attribute)]
797///     a: String,
798/// }
799///
800/// let mut opts = rxml::Options::default();
801/// opts.comments = rxml::parser::CommentMode::Discard;
802/// // let file = .. // containing XML comments
803/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>"[..];
804/// assert_eq!(
805///     Foo { a: "some-value".to_owned() },
806///     from_reader_with_options(BufReader::new(file), opts).unwrap(),
807/// );
808/// ```
809#[cfg(feature = "std")]
810pub fn from_reader_with_options<T: FromXml, R: io::BufRead>(
811    r: R,
812    options: rxml::Options,
813) -> io::Result<T> {
814    from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::with_options(
815        r, options,
816    )))
817}
818
819/// # Serialize a value to UTF-8-encoded XML
820///
821/// This function takes `xso`, converts it into XML using its [`AsXml`]
822/// implementation and serialises the resulting XML events into a `Vec<u8>`.
823///
824/// If serialisation fails, an error is returned instead.
825///
826/// ## Example
827///
828#[cfg_attr(
829    not(feature = "macros"),
830    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
831)]
832#[cfg_attr(feature = "macros", doc = "\n```\n")]
833/// # use xso::{AsXml, FromXml, to_vec};
834/// # use std::io::BufReader;
835/// #[derive(AsXml, PartialEq, Debug)]
836/// #[xml(namespace = "urn:example", name = "foo")]
837/// struct Foo {
838///     #[xml(attribute)]
839///     a: String,
840/// }
841///
842/// assert_eq!(
843///     b"<foo xmlns='urn:example' a='some-value'></foo>",
844///     &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
845/// );
846/// ```
847pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
848    let iter = xso.as_xml_iter()?;
849    let mut writer = rxml::writer::Encoder::new();
850    let mut buf = Vec::new();
851    for item in iter {
852        let item = item?;
853        writer.encode(item.as_rxml_item(), &mut buf)?;
854    }
855    Ok(buf)
856}
857
858/// # Test if a string contains exclusively XML whitespace
859///
860/// This function returns true if `s` contains only XML whitespace. XML
861/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
862/// and U+000d (carriage return), so this test is implemented on bytes instead
863/// of codepoints for efficiency.
864///
865/// # Example
866///
867/// ```
868/// # use xso::is_xml_whitespace;
869/// assert!(is_xml_whitespace(" \t\r\n  "));
870/// assert!(!is_xml_whitespace("  hello  "));
871/// ```
872pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
873    s.as_ref()
874        .iter()
875        .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
876}