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