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