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
781 lines
28 KiB
Rust
781 lines
28 KiB
Rust
#![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')
|
||
}
|