1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4/*!
5# XML Streamed Objects -- serde-like parsing for XML
6
7This crate provides the traits for parsing XML data into Rust structs, and
8vice versa.
9
10While it is in 0.0.x versions, many features still need to be developed, but
11rest assured that there is a solid plan to get it fully usable for even
12advanced XML scenarios.
13
14XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
15use of this library in parsing XML streams like specified in RFC 6120.
16*/
17
18// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
19//
20// This Source Code Form is subject to the terms of the Mozilla Public
21// License, v. 2.0. If a copy of the MPL was not distributed with this
22// file, You can obtain one at http://mozilla.org/MPL/2.0/.
23pub mod error;
24#[cfg(feature = "minidom")]
25#[cfg_attr(docsrs, doc(cfg(feature = "minidom")))]
26pub mod minidom_compat;
27mod rxml_util;
28pub mod text;
29
30#[doc(hidden)]
31pub mod exports {
32 #[cfg(feature = "minidom")]
33 pub use minidom;
34 pub use rxml;
35}
36
37use std::borrow::Cow;
38
39#[doc(inline)]
40pub use text::TextCodec;
41
42#[doc(inline)]
43pub use rxml_util::Item;
44
45#[doc = include_str!("from_xml_doc.md")]
46#[doc(inline)]
47#[cfg(feature = "macros")]
48pub use xso_proc::FromXml;
49
50/// # Make a struct or enum serialisable to XML
51///
52/// This derives the [`AsXml`] trait on a struct or enum. It is the
53/// counterpart to [`macro@FromXml`].
54///
55/// The attributes necessary and available for the derivation to work are
56/// documented on [`macro@FromXml`].
57#[doc(inline)]
58#[cfg(feature = "macros")]
59pub use xso_proc::AsXml;
60
61/// Trait allowing to iterate a struct's contents as serialisable
62/// [`Item`]s.
63///
64/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
65/// type is considered a non-breaking change for any given implementation of
66/// this trait. Always refer to a type's iterator type using fully-qualified
67/// notation, for example: `<T as xso::AsXml>::ItemIter`.
68pub trait AsXml {
69 /// The iterator type.
70 ///
71 /// **Important:** Changing this type is considered a non-breaking change
72 /// for any given implementation of this trait. Always refer to a type's
73 /// iterator type using fully-qualified notation, for example:
74 /// `<T as xso::AsXml>::ItemIter`.
75 type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
76 where
77 Self: 'x;
78
79 /// Return an iterator which emits the contents of the struct or enum as
80 /// serialisable [`Item`] items.
81 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
82}
83
84/// Trait for a temporary object allowing to construct a struct from
85/// [`rxml::Event`] items.
86///
87/// Objects of this type are generally constructed through
88/// [`FromXml::from_events`] and are used to build Rust structs or enums from
89/// XML data. The XML data must be fed as `rxml::Event` to the
90/// [`feed`][`Self::feed`] method.
91pub trait FromEventsBuilder {
92 /// The type which will be constructed by this builder.
93 type Output;
94
95 /// Feed another [`rxml::Event`] into the element construction
96 /// process.
97 ///
98 /// Once the construction process completes, `Ok(Some(_))` is returned.
99 /// When valid data has been fed but more events are needed to fully
100 /// construct the resulting struct, `Ok(None)` is returned.
101 ///
102 /// If the construction fails, `Err(_)` is returned. Errors are generally
103 /// fatal and the builder should be assumed to be broken at that point.
104 /// Feeding more events after an error may result in panics, errors or
105 /// inconsistent result data, though it may never result in unsound or
106 /// unsafe behaviour.
107 fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
108}
109
110/// Trait allowing to construct a struct from a stream of
111/// [`rxml::Event`] items.
112///
113/// To use this, first call [`FromXml::from_events`] with the qualified
114/// name and the attributes of the corresponding
115/// [`rxml::Event::StartElement`] event. If the call succeeds, the
116/// returned builder object must be fed with the events representing the
117/// contents of the element, and then with the `EndElement` event.
118///
119/// The `StartElement` passed to `from_events` must not be passed to `feed`.
120///
121/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
122/// is considered a non-breaking change for any given implementation of this
123/// trait. Always refer to a type's builder type using fully-qualified
124/// notation, for example: `<T as xso::FromXml>::Builder`.
125pub trait FromXml {
126 /// A builder type used to construct the element.
127 ///
128 /// **Important:** Changing this type is considered a non-breaking change
129 /// for any given implementation of this trait. Always refer to a type's
130 /// builder type using fully-qualified notation, for example:
131 /// `<T as xso::FromXml>::Builder`.
132 type Builder: FromEventsBuilder<Output = Self>;
133
134 /// Attempt to initiate the streamed construction of this struct from XML.
135 ///
136 /// If the passed qualified `name` and `attrs` match the element's type,
137 /// the [`Self::Builder`] is returned and should be fed with XML events
138 /// by the caller.
139 ///
140 /// Otherwise, an appropriate error is returned.
141 fn from_events(
142 name: rxml::QName,
143 attrs: rxml::AttrMap,
144 ) -> Result<Self::Builder, self::error::FromEventsError>;
145}
146
147/// Trait allowing to convert XML text to a value.
148///
149/// This trait is similar to [`std::str::FromStr`], however, due to
150/// restrictions imposed by the orphan rule, a separate trait is needed.
151/// Implementations for many standard library types are available. In
152/// addition, the following feature flags can enable more implementations:
153///
154/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
155/// - `uuid`: `uuid::Uuid`
156///
157/// Because of this unfortunate situation, we are **extremely liberal** with
158/// accepting optional dependencies for this purpose. You are very welcome to
159/// make merge requests against this crate adding support for parsing
160/// third-party crates.
161pub trait FromXmlText: Sized {
162 /// Convert the given XML text to a value.
163 fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
164}
165
166impl FromXmlText for String {
167 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
168 Ok(data)
169 }
170}
171
172impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
173 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
174 Ok(Cow::Owned(T::from_xml_text(data)?))
175 }
176}
177
178impl<T: FromXmlText> FromXmlText for Option<T> {
179 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
180 Ok(Some(T::from_xml_text(data)?))
181 }
182}
183
184impl<T: FromXmlText> FromXmlText for Box<T> {
185 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
186 Ok(Box::new(T::from_xml_text(data)?))
187 }
188}
189
190/// Trait to convert a value to an XML text string.
191///
192/// This trait is implemented for many standard library types implementing
193/// [`std::fmt::Display`]. In addition, the following feature flags can enable
194/// more implementations:
195///
196/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
197/// - `uuid`: `uuid::Uuid`
198///
199/// Because of the unfortunate situation as described in [`FromXmlText`], we
200/// are **extremely liberal** with accepting optional dependencies for this
201/// purpose. You are very welcome to make merge requests against this crate
202/// adding support for parsing third-party crates.
203pub trait AsXmlText {
204 /// Convert the value to an XML string in a context where an absent value
205 /// cannot be represented.
206 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
207
208 /// Convert the value to an XML string in a context where an absent value
209 /// can be represented.
210 ///
211 /// The provided implementation will always return the result of
212 /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
213 /// this method, implementors can customize the behaviour for certain
214 /// values.
215 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
216 Ok(Some(self.as_xml_text()?))
217 }
218}
219
220impl AsXmlText for String {
221 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
222 Ok(Cow::Borrowed(self.as_str()))
223 }
224}
225
226impl AsXmlText for &str {
227 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
228 Ok(Cow::Borrowed(&**self))
229 }
230}
231
232impl<T: AsXmlText> AsXmlText for Box<T> {
233 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
234 T::as_xml_text(self)
235 }
236}
237
238impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
239 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
240 B::as_xml_text(self.as_ref())
241 }
242}
243
244/// Specialized variant of [`AsXmlText`].
245///
246/// Do **not** implement this unless you cannot implement [`AsXmlText`]:
247/// implementing [`AsXmlText`] is more versatile and an
248/// [`AsOptionalXmlText`] implementation is automatically provided.
249///
250/// If you need to customize the behaviour of the [`AsOptionalXmlText`]
251/// blanket implementation, implement a custom
252/// [`AsXmlText::as_optional_xml_text`] instead.
253pub trait AsOptionalXmlText {
254 /// Convert the value to an XML string in a context where an absent value
255 /// can be represented.
256 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
257}
258
259impl<T: AsXmlText> AsOptionalXmlText for T {
260 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
261 <Self as AsXmlText>::as_optional_xml_text(self)
262 }
263}
264
265impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
266 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
267 self.as_ref()
268 .map(T::as_optional_xml_text)
269 .transpose()
270 .map(Option::flatten)
271 }
272}
273
274/// Attempt to transform a type implementing [`AsXml`] into another
275/// type which implements [`FromXml`].
276pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {
277 let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
278 let (qname, attrs) = match iter.next() {
279 Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
280 Some(Err(e)) => return Err(e),
281 _ => panic!("into_event_iter did not start with StartElement event!"),
282 };
283 let mut sink = match T::from_events(qname, attrs) {
284 Ok(v) => v,
285 Err(self::error::FromEventsError::Mismatch { .. }) => {
286 return Err(self::error::Error::TypeMismatch)
287 }
288 Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
289 };
290 for event in iter {
291 let event = event?;
292 if let Some(v) = sink.feed(event)? {
293 return Ok(v);
294 }
295 }
296 Err(self::error::Error::XmlError(
297 rxml::error::XmlError::InvalidEof("during transform"),
298 ))
299}
300
301/// Attempt to convert a [`minidom::Element`] into a type implementing
302/// [`FromXml`], fallably.
303///
304/// Unlike [`transform`] (which can also be used with an element), this
305/// function will return the element unharmed if its element header does not
306/// match the expectations of `T`.
307#[cfg(feature = "minidom")]
308#[cfg_attr(docsrs, doc(cfg(feature = "minidom")))]
309pub fn try_from_element<T: FromXml>(
310 from: minidom::Element,
311) -> Result<T, self::error::FromElementError> {
312 let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
313 let mut sink = match T::from_events(qname, attrs) {
314 Ok(v) => v,
315 Err(self::error::FromEventsError::Mismatch { .. }) => {
316 return Err(self::error::FromElementError::Mismatch(from))
317 }
318 Err(self::error::FromEventsError::Invalid(e)) => {
319 return Err(self::error::FromElementError::Invalid(e))
320 }
321 };
322
323 let mut iter = from.as_xml_iter()?;
324 // consume the element header
325 for item in &mut iter {
326 let item = item?;
327 match item {
328 // discard the element header
329 Item::XmlDeclaration(..) => (),
330 Item::ElementHeadStart(..) => (),
331 Item::Attribute(..) => (),
332 Item::ElementHeadEnd => {
333 // now that the element header is over, we break out
334 break;
335 }
336 Item::Text(..) => panic!("text before end of element header"),
337 Item::ElementFoot => panic!("element foot before end of element header"),
338 }
339 }
340 let iter = self::rxml_util::ItemToEvent::new(iter);
341 for event in iter {
342 let event = event?;
343 if let Some(v) = sink.feed(event)? {
344 return Ok(v);
345 }
346 }
347 // unreachable! instead of error here, because minidom::Element always
348 // produces the complete event sequence of a single element, and FromXml
349 // implementations must be constructible from that.
350 unreachable!("minidom::Element did not produce enough events to complete element")
351}
352
353fn map_nonio_error<T>(r: Result<T, rxml::Error>) -> Result<T, self::error::Error> {
354 match r {
355 Ok(v) => Ok(v),
356 Err(rxml::Error::IO(_)) => unreachable!(),
357 Err(rxml::Error::Xml(e)) => Err(e.into()),
358 Err(rxml::Error::InvalidUtf8Byte(_)) => Err(self::error::Error::Other("invalid utf-8")),
359 Err(rxml::Error::InvalidChar(_)) => {
360 Err(self::error::Error::Other("non-character encountered"))
361 }
362 Err(rxml::Error::RestrictedXml(_)) => Err(self::error::Error::Other("restricted xml")),
363 }
364}
365
366fn read_start_event<I: std::io::BufRead>(
367 r: &mut rxml::Reader<I>,
368) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
369 for ev in r {
370 match map_nonio_error(ev)? {
371 rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
372 rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
373 _ => {
374 return Err(self::error::Error::Other(
375 "Unexpected event at start of document",
376 ))
377 }
378 }
379 }
380 Err(self::error::Error::XmlError(
381 rxml::error::XmlError::InvalidEof("before start of element"),
382 ))
383}
384
385/// Attempt to parse a type implementing [`FromXml`] from a byte buffer
386/// containing XML data.
387pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
388 let mut reader = rxml::Reader::new(&mut buf);
389 let (name, attrs) = read_start_event(&mut reader)?;
390 let mut builder = match T::from_events(name, attrs) {
391 Ok(v) => v,
392 Err(self::error::FromEventsError::Mismatch { .. }) => {
393 return Err(self::error::Error::TypeMismatch)
394 }
395 Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
396 };
397 for ev in reader {
398 if let Some(v) = builder.feed(map_nonio_error(ev)?)? {
399 return Ok(v);
400 }
401 }
402 Err(self::error::Error::XmlError(
403 rxml::error::XmlError::InvalidEof("while parsing FromXml impl"),
404 ))
405}