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