xso 0.4.0

XML Streamed Objects: similar to serde, but XML-native.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
/*!
# XML Streamed Objects -- serde-like parsing for XML

This crate provides the traits for parsing XML data into Rust structs, and
vice versa.

While it is in 0.0.x versions, many features still need to be developed, but
rest assured that there is a solid plan to get it fully usable for even
advanced XML scenarios.

XSO is an acronym for XML Stream(ed) Objects, referring to the main field of
use of this library in parsing XML streams like specified in RFC 6120.
*/

// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![no_std]

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::io;

pub mod asxml;
pub mod dynxso;
pub mod error;
pub mod fromxml;
#[cfg(feature = "minidom")]
pub mod minidom_compat;
mod rxml_util;
pub mod text;

// This is a hack to not make `const_str_eq` publicly available, except
// through the `exports` module if the `macros` feature is enabled, but have
// it available internally in all cases.
mod util {
    /// Compile-time comparison of two strings.
    ///
    /// Used by macro-generated code.
    ///
    /// This is necessary because `<str as PartialEq>::eq` is not `const`.
    pub const fn const_str_eq(a: &str, b: &str) -> bool {
        let a = a.as_bytes();
        let b = b.as_bytes();
        if a.len() != b.len() {
            return false;
        }

        let mut i = 0;
        while i < a.len() {
            if a[i] != b[i] {
                return false;
            }
            i += 1;
        }

        true
    }
}

#[doc(hidden)]
pub mod exports {
    #[cfg(all(feature = "minidom", feature = "macros"))]
    pub use minidom;
    #[cfg(feature = "macros")]
    pub use rxml;

    // These re-exports are necessary to support both std and no_std in code
    // generated by the macros.
    //
    // If we attempted to use ::alloc directly from macros, std builds would
    // not work because alloc is not generally present in builds using std.
    // If we used ::std, no_std builds would obviously not work. By exporting
    // std as alloc in std builds, we can safely use the alloc types from
    // there.
    //
    // Obviously, we have to be careful in xso-proc to not refer to types
    // which are not in alloc.
    #[cfg(not(feature = "std"))]
    pub extern crate alloc;
    #[cfg(feature = "std")]
    pub extern crate std as alloc;

    /// The built-in `bool` type.
    ///
    /// This is re-exported for use by macros in cases where we cannot rely on
    /// people not having done `type bool = str` or some similar shenanigans.
    #[cfg(feature = "macros")]
    pub type CoreBool = bool;

    /// The built-in `u8` type.
    ///
    /// This is re-exported for use by macros in cases where we cannot rely on
    /// people not having done `type u8 = str` or some similar shenanigans.
    #[cfg(feature = "macros")]
    pub type CoreU8 = u8;

    #[cfg(feature = "macros")]
    pub use super::util::const_str_eq;
}

use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};

#[doc(inline)]
pub use fromxml::Context;
use fromxml::XmlNameMatcher;

pub use text::TextCodec;

#[doc(inline)]
pub use rxml_util::Item;

pub use asxml::PrintRawXml;

#[doc = include_str!("from_xml_doc.md")]
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::FromXml;

/// # Make a struct or enum serialisable to XML
///
/// This derives the [`AsXml`] trait on a struct or enum. It is the
/// counterpart to [`macro@FromXml`].
///
/// The attributes necessary and available for the derivation to work are
/// documented on [`macro@FromXml`].
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::AsXml;

/// Trait allowing to iterate a struct's contents as serialisable
/// [`Item`]s.
///
/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated
/// type is considered a non-breaking change for any given implementation of
/// this trait. Always refer to a type's iterator type using fully-qualified
/// notation, for example: `<T as xso::AsXml>::ItemIter`.
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be serialised as XML")]
pub trait AsXml {
    /// The iterator type.
    ///
    /// **Important:** Changing this type is considered a non-breaking change
    /// for any given implementation of this trait. Always refer to a type's
    /// iterator type using fully-qualified notation, for example:
    /// `<T as xso::AsXml>::ItemIter`.
    type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
    where
        Self: 'x;

    /// Return an iterator which emits the contents of the struct or enum as
    /// serialisable [`Item`] items.
    fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;

    /// Return the same iterator as [`as_xml_iter`][`Self::as_xml_iter`], but
    /// boxed to erase the concrete iterator type.
    ///
    /// The provided implementation uses a simple cast. In most cases, it does
    /// not make sense to override the implementation. The only exception is
    /// if [`Self::ItemIter`] is already a boxed type, in which case
    /// overriding this method can avoid double-boxing the iterator.
    fn as_xml_dyn_iter<'x>(
        &'x self,
    ) -> Result<
        Box<dyn Iterator<Item = Result<Item<'x>, self::error::Error>> + 'x>,
        self::error::Error,
    > {
        self.as_xml_iter().map(|x| Box::new(x) as Box<_>)
    }
}

/// Trait for a temporary object allowing to construct a struct from
/// [`rxml::Event`] items.
///
/// Objects of this type are generally constructed through
/// [`FromXml::from_events`] and are used to build Rust structs or enums from
/// XML data. The XML data must be fed as `rxml::Event` to the
/// [`feed`][`Self::feed`] method.
pub trait FromEventsBuilder {
    /// The type which will be constructed by this builder.
    type Output;

    /// Feed another [`rxml::Event`] into the element construction
    /// process.
    ///
    /// Once the construction process completes, `Ok(Some(_))` is returned.
    /// When valid data has been fed but more events are needed to fully
    /// construct the resulting struct, `Ok(None)` is returned.
    ///
    /// If the construction fails, `Err(_)` is returned. Errors are generally
    /// fatal and the builder should be assumed to be broken at that point.
    /// Feeding more events after an error may result in panics, errors or
    /// inconsistent result data, though it may never result in unsound or
    /// unsafe behaviour.
    fn feed(
        &mut self,
        ev: rxml::Event,
        ctx: &Context<'_>,
    ) -> Result<Option<Self::Output>, self::error::Error>;
}

/// Trait allowing to construct a struct from a stream of
/// [`rxml::Event`] items.
///
/// To use this, first call [`FromXml::from_events`] with the qualified
/// name and the attributes of the corresponding
/// [`rxml::Event::StartElement`] event. If the call succeeds, the
/// returned builder object must be fed with the events representing the
/// contents of the element, and then with the `EndElement` event.
///
/// The `StartElement` passed to `from_events` must not be passed to `feed`.
///
/// **Important:** Changing the [`Builder`][`Self::Builder`] associated type
/// is considered a non-breaking change for any given implementation of this
/// trait. Always refer to a type's builder type using fully-qualified
/// notation, for example: `<T as xso::FromXml>::Builder`.
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be parsed from XML")]
pub trait FromXml {
    /// A builder type used to construct the element.
    ///
    /// **Important:** Changing this type is considered a non-breaking change
    /// for any given implementation of this trait. Always refer to a type's
    /// builder type using fully-qualified notation, for example:
    /// `<T as xso::FromXml>::Builder`.
    type Builder: FromEventsBuilder<Output = Self>;

    /// Attempt to initiate the streamed construction of this struct from XML.
    ///
    /// If the passed qualified `name` and `attrs` match the element's type,
    /// the [`Self::Builder`] is returned and should be fed with XML events
    /// by the caller.
    ///
    /// Otherwise, an appropriate error is returned.
    fn from_events(
        name: rxml::QName,
        attrs: rxml::AttrMap,
        ctx: &Context<'_>,
    ) -> Result<Self::Builder, self::error::FromEventsError>;

    /// Return a predicate which determines if `Self` *may* be parsed from
    /// a given XML element.
    ///
    /// The returned matcher **must** match all elements from which `Self`
    /// can be parsed, but it may also match elements from which `Self`
    /// cannot be parsed.
    ///
    /// This is an optimisation utility for code locations which have to
    /// disambiguate between many `FromXml` implementations. The provided
    /// implementation returns a matcher which matches all elements, which is
    /// correct, but also very inefficient.
    fn xml_name_matcher() -> XmlNameMatcher<'static> {
        XmlNameMatcher::Any
    }
}

