xso: add support for passing rxml::Options
This commit is contained in:
parent
bb02836097
commit
d9f030c07e
2 changed files with 156 additions and 61 deletions
|
|
@ -2,6 +2,9 @@ Version NEXT:
|
||||||
* Changes
|
* Changes
|
||||||
- Fix some Clippy warnings
|
- Fix some Clippy warnings
|
||||||
- Fix build with minidom and without std (!661)
|
- Fix build with minidom and without std (!661)
|
||||||
|
* Added
|
||||||
|
- xso::from_bytes_with_options and xso::from_reader_with_options to allow
|
||||||
|
passing custom parser configuration.
|
||||||
|
|
||||||
Version 0.3.0, release 2025-10-28:
|
Version 0.3.0, release 2025-10-28:
|
||||||
* Changes
|
* Changes
|
||||||
|
|
|
||||||
214
xso/src/lib.rs
214
xso/src/lib.rs
|
|
@ -545,42 +545,13 @@ pub fn try_from_element<T: FromXml>(
|
||||||
unreachable!("minidom::Element did not produce enough events to complete element")
|
unreachable!("minidom::Element did not produce enough events to complete element")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Parse a value from a byte slice containing XML data
|
fn from_bytes_inner<T: FromXml>(
|
||||||
///
|
mut parser: rxml::Parser,
|
||||||
/// This function parses the XML found in `buf`, assuming it contains a
|
mut buf: &[u8],
|
||||||
/// complete XML document (with optional XML declaration) and builds a `T`
|
) -> Result<T, self::error::Error> {
|
||||||
/// 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};
|
use rxml::{error::EndOrError, Parse};
|
||||||
|
|
||||||
let mut languages = rxml::xml_lang::XmlLangStack::new();
|
let mut languages = rxml::xml_lang::XmlLangStack::new();
|
||||||
let mut parser = rxml::Parser::new();
|
|
||||||
let (name, attrs) = loop {
|
let (name, attrs) = loop {
|
||||||
match parser.parse(&mut buf, true) {
|
match parser.parse(&mut buf, true) {
|
||||||
Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
|
Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
|
||||||
|
|
@ -631,6 +602,82 @@ pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # 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>(buf: &[u8]) -> Result<T, self::error::Error> {
|
||||||
|
let parser = rxml::Parser::new();
|
||||||
|
from_bytes_inner(parser, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Parse a value from a byte slice with specific parser options.
|
||||||
|
///
|
||||||
|
/// This is the same as [`from_bytes`], except that the rxml parser
|
||||||
|
/// [`Options`][`rxml::Options`] can be specified explicitly.
|
||||||
|
///
|
||||||
|
/// ## 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_with_options};
|
||||||
|
/// #[derive(FromXml, PartialEq, Debug)]
|
||||||
|
/// #[xml(namespace = "urn:example", name = "foo")]
|
||||||
|
/// struct Foo {
|
||||||
|
/// #[xml(attribute)]
|
||||||
|
/// a: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut opts = rxml::Options::default();
|
||||||
|
/// opts.comments = rxml::parser::CommentMode::Discard;
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// Foo { a: "some-value".to_owned() },
|
||||||
|
/// from_bytes_with_options(
|
||||||
|
/// b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>",
|
||||||
|
/// opts,
|
||||||
|
/// ).unwrap(),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn from_bytes_with_options<T: FromXml>(
|
||||||
|
buf: &[u8],
|
||||||
|
opts: rxml::Options,
|
||||||
|
) -> Result<T, self::error::Error> {
|
||||||
|
use rxml::WithOptions;
|
||||||
|
let parser = rxml::Parser::with_options(opts);
|
||||||
|
from_bytes_inner(parser, buf)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
fn read_start_event_io(
|
fn read_start_event_io(
|
||||||
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
|
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
|
||||||
|
|
@ -655,6 +702,38 @@ fn read_start_event_io(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_reader_inner<T: FromXml, R: io::BufRead>(
|
||||||
|
mut reader: rxml::XmlLangTracker<rxml::Reader<R>>,
|
||||||
|
) -> io::Result<T> {
|
||||||
|
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)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
|
/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
|
||||||
///
|
///
|
||||||
/// This function parses the XML found in `r`, assuming it contains a
|
/// This function parses the XML found in `r`, assuming it contains a
|
||||||
|
|
@ -691,34 +770,47 @@ fn read_start_event_io(
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
|
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
|
||||||
let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
|
from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::new(r)))
|
||||||
let (name, attrs) = read_start_event_io(&mut reader)?;
|
}
|
||||||
let mut builder = match T::from_events(
|
|
||||||
name,
|
/// # Parse a value using a specific parser config
|
||||||
attrs,
|
///
|
||||||
&Context::empty().with_language(reader.language()),
|
/// This is the same as [`from_reader`], except that the rxml parser
|
||||||
) {
|
/// [`Options`][`rxml::Options`] can be specified explicitly.
|
||||||
Ok(v) => v,
|
///
|
||||||
Err(self::error::FromEventsError::Mismatch { .. }) => {
|
/// ## Example
|
||||||
return Err(self::error::Error::TypeMismatch)
|
///
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
#[cfg_attr(
|
||||||
}
|
not(feature = "macros"),
|
||||||
Err(self::error::FromEventsError::Invalid(e)) => {
|
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
|
||||||
return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
)]
|
||||||
}
|
#[cfg_attr(feature = "macros", doc = "\n```\n")]
|
||||||
};
|
/// # use xso::{AsXml, FromXml, from_reader_with_options};
|
||||||
while let Some(ev) = reader.next() {
|
/// # use std::io::BufReader;
|
||||||
if let Some(v) = builder
|
/// #[derive(FromXml, PartialEq, Debug)]
|
||||||
.feed(ev?, &Context::empty().with_language(reader.language()))
|
/// #[xml(namespace = "urn:example", name = "foo")]
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
|
/// struct Foo {
|
||||||
{
|
/// #[xml(attribute)]
|
||||||
return Ok(v);
|
/// a: String,
|
||||||
}
|
/// }
|
||||||
}
|
///
|
||||||
Err(io::Error::new(
|
/// let mut opts = rxml::Options::default();
|
||||||
io::ErrorKind::UnexpectedEof,
|
/// opts.comments = rxml::parser::CommentMode::Discard;
|
||||||
self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
|
/// // let file = .. // containing XML comments
|
||||||
))
|
/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>"[..];
|
||||||
|
/// assert_eq!(
|
||||||
|
/// Foo { a: "some-value".to_owned() },
|
||||||
|
/// from_reader_with_options(BufReader::new(file), opts).unwrap(),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn from_reader_with_options<T: FromXml, R: io::BufRead>(
|
||||||
|
r: R,
|
||||||
|
options: rxml::Options,
|
||||||
|
) -> io::Result<T> {
|
||||||
|
from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::with_options(
|
||||||
|
r, options,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Serialize a value to UTF-8-encoded XML
|
/// # Serialize a value to UTF-8-encoded XML
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue