xso/
rxml_util.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Utilities which may eventually move upstream to the `rxml` crate.
8
9use alloc::borrow::Cow;
10
11use rxml::{parser::EventMetrics, AttrMap, Event, Namespace, NcName, NcNameStr, XmlVersion};
12
13/// # Serialisable piece of XML
14///
15/// This item represents a piece of an XML document which can be serialised
16/// into bytes by converting it to an [`rxml::Item`] and then feeding it to a
17/// [`rxml::Encoder`].
18///
19/// Unlike `rxml::Item`, the contents of this item may either be owned or
20/// borrowed, individually. This enables the use in an [`crate::AsXml`] trait
21/// even if data needs to be generated during serialisation.
22#[derive(Debug)]
23pub enum Item<'x> {
24    /// XML declaration
25    XmlDeclaration(XmlVersion),
26
27    /// Start of an element header
28    ElementHeadStart(
29        /// Namespace name
30        Namespace<'x>,
31        /// Local name of the attribute
32        Cow<'x, NcNameStr>,
33    ),
34
35    /// An attribute key/value pair
36    Attribute(
37        /// Namespace name
38        Namespace<'x>,
39        /// Local name of the attribute
40        Cow<'x, NcNameStr>,
41        /// Value of the attribute
42        Cow<'x, str>,
43    ),
44
45    /// End of an element header
46    ElementHeadEnd,
47
48    /// A piece of text (in element content, not attributes)
49    Text(Cow<'x, str>),
50
51    /// Footer of an element
52    ///
53    /// This can be used either in places where [`Text`] could be used to
54    /// close the most recently opened unclosed element, or it can be used
55    /// instead of [`ElementHeadEnd`] to close the element using `/>`, without
56    /// any child content.
57    ///
58    ///   [`Text`]: Self::Text
59    ///   [`ElementHeadEnd`]: Self::ElementHeadEnd
60    ElementFoot,
61}
62
63impl Item<'_> {
64    /// Exchange all borrowed pieces inside this item for owned items, cloning
65    /// them if necessary.
66    pub fn into_owned(self) -> Item<'static> {
67        match self {
68            Self::XmlDeclaration(v) => Item::XmlDeclaration(v),
69            Self::ElementHeadStart(ns, name) => {
70                Item::ElementHeadStart(ns.into_static(), Cow::Owned(name.into_owned()))
71            }
72            Self::Attribute(ns, name, value) => Item::Attribute(
73                ns.into_static(),
74                Cow::Owned(name.into_owned()),
75                Cow::Owned(value.into_owned()),
76            ),
77            Self::ElementHeadEnd => Item::ElementHeadEnd,
78            Self::Text(value) => Item::Text(Cow::Owned(value.into_owned())),
79            Self::ElementFoot => Item::ElementFoot,
80        }
81    }
82
83    /// Return an [`rxml::Item`], which borrows data from this item.
84    pub fn as_rxml_item(&self) -> rxml::Item<'_> {
85        match self {
86            Self::XmlDeclaration(ref v) => rxml::Item::XmlDeclaration(*v),
87            Self::ElementHeadStart(ref ns, ref name) => {
88                rxml::Item::ElementHeadStart(ns.clone(), name)
89            }
90            Self::Attribute(ref ns, ref name, ref value) => {
91                rxml::Item::Attribute(ns.clone(), name, value)
92            }
93            Self::ElementHeadEnd => rxml::Item::ElementHeadEnd,
94            Self::Text(ref value) => rxml::Item::Text(value),
95            Self::ElementFoot => rxml::Item::ElementFoot,
96        }
97    }
98}
99
100/// Iterator adapter which converts an iterator over [`Event`][`rxml::Event`]
101/// to an iterator over [`Item<'static>`][`Item`].
102///
103/// This iterator consumes the events and returns items which contain the data
104/// in an owned fashion.
105#[cfg(feature = "minidom")]
106pub(crate) struct EventToItem<I> {
107    inner: I,
108    attributes: Option<rxml::xml_map::IntoIter<alloc::string::String>>,
109}
110
111#[cfg(feature = "minidom")]
112impl<I> EventToItem<I> {
113    pub(crate) fn new(inner: I) -> Self {
114        Self {
115            inner,
116            attributes: None,
117        }
118    }
119
120    fn drain(&mut self) -> Option<Item<'static>> {
121        match self.attributes {
122            Some(ref mut attrs) => {
123                if let Some(((ns, name), value)) = attrs.next() {
124                    Some(Item::Attribute(ns, Cow::Owned(name), Cow::Owned(value)))
125                } else {
126                    self.attributes = None;
127                    Some(Item::ElementHeadEnd)
128                }
129            }
130            None => None,
131        }
132    }
133
134    fn update(&mut self, ev: Event) -> Item<'static> {
135        assert!(self.attributes.is_none());
136        match ev {
137            Event::XmlDeclaration(_, v) => Item::XmlDeclaration(v),
138            Event::StartElement(_, (ns, name), attrs) => {
139                self.attributes = Some(attrs.into_iter());
140                Item::ElementHeadStart(ns, Cow::Owned(name))
141            }
142            Event::Text(_, value) => Item::Text(Cow::Owned(value)),
143            Event::EndElement(_) => Item::ElementFoot,
144        }
145    }
146}
147
148#[cfg(feature = "minidom")]
149impl<I: Iterator<Item = Result<Event, crate::error::Error>>> Iterator for EventToItem<I> {
150    type Item = Result<Item<'static>, crate::error::Error>;
151
152    fn next(&mut self) -> Option<Self::Item> {
153        if let Some(item) = self.drain() {
154            return Some(Ok(item));
155        }
156        let next = match self.inner.next() {
157            Some(Ok(v)) => v,
158            Some(Err(e)) => return Some(Err(e)),
159            None => return None,
160        };
161        Some(Ok(self.update(next)))
162    }
163
164    fn size_hint(&self) -> (usize, Option<usize>) {
165        // we may create an indefinte amount of items for a single event,
166        // so we cannot provide a reasonable upper bound.
167        (self.inner.size_hint().0, None)
168    }
169}
170
171/// Iterator adapter which converts an iterator over [`Item`] to
172/// an iterator over [`Event`][`crate::Event`].
173///
174/// As `Event` does not support borrowing data, this iterator copies the data
175/// from the items on the fly.
176pub(crate) struct ItemToEvent<I> {
177    inner: I,
178    event_buffer: Option<Event>,
179    elem_buffer: Option<(Namespace<'static>, NcName, AttrMap)>,
180}
181
182impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> ItemToEvent<I> {
183    /// Create a new adapter with `inner` as the source iterator.
184    pub(crate) fn new(inner: I) -> Self {
185        Self {
186            inner,
187            event_buffer: None,
188            elem_buffer: None,
189        }
190    }
191}
192
193impl<I> ItemToEvent<I> {
194    fn update(&mut self, item: Item<'_>) -> Result<Option<Event>, crate::error::Error> {
195        assert!(self.event_buffer.is_none());
196        match item {
197            Item::XmlDeclaration(v) => {
198                assert!(self.elem_buffer.is_none());
199                Ok(Some(Event::XmlDeclaration(EventMetrics::zero(), v)))
200            }
201            Item::ElementHeadStart(ns, name) => {
202                if self.elem_buffer.is_some() {
203                    // this is only used with AsXml implementations, so
204                    // triggering this is always a coding failure instead of a
205                    // runtime error.
206                    panic!("got a second ElementHeadStart items without ElementHeadEnd inbetween: ns={:?} name={:?} (state={:?})", ns, name, self.elem_buffer);
207                }
208                self.elem_buffer = Some((ns.into_static(), name.into_owned(), AttrMap::new()));
209                Ok(None)
210            }
211            Item::Attribute(ns, name, value) => {
212                let Some((_, _, attrs)) = self.elem_buffer.as_mut() else {
213                    // this is only used with AsXml implementations, so
214                    // triggering this is always a coding failure instead of a
215                    // runtime error.
216                    panic!(
217                        "got a second Attribute item without ElementHeadStart: ns={:?}, name={:?}",
218                        ns, name
219                    );
220                };
221                attrs.insert(ns.into_static(), name.into_owned(), value.into_owned());
222                Ok(None)
223            }
224            Item::ElementHeadEnd => {
225                let Some((ns, name, attrs)) = self.elem_buffer.take() else {
226                    // this is only used with AsXml implementations, so
227                    // triggering this is always a coding failure instead of a
228                    // runtime error.
229                    panic!(
230                        "got ElementHeadEnd item without ElementHeadStart: {:?}",
231                        item
232                    );
233                };
234                Ok(Some(Event::StartElement(
235                    EventMetrics::zero(),
236                    (ns, name),
237                    attrs,
238                )))
239            }
240            Item::Text(value) => {
241                if let Some(elem_buffer) = self.elem_buffer.as_ref() {
242                    // this is only used with AsXml implementations, so
243                    // triggering this is always a coding failure instead of a
244                    // runtime error.
245                    panic!("got Text after ElementHeadStart but before ElementHeadEnd: Text({:?}) (state = {:?})", value, elem_buffer);
246                }
247                Ok(Some(Event::Text(EventMetrics::zero(), value.into_owned())))
248            }
249            Item::ElementFoot => {
250                let end_ev = Event::EndElement(EventMetrics::zero());
251                let result = if let Some((ns, name, attrs)) = self.elem_buffer.take() {
252                    // content-less element
253                    self.event_buffer = Some(end_ev);
254                    Event::StartElement(EventMetrics::zero(), (ns, name), attrs)
255                } else {
256                    end_ev
257                };
258                Ok(Some(result))
259            }
260        }
261    }
262}
263
264impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> Iterator for ItemToEvent<I> {
265    type Item = Result<Event, crate::error::Error>;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        if let Some(event) = self.event_buffer.take() {
269            return Some(Ok(event));
270        }
271        loop {
272            let item = match self.inner.next() {
273                Some(Ok(v)) => v,
274                Some(Err(e)) => return Some(Err(e)),
275                None => return None,
276            };
277            if let Some(v) = self.update(item).transpose() {
278                return Some(v);
279            }
280        }
281    }
282
283    fn size_hint(&self) -> (usize, Option<usize>) {
284        // we may create an indefinte amount of items for a single event,
285        // so we cannot provide a reasonable upper bound.
286        (self.inner.size_hint().0, None)
287    }
288}
289
290#[cfg(all(test, feature = "minidom"))]
291mod tests_minidom {
292    use super::*;
293
294    use alloc::{borrow::ToOwned, string::ToString, vec, vec::Vec};
295
296    fn events_to_items<I: Iterator<Item = Event>>(events: I) -> Vec<Item<'static>> {
297        let iter = EventToItem {
298            inner: events.map(|ev| Ok(ev)),
299            attributes: None,
300        };
301        let mut result = Vec::new();
302        for item in iter {
303            let item = item.unwrap();
304            result.push(item);
305        }
306        result
307    }
308
309    #[test]
310    fn event_to_item_xml_declaration() {
311        let events = vec![Event::XmlDeclaration(
312            EventMetrics::zero(),
313            XmlVersion::V1_0,
314        )];
315        let items = events_to_items(events.into_iter());
316        assert_eq!(items.len(), 1);
317        match items[0] {
318            Item::XmlDeclaration(XmlVersion::V1_0) => (),
319            ref other => panic!("unexpected item in position 0: {:?}", other),
320        };
321    }
322
323    #[test]
324    fn event_to_item_empty_element() {
325        let events = vec![
326            Event::StartElement(
327                EventMetrics::zero(),
328                (Namespace::NONE, "elem".try_into().unwrap()),
329                AttrMap::new(),
330            ),
331            Event::EndElement(EventMetrics::zero()),
332        ];
333        let items = events_to_items(events.into_iter());
334        assert_eq!(items.len(), 3);
335        match items[0] {
336            Item::ElementHeadStart(ref ns, ref name) => {
337                assert_eq!(&**ns, Namespace::none());
338                assert_eq!(&**name, "elem");
339            }
340            ref other => panic!("unexpected item in position 0: {:?}", other),
341        };
342        match items[1] {
343            Item::ElementHeadEnd => (),
344            ref other => panic!("unexpected item in position 1: {:?}", other),
345        };
346        match items[2] {
347            Item::ElementFoot => (),
348            ref other => panic!("unexpected item in position 2: {:?}", other),
349        };
350    }
351
352    #[test]
353    fn event_to_item_element_with_attributes() {
354        let mut attrs = AttrMap::new();
355        attrs.insert(
356            Namespace::NONE,
357            "attr".try_into().unwrap(),
358            "value".to_string(),
359        );
360        let events = vec![
361            Event::StartElement(
362                EventMetrics::zero(),
363                (Namespace::NONE, "elem".try_into().unwrap()),
364                attrs,
365            ),
366            Event::EndElement(EventMetrics::zero()),
367        ];
368        let items = events_to_items(events.into_iter());
369        assert_eq!(items.len(), 4);
370        match items[0] {
371            Item::ElementHeadStart(ref ns, ref name) => {
372                assert_eq!(&**ns, Namespace::none());
373                assert_eq!(&**name, "elem");
374            }
375            ref other => panic!("unexpected item in position 0: {:?}", other),
376        };
377        match items[1] {
378            Item::Attribute(ref ns, ref name, ref value) => {
379                assert_eq!(&**ns, Namespace::none());
380                assert_eq!(&**name, "attr");
381                assert_eq!(&**value, "value");
382            }
383            ref other => panic!("unexpected item in position 1: {:?}", other),
384        };
385        match items[2] {
386            Item::ElementHeadEnd => (),
387            ref other => panic!("unexpected item in position 2: {:?}", other),
388        };
389        match items[3] {
390            Item::ElementFoot => (),
391            ref other => panic!("unexpected item in position 3: {:?}", other),
392        };
393    }
394
395    #[test]
396    fn event_to_item_element_with_text() {
397        let events = vec![
398            Event::StartElement(
399                EventMetrics::zero(),
400                (Namespace::NONE, "elem".try_into().unwrap()),
401                AttrMap::new(),
402            ),
403            Event::Text(EventMetrics::zero(), "Hello World!".to_owned()),
404            Event::EndElement(EventMetrics::zero()),
405        ];
406        let items = events_to_items(events.into_iter());
407        assert_eq!(items.len(), 4);
408        match items[0] {
409            Item::ElementHeadStart(ref ns, ref name) => {
410                assert_eq!(&**ns, Namespace::none());
411                assert_eq!(&**name, "elem");
412            }
413            ref other => panic!("unexpected item in position 0: {:?}", other),
414        };
415        match items[1] {
416            Item::ElementHeadEnd => (),
417            ref other => panic!("unexpected item in position 1: {:?}", other),
418        };
419        match items[2] {
420            Item::Text(ref value) => {
421                assert_eq!(value, "Hello World!");
422            }
423            ref other => panic!("unexpected item in position 2: {:?}", other),
424        };
425        match items[3] {
426            Item::ElementFoot => (),
427            ref other => panic!("unexpected item in position 3: {:?}", other),
428        };
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    use alloc::{vec, vec::Vec};
437
438    fn items_to_events<'x, I: IntoIterator<Item = Item<'x>>>(
439        items: I,
440    ) -> Result<Vec<Event>, crate::error::Error> {
441        let iter = ItemToEvent {
442            inner: items.into_iter().map(|x| Ok(x)),
443            event_buffer: None,
444            elem_buffer: None,
445        };
446        let mut result = Vec::new();
447        for ev in iter {
448            let ev = ev?;
449            result.push(ev);
450        }
451        Ok(result)
452    }
453
454    #[test]
455    fn item_to_event_xml_decl() {
456        let items = vec![Item::XmlDeclaration(XmlVersion::V1_0)];
457        let events = items_to_events(items).expect("item conversion");
458        assert_eq!(events.len(), 1);
459        match events[0] {
460            Event::XmlDeclaration(_, XmlVersion::V1_0) => (),
461            ref other => panic!("unexpected event in position 0: {:?}", other),
462        };
463    }
464
465    #[test]
466    fn item_to_event_simple_empty_element() {
467        let items = vec![
468            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
469            Item::ElementHeadEnd,
470            Item::ElementFoot,
471        ];
472        let events = items_to_events(items).expect("item conversion");
473        assert_eq!(events.len(), 2);
474        match events[0] {
475            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
476                assert_eq!(attrs.len(), 0);
477                assert_eq!(ns, Namespace::none());
478                assert_eq!(name, "elem");
479            }
480            ref other => panic!("unexpected event in position 0: {:?}", other),
481        };
482        match events[1] {
483            Event::EndElement(_) => (),
484            ref other => panic!("unexpected event in position 1: {:?}", other),
485        };
486    }
487
488    #[test]
489    fn item_to_event_short_empty_element() {
490        let items = vec![
491            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
492            Item::ElementFoot,
493        ];
494        let events = items_to_events(items).expect("item conversion");
495        assert_eq!(events.len(), 2);
496        match events[0] {
497            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
498                assert_eq!(attrs.len(), 0);
499                assert_eq!(ns, Namespace::none());
500                assert_eq!(name, "elem");
501            }
502            ref other => panic!("unexpected event in position 0: {:?}", other),
503        };
504        match events[1] {
505            Event::EndElement(_) => (),
506            ref other => panic!("unexpected event in position 1: {:?}", other),
507        };
508    }
509
510    #[test]
511    fn item_to_event_element_with_text_content() {
512        let items = vec![
513            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
514            Item::ElementHeadEnd,
515            Item::Text(Cow::Borrowed("Hello World!")),
516            Item::ElementFoot,
517        ];
518        let events = items_to_events(items).expect("item conversion");
519        assert_eq!(events.len(), 3);
520        match events[0] {
521            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
522                assert_eq!(attrs.len(), 0);
523                assert_eq!(ns, Namespace::none());
524                assert_eq!(name, "elem");
525            }
526            ref other => panic!("unexpected event in position 0: {:?}", other),
527        };
528        match events[1] {
529            Event::Text(_, ref value) => {
530                assert_eq!(value, "Hello World!");
531            }
532            ref other => panic!("unexpected event in position 1: {:?}", other),
533        };
534        match events[2] {
535            Event::EndElement(_) => (),
536            ref other => panic!("unexpected event in position 2: {:?}", other),
537        };
538    }
539
540    #[test]
541    fn item_to_event_element_with_attributes() {
542        let items = vec![
543            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
544            Item::Attribute(
545                Namespace::NONE,
546                Cow::Borrowed("attr".try_into().unwrap()),
547                Cow::Borrowed("value"),
548            ),
549            Item::ElementHeadEnd,
550            Item::ElementFoot,
551        ];
552        let events = items_to_events(items).expect("item conversion");
553        assert_eq!(events.len(), 2);
554        match events[0] {
555            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
556                assert_eq!(ns, Namespace::none());
557                assert_eq!(name, "elem");
558                assert_eq!(attrs.len(), 1);
559                assert_eq!(
560                    attrs.get(Namespace::none(), "attr").map(|x| x.as_str()),
561                    Some("value")
562                );
563            }
564            ref other => panic!("unexpected event in position 0: {:?}", other),
565        };
566        match events[1] {
567            Event::EndElement(_) => (),
568            ref other => panic!("unexpected event in position 2: {:?}", other),
569        };
570    }
571}