/// Trait allowing to convert XML text to a value.
///
/// This trait is similar to [`FromStr`][`core::str::FromStr`], however, to
/// allow specialisation for XML<->Text conversion, a separate trait is
/// introduced. Unlike `FromStr`, this trait allows taking ownership of the
/// original text data, potentially saving allocations.
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot be parsed from XML text",
    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});`."
)]
pub trait FromXmlText: Sized {
    /// Convert the given XML text to a value.
    fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
}

/// Trait to convert a value to an XML text string.
///
/// Implementing this trait for a type allows it to be used both for XML
/// character data within elements and for XML attributes. For XML attributes,
/// the behaviour is defined by [`AsXmlText::as_optional_xml_text`], while
/// XML element text content uses [`AsXmlText::as_xml_text`]. Implementing
/// [`AsXmlText`] automatically provides an implementation of
/// [`AsOptionalXmlText`].
///
/// If your type should only be used in XML attributes and has no correct
/// serialisation in XML text, you should *only* implement
/// [`AsOptionalXmlText`] and omit the [`AsXmlText`] implementation.
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot be serialised to XML text",
    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});`."
)]
pub trait AsXmlText {
    /// Convert the value to an XML string in a context where an absent value
    /// cannot be represented.
    fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;

    /// Convert the value to an XML string in a context where an absent value
    /// can be represented.
    ///
    /// The provided implementation will always return the result of
    /// [`Self::as_xml_text`] wrapped into `Some(.)`. By re-implementing
    /// this method, implementors can customize the behaviour for certain
    /// values.
    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
        Ok(Some(self.as_xml_text()?))
    }
}

/// Specialized variant of [`AsXmlText`].
///
/// Normally, it should not be necessary to implement this trait as it is
/// automatically implemented for all types implementing [`AsXmlText`].
/// However, if your type can only be serialised as an XML attribute (for
/// example because an absent value has a particular meaning), it is correct
/// to implement [`AsOptionalXmlText`] **instead of** [`AsXmlText`].
///
/// If your type can be serialised as both (text and attribute) but needs
/// special handling in attributes, implement [`AsXmlText`] but provide a
/// custom implementation of [`AsXmlText::as_optional_xml_text`].
///
/// **Important:** See the [`text`][`crate::text`] module's documentation
/// for notes regarding implementations for types from third-party crates.
#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot be serialised as XML attribute",
    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});`."
)]
pub trait AsOptionalXmlText {
    /// Convert the value to an XML string in a context where an absent value
    /// can be represented.
    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
}

/// # Control how unknown attributes are handled
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how attributes, which are
/// not handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownAttributePolicy {
    /// All unknown attributes are discarded.
    ///
    /// This is the default policy if the crate is built without the
    /// `pedantic` feature.
    #[cfg_attr(not(feature = "pedantic"), default)]
    Discard,

    /// The first unknown attribute which is encountered generates a fatal
    /// parsing error.
    ///
    /// This is the default policy if the crate is built with the
    /// `pedantic` feature.
    #[cfg_attr(feature = "pedantic", default)]
    Fail,
}

impl UnknownAttributePolicy {
    #[doc(hidden)]
    /// Implementation of the policy.
    ///
    /// This is an internal API and not subject to semver versioning.
    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
        match self {
            Self::Fail => Err(self::error::Error::Other(msg)),
            Self::Discard => Ok(()),
        }
    }
}

/// # Control how unknown child elements are handled
///
/// The variants of this enum are referenced in the
/// `#[xml(on_unknown_child = ..)]` which can be used on structs and
/// enum variants. The specified variant controls how children, which are not
/// handled by any member of the compound, are handled during parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownChildPolicy {
    /// All unknown children are discarded.
    ///
    /// This is the default policy if the crate is built without the
    /// `pedantic` feature.
    #[cfg_attr(not(feature = "pedantic"), default)]
    Discard,

    /// The first unknown child which is encountered generates a fatal
    /// parsing error.
    ///
    /// This is the default policy if the crate is built with the
    /// `pedantic` feature.
    #[cfg_attr(feature = "pedantic", default)]
    Fail,
}

