lib.rs

  1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
  2#![cfg_attr(docsrs, feature(doc_cfg))]
  3#![forbid(unsafe_code)]
  4#![warn(missing_docs)]
  5/*!
  6# XML Streamed Objects -- serde-like parsing for XML
  7
  8This crate provides the traits for parsing XML data into Rust structs, and
  9vice versa.
 10
 11While it is in 0.0.x versions, many features still need to be developed, but
 12rest assured that there is a solid plan to get it fully usable for even
 13advanced XML scenarios.
 14
 15XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
 16use of this library in parsing XML streams like specified in RFC 6120.
 17*/
 18
 19// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
 20//
 21// This Source Code Form is subject to the terms of the Mozilla Public
 22// License, v. 2.0. If a copy of the MPL was not distributed with this
 23// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 24
 25#![no_std]
 26
 27extern crate alloc;
 28#[cfg(feature = "std")]
 29extern crate std;
 30
 31pub mod asxml;
 32pub mod error;
 33pub mod fromxml;
 34#[cfg(feature = "minidom")]
 35pub mod minidom_compat;
 36mod rxml_util;
 37pub mod text;
 38
 39#[doc(hidden)]
 40pub mod exports {
 41    #[cfg(feature = "minidom")]
 42    pub use minidom;
 43    pub use rxml;
 44}
 45
 46use alloc::{
 47    borrow::{Cow, ToOwned},
 48    boxed::Box,
 49    string::String,
 50    vec::Vec,
 51};
 52
 53#[doc(inline)]
 54pub use text::TextCodec;
 55
 56#[doc(inline)]
 57pub use rxml_util::Item;
 58
 59#[doc = include_str!("from_xml_doc.md")]
 60#[doc(inline)]
 61#[cfg(feature = "macros")]
 62pub use xso_proc::FromXml;
 63
 64/// # Make a struct or enum serialisable to XML
 65///
 66/// This derives the [`AsXml`] trait on a struct or enum. It is the
 67/// counterpart to [`macro@FromXml`].
 68///
 69/// The attributes necessary and available for the derivation to work are
 70/// documented on [`macro@FromXml`].
 71#[doc(inline)]
 72#[cfg(feature = "macros")]
 73pub use xso_proc::AsXml;
 74
 75/// Trait allowing to iterate a struct's contents as serialisable
 76/// [`Item`]s.
 77///
 78/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
 79/// type is considered a non-breaking change for any given implementation of
 80/// this trait. Always refer to a type's iterator type using fully-qualified
 81/// notation, for example: `<T as xso::AsXml>::ItemIter`.
 82pub trait AsXml {
 83    /// The iterator type.
 84    ///
 85    /// **Important:** Changing this type is considered a non-breaking change
 86    /// for any given implementation of this trait. Always refer to a type's
 87    /// iterator type using fully-qualified notation, for example:
 88    /// `<T as xso::AsXml>::ItemIter`.
 89    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
 90    where
 91        Self: 'x;
 92
 93    /// Return an iterator which emits the contents of the struct or enum as
 94    /// serialisable [`Item`] items.
 95    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
 96}
 97
 98/// Trait for a temporary object allowing to construct a struct from
 99/// [`rxml::Event`] items.
