From d9f030c07e654e97c07f9cc7377c85487fb2e8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 27 Sep 2025 13:45:41 +0200 Subject: [PATCH] xso: add support for passing rxml::Options --- xso/ChangeLog | 3 + xso/src/lib.rs | 214 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 156 insertions(+), 61 deletions(-) diff --git a/xso/ChangeLog b/xso/ChangeLog index 60cff960..2cd18a79 100644 --- a/xso/ChangeLog +++ b/xso/ChangeLog @@ -2,6 +2,9 @@ Version NEXT: * Changes - Fix some Clippy warnings - 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: * Changes diff --git a/xso/src/lib.rs b/xso/src/lib.rs index edd46ca7..6a16e033 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -545,42 +545,13 @@ pub fn try_from_element( 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"").unwrap(), -/// ); -/// ``` -pub fn from_bytes(mut buf: &[u8]) -> Result { +fn from_bytes_inner( + mut parser: rxml::Parser, + mut buf: &[u8], +) -> Result { 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))) => (), @@ -631,6 +602,82 @@ pub fn from_bytes(mut buf: &[u8]) -> Result { } } +/// # 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"").unwrap(), +/// ); +/// ``` +pub fn from_bytes(buf: &[u8]) -> Result { + 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"", +/// opts, +/// ).unwrap(), +/// ); +/// ``` +pub fn from_bytes_with_options( + buf: &[u8], + opts: rxml::Options, +) -> Result { + use rxml::WithOptions; + let parser = rxml::Parser::with_options(opts); + from_bytes_inner(parser, buf) +} + #[cfg(feature = "std")] fn read_start_event_io( r: &mut impl Iterator>, @@ -655,6 +702,38 @@ fn read_start_event_io( )) } +fn from_reader_inner( + mut reader: rxml::XmlLangTracker>, +) -> io::Result { + 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`] /// /// This function parses the XML found in `r`, assuming it contains a @@ -691,34 +770,47 @@ fn read_start_event_io( /// ``` #[cfg(feature = "std")] pub fn from_reader(r: R) -> io::Result { - 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)), - )) + from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::new(r))) +} + +/// # Parse a value using a specific parser config +/// +/// This is the same as [`from_reader`], 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_reader_with_options}; +/// # use std::io::BufReader; +/// #[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; +/// // let file = .. // containing XML comments +/// # let file = &mut &b""[..]; +/// 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( + r: R, + options: rxml::Options, +) -> io::Result { + from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::with_options( + r, options, + ))) } /// # Serialize a value to UTF-8-encoded XML