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, ToOwned};
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,
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,
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, Cow::Owned(name.into_owned()))
71            }
72            Self::Attribute(ns, name, value) => Item::Attribute(
73                ns,
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) => rxml::Item::ElementHeadStart(ns, name),
88            Self::Attribute(ref ns, ref name, ref value) => rxml::Item::Attribute(ns, name, value),
89            Self::ElementHeadEnd => rxml::Item::ElementHeadEnd,
90            Self::Text(ref value) => rxml::Item::Text(value),
91            Self::ElementFoot => rxml::Item::ElementFoot,
92        }
93    }
94}
95
96/// Iterator adapter which converts an iterator over [`Event`][`rxml::Event`]
97/// to an iterator over [`Item<'static>`][`Item`].
98///
99/// This iterator consumes the events and returns items which contain the data
100/// in an owned fashion.
101#[cfg(feature = "minidom")]
102pub(crate) struct EventToItem<I> {
103    inner: I,
104    attributes: Option<rxml::xml_map::IntoIter<alloc::string::String>>,
105}
106
107#[cfg(feature = "minidom")]
108impl<I> EventToItem<I> {
109    pub(crate) fn new(inner: I) -> Self {
110        Self {
111            inner,
112            attributes: None,
113        }
114    }
115
116    fn drain(&mut self) -> Option<Item<'static>> {
117        match self.attributes {
118            Some(ref mut attrs) => {
119                if let Some(((ns, name), value)) = attrs.next() {
120                    Some(Item::Attribute(ns, Cow::Owned(name), Cow::Owned(value)))
121                } else {
122                    self.attributes = None;
123                    Some(Item::ElementHeadEnd)
124                }
125            }
126            None => None,
127        }
128    }
129
130    fn update(&mut self, ev: Event) -> Item<'static> {
131        assert!(self.attributes.is_none());
132        match ev {
133            Event::XmlDeclaration(_, v) => Item::XmlDeclaration(v),
134            Event::StartElement(_, (ns, name), attrs) => {
135                self.attributes = Some(attrs.into_iter());
136                Item::ElementHeadStart(ns, Cow::Owned(name))
137            }
138            Event::Text(_, value) => Item::Text(Cow::Owned(value)),
139            Event::EndElement(_) => Item::ElementFoot,
140        }
141    }
142}
143
144#[cfg(feature = "minidom")]
145impl<I: Iterator<Item = Result<Event, crate::error::Error>>> Iterator for EventToItem<I> {
146    type Item = Result<Item<'static>, crate::error::Error>;
147
148    fn next(&mut self) -> Option<Self::Item> {
149        if let Some(item) = self.drain() {
150            return Some(Ok(item));
151        }
152        let next = match self.inner.next() {
153            Some(Ok(v)) => v,
154            Some(Err(e)) => return Some(Err(e)),
155            None => return None,
156        };
157        Some(Ok(self.update(next)))
158    }
159
160    fn size_hint(&self) -> (usize, Option<usize>) {
161        // we may create an indefinte amount of items for a single event,
162        // so we cannot provide a reasonable upper bound.
163        (self.inner.size_hint().0, None)
164    }
165}
166
167/// Iterator adapter which converts an iterator over [`Item`] to
168/// an iterator over [`Event`][`crate::Event`].
169///
170/// As `Event` does not support borrowing data, this iterator copies the data
171/// from the items on the fly.
172pub(crate) struct ItemToEvent<I> {
173    inner: I,
174    event_buffer: Option<Event>,
175    elem_buffer: Option<(Namespace, NcName, AttrMap)>,
176}
177
178impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> ItemToEvent<I> {
179    /// Create a new adapter with `inner` as the source iterator.
180    pub(crate) fn new(inner: I) -> Self {
181        Self {
182            inner,
183            event_buffer: None,
184            elem_buffer: None,
185        }
186    }
187}
188
189impl<I> ItemToEvent<I> {
190    fn update(&mut self, item: Item<'_>) -> Result<Option<Event>, crate::error::Error> {
191        assert!(self.event_buffer.is_none());
192        match item {
193            Item::XmlDeclaration(v) => {
194                assert!(self.elem_buffer.is_none());
195                Ok(Some(Event::XmlDeclaration(EventMetrics::zero(), v)))
196            }
197            Item::ElementHeadStart(ns, name) => {
198                if self.elem_buffer.is_some() {
199                    // this is only used with AsXml implementations, so
200                    // triggering this is always a coding failure instead of a
201                    // runtime error.
202                    panic!("got a second ElementHeadStart items without ElementHeadEnd inbetween: ns={:?} name={:?} (state={:?})", ns, name, self.elem_buffer);
203                }
204                self.elem_buffer = Some((ns.to_owned(), name.into_owned(), AttrMap::new()));
205                Ok(None)
206            }
207            Item::Attribute(ns, name, value) => {
208                let Some((_, _, attrs)) = self.elem_buffer.as_mut() else {
209                    // this is only used with AsXml implementations, so
210                    // triggering this is always a coding failure instead of a
211                    // runtime error.
212                    panic!(
213                        "got a second Attribute item without ElementHeadStart: ns={:?}, name={:?}",
214                        ns, name
215                    );
216                };
217                attrs.insert(ns, name.into_owned(), value.into_owned());
218                Ok(None)
219            }
220            Item::ElementHeadEnd => {
221                let Some((ns, name, attrs)) = self.elem_buffer.take() else {
222                    // this is only used with AsXml implementations, so
223                    // triggering this is always a coding failure instead of a
224                    // runtime error.
225                    panic!(
226                        "got ElementHeadEnd item without ElementHeadStart: {:?}",
227                        item
228                    );
229                };
230                Ok(Some(Event::StartElement(
231                    EventMetrics::zero(),
232                    (ns, name),
233                    attrs,
234                )))
235            }
236            Item::Text(value) => {
237                if let Some(elem_buffer) = self.elem_buffer.as_ref() {
238                    // this is only used with AsXml implementations, so
239                    // triggering this is always a coding failure instead of a
240                    // runtime error.
241                    panic!("got Text after ElementHeadStart but before ElementHeadEnd: Text({:?}) (state = {:?})", value, elem_buffer);
242                }
243                Ok(Some(Event::Text(EventMetrics::zero(), value.into_owned())))
244            }
245            Item::ElementFoot => {
246                let end_ev = Event::EndElement(EventMetrics::zero());
247                let result = if let Some((ns, name, attrs)) = self.elem_buffer.take() {
248                    // content-less element
249                    self.event_buffer = Some(end_ev);
250                    Event::StartElement(EventMetrics::zero(), (ns, name), attrs)
251                } else {
252                    end_ev
253                };
254                Ok(Some(result))
255            }
256        }
257    }
258}
259
260impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> Iterator for ItemToEvent<I> {
261    type Item = Result<Event, crate::error::Error>;
262
263    fn next(&mut self) -> Option<Self::Item> {
264        if let Some(event) = self.event_buffer.take() {
265            return Some(Ok(event));
266        }
267        loop {
268            let item = match self.inner.next() {
269                Some(Ok(v)) => v,
270                Some(Err(e)) => return Some(Err(e)),
271                None => return None,
272            };
273            if let Some(v) = self.update(item).transpose() {
274                return Some(v);
275            }
276        }
277    }
278
279    fn size_hint(&self) -> (usize, Option<usize>) {
280        // we may create an indefinte amount of items for a single event,
281        // so we cannot provide a reasonable upper bound.
282        (self.inner.size_hint().0, None)
283    }
284}
285
286#[cfg(all(test, feature = "minidom"))]
287mod tests_minidom {
288    use super::*;
289
290    use alloc::{string::ToString, vec, vec::Vec};
291
292    fn events_to_items<I: Iterator<Item = Event>>(events: I) -> Vec<Item<'static>> {
293        let iter = EventToItem {
294            inner: events.map(|ev| Ok(ev)),
295            attributes: None,
296        };
297        let mut result = Vec::new();
298        for item in iter {
299            let item = item.unwrap();
300            result.push(item);
301        }
302        result
303    }
304
305    #[test]
306    fn event_to_item_xml_declaration() {
307        let events = vec![Event::XmlDeclaration(
308            EventMetrics::zero(),
309            XmlVersion::V1_0,
310        )];
311        let items = events_to_items(events.into_iter());
312        assert_eq!(items.len(), 1);
313        match items[0] {
314            Item::XmlDeclaration(XmlVersion::V1_0) => (),
315            ref other => panic!("unexpected item in position 0: {:?}", other),
316        };
317    }
318
319    #[test]
320    fn event_to_item_empty_element() {
321        let events = vec![
322            Event::StartElement(
323                EventMetrics::zero(),
324                (Namespace::NONE, "elem".try_into().unwrap()),
325                AttrMap::new(),
326            ),
327            Event::EndElement(EventMetrics::zero()),
328        ];
329        let items = events_to_items(events.into_iter());
330        assert_eq!(items.len(), 3);
331        match items[0] {
332            Item::ElementHeadStart(ref ns, ref name) => {
333                assert_eq!(&**ns, Namespace::none());
334                assert_eq!(&**name, "elem");
335            }
336            ref other => panic!("unexpected item in position 0: {:?}", other),
337        };
338        match items[1] {
339            Item::ElementHeadEnd => (),
340            ref other => panic!("unexpected item in position 1: {:?}", other),
341        };
342        match items[2] {
343            Item::ElementFoot => (),
344            ref other => panic!("unexpected item in position 2: {:?}", other),
345        };
346    }
347
348    #[test]
349    fn event_to_item_element_with_attributes() {
350        let mut attrs = AttrMap::new();
351        attrs.insert(
352            Namespace::NONE,
353            "attr".try_into().unwrap(),
354            "value".to_string(),
355        );
356        let events = vec![
357            Event::StartElement(
358                EventMetrics::zero(),
359                (Namespace::NONE, "elem".try_into().unwrap()),
360                attrs,
361            ),
362            Event::EndElement(EventMetrics::zero()),
363        ];
364        let items = events_to_items(events.into_iter());
365        assert_eq!(items.len(), 4);
366        match items[0] {
367            Item::ElementHeadStart(ref ns, ref name) => {
368                assert_eq!(&**ns, Namespace::none());
369                assert_eq!(&**name, "elem");
370            }
371            ref other => panic!("unexpected item in position 0: {:?}", other),
372        };
373        match items[1] {
374            Item::Attribute(ref ns, ref name, ref value) => {
375                assert_eq!(&**ns, Namespace::none());
376                assert_eq!(&**name, "attr");
377                assert_eq!(&**value, "value");
378            }
379            ref other => panic!("unexpected item in position 1: {:?}", other),
380        };
381        match items[2] {
382            Item::ElementHeadEnd => (),
383            ref other => panic!("unexpected item in position 2: {:?}", other),
384        };
385        match items[3] {
386            Item::ElementFoot => (),
387            ref other => panic!("unexpected item in position 3: {:?}", other),
388        };
389    }
390
391    #[test]
392    fn event_to_item_element_with_text() {
393        let events = vec![
394            Event::StartElement(
395                EventMetrics::zero(),
396                (Namespace::NONE, "elem".try_into().unwrap()),
397                AttrMap::new(),
398            ),
399            Event::Text(EventMetrics::zero(), "Hello World!".to_owned()),
400            Event::EndElement(EventMetrics::zero()),
401        ];
402        let items = events_to_items(events.into_iter());
403        assert_eq!(items.len(), 4);
404        match items[0] {
405            Item::ElementHeadStart(ref ns, ref name) => {
406                assert_eq!(&**ns, Namespace::none());
407                assert_eq!(&**name, "elem");
408            }
409            ref other => panic!("unexpected item in position 0: {:?}", other),
410        };
411        match items[1] {
412            Item::ElementHeadEnd => (),
413            ref other => panic!("unexpected item in position 1: {:?}", other),
414        };
415        match items[2] {
416            Item::Text(ref value) => {
417                assert_eq!(value, "Hello World!");
418            }
419            ref other => panic!("unexpected item in position 2: {:?}", other),
420        };
421        match items[3] {
422            Item::ElementFoot => (),
423            ref other => panic!("unexpected item in position 3: {:?}", other),
424        };
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    use alloc::{vec, vec::Vec};
433
434    fn items_to_events<'x, I: IntoIterator<Item = Item<'x>>>(
435        items: I,
436    ) -> Result<Vec<Event>, crate::error::Error> {
437        let iter = ItemToEvent {
438            inner: items.into_iter().map(|x| Ok(x)),
439            event_buffer: None,
440            elem_buffer: None,
441        };
442        let mut result = Vec::new();
443        for ev in iter {
444            let ev = ev?;
445            result.push(ev);
446        }
447        Ok(result)
448    }
449
450    #[test]
451    fn item_to_event_xml_decl() {
452        let items = vec![Item::XmlDeclaration(XmlVersion::V1_0)];
453        let events = items_to_events(items).expect("item conversion");
454        assert_eq!(events.len(), 1);
455        match events[0] {
456            Event::XmlDeclaration(_, XmlVersion::V1_0) => (),
457            ref other => panic!("unexpected event in position 0: {:?}", other),
458        };
459    }
460
461    #[test]
462    fn item_to_event_simple_empty_element() {
463        let items = vec![
464            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
465            Item::ElementHeadEnd,
466            Item::ElementFoot,
467        ];
468        let events = items_to_events(items).expect("item conversion");
469        assert_eq!(events.len(), 2);
470        match events[0] {
471            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
472                assert_eq!(attrs.len(), 0);
473                assert_eq!(ns, Namespace::none());
474                assert_eq!(name, "elem");
475            }
476            ref other => panic!("unexpected event in position 0: {:?}", other),
477        };
478        match events[1] {
479            Event::EndElement(_) => (),
480            ref other => panic!("unexpected event in position 1: {:?}", other),
481        };
482    }
483
484    #[test]
485    fn item_to_event_short_empty_element() {
486        let items = vec![
487            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
488            Item::ElementFoot,
489        ];
490        let events = items_to_events(items).expect("item conversion");
491        assert_eq!(events.len(), 2);
492        match events[0] {
493            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
494                assert_eq!(attrs.len(), 0);
495                assert_eq!(ns, Namespace::none());
496                assert_eq!(name, "elem");
497            }
498            ref other => panic!("unexpected event in position 0: {:?}", other),
499        };
500        match events[1] {
501            Event::EndElement(_) => (),
502            ref other => panic!("unexpected event in position 1: {:?}", other),
503        };
504    }
505
506    #[test]
507    fn item_to_event_element_with_text_content() {
508        let items = vec![
509            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
510            Item::ElementHeadEnd,
511            Item::Text(Cow::Borrowed("Hello World!")),
512            Item::ElementFoot,
513        ];
514        let events = items_to_events(items).expect("item conversion");
515        assert_eq!(events.len(), 3);
516        match events[0] {
517            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
518                assert_eq!(attrs.len(), 0);
519                assert_eq!(ns, Namespace::none());
520                assert_eq!(name, "elem");
521            }
522            ref other => panic!("unexpected event in position 0: {:?}", other),
523        };
524        match events[1] {
525            Event::Text(_, ref value) => {
526                assert_eq!(value, "Hello World!");
527            }
528            ref other => panic!("unexpected event in position 1: {:?}", other),
529        };
530        match events[2] {
531            Event::EndElement(_) => (),
532            ref other => panic!("unexpected event in position 2: {:?}", other),
533        };
534    }
535
536    #[test]
537    fn item_to_event_element_with_attributes() {
538        let items = vec![
539            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
540            Item::Attribute(
541                Namespace::NONE,
542                Cow::Borrowed("attr".try_into().unwrap()),
543                Cow::Borrowed("value"),
544            ),
545            Item::ElementHeadEnd,
546            Item::ElementFoot,
547        ];
548        let events = items_to_events(items).expect("item conversion");
549        assert_eq!(events.len(), 2);
550        match events[0] {
551            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
552                assert_eq!(ns, Namespace::none());
553                assert_eq!(name, "elem");
554                assert_eq!(attrs.len(), 1);
555                assert_eq!(
556                    attrs.get(Namespace::none(), "attr").map(|x| x.as_str()),
557                    Some("value")
558                );
559            }
560            ref other => panic!("unexpected event in position 0: {:?}", other),
561        };
562        match events[1] {
563            Event::EndElement(_) => (),
564            ref other => panic!("unexpected event in position 2: {:?}", other),
565        };
566    }
567}