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