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
107pub use text::TextCodec;
108
109#[doc(inline)]
110pub use rxml_util::Item;
111
112pub use asxml::PrintRawXml;
113
114#[doc = include_str!("from_xml_doc.md")]
115#[doc(inline)]
116#[cfg(feature = "macros")]
117pub use xso_proc::FromXml;
118
119/// # Make a struct or enum serialisable to XML
120///
121/// This derives the [`AsXml`] trait on a struct or enum. It is the
122/// counterpart to [`macro@FromXml`].
123///
124/// The attributes necessary and available for the derivation to work are
125/// documented on [`macro@FromXml`].
126#[doc(inline)]
127#[cfg(feature = "macros")]
128pub use xso_proc::AsXml;
129
130/// Trait allowing to iterate a struct's contents as serialisable
131/// [`Item`]s.
132///
133/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
134/// type is considered a non-breaking change for any given implementation of
135/// this trait. Always refer to a type's iterator type using fully-qualified
136/// notation, for example: `<T as xso::AsXml>::ItemIter`.
137pub trait AsXml {
138 /// The iterator type.
139 ///
140 /// **Important:** Changing this type is considered a non-breaking change
141 /// for any given implementation of this trait. Always refer to a type's
142 /// iterator type using fully-qualified notation, for example:
143 /// `<T as xso::AsXml>::ItemIter`.
144 type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
145 where
146 Self: 'x;
147
148 /// Return an iterator which emits the contents of the struct or enum as
149 /// serialisable [`Item`] items.
150 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
151}
152
153/// # Parsing context for [`FromEventsBuilder`]
154///
155/// For the most part, [`FromEventsBuilder`] implementations can work with
156/// only the information inside the [`rxml::Event`] which is delivered to
157/// them (and any information they may have stored from previous events).
158///
159/// However, there is (currently) one special case: the `xml:lang` attribute.
160/// That attribute is inherited across the entire document tree hierarchy. If
161/// the parsed element is not the top-level element, there may be an implicit
162/// value for `xml:lang`.
163#[derive(Debug)]
164pub struct Context<'x> {
165 language: Option<&'x str>,
166}
167
168impl<'x> Context<'x> {
169 /// A context suitable for the beginning of the document.
170 ///
171 /// `xml:lang` is assumed to be unset.
172 pub fn empty() -> Self {
173 Self { language: None }
174 }
175
176 /// Set the effective `xml:lang` value on the context and return it.
177 pub fn with_language(mut self, language: Option<&'x str>) -> Self {
178 self.language = language;
179 self
180 }
181
182 /// Return the `xml:lang` value in effect at the end of the event which
183 /// is currently being processed.
184 pub fn language(&self) -> Option<&str> {
185 self.language.as_deref()
186 }
187}
188
189/// Trait for a temporary object allowing to construct a struct from
190/// [`rxml::Event`] items.
191///
192/// Objects of this type are generally constructed through
193/// [`FromXml::from_events`] and are used to build Rust structs or enums from
194/// XML data. The XML data must be fed as `rxml::Event` to the
195/// [`feed`][`Self::feed`] method.
196pub trait FromEventsBuilder {
197 /// The type which will be constructed by this builder.
198 type Output;
199
200 /// Feed another [`rxml::Event`] into the element construction
201 /// process.
202 ///
203 /// Once the construction process completes, `Ok(Some(_))` is returned.
204 /// When valid data has been fed but more events are needed to fully
205 /// construct the resulting struct, `Ok(None)` is returned.
206 ///
207 /// If the construction fails, `Err(_)` is returned. Errors are generally
208 /// fatal and the builder should be assumed to be broken at that point.
209 /// Feeding more events after an error may result in panics, errors or
210 /// inconsistent result data, though it may never result in unsound or
211 /// unsafe behaviour.
212 fn feed(
213 &mut self,
214 ev: rxml::Event,
215 ctx: &Context<'_>,
216 ) -> Result<Option<Self::Output>, self::error::Error>;
217}
218
219/// Trait allowing to construct a struct from a stream of
220/// [`rxml::Event`] items.
221///
222/// To use this, first call [`FromXml::from_events`] with the qualified
223/// name and the attributes of the corresponding
224/// [`rxml::Event::StartElement`] event. If the call succeeds, the
225/// returned builder object must be fed with the events representing the
226/// contents of the element, and then with the `EndElement` event.
227///
228/// The `StartElement` passed to `from_events` must not be passed to `feed`.
229///
230/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
231/// is considered a non-breaking change for any given implementation of this
232/// trait. Always refer to a type's builder type using fully-qualified
233/// notation, for example: `<T as xso::FromXml>::Builder`.
234pub trait FromXml {
235 /// A builder type used to construct the element.
236 ///
237 /// **Important:** Changing this type is considered a non-breaking change
238 /// for any given implementation of this trait. Always refer to a type's
239 /// builder type using fully-qualified notation, for example:
240 /// `<T as xso::FromXml>::Builder`.
241 type Builder: FromEventsBuilder<Output = Self>;
242
243 /// Attempt to initiate the streamed construction of this struct from XML.
244 ///
245 /// If the passed qualified `name` and `attrs` match the element's type,
246 /// the [`Self::Builder`] is returned and should be fed with XML events
247 /// by the caller.
248 ///
249 /// Otherwise, an appropriate error is returned.
250 fn from_events(
251 name: rxml::QName,
252 attrs: rxml::AttrMap,
253 ctx: &Context<'_>,
254 ) -> Result<Self::Builder, self::error::FromEventsError>;
255}
256
257/// Trait allowing to convert XML text to a value.
258///
259/// This trait is similar to [`FromStr`][`core::str::FromStr`], however, to
260/// allow specialisation for XML<->Text conversion, a separate trait is
261/// introduced. Unlike `FromStr`, this trait allows taking ownership of the
262/// original text data, potentially saving allocations.
263///
264/// **Important:** See the [`text`][`crate::text`] module's documentation
265/// for notes regarding implementations for types from third-party crates.
266pub trait FromXmlText: Sized {
267 /// Convert the given XML text to a value.
268 fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
269}
270
271impl FromXmlText for String {
272 /// Return the string unchanged.
273 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
274 Ok(data)
275 }
276}
277
278impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
279 /// Return a [`Cow::Owned`] containing the parsed value.
280 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
281 Ok(Cow::Owned(T::from_xml_text(data)?))
282 }
283}
284
285impl<T: FromXmlText> FromXmlText for Option<T> {
286 /// Return a [`Some`] containing the parsed value.
287 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
288 Ok(Some(T::from_xml_text(data)?))
289 }
290}
291
292impl<T: FromXmlText> FromXmlText for Box<T> {
293 /// Return a [`Box`] containing the parsed value.
294 fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
295 Ok(Box::new(T::from_xml_text(data)?))
296 }
297}
298
299/// Trait to convert a value to an XML text string.
300///
301/// Implementing this trait for a type allows it to be used both for XML
302/// character data within elements and for XML attributes. For XML attributes,
303/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
304/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
305/// [`AsXmlText`] automatically provides an implementation of
306/// [`AsOptionalXmlText`].
307///
308/// If your type should only be used in XML attributes and has no correct
309/// serialisation in XML text, you should *only* implement
310/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
311///
312/// **Important:** See the [`text`][`crate::text`] module's documentation
313/// for notes regarding implementations for types from third-party crates.
314pub trait AsXmlText {
315 /// Convert the value to an XML string in a context where an absent value
316 /// cannot be represented.
317 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
318
319 /// Convert the value to an XML string in a context where an absent value
320 /// can be represented.
321 ///
322 /// The provided implementation will always return the result of
323 /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
324 /// this method, implementors can customize the behaviour for certain
325 /// values.
326 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
327 Ok(Some(self.as_xml_text()?))
328 }
329}
330
331impl AsXmlText for String {
332 /// Return the borrowed string contents.
333 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
334 Ok(Cow::Borrowed(self))
335 }
336}
337
338impl AsXmlText for str {
339 /// Return the borrowed string contents.
340 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
341 Ok(Cow::Borrowed(self))
342 }
343}
344
345impl AsXmlText for &str {
346 /// Return the borrowed string contents.
347 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
348 Ok(Cow::Borrowed(self))
349 }
350}
351
352impl<T: AsXmlText> AsXmlText for Box<T> {
353 /// Return the borrowed [`Box`] contents.
354 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
355 T::as_xml_text(self)
356 }
357}
358
359impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
360 /// Return the borrowed [`Cow`] contents.
361 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
362 B::as_xml_text(self)
363 }
364}
365
366impl<T: AsXmlText> AsXmlText for &T {
367 /// Delegate to the `AsXmlText` implementation on `T`.
368 fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error> {
369 T::as_xml_text(*self)
370 }
371}
372
373/// Specialized variant of [`AsXmlText`].
374///
375/// Normally, it should not be necessary to implement this trait as it is
376/// automatically implemented for all types implementing [`AsXmlText`].
377/// However, if your type can only be serialised as an XML attribute (for
378/// example because an absent value has a particular meaning), it is correct
379/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
380///
381/// If your type can be serialised as both (text and attribute) but needs
382/// special handling in attributes, implement [`AsXmlText`] but provide a
383/// custom implementation of [`AsXmlText::as_optional_xml_text`].
384///
385/// **Important:** See the [`text`][`crate::text`] module's documentation
386/// for notes regarding implementations for types from third-party crates.
387pub trait AsOptionalXmlText {
388 /// Convert the value to an XML string in a context where an absent value
389 /// can be represented.
390 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
391}
392
393impl<T: AsXmlText> AsOptionalXmlText for T {
394 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
395 <Self as AsXmlText>::as_optional_xml_text(self)
396 }
397}
398
399impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
400 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
401 self.as_ref()
402 .map(T::as_optional_xml_text)
403 .transpose()
404 .map(Option::flatten)
405 }
406}
407
408/// # Control how unknown attributes are handled
409///
410/// The variants of this enum are referenced in the
411/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
412/// enum variants. The specified variant controls how attributes, which are
413/// not handled by any member of the compound, are handled during parsing.
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
415pub enum UnknownAttributePolicy {
416 /// All unknown attributes are discarded.
417 ///
418 /// This is the default policy if the crate is built with the
419 /// `non-pedantic` feature.
420 #[cfg_attr(feature = "non-pedantic", default)]
421 Discard,
422
423 /// The first unknown attribute which is encountered generates a fatal
424 /// parsing error.
425 ///
426 /// This is the default policy if the crate is built **without** the
427 /// `non-pedantic` feature.
428 #[cfg_attr(not(feature = "non-pedantic"), default)]
429 Fail,
430}
431
432impl UnknownAttributePolicy {
433 #[doc(hidden)]
434 /// Implementation of the policy.
435 ///
436 /// This is an internal API and not subject to semver versioning.
437 pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
438 match self {
439 Self::Fail => Err(self::error::Error::Other(msg)),
440 Self::Discard => Ok(()),
441 }
442 }
443}
444
445/// # Control how unknown child elements are handled
446///
447/// The variants of this enum are referenced in the
448/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
449/// enum variants. The specified variant controls how children, which are not
450/// handled by any member of the compound, are handled during parsing.
451#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
452pub enum UnknownChildPolicy {
453 /// All unknown children are discarded.
454 ///
455 /// This is the default policy if the crate is built with the
456 /// `non-pedantic` feature.
457 #[cfg_attr(feature = "non-pedantic", default)]
458 Discard,
459
460 /// The first unknown child which is encountered generates a fatal
461 /// parsing error.
462 ///
463 /// This is the default policy if the crate is built **without** the
464 /// `non-pedantic` feature.
465 #[cfg_attr(not(feature = "non-pedantic"), default)]
466 Fail,
467}
468
469impl UnknownChildPolicy {
470 #[doc(hidden)]
471 /// Implementation of the policy.
472 ///
473 /// This is an internal API and not subject to semver versioning.
474 pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
475 match self {
476 Self::Fail => Err(self::error::Error::Other(msg)),
477 Self::Discard => Ok(()),
478 }
479 }
480}
481
482/// # Transform a value into another value via XML
483///
484/// This function takes `from`, converts it into XML using its [`AsXml`]
485/// implementation and builds a `T` from it (without buffering the tree in
486/// memory).
487///
488/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
489/// particular, if `T` expects a different element header than the header
490/// provided by `from`,
491/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
492///
493/// ## Example
494///
495#[cfg_attr(
496 not(all(feature = "std", feature = "macros")),
497 doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
498)]
499#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
500/// # use xso::{AsXml, FromXml, transform};
501/// #[derive(AsXml)]
502/// #[xml(namespace = "urn:example", name = "foo")]
503/// struct Source {
504/// #[xml(attribute = "xml:lang")]
505/// lang: &'static str,
506/// }
507///
508/// #[derive(FromXml, PartialEq, Debug)]
509/// #[xml(namespace = "urn:example", name = "foo")]
510/// struct Dest {
511/// #[xml(lang)]
512/// lang: Option<String>,
513/// }
514///
515/// assert_eq!(
516/// Dest { lang: Some("en".to_owned()) },
517/// transform(&Source { lang: "en" }).unwrap(),
518/// );
519/// ```
520pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
521 let mut languages = rxml::xml_lang::XmlLangStack::new();
522 let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
523 let (qname, attrs) = match iter.next() {
524 Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
525 Some(Err(e)) => return Err(e),
526 _ => panic!("into_event_iter did not start with StartElement event!"),
527 };
528 languages.push_from_attrs(&attrs);
529 let mut sink = match T::from_events(
530 qname,
531 attrs,
532 &Context::empty().with_language(languages.current()),
533 ) {
534 Ok(v) => v,
535 Err(self::error::FromEventsError::Mismatch { .. }) => {
536 return Err(self::error::Error::TypeMismatch)
537 }
538 Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
539 };
540 for event in iter {
541 let event = event?;
542 languages.handle_event(&event);
543 if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
544 return Ok(v);
545 }
546 }
547 Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
548}
549
550/// Attempt to convert a [`minidom::Element`] into a type implementing
551/// [`FromXml`], fallably.
552///
553/// Unlike [`transform`] (which can also be used with an element), this
554/// function will return the element unharmed if its element header does not
555/// match the expectations of `T`.
556#[cfg(feature = "minidom")]
557#[deprecated(
558 since = "0.1.3",
559 note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
560)]
561pub fn try_from_element<T: FromXml>(
562 from: minidom::Element,
563) -> Result<T, self::error::FromElementError> {
564 let mut languages = rxml::xml_lang::XmlLangStack::new();
565 #[allow(deprecated)]
566 let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
567
568 languages.push_from_attrs(&attrs);
569 let mut sink = match T::from_events(
570 qname,
571 attrs,
572 &Context::empty().with_language(languages.current()),
573 ) {
574 Ok(v) => v,
575 Err(self::error::FromEventsError::Mismatch { .. }) => {
576 return Err(self::error::FromElementError::Mismatch(from))
577 }
578 Err(self::error::FromEventsError::Invalid(e)) => {
579 return Err(self::error::FromElementError::Invalid(e))
580 }
581 };
582
583 let mut iter = from.as_xml_iter()?;
584 // consume the element header
585 for item in &mut iter {
586 let item = item?;
587 match item {
588 // discard the element header
589 Item::XmlDeclaration(..) => (),
590 Item::ElementHeadStart(..) => (),
591 Item::Attribute(..) => (),
592 Item::ElementHeadEnd => {
593 // now that the element header is over, we break out
594 break;
595 }
596 Item::Text(..) => panic!("text before end of element header"),
597 Item::ElementFoot => panic!("element foot before end of element header"),
598 }
599 }
600 let iter = self::rxml_util::ItemToEvent::new(iter);
601 for event in iter {
602 let event = event?;
603 languages.handle_event(&event);
604 if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
605 return Ok(v);
606 }
607 }
608 // unreachable! instead of error here, because minidom::Element always
609 // produces the complete event sequence of a single element, and FromXml
610 // implementations must be constructible from that.
611 unreachable!("minidom::Element did not produce enough events to complete element")
612}
613
614/// # Parse a value from a byte slice containing XML data
615///
616/// This function parses the XML found in `buf`, assuming it contains a
617/// complete XML document (with optional XML declaration) and builds a `T`
618/// from it (without buffering the tree in memory).
619///
620/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
621/// particular, if `T` expects a different element header than the element
622/// header at the root of the document in `bytes`,
623/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
624///
625/// ## Example
626///
627#[cfg_attr(
628 not(feature = "macros"),
629 doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
630)]
631#[cfg_attr(feature = "macros", doc = "\n```\n")]
632/// # use xso::{AsXml, FromXml, from_bytes};
633/// #[derive(FromXml, PartialEq, Debug)]
634/// #[xml(namespace = "urn:example", name = "foo")]
635/// struct Foo {
636/// #[xml(attribute)]
637/// a: String,
638/// }
639///
640/// assert_eq!(
641/// Foo { a: "some-value".to_owned() },
642/// from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
643/// );
644/// ```
645pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
646 use rxml::{error::EndOrError, Parse};
647
648 let mut languages = rxml::xml_lang::XmlLangStack::new();
649 let mut parser = rxml::Parser::new();
650 let (name, attrs) = loop {
651 match parser.parse(&mut buf, true) {
652 Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
653 Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
654 Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
655 Ok(None) | Err(EndOrError::NeedMoreData) => {
656 return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
657 rxml::error::ErrorContext::DocumentBegin,
658 ))))
659 }
660 Ok(Some(_)) => {
661 return Err(self::error::Error::Other(
662 "Unexpected event at start of document",
663 ))
664 }
665 }
666 };
667 languages.push_from_attrs(&attrs);
668 let mut builder = match T::from_events(
669 name,
670 attrs,
671 &Context::empty().with_language(languages.current()),
672 ) {
673 Ok(v) => v,
674 Err(self::error::FromEventsError::Mismatch { .. }) => {
675 return Err(self::error::Error::TypeMismatch);
676 }
677 Err(self::error::FromEventsError::Invalid(e)) => {
678 return Err(e);
679 }
680 };
681
682 loop {
683 match parser.parse(&mut buf, true) {
684 Ok(Some(ev)) => {
685 languages.handle_event(&ev);
686 if let Some(v) =
687 builder.feed(ev, &Context::empty().with_language(languages.current()))?
688 {
689 return Ok(v);
690 }
691 }
692 Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
693 Ok(None) | Err(EndOrError::NeedMoreData) => {
694 return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
695 }
696 }
697 }
698}
699
700#[cfg(feature = "std")]
701fn read_start_event_io(
702 r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
703) -> io::Result<(rxml::QName, rxml::AttrMap)> {
704 for ev in r {
705 match ev? {
706 rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
707 rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
708 _ => {
709 return Err(io::Error::new(
710 io::ErrorKind::InvalidData,
711 self::error::Error::Other("Unexpected event at start of document"),
712 ))
713 }
714 }
715 }
716 Err(io::Error::new(
717 io::ErrorKind::InvalidData,
718 self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
719 rxml::error::ErrorContext::DocumentBegin,
720 ))),
721 ))
722}
723
724/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
725///
726/// This function parses the XML found in `r`, assuming it contains a
727/// complete XML document (with optional XML declaration) and builds a `T`
728/// from it (without buffering the tree in memory).
729///
730/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
731/// particular, if `T` expects a different element header than the element
732/// header at the root of the document in `r`,
733/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
734///
735/// ## Example
736///
737#[cfg_attr(
738 not(feature = "macros"),
739 doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
740)]
741#[cfg_attr(feature = "macros", doc = "\n```\n")]
742/// # use xso::{AsXml, FromXml, from_reader};
743/// # use std::io::BufReader;
744/// #[derive(FromXml, PartialEq, Debug)]
745/// #[xml(namespace = "urn:example", name = "foo")]
746/// struct Foo {
747/// #[xml(attribute)]
748/// a: String,
749/// }
750///
751/// // let file = ..
752/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
753/// assert_eq!(
754/// Foo { a: "some-value".to_owned() },
755/// from_reader(BufReader::new(file)).unwrap(),
756/// );
757/// ```
758#[cfg(feature = "std")]
759pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
760 let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
761 let (name, attrs) = read_start_event_io(&mut reader)?;
762 let mut builder = match T::from_events(
763 name,
764 attrs,
765 &Context::empty().with_language(reader.language()),
766 ) {
767 Ok(v) => v,
768 Err(self::error::FromEventsError::Mismatch { .. }) => {
769 return Err(self::error::Error::TypeMismatch)
770 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
771 }
772 Err(self::error::FromEventsError::Invalid(e)) => {
773 return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
774 }
775 };
776 while let Some(ev) = reader.next() {
777 if let Some(v) = builder
778 .feed(ev?, &Context::empty().with_language(reader.language()))
779 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
780 {
781 return Ok(v);
782 }
783 }
784 Err(io::Error::new(
785 io::ErrorKind::UnexpectedEof,
786 self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
787 ))
788}
789
790/// # Serialize a value to UTF-8-encoded XML
791///
792/// This function takes `xso`, converts it into XML using its [`AsXml`]
793/// implementation and serialises the resulting XML events into a `Vec<u8>`.
794///
795/// If serialisation fails, an error is returned instead.
796///
797/// ## Example
798///
799#[cfg_attr(
800 not(feature = "macros"),
801 doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
802)]
803#[cfg_attr(feature = "macros", doc = "\n```\n")]
804/// # use xso::{AsXml, FromXml, to_vec};
805/// # use std::io::BufReader;
806/// #[derive(AsXml, PartialEq, Debug)]
807/// #[xml(namespace = "urn:example", name = "foo")]
808/// struct Foo {
809/// #[xml(attribute)]
810/// a: String,
811/// }
812///
813/// assert_eq!(
814/// b"<foo xmlns='urn:example' a='some-value'></foo>",
815/// &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
816/// );
817/// ```
818pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
819 let iter = xso.as_xml_iter()?;
820 let mut writer = rxml::writer::Encoder::new();
821 let mut buf = Vec::new();
822 for item in iter {
823 let item = item?;
824 writer.encode(item.as_rxml_item(), &mut buf)?;
825 }
826 Ok(buf)
827}
828
829/// # Test if a string contains exclusively XML whitespace
830///
831/// This function returns true if `s` contains only XML whitespace. XML
832/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
833/// and U+000d (carriage return), so this test is implemented on bytes instead
834/// of codepoints for efficiency.
835///
836/// # Example
837///
838/// ```
839/// # use xso::is_xml_whitespace;
840/// assert!(is_xml_whitespace(" \t\r\n "));
841/// assert!(!is_xml_whitespace(" hello "));
842/// ```
843pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
844 s.as_ref()
845 .iter()
846 .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
847}