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