lib.rs

  1#![forbid(unsafe_code)]
  2#![warn(missing_docs)]
  3/*!
  4# XML Streamed Objects -- serde-like parsing for XML
  5
  6This crate provides the traits for parsing XML data into Rust structs, and
  7vice versa.
  8
  9While it is in 0.0.x versions, many features still need to be developed, but
 10rest assured that there is a solid plan to get it fully usable for even
 11advanced XML scenarios.
 12
 13XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
 14use of this library in parsing XML streams like specified in RFC 6120.
 15*/
 16
 17// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
 18//
 19// This Source Code Form is subject to the terms of the Mozilla Public
 20// License, v. 2.0. If a copy of the MPL was not distributed with this
 21// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 22pub mod error;
 23#[cfg(feature = "minidom")]
 24pub mod minidom_compat;
 25
 26#[doc(hidden)]
 27pub mod exports {
 28    #[cfg(feature = "minidom")]
 29    pub use minidom;
 30    pub use rxml;
 31}
 32
 33#[doc = include_str!("from_xml_doc.md")]
 34#[doc(inline)]
 35#[cfg(feature = "macros")]
 36pub use xso_proc::FromXml;
 37
 38/// # Make a struct or enum serialisable to XML
 39///
 40/// This derives the [`IntoXml`] trait on a struct or enum. It is the
 41/// counterpart to [`macro@FromXml`].
 42///
 43/// The attributes necessary and available for the derivation to work are
 44/// documented on [`macro@FromXml`].
 45#[doc(inline)]
 46#[cfg(feature = "macros")]
 47pub use xso_proc::IntoXml;
 48
 49/// Trait allowing to consume a struct and iterate its contents as
 50/// serialisable [`rxml::Event`] items.
 51///
 52/// **Important:** Changing the [`EventIter`][`Self::EventIter`] associated
 53/// type is considered a non-breaking change for any given implementation of
 54/// this trait. Always refer to a type's iterator type using fully-qualified
 55/// notation, for example: `<T as xso::IntoXml>::EventIter`.
 56pub trait IntoXml {
 57    /// The iterator type.
 58    ///
 59    /// **Important:** Changing this type is considered a non-breaking change
 60    /// for any given implementation of this trait. Always refer to a type's
 61    /// iterator type using fully-qualified notation, for example:
 62    /// `<T as xso::IntoXml>::EventIter`.
 63    type EventIter: Iterator<Item = Result<rxml::Event, self::error::Error>>;
 64
 65    /// Return an iterator which emits the contents of the struct or enum as
 66    /// serialisable [`rxml::Event`] items.
 67    fn into_event_iter(self) -> Result<Self::EventIter, self::error::Error>;
 68}
 69
 70/// Trait for a temporary object allowing to construct a struct from
 71/// [`rxml::Event`] items.
 72///
 73/// Objects of this type are generally constructed through
 74/// [`FromXml::from_events`] and are used to build Rust structs or enums from
 75/// XML data. The XML data must be fed as `rxml::Event` to the
 76/// [`feed`][`Self::feed`] method.
 77pub trait FromEventsBuilder {
 78    /// The type which will be constructed by this builder.
 79    type Output;
 80
 81    /// Feed another [`rxml::Event`] into the element construction
 82    /// process.
 83    ///
 84    /// Once the construction process completes, `Ok(Some(_))` is returned.
 85    /// When valid data has been fed but more events are needed to fully
 86    /// construct the resulting struct, `Ok(None)` is returned.
 87    ///
 88    /// If the construction fails, `Err(_)` is returned. Errors are generally
 89    /// fatal and the builder should be assumed to be broken at that point.
 90    /// Feeding more events after an error may result in panics, errors or
 91    /// inconsistent result data, though it may never result in unsound or
 92    /// unsafe behaviour.
 93    fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
 94}
 95
 96/// Trait allowing to construct a struct from a stream of
 97/// [`rxml::Event`] items.
 98///
 99/// To use this, first call [`FromXml::from_events`] with the qualified
