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