impl UnknownChildPolicy {
    #[doc(hidden)]
    /// Implementation of the policy.
    ///
    /// This is an internal API and not subject to semver versioning.
    pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
        match self {
            Self::Fail => Err(self::error::Error::Other(msg)),
            Self::Discard => Ok(()),
        }
    }
}

/// # Transform a value into another value via XML
///
/// This function takes `from`, converts it into XML using its [`AsXml`]
/// implementation and builds a `T` from it (without buffering the tree in
/// memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the header
/// provided by `from`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
    not(all(feature = "std", feature = "macros")),
    doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, transform};
/// #[derive(AsXml)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Source {
///     #[xml(attribute = "xml:lang")]
///     lang: &'static str,
/// }
///
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Dest {
///     #[xml(lang)]
///     lang: Option<String>,
/// }
///
/// assert_eq!(
///     Dest { lang: Some("en".to_owned()) },
///     transform(&Source { lang: "en" }).unwrap(),
/// );
/// ```
pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
    let mut languages = rxml::xml_lang::XmlLangStack::new();
    let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
    let (qname, attrs) = match iter.next() {
        Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
        Some(Err(e)) => return Err(e),
        _ => panic!("into_event_iter did not start with StartElement event!"),
    };
    languages.push_from_attrs(&attrs);
    let mut sink = match T::from_events(
        qname,
        attrs,
        &Context::empty().with_language(languages.current()),
    ) {
        Ok(v) => v,
        Err(self::error::FromEventsError::Mismatch { .. }) => {
            return Err(self::error::Error::TypeMismatch)
        }
        Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
    };
    for event in iter {
        let event = event?;
        languages.handle_event(&event);
        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
            return Ok(v);
        }
    }
    Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}

/// Attempt to convert a [`minidom::Element`] into a type implementing
/// [`FromXml`], fallably.
///
/// Unlike [`transform`] (which can also be used with an element), this
/// function will return the element unharmed if its element header does not
/// match the expectations of `T`.
#[cfg(feature = "minidom")]
#[deprecated(
    since = "0.1.3",
    note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
)]
pub fn try_from_element<T: FromXml>(
    from: minidom::Element,
) -> Result<T, self::error::FromElementError> {
    let mut languages = rxml::xml_lang::XmlLangStack::new();
    #[allow(deprecated)]
    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;

    languages.push_from_attrs(&attrs);
    let mut sink = match T::from_events(
        qname,
        attrs,
        &Context::empty().with_language(languages.current()),
    ) {
        Ok(v) => v,
        Err(self::error::FromEventsError::Mismatch { .. }) => {
            return Err(self::error::FromElementError::Mismatch(from))
        }
        Err(self::error::FromEventsError::Invalid(e)) => {
            return Err(self::error::FromElementError::Invalid(e))
        }
    };

    let mut iter = from.as_xml_iter()?;
    // consume the element header
    for item in &mut iter {
        let item = item?;
        match item {
            // discard the element header
            Item::XmlDeclaration(..) => (),
            Item::ElementHeadStart(..) => (),
            Item::Attribute(..) => (),
            Item::ElementHeadEnd => {
                // now that the element header is over, we break out
                break;
            }
            Item::Text(..) => panic!("text before end of element header"),
            Item::ElementFoot => panic!("element foot before end of element header"),
        }
    }
    let iter = self::rxml_util::ItemToEvent::new(iter);
    for event in iter {
        let event = event?;
        languages.handle_event(&event);
        if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
            return Ok(v);
        }
    }
    // unreachable! instead of error here, because minidom::Element always
    // produces the complete event sequence of a single element, and FromXml
    // implementations must be constructible from that.
    unreachable!("minidom::Element did not produce enough events to complete element")
}