100/// name and the attributes of the corresponding
101/// [`rxml::Event::StartElement`] event. If the call succeeds, the
102/// returned builder object must be fed with the events representing the
103/// contents of the element, and then with the `EndElement` event.
104///
105/// The `StartElement` passed to `from_events` must not be passed to `feed`.
106///
107/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
108/// is considered a non-breaking change for any given implementation of this
109/// trait. Always refer to a type's builder type using fully-qualified
110/// notation, for example: `<T as xso::FromXml>::Builder`.
111pub trait FromXml {
112    /// A builder type used to construct the element.
113    ///
114    /// **Important:** Changing this type is considered a non-breaking change
115    /// for any given implementation of this trait. Always refer to a type's
116    /// builder type using fully-qualified notation, for example:
117    /// `<T as xso::FromXml>::Builder`.
118    type Builder: FromEventsBuilder<Output = Self>;
119
120    /// Attempt to initiate the streamed construction of this struct from XML.
121    ///
122    /// If the passed qualified `name` and `attrs` match the element's type,
123    /// the [`Self::Builder`] is returned and should be fed with XML events
124    /// by the caller.
125    ///
126    /// Otherwise, an appropriate error is returned.
127    fn from_events(
128        name: rxml::QName,
129        attrs: rxml::AttrMap,
130    ) -> Result<Self::Builder, self::error::FromEventsError>;
131}
132
133/// Attempt to transform a type implementing [`IntoXml`] into another
134/// type which implements [`FromXml`].
135pub fn transform<T: FromXml, F: IntoXml>(from: F) -> Result<T, self::error::Error> {
136    let mut iter = from.into_event_iter()?;
137    let (qname, attrs) = match iter.next() {
138        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
139        Some(Err(e)) => return Err(e),
140        _ => panic!("into_event_iter did not start with StartElement event!"),
141    };
142    let mut sink = match T::from_events(qname, attrs) {
143        Ok(v) => v,
144        Err(self::error::FromEventsError::Mismatch { .. }) => {
145            return Err(self::error::Error::TypeMismatch)
146        }
147        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
148    };
149    for event in iter {
150        let event = event?;
151        match sink.feed(event)? {
152            Some(v) => return Ok(v),
153            None => (),
154        }
155    }
156    Err(self::error::Error::XmlError(
157        rxml::error::XmlError::InvalidEof("during transform"),
158    ))
159}
160
161/// Attempt to convert a [`minidom::Element`] into a type implementing
162/// [`FromXml`], fallably.
163///
164/// Unlike [`transform`] (which can also be used with an element), this
165/// function will return the element unharmed if its element header does not
166/// match the expectations of `T`.
167#[cfg(feature = "minidom")]
168pub fn try_from_element<T: FromXml>(
169    from: minidom::Element,
170) -> Result<T, self::error::FromElementError> {
171    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
172    let mut sink = match T::from_events(qname, attrs) {
173        Ok(v) => v,
174        Err(self::error::FromEventsError::Mismatch { .. }) => {
175            return Err(self::error::FromElementError::Mismatch(from))
176        }
177        Err(self::error::FromEventsError::Invalid(e)) => {
178            return Err(self::error::FromElementError::Invalid(e))
179        }
180    };
181
182    let mut iter = from.into_event_iter()?;
183    iter.next().expect("first event from minidom::Element")?;
184    for event in iter {
185        let event = event?;
186        match sink.feed(event)? {
187            Some(v) => return Ok(v),
188            None => (),
189        }
190    }
191    // unreachable! instead of error here, because minidom::Element always
192    // produces the complete event sequence of a single element, and FromXml
193    // implementations must be constructible from that.
194    unreachable!("minidom::Element did not produce enough events to complete element")
195}
196
197fn map_nonio_error<T>(r: Result<T, rxml::Error>) -> Result<T, self::error::Error> {
198    match r {
199        Ok(v) => Ok(v),
200        Err(rxml::Error::IO(_)) => unreachable!(),
201        Err(rxml::Error::Xml(e)) => Err(e.into()),
202        Err(rxml::Error::InvalidUtf8Byte(_)) => Err(self::error::Error::Other("invalid utf-8")),
203        Err(rxml::Error::InvalidChar(_)) => {
204            Err(self::error::Error::Other("non-character encountered"))
205        }
206        Err(rxml::Error::RestrictedXml(_)) => Err(self::error::Error::Other("restricted xml")),
207    }
208}
209
210fn read_start_event<I: std::io::BufRead>(
211    r: &mut rxml::Reader<I>,
212) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
213    for ev in r {
214        match map_nonio_error(ev)? {
215            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
216            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
217            _ => {
218                return Err(self::error::Error::Other(
219                    "Unexpected event at start of document",
220                ))
221            }
222        }
223    }
224    Err(self::error::Error::XmlError(
225        rxml::error::XmlError::InvalidEof("before start of element"),
226    ))
227}
228
229/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
230/// containing XML data.
231pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
232    let mut reader = rxml::Reader::new(&mut buf);
233    let (name, attrs) = read_start_event(&mut reader)?;
234    let mut builder = match T::from_events(name, attrs) {
235        Ok(v) => v,
236        Err(self::error::FromEventsError::Mismatch { .. }) => {
237            return Err(self::error::Error::TypeMismatch)
238        }
239        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
240    };
241    for ev in reader {
242        match builder.feed(map_nonio_error(ev)?)? {
243            Some(v) => return Ok(v),
244            None => (),
245        }
246    }
247    Err(self::error::Error::XmlError(
248        rxml::error::XmlError::InvalidEof("while parsing FromXml impl"),
249    ))
250}