100///
101/// Objects of this type are generally constructed through
102/// [`FromXml::from_events`] and are used to build Rust structs or enums from
103/// XML data. The XML data must be fed as `rxml::Event` to the
104/// [`feed`][`Self::feed`] method.
105pub trait FromEventsBuilder {
106    /// The type which will be constructed by this builder.
107    type Output;
108
109    /// Feed another [`rxml::Event`] into the element construction
110    /// process.
111    ///
112    /// Once the construction process completes, `Ok(Some(_))` is returned.
113    /// When valid data has been fed but more events are needed to fully
114    /// construct the resulting struct, `Ok(None)` is returned.
115    ///
116    /// If the construction fails, `Err(_)` is returned. Errors are generally
117    /// fatal and the builder should be assumed to be broken at that point.
118    /// Feeding more events after an error may result in panics, errors or
119    /// inconsistent result data, though it may never result in unsound or
120    /// unsafe behaviour.
121    fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
122}
123
124/// Trait allowing to construct a struct from a stream of
125/// [`rxml::Event`] items.
126///
127/// To use this, first call [`FromXml::from_events`] with the qualified
128/// name and the attributes of the corresponding
129/// [`rxml::Event::StartElement`] event. If the call succeeds, the
130/// returned builder object must be fed with the events representing the
131/// contents of the element, and then with the `EndElement` event.
132///
133/// The `StartElement` passed to `from_events` must not be passed to `feed`.
134///
135/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
136/// is considered a non-breaking change for any given implementation of this
137/// trait. Always refer to a type's builder type using fully-qualified
138/// notation, for example: `<T as xso::FromXml>::Builder`.
139pub trait FromXml {
140    /// A builder type used to construct the element.
141    ///
142    /// **Important:** Changing this type is considered a non-breaking change
143    /// for any given implementation of this trait. Always refer to a type's
144    /// builder type using fully-qualified notation, for example:
145    /// `<T as xso::FromXml>::Builder`.
146    type Builder: FromEventsBuilder<Output = Self>;
147
148    /// Attempt to initiate the streamed construction of this struct from XML.
149    ///
150    /// If the passed qualified `name` and `attrs` match the element's type,
151    /// the [`Self::Builder`] is returned and should be fed with XML events
152    /// by the caller.
153    ///
154    /// Otherwise, an appropriate error is returned.
155    fn from_events(
156        name: rxml::QName,
157        attrs: rxml::AttrMap,
158    ) -> Result<Self::Builder, self::error::FromEventsError>;
159}
160
161/// Trait allowing to convert XML text to a value.
162///
163/// This trait is similar to [`core::str::FromStr`], however, due to
164/// restrictions imposed by the orphan rule, a separate trait is needed.
165/// Implementations for many standard library types are available. In
166/// addition, the following feature flags can enable more implementations:
167///
168/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
169/// - `uuid`: `uuid::Uuid`
170///
171/// Because of this unfortunate situation, we are **extremely liberal** with
172/// accepting optional dependencies for this purpose. You are very welcome to
173/// make merge requests against this crate adding support for parsing
174/// third-party crates.
175pub trait FromXmlText: Sized {
176    /// Convert the given XML text to a value.
177    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
178}
179
180impl FromXmlText for String {
181    /// Return the string unchanged.
182    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
183        Ok(data)
184    }
185}
186
187impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
188    /// Return a [`Cow::Owned`] containing the parsed value.
189    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
190        Ok(Cow::Owned(T::from_xml_text(data)?))
191    }
192}
193
194impl<T: FromXmlText> FromXmlText for Option<T> {
195    /// Return a [`Some`] containing the parsed value.
196    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
197        Ok(Some(T::from_xml_text(data)?))
198    }
199}
200
201impl<T: FromXmlText> FromXmlText for Box<T> {
202    /// Return a [`Box`] containing the parsed value.
203    fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
204        Ok(Box::new(T::from_xml_text(data)?))
205    }
206}
207
208/// Trait to convert a value to an XML text string.
209///
210/// Implementing this trait for a type allows it to be used both for XML
211/// character data within elements and for XML attributes. For XML attributes,
212/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
213/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
214/// [`AsXmlText`] automatically provides an implementation of
215/// [`AsOptionalXmlText`].
216///
217/// If your type should only be used in XML attributes and has no correct
218/// serialisation in XML text, you should *only* implement
219/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
220///
221/// This trait is implemented for many standard library types implementing
222/// [`core::fmt::Display`]. In addition, the following feature flags can enable
223/// more implementations:
224///
225/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
226/// - `uuid`: `uuid::Uuid`
227///
228/// Because of the unfortunate situation as described in [`FromXmlText`], we
229/// are **extremely liberal** with accepting optional dependencies for this
230/// purpose. You are very welcome to make merge requests against this crate
231/// adding support for parsing third-party crates.
232pub trait AsXmlText {
233    /// Convert the value to an XML string in a context where an absent value
234    /// cannot be represented.
235    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
236
237    /// Convert the value to an XML string in a context where an absent value
238    /// can be represented.
239    ///
240    /// The provided implementation will always return the result of
241    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
242    /// this method, implementors can customize the behaviour for certain
243    /// values.
244    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
245        Ok(Some(self.as_xml_text()?))
246    }
247}
248
249impl AsXmlText for String {
250    /// Return the borrowed string contents.
251    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
252        Ok(Cow::Borrowed(self.as_str()))
253    }
254}
255
256impl AsXmlText for str {
257    /// Return the borrowed string contents.
258    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
259        Ok(Cow::Borrowed(&*self))
260    }
261}
262
263impl<T: AsXmlText> AsXmlText for Box<T> {
264    /// Return the borrowed [`Box`] contents.
265    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
266        T::as_xml_text(self)
267    }
268}
269
270impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
271    /// Return the borrowed [`Cow`] contents.
272    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
273        B::as_xml_text(self.as_ref())
274    }
275}
276
277impl<T: AsXmlText> AsXmlText for &T {
278    /// Delegate to the `AsXmlText` implementation on `T`.
279    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
280        T::as_xml_text(*self)
281    }
282}
283
284/// Specialized variant of [`AsXmlText`].
285///
286/// Normally, it should not be necessary to implement this trait as it is
287/// automatically implemented for all types implementing [`AsXmlText`].
288/// However, if your type can only be serialised as an XML attribute (for
289/// example because an absent value has a particular meaning), it is correct
290/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
291///
292/// If your type can be serialised as both (text and attribute) but needs
293/// special handling in attributes, implement [`AsXmlText`] but provide a
294/// custom implementation of [`AsXmlText::as_optional_xml_text`].
295pub trait AsOptionalXmlText {
296    /// Convert the value to an XML string in a context where an absent value
297    /// can be represented.
298    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
299}
300
301impl<T: AsXmlText> AsOptionalXmlText for T {
302    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
303        <Self as AsXmlText>::as_optional_xml_text(self)
304    }
305}
306
307impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
308    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
309        self.as_ref()
310            .map(T::as_optional_xml_text)
311            .transpose()
312            .map(Option::flatten)
313    }
314}
315
316/// Control how unknown attributes are handled.
317///
318/// The variants of this enum are referenced in the
319/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
320/// enum variants. The specified variant controls how attributes, which are
321/// not handled by any member of the compound, are handled during parsing.
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
323pub enum UnknownAttributePolicy {
324    /// All unknown attributes are discarded.
325    Discard,
326
327    /// The first unknown attribute which is encountered generates a fatal
328    /// parsing error.
329    ///
330    /// This is the default policy.
331    #[default]
332    Fail,
333}
334
335impl UnknownAttributePolicy {
336    #[doc(hidden)]
337    /// Implementation of the policy.
338    ///
339    /// This is an internal API and not subject to semver versioning.
340    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
341        match self {
342            Self::Fail => Err(self::error::Error::Other(msg)),
343            Self::Discard => Ok(()),
344        }
345    }
346}
347
348/// Control how unknown children are handled.
349///
350/// The variants of this enum are referenced in the
351/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
352/// enum variants. The specified variant controls how children, which are not
353/// handled by any member of the compound, are handled during parsing.
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
355pub enum UnknownChildPolicy {
356    /// All unknown children are discarded.
357    Discard,
358
359    /// The first unknown child which is encountered generates a fatal
360    /// parsing error.
361    ///
362    /// This is the default policy.
363    #[default]
364    Fail,
365}
366
367impl UnknownChildPolicy {
368    #[doc(hidden)]
369    /// Implementation of the policy.
370    ///
371    /// This is an internal API and not subject to semver versioning.
372    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
373        match self {
374            Self::Fail => Err(self::error::Error::Other(msg)),
375            Self::Discard => Ok(()),
376        }
377    }
378}
379
380/// Attempt to transform a type implementing [`AsXml`] into another
381/// type which implements [`FromXml`].
382pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {
383    let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
384    let (qname, attrs) = match iter.next() {
385        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
386        Some(Err(e)) => return Err(e),
387        _ => panic!("into_event_iter did not start with StartElement event!"),
388    };
389    let mut sink = match T::from_events(qname, attrs) {
390        Ok(v) => v,
391        Err(self::error::FromEventsError::Mismatch { .. }) => {
392            return Err(self::error::Error::TypeMismatch)
393        }
394        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
395    };
396    for event in iter {
397        let event = event?;
398        if let Some(v) = sink.feed(event)? {
399            return Ok(v);
400        }
401    }
402    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
403}
404
405/// Attempt to convert a [`minidom::Element`] into a type implementing
406/// [`FromXml`], fallably.
407///
408/// Unlike [`transform`] (which can also be used with an element), this
409/// function will return the element unharmed if its element header does not
410/// match the expectations of `T`.
411#[cfg(feature = "minidom")]
412pub fn try_from_element<T: FromXml>(
413    from: minidom::Element,
414) -> Result<T, self::error::FromElementError> {
415    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
416    let mut sink = match T::from_events(qname, attrs) {
417        Ok(v) => v,
418        Err(self::error::FromEventsError::Mismatch { .. }) => {
419            return Err(self::error::FromElementError::Mismatch(from))
420        }
421        Err(self::error::FromEventsError::Invalid(e)) => {
422            return Err(self::error::FromElementError::Invalid(e))
423        }
424    };
425
426    let mut iter = from.as_xml_iter()?;
427    // consume the element header
428    for item in &mut iter {
429        let item = item?;
430        match item {
431            // discard the element header
432            Item::XmlDeclaration(..) => (),
433            Item::ElementHeadStart(..) => (),
434            Item::Attribute(..) => (),
435            Item::ElementHeadEnd => {
436                // now that the element header is over, we break out
437                break;
438            }
439            Item::Text(..) => panic!("text before end of element header"),
440            Item::ElementFoot => panic!("element foot before end of element header"),
441        }
442    }
443    let iter = self::rxml_util::ItemToEvent::new(iter);
444    for event in iter {
445        let event = event?;
446        if let Some(v) = sink.feed(event)? {
447            return Ok(v);
448        }
449    }
450    // unreachable! instead of error here, because minidom::Element always
451    // produces the complete event sequence of a single element, and FromXml
452    // implementations must be constructible from that.
453    unreachable!("minidom::Element did not produce enough events to complete element")
454}
455
456#[cfg(feature = "std")]
457fn map_nonio_error<T>(r: Result<T, std::io::Error>) -> Result<T, self::error::Error> {
458    match r {
459        Ok(v) => Ok(v),
460        Err(e) => match e.downcast::<rxml::Error>() {
461            Ok(e) => Err(e.into()),
462            Err(_) => unreachable!("I/O error cannot be caused by &[]"),
463        },
464    }
465}
466
467#[cfg(feature = "std")]
468fn read_start_event<I: std::io::BufRead>(
469    r: &mut rxml::Reader<I>,
470) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
471    for ev in r {
472        match map_nonio_error(ev)? {
473            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
474            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
475            _ => {
476                return Err(self::error::Error::Other(
477                    "Unexpected event at start of document",
478                ))
479            }
480        }
481    }
482    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
483        rxml::error::ErrorContext::DocumentBegin,
484    ))))
485}
486
487/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
488/// containing XML data.
489#[cfg(feature = "std")]
490pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
491    let mut reader = rxml::Reader::new(&mut buf);
492    let (name, attrs) = read_start_event(&mut reader)?;
493    let mut builder = match T::from_events(name, attrs) {
494        Ok(v) => v,
495        Err(self::error::FromEventsError::Mismatch { .. }) => {
496            return Err(self::error::Error::TypeMismatch)
497        }
498        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
499    };
500    for ev in reader {
501        if let Some(v) = builder.feed(map_nonio_error(ev)?)? {
502            return Ok(v);
503        }
504    }
505    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
506}
507
508#[cfg(feature = "std")]
509fn read_start_event_io<I: std::io::BufRead>(
510    r: &mut rxml::Reader<I>,
511) -> std::io::Result<(rxml::QName, rxml::AttrMap)> {
512    for ev in r {
513        match ev? {
514            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
515            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
516            _ => {
517                return Err(std::io::Error::new(
518                    std::io::ErrorKind::InvalidData,
519                    self::error::Error::Other("Unexpected event at start of document"),
520                ))
521            }
522        }
523    }
524    Err(std::io::Error::new(
525        std::io::ErrorKind::InvalidData,
526        self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
527            rxml::error::ErrorContext::DocumentBegin,
528        ))),
529    ))
530}
531
532/// Attempt to parse a type implementing [`FromXml`] from a reader.
533#[cfg(feature = "std")]
534pub fn from_reader<T: FromXml, R: std::io::BufRead>(r: R) -> std::io::Result<T> {
535    let mut reader = rxml::Reader::new(r);
536    let (name, attrs) = read_start_event_io(&mut reader)?;
537    let mut builder = match T::from_events(name, attrs) {
538        Ok(v) => v,
539        Err(self::error::FromEventsError::Mismatch { .. }) => {
540            return Err(self::error::Error::TypeMismatch)
541                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
542        }
543        Err(self::error::FromEventsError::Invalid(e)) => {
544            return Err(e).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
545        }
546    };
547    for ev in reader {
548        if let Some(v) = builder
549            .feed(ev?)
550            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
551        {
552            return Ok(v);
553        }
554    }
555    Err(std::io::Error::new(
556        std::io::ErrorKind::UnexpectedEof,
557        self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
558    ))
559}
560
561/// Attempt to serialise a type implementing [`AsXml`] to a vector of bytes.
562pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
563    let iter = xso.as_xml_iter()?;
564    let mut writer = rxml::writer::Encoder::new();
565    let mut buf = Vec::new();
566    for item in iter {
567        let item = item?;
568        writer.encode(item.as_rxml_item(), &mut buf)?;
569    }
570    Ok(buf)
571}
572
573/// Return true if the string contains exclusively XML whitespace.
574///
575/// XML whitespace is defined as U+0020 (space), U+0009 (tab), U+000a
576/// (newline) and U+000d (carriage return).
577pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
578    s.as_ref()
579        .iter()
580        .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
581}