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;
 23pub mod minidom_compat;
 24
 25#[doc(hidden)]
 26pub mod exports {
 27    pub use rxml;
 28}
 29
 30/// Trait allowing to consume a struct and iterate its contents as
 31/// serialisable [`rxml::Event`] items.
 32///
 33/// **Important:** Changing the [`EventIter`][`Self::EventIter`] associated
 34/// type is considered a non-breaking change for any given implementation of
 35/// this trait. Always refer to a type's iterator type using fully-qualified
 36/// notation, for example: `<T as xso::IntoXml>::EventIter`.
 37pub trait IntoXml {
 38    /// The iterator type.
 39    ///
 40    /// **Important:** Changing this type is considered a non-breaking change
 41    /// for any given implementation of this trait. Always refer to a type's
 42    /// iterator type using fully-qualified notation, for example:
 43    /// `<T as xso::IntoXml>::EventIter`.
 44    type EventIter: Iterator<Item = Result<rxml::Event, self::error::Error>>;
 45
 46    /// Return an iterator which emits the contents of the struct or enum as
 47    /// serialisable [`rxml::Event`] items.
 48    fn into_event_iter(self) -> Result<Self::EventIter, self::error::Error>;
 49}
 50
 51/// Trait for a temporary object allowing to construct a struct from
 52/// [`rxml::Event`] items.
 53///
 54/// Objects of this type are generally constructed through
 55/// [`FromXml::from_events`] and are used to build Rust structs or enums from
 56/// XML data. The XML data must be fed as `rxml::Event` to the
 57/// [`feed`][`Self::feed`] method.
 58pub trait FromEventsBuilder {
 59    /// The type which will be constructed by this builder.
 60    type Output;
 61
 62    /// Feed another [`rxml::Event`] into the element construction
 63    /// process.
 64    ///
 65    /// Once the construction process completes, `Ok(Some(_))` is returned.
 66    /// When valid data has been fed but more events are needed to fully
 67    /// construct the resulting struct, `Ok(None)` is returned.
 68    ///
 69    /// If the construction fails, `Err(_)` is returned. Errors are generally
 70    /// fatal and the builder should be assumed to be broken at that point.
 71    /// Feeding more events after an error may result in panics, errors or
 72    /// inconsistent result data, though it may never result in unsound or
 73    /// unsafe behaviour.
 74    fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
 75}
 76
 77/// Trait allowing to construct a struct from a stream of
 78/// [`rxml::Event`] items.
 79///
 80/// To use this, first call [`FromXml::from_events`] with the qualified
 81/// name and the attributes of the corresponding
 82/// [`rxml::Event::StartElement`] event. If the call succeeds, the
 83/// returned builder object must be fed with the events representing the
 84/// contents of the element, and then with the `EndElement` event.
 85///
 86/// The `StartElement` passed to `from_events` must not be passed to `feed`.
 87///
 88/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
 89/// is considered a non-breaking change for any given implementation of this
 90/// trait. Always refer to a type's builder type using fully-qualified
 91/// notation, for example: `<T as xso::FromXml>::Builder`.
 92pub trait FromXml {
 93    /// A builder type used to construct the element.
 94    ///
 95    /// **Important:** Changing this type is considered a non-breaking change
 96    /// for any given implementation of this trait. Always refer to a type's
 97    /// builder type using fully-qualified notation, for example:
 98    /// `<T as xso::FromXml>::Builder`.
 99    type Builder: FromEventsBuilder<Output = Self>;
100
101    /// Attempt to initiate the streamed construction of this struct from XML.
102    ///
103    /// If the passed qualified `name` and `attrs` match the element's type,
104    /// the [`Self::Builder`] is returned and should be fed with XML events
105    /// by the caller.
106    ///
107    /// Otherwise, an appropriate error is returned.
108    fn from_events(
109        name: rxml::QName,
110        attrs: rxml::AttrMap,
111    ) -> Result<Self::Builder, self::error::FromEventsError>;
112}
113
114/// Attempt to transform a type implementing [`IntoXml`] into another
115/// type which implements [`FromXml`].
116pub fn transform<T: FromXml, F: IntoXml>(from: F) -> Result<T, self::error::Error> {
117    let mut iter = from.into_event_iter()?;
118    let (qname, attrs) = match iter.next() {
119        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
120        Some(Err(e)) => return Err(e),
121        _ => panic!("into_event_iter did not start with StartElement event!"),
122    };
123    let mut sink = match T::from_events(qname, attrs) {
124        Ok(v) => v,
125        Err(self::error::FromEventsError::Mismatch { .. }) => {
126            return Err(self::error::Error::TypeMismatch)
127        }
128        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
129    };
130    for event in iter {
131        let event = event?;
132        match sink.feed(event)? {
133            Some(v) => return Ok(v),
134            None => (),
135        }
136    }
137    Err(self::error::Error::XmlError(
138        rxml::error::XmlError::InvalidEof("during transform"),
139    ))
140}
141
142/// Attempt to convert a [`minidom::Element`] into a type implementing
143/// [`FromXml`], fallably.
144///
145/// Unlike [`transform`] (which can also be used with an element), this
146/// function will return the element unharmed if its element header does not
147/// match the expectations of `T`.
148pub fn try_from_element<T: FromXml>(
149    from: minidom::Element,
150) -> Result<T, self::error::FromElementError> {
151    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
152    let mut sink = match T::from_events(qname, attrs) {
153        Ok(v) => v,
154        Err(self::error::FromEventsError::Mismatch { .. }) => {
155            return Err(self::error::FromElementError::Mismatch(from))
156        }
157        Err(self::error::FromEventsError::Invalid(e)) => {
158            return Err(self::error::FromElementError::Invalid(e))
159        }
160    };
161
162    let mut iter = from.into_event_iter()?;
163    iter.next().expect("first event from minidom::Element")?;
164    for event in iter {
165        let event = event?;
166        match sink.feed(event)? {
167            Some(v) => return Ok(v),
168            None => (),
169        }
170    }
171    // unreachable! instead of error here, because minidom::Element always
172    // produces the complete event sequence of a single element, and FromXml
173    // implementations must be constructible from that.
174    unreachable!("minidom::Element did not produce enough events to complete element")
175}