xmpp-rs/xso/src/lib.rs
Jonas Schäfer ccdf27117d Fix docs build with current nightly
skip-changelog, we don't officially support nightly.

See-Also: https://github.com/rust-lang/rfcs/pull/3631
See-Also: https://github.com/rust-lang/rust/pull/138907
2025-10-08 13:03:39 +02:00

781 lines
28 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
/*!
# XML Streamed Objects -- serde-like parsing for XML
This crate provides the traits for parsing XML data into Rust structs, and
vice versa.
While it is in 0.0.x versions, many features still need to be developed, but
rest assured that there is a solid plan to get it fully usable for even
advanced XML scenarios.
XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
use of this library in parsing XML streams like specified in RFC 6120.
*/
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::io;
pub mod asxml;
pub mod dynxso;
pub mod error;
pub mod fromxml;
#[cfg(feature = "minidom")]
pub mod minidom_compat;
mod rxml_util;
pub mod text;
// This is a hack to not make `const_str_eq` publicly available, except
// through the `exports` module if the `macros` feature is enabled, but have
// it available internally in all cases.
mod util {
/// Compile-time comparison of two strings.
///
/// Used by macro-generated code.
///
/// This is necessary because `<str as PartialEq>::eq` is not `const`.
pub const fn const_str_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
}
#[doc(hidden)]
pub mod exports {
#[cfg(all(feature = "minidom", feature = "macros"))]
pub use minidom;
#[cfg(feature = "macros")]
pub use rxml;
// These re-exports are necessary to support both std and no_std in code
// generated by the macros.
//
// If we attempted to use ::alloc directly from macros, std builds would
// not work because alloc is not generally present in builds using std.
// If we used ::std, no_std builds would obviously not work. By exporting
// std as alloc in std builds, we can safely use the alloc types from
// there.
//
// Obviously, we have to be careful in xso-proc to not refer to types
// which are not in alloc.
#[cfg(not(feature = "std"))]
pub extern crate alloc;
#[cfg(feature = "std")]
pub extern crate std as alloc;
/// The built-in `bool` type.
///
/// This is re-exported for use by macros in cases where we cannot rely on
/// people not having done `type bool = str` or some similar shenanigans.
#[cfg(feature = "macros")]
pub type CoreBool = bool;
/// The built-in `u8` type.
///
/// This is re-exported for use by macros in cases where we cannot rely on
/// people not having done `type u8 = str` or some similar shenanigans.
#[cfg(feature = "macros")]
pub type CoreU8 = u8;
#[cfg(feature = "macros")]
pub use super::util::const_str_eq;
}
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
#[doc(inline)]
pub use fromxml::Context;
use fromxml::XmlNameMatcher;
pub use text::TextCodec;
#[doc(inline)]
pub use rxml_util::Item;
pub use asxml::PrintRawXml;
#[doc = include_str!("from_xml_doc.md")]
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::FromXml;
/// # Make a struct or enum serialisable to XML
///
/// This derives the [`AsXml`] trait on a struct or enum. It is the
/// counterpart to [`macro@FromXml`].
///
/// The attributes necessary and available for the derivation to work are
/// documented on [`macro@FromXml`].
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::AsXml;
/// Trait allowing to iterate a struct's contents as serialisable
/// [`Item`]s.
///
/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
/// type is considered a non-breaking change for any given implementation of
/// this trait. Always refer to a type's iterator type using fully-qualified
/// notation, for example: `<T as xso::AsXml>::ItemIter`.
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be serialised as XML")]
pub trait AsXml {
/// The iterator type.
///
/// **Important:** Changing this type is considered a non-breaking change
/// for any given implementation of this trait. Always refer to a type's
/// iterator type using fully-qualified notation, for example:
/// `<T as xso::AsXml>::ItemIter`.
type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
where
Self: 'x;
/// Return an iterator which emits the contents of the struct or enum as
/// serialisable [`Item`] items.
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
/// Return the same iterator as [`as_xml_iter`][`Self::as_xml_iter`], but
/// boxed to erase the concrete iterator type.
///
/// The provided implementation uses a simple cast. In most cases, it does
/// not make sense to override the implementation. The only exception is
/// if [`Self::ItemIter`] is already a boxed type, in which case
/// overriding this method can avoid double-boxing the iterator.
fn as_xml_dyn_iter<'x>(
&'x self,
) -> Result<
Box<dyn Iterator<Item = Result<Item<'x>, self::error::Error>> + 'x>,
self::error::Error,
> {
self.as_xml_iter().map(|x| Box::new(x) as Box<_>)
}
}
/// Trait for a temporary object allowing to construct a struct from
/// [`rxml::Event`] items.
///
/// Objects of this type are generally constructed through
/// [`FromXml::from_events`] and are used to build Rust structs or enums from
/// XML data. The XML data must be fed as `rxml::Event` to the
/// [`feed`][`Self::feed`] method.
pub trait FromEventsBuilder {
/// The type which will be constructed by this builder.
type Output;
/// Feed another [`rxml::Event`] into the element construction
/// process.
///
/// Once the construction process completes, `Ok(Some(_))` is returned.
/// When valid data has been fed but more events are needed to fully
/// construct the resulting struct, `Ok(None)` is returned.
///
/// If the construction fails, `Err(_)` is returned. Errors are generally
/// fatal and the builder should be assumed to be broken at that point.
/// Feeding more events after an error may result in panics, errors or
/// inconsistent result data, though it may never result in unsound or
/// unsafe behaviour.
fn feed(
&mut self,
ev: rxml::Event,
ctx: &Context<'_>,
) -> Result<Option<Self::Output>, self::error::Error>;
}
/// Trait allowing to construct a struct from a stream of
/// [`rxml::Event`] items.
///
/// To use this, first call [`FromXml::from_events`] with the qualified
/// name and the attributes of the corresponding
/// [`rxml::Event::StartElement`] event. If the call succeeds, the
/// returned builder object must be fed with the events representing the
/// contents of the element, and then with the `EndElement` event.
///
/// The `StartElement` passed to `from_events` must not be passed to `feed`.
///
/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
/// is considered a non-breaking change for any given implementation of this
/// trait. Always refer to a type's builder type using fully-qualified
/// notation, for example: `<T as xso::FromXml>::Builder`.
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be parsed from XML")]
pub trait FromXml {
/// A builder type used to construct the element.
///
/// **Important:** Changing this type is considered a non-breaking change
/// for any given implementation of this trait. Always refer to a type's
/// builder type using fully-qualified notation, for example:
/// `<T as xso::FromXml>::Builder`.
type Builder: FromEventsBuilder<Output = Self>;
/// Attempt to initiate the streamed construction of this struct from XML.
///
/// If the passed qualified `name` and `attrs` match the element's type,
/// the [`Self::Builder`] is returned and should be fed with XML events
/// by the caller.
///
/// Otherwise, an appropriate error is returned.
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, self::error::FromEventsError>;
/// Return a predicate which determines if `Self` *may* be parsed from
/// a given XML element.
///
/// The returned matcher **must** match all elements from which `Self`
/// can be parsed, but it may also match elements from which `Self`
/// cannot be parsed.
///
/// This is an optimisation utility for code locations which have to
/// disambiguate between many `FromXml` implementations. The provided
/// implementation returns a matcher which matches all elements, which is
/// correct, but also very inefficient.
fn xml_name_matcher() -> XmlNameMatcher<'static> {
XmlNameMatcher::Any
}
}
/// Trait allowing to convert XML text to a value.
///
/// This trait is similar to [`FromStr`][`core::str::FromStr`], however, to
/// allow specialisation for XML<->Text conversion, a separate trait is
/// introduced. Unlike `FromStr`, this trait allows taking ownership of the
/// original text data, potentially saving allocations.
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be parsed from XML text",
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});`."
)]
pub trait FromXmlText: Sized {
/// Convert the given XML text to a value.
fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
}
/// Trait to convert a value to an XML text string.
///
/// Implementing this trait for a type allows it to be used both for XML
/// character data within elements and for XML attributes. For XML attributes,
/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
/// [`AsXmlText`] automatically provides an implementation of
/// [`AsOptionalXmlText`].
///
/// If your type should only be used in XML attributes and has no correct
/// serialisation in XML text, you should *only* implement
/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be serialised to XML text",
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});`."
)]
pub trait AsXmlText {
/// Convert the value to an XML string in a context where an absent value
/// cannot be represented.
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
/// Convert the value to an XML string in a context where an absent value
/// can be represented.
///
/// The provided implementation will always return the result of
/// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
/// this method, implementors can customize the behaviour for certain
/// values.
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
Ok(Some(self.as_xml_text()?))
}
}
/// Specialized variant of [`AsXmlText`].
///
/// Normally, it should not be necessary to implement this trait as it is
/// automatically implemented for all types implementing [`AsXmlText`].
/// However, if your type can only be serialised as an XML attribute (for
/// example because an absent value has a particular meaning), it is correct
/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
///
/// If your type can be serialised as both (text and attribute) but needs
/// special handling in attributes, implement [`AsXmlText`] but provide a
/// custom implementation of [`AsXmlText::as_optional_xml_text`].
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be serialised as XML attribute",
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});`."
)]
pub trait AsOptionalXmlText {
/// Convert the value to an XML string in a context where an absent value
/// can be represented.
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
}
/// # Control how unknown attributes are handled
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how attributes, which are
/// not handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownAttributePolicy {
/// All unknown attributes are discarded.
///
/// This is the default policy if the crate is built with the
/// `non-pedantic` feature.
#[cfg_attr(feature = "non-pedantic", default)]
Discard,
/// The first unknown attribute which is encountered generates a fatal
/// parsing error.
///
/// This is the default policy if the crate is built **without** the
/// `non-pedantic` feature.
#[cfg_attr(not(feature = "non-pedantic"), default)]
Fail,
}
impl UnknownAttributePolicy {
#[doc(hidden)]
/// Implementation of the policy.
///
/// This is an internal API and not subject to semver versioning.
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
/// # Control how unknown child elements are handled
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how children, which are not
/// handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownChildPolicy {
/// All unknown children are discarded.
///
/// This is the default policy if the crate is built with the
/// `non-pedantic` feature.
#[cfg_attr(feature = "non-pedantic", default)]
Discard,
/// The first unknown child which is encountered generates a fatal
/// parsing error.
///
/// This is the default policy if the crate is built **without** the
/// `non-pedantic` feature.
#[cfg_attr(not(feature = "non-pedantic"), default)]
Fail,
}
impl UnknownChildPolicy {
#[doc(hidden)]
/// Implementation of the policy.
///
/// This is an internal API and not subject to semver versioning.
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
/// # Transform a value into another value via XML
///
/// This function takes `from`, converts it into XML using its [`AsXml`]
/// implementation and builds a `T` from it (without buffering the tree in
/// memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the header
/// provided by `from`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
not(all(feature = "std", feature = "macros")),
doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, transform};
/// #[derive(AsXml)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Source {
/// #[xml(attribute = "xml:lang")]
/// lang: &'static str,
/// }
///
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Dest {
/// #[xml(lang)]
/// lang: Option<String>,
/// }
///
/// assert_eq!(
/// Dest { lang: Some("en".to_owned()) },
/// transform(&Source { lang: "en" }).unwrap(),
/// );
/// ```
pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
let (qname, attrs) = match iter.next() {
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
Some(Err(e)) => return Err(e),
_ => panic!("into_event_iter did not start with StartElement event!"),
};
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
}
Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
};
for event in iter {
let event = event?;
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v);
}
}
Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
/// Attempt to convert a [`minidom::Element`] into a type implementing
/// [`FromXml`], fallably.
///
/// Unlike [`transform`] (which can also be used with an element), this
/// function will return the element unharmed if its element header does not
/// match the expectations of `T`.
#[cfg(feature = "minidom")]
#[deprecated(
since = "0.1.3",
note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
)]
pub fn try_from_element<T: FromXml>(
from: minidom::Element,
) -> Result<T, self::error::FromElementError> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
#[allow(deprecated)]
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::FromElementError::Mismatch(from))
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(self::error::FromElementError::Invalid(e))
}
};
let mut iter = from.as_xml_iter()?;
// consume the element header
for item in &mut iter {
let item = item?;
match item {
// discard the element header
Item::XmlDeclaration(..) => (),
Item::ElementHeadStart(..) => (),
Item::Attribute(..) => (),
Item::ElementHeadEnd => {
// now that the element header is over, we break out
break;
}
Item::Text(..) => panic!("text before end of element header"),
Item::ElementFoot => panic!("element foot before end of element header"),
}
}
let iter = self::rxml_util::ItemToEvent::new(iter);
for event in iter {
let event = event?;
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v);
}
}
// unreachable! instead of error here, because minidom::Element always
// produces the complete event sequence of a single element, and FromXml
// implementations must be constructible from that.
unreachable!("minidom::Element did not produce enough events to complete element")
}
/// # Parse a value from a byte slice containing XML data
///
/// This function parses the XML found in `buf`, assuming it contains a
/// complete XML document (with optional XML declaration) and builds a `T`
/// from it (without buffering the tree in memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the element
/// header at the root of the document in `bytes`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_bytes};
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
/// #[xml(attribute)]
/// a: String,
/// }
///
/// assert_eq!(
/// Foo { a: "some-value".to_owned() },
/// from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
/// );
/// ```
pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
use rxml::{error::EndOrError, Parse};
let mut languages = rxml::xml_lang::XmlLangStack::new();
let mut parser = rxml::Parser::new();
let (name, attrs) = loop {
match parser.parse(&mut buf, true) {
Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
Ok(None) | Err(EndOrError::NeedMoreData) => {
return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))))
}
Ok(Some(_)) => {
return Err(self::error::Error::Other(
"Unexpected event at start of document",
))
}
}
};
languages.push_from_attrs(&attrs);
let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch);
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(e);
}
};
loop {
match parser.parse(&mut buf, true) {
Ok(Some(ev)) => {
languages.handle_event(&ev);
if let Some(v) =
builder.feed(ev, &Context::empty().with_language(languages.current()))?
{
return Ok(v);
}
}
Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
Ok(None) | Err(EndOrError::NeedMoreData) => {
return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
}
}
}
#[cfg(feature = "std")]
fn read_start_event_io(
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> {
for ev in r {
match ev? {
rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::Other("Unexpected event at start of document"),
))
}
}
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))),
))
}
/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
///
/// This function parses the XML found in `r`, assuming it contains a
/// complete XML document (with optional XML declaration) and builds a `T`
/// from it (without buffering the tree in memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the element
/// header at the root of the document in `r`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_reader};
/// # use std::io::BufReader;
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
/// #[xml(attribute)]
/// a: String,
/// }
///
/// // let file = ..
/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
/// assert_eq!(
/// Foo { a: "some-value".to_owned() },
/// from_reader(BufReader::new(file)).unwrap(),
/// );
/// ```
#[cfg(feature = "std")]
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
let (name, attrs) = read_start_event_io(&mut reader)?;
let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(reader.language()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
};
while let Some(ev) = reader.next() {
if let Some(v) = builder
.feed(ev?, &Context::empty().with_language(reader.language()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
{
return Ok(v);
}
}
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
))
}
/// # Serialize a value to UTF-8-encoded XML
///
/// This function takes `xso`, converts it into XML using its [`AsXml`]
/// implementation and serialises the resulting XML events into a `Vec<u8>`.
///
/// If serialisation fails, an error is returned instead.
///
/// ## Example
///
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, to_vec};
/// # use std::io::BufReader;
/// #[derive(AsXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
/// #[xml(attribute)]
/// a: String,
/// }
///
/// assert_eq!(
/// b"<foo xmlns='urn:example' a='some-value'></foo>",
/// &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
/// );
/// ```
pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
let iter = xso.as_xml_iter()?;
let mut writer = rxml::writer::Encoder::new();
let mut buf = Vec::new();
for item in iter {
let item = item?;
writer.encode(item.as_rxml_item(), &mut buf)?;
}
Ok(buf)
}
/// # Test if a string contains exclusively XML whitespace
///
/// This function returns true if `s` contains only XML whitespace. XML
/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
/// and U+000d (carriage return), so this test is implemented on bytes instead
/// of codepoints for efficiency.
///
/// # Example
///
/// ```
/// # use xso::is_xml_whitespace;
/// assert!(is_xml_whitespace(" \t\r\n "));
/// assert!(!is_xml_whitespace(" hello "));
/// ```
pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
s.as_ref()
.iter()
.all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
}