fn from_bytes_inner<T: FromXml>(
    mut parser: rxml::Parser,
    mut buf: &[u8],
) -> Result<T, self::error::Error> {
    use rxml::{error::EndOrError, Parse};

    let mut languages = rxml::xml_lang::XmlLangStack::new();
    let (name, attrs) = loop {
        match parser.parse(&mut buf, true) {
            Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
            Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
            Ok(None) | Err(EndOrError::NeedMoreData) => {
                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
                    rxml::error::ErrorContext::DocumentBegin,
                ))))
            }
            Ok(Some(_)) => {
                return Err(self::error::Error::Other(
                    "Unexpected event at start of document",
                ))
            }
        }
    };
    languages.push_from_attrs(&attrs);
    let mut builder = match T::from_events(
        name,
        attrs,
        &Context::empty().with_language(languages.current()),
    ) {
        Ok(v) => v,
        Err(self::error::FromEventsError::Mismatch { .. }) => {
            return Err(self::error::Error::TypeMismatch);
        }
        Err(self::error::FromEventsError::Invalid(e)) => {
            return Err(e);
        }
    };

    loop {
        match parser.parse(&mut buf, true) {
            Ok(Some(ev)) => {
                languages.handle_event(&ev);
                if let Some(v) =
                    builder.feed(ev, &Context::empty().with_language(languages.current()))?
                {
                    return Ok(v);
                }
            }
            Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
            Ok(None) | Err(EndOrError::NeedMoreData) => {
                return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
            }
        }
    }
}

/// # Parse a value from a byte slice containing XML data
///
/// This function parses the XML found in `buf`, assuming it contains a
/// complete XML document (with optional XML declaration) and builds a `T`
/// from it (without buffering the tree in memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the element
/// header at the root of the document in `bytes`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
    not(feature = "macros"),
    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_bytes};
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
///     #[xml(attribute)]
///     a: String,
/// }
///
/// assert_eq!(
///     Foo { a: "some-value".to_owned() },
///     from_bytes(b"<foo xmlns='urn:example' a='some-value'/>").unwrap(),
/// );
/// ```
pub fn from_bytes<T: FromXml>(buf: &[u8]) -> Result<T, self::error::Error> {
    let parser = rxml::Parser::new();
    from_bytes_inner(parser, buf)
}

/// # Parse a value from a byte slice with specific parser options.
///
/// This is the same as [`from_bytes`], except that the rxml parser
/// [`Options`][`rxml::Options`] can be specified explicitly.
///
/// ## Example
///
#[cfg_attr(
    not(feature = "macros"),
    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_bytes_with_options};
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
///     #[xml(attribute)]
///     a: String,
/// }
///
/// let mut opts = rxml::Options::default();
/// opts.comments = rxml::parser::CommentMode::Discard;
///
/// assert_eq!(
///     Foo { a: "some-value".to_owned() },
///     from_bytes_with_options(
///         b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>",
///         opts,
///     ).unwrap(),
/// );
/// ```
pub fn from_bytes_with_options<T: FromXml>(
    buf: &[u8],
    opts: rxml::Options,
) -> Result<T, self::error::Error> {
    use rxml::WithOptions;
    let parser = rxml::Parser::with_options(opts);
    from_bytes_inner(parser, buf)
}

#[cfg(feature = "std")]
fn read_start_event_io(
    r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> {
    for ev in r {
        match ev? {
            rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
            rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    self::error::Error::Other("Unexpected event at start of document"),
                ))
            }
        }
    }
    Err(io::Error::new(
        io::ErrorKind::InvalidData,
        self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
            rxml::error::ErrorContext::DocumentBegin,
        ))),
    ))
}

#[cfg(feature = "std")]
fn from_reader_inner<T: FromXml, R: io::BufRead>(
    mut reader: rxml::XmlLangTracker<rxml::Reader<R>>,
) -> io::Result<T> {
    let (name, attrs) = read_start_event_io(&mut reader)?;
    let mut builder = match T::from_events(
        name,
        attrs,
        &Context::empty().with_language(reader.language()),
    ) {
        Ok(v) => v,
        Err(self::error::FromEventsError::Mismatch { .. }) => {
            return Err(self::error::Error::TypeMismatch)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
        }
        Err(self::error::FromEventsError::Invalid(e)) => {
            return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
        }
    };
    while let Some(ev) = reader.next() {
        if let Some(v) = builder
            .feed(ev?, &Context::empty().with_language(reader.language()))
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
        {
            return Ok(v);
        }
    }
    Err(io::Error::new(
        io::ErrorKind::UnexpectedEof,
        self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
    ))
}

/// # Parse a value from a [`io::BufRead`][`std::io::BufRead`]
///
/// This function parses the XML found in `r`, assuming it contains a
/// complete XML document (with optional XML declaration) and builds a `T`
/// from it (without buffering the tree in memory).
///
/// If conversion fails, a [`Error`][`crate::error::Error`] is returned. In
/// particular, if `T` expects a different element header than the element
/// header at the root of the document in `r`,
/// [`Error::TypeMismatch`][`crate::error::Error::TypeMismatch`] is returned.
///
/// ## Example
///
#[cfg_attr(
    not(feature = "macros"),
    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_reader};
/// # use std::io::BufReader;
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
///     #[xml(attribute)]
///     a: String,
/// }
///
/// // let file = ..
/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'/>"[..];
/// assert_eq!(
///     Foo { a: "some-value".to_owned() },
///     from_reader(BufReader::new(file)).unwrap(),
/// );
/// ```
#[cfg(feature = "std")]
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
    from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::new(r)))
}

/// # Parse a value using a specific parser config
///
/// This is the same as [`from_reader`], except that the rxml parser
/// [`Options`][`rxml::Options`] can be specified explicitly.
///
/// ## Example
///
#[cfg_attr(
    not(feature = "macros"),
    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, from_reader_with_options};
/// # use std::io::BufReader;
/// #[derive(FromXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
///     #[xml(attribute)]
///     a: String,
/// }
///
/// let mut opts = rxml::Options::default();
/// opts.comments = rxml::parser::CommentMode::Discard;
/// // let file = .. // containing XML comments
/// # let file = &mut &b"<foo xmlns='urn:example' a='some-value'><!-- comment --></foo>"[..];
/// assert_eq!(
///     Foo { a: "some-value".to_owned() },
///     from_reader_with_options(BufReader::new(file), opts).unwrap(),
/// );
/// ```
#[cfg(feature = "std")]
pub fn from_reader_with_options<T: FromXml, R: io::BufRead>(
    r: R,
    options: rxml::Options,
) -> io::Result<T> {
    from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::with_options(
        r, options,
    )))
}

/// # Serialize a value to UTF-8-encoded XML
///
/// This function takes `xso`, converts it into XML using its [`AsXml`]
/// implementation and serialises the resulting XML events into a `Vec<u8>`.
///
/// If serialisation fails, an error is returned instead.
///
/// ## Example
///
#[cfg_attr(
    not(feature = "macros"),
    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
/// # use xso::{AsXml, FromXml, to_vec};
/// # use std::io::BufReader;
/// #[derive(AsXml, PartialEq, Debug)]
/// #[xml(namespace = "urn:example", name = "foo")]
/// struct Foo {
///     #[xml(attribute)]
///     a: String,
/// }
///
/// assert_eq!(
///     b"<foo xmlns='urn:example' a='some-value'></foo>",
///     &to_vec(&Foo { a: "some-value".to_owned() }).unwrap()[..],
/// );
/// ```
pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
    let iter = xso.as_xml_iter()?;
    let mut writer = rxml::writer::Encoder::new();
    let mut buf = Vec::new();
    for item in iter {
        let item = item?;
        writer.encode(item.as_rxml_item(), &mut buf)?;
    }
    Ok(buf)
}

/// # Test if a string contains exclusively XML whitespace
///
/// This function returns true if `s` contains only XML whitespace. XML
/// whitespace is defined as U+0020 (space), U+0009 (tab), U+000a (newline)
/// and U+000d (carriage return), so this test is implemented on bytes instead
/// of codepoints for efficiency.
///
/// # Example
///
/// ```
/// # use xso::is_xml_whitespace;
/// assert!(is_xml_whitespace(" \t\r\n  "));
/// assert!(!is_xml_whitespace("  hello  "));
/// ```
pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
    s.as_ref()
        .iter()
        .all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
}