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 std::borrow::Cow;
 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<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    fn events_to_items<I: Iterator<Item = Event>>(events: I) -> Vec<Item<'static>> {
287        let iter = EventToItem {
288            inner: events.map(|ev| Ok(ev)),
289            attributes: None,
290        };
291        let mut result = Vec::new();
292        for item in iter {
293            let item = item.unwrap();
294            result.push(item);
295        }
296        result
297    }
298
299    #[test]
300    fn event_to_item_xml_declaration() {
301        let events = vec![Event::XmlDeclaration(
302            EventMetrics::zero(),
303            XmlVersion::V1_0,
304        )];
305        let items = events_to_items(events.into_iter());
306        assert_eq!(items.len(), 1);
307        match items[0] {
308            Item::XmlDeclaration(XmlVersion::V1_0) => (),
309            ref other => panic!("unexected item in position 0: {:?}", other),
310        };
311    }
312
313    #[test]
314    fn event_to_item_empty_element() {
315        let events = vec![
316            Event::StartElement(
317                EventMetrics::zero(),
318                (Namespace::NONE, "elem".try_into().unwrap()),
319                AttrMap::new(),
320            ),
321            Event::EndElement(EventMetrics::zero()),
322        ];
323        let items = events_to_items(events.into_iter());
324        assert_eq!(items.len(), 3);
325        match items[0] {
326            Item::ElementHeadStart(ref ns, ref name) => {
327                assert_eq!(&**ns, Namespace::none());
328                assert_eq!(&**name, "elem");
329            }
330            ref other => panic!("unexected item in position 0: {:?}", other),
331        };
332        match items[1] {
333            Item::ElementHeadEnd => (),
334            ref other => panic!("unexected item in position 1: {:?}", other),
335        };
336        match items[2] {
337            Item::ElementFoot => (),
338            ref other => panic!("unexected item in position 2: {:?}", other),
339        };
340    }
341
342    #[test]
343    fn event_to_item_element_with_attributes() {
344        let mut attrs = AttrMap::new();
345        attrs.insert(
346            Namespace::NONE,
347            "attr".try_into().unwrap(),
348            "value".to_string(),
349        );
350        let events = vec![
351            Event::StartElement(
352                EventMetrics::zero(),
353                (Namespace::NONE, "elem".try_into().unwrap()),
354                attrs,
355            ),
356            Event::EndElement(EventMetrics::zero()),
357        ];
358        let items = events_to_items(events.into_iter());
359        assert_eq!(items.len(), 4);
360        match items[0] {
361            Item::ElementHeadStart(ref ns, ref name) => {
362                assert_eq!(&**ns, Namespace::none());
363                assert_eq!(&**name, "elem");
364            }
365            ref other => panic!("unexected item in position 0: {:?}", other),
366        };
367        match items[1] {
368            Item::Attribute(ref ns, ref name, ref value) => {
369                assert_eq!(&**ns, Namespace::none());
370                assert_eq!(&**name, "attr");
371                assert_eq!(&**value, "value");
372            }
373            ref other => panic!("unexected item in position 1: {:?}", other),
374        };
375        match items[2] {
376            Item::ElementHeadEnd => (),
377            ref other => panic!("unexected item in position 2: {:?}", other),
378        };
379        match items[3] {
380            Item::ElementFoot => (),
381            ref other => panic!("unexected item in position 3: {:?}", other),
382        };
383    }
384
385    #[test]
386    fn event_to_item_element_with_text() {
387        let events = vec![
388            Event::StartElement(
389                EventMetrics::zero(),
390                (Namespace::NONE, "elem".try_into().unwrap()),
391                AttrMap::new(),
392            ),
393            Event::Text(EventMetrics::zero(), "Hello World!".to_owned()),
394            Event::EndElement(EventMetrics::zero()),
395        ];
396        let items = events_to_items(events.into_iter());
397        assert_eq!(items.len(), 4);
398        match items[0] {
399            Item::ElementHeadStart(ref ns, ref name) => {
400                assert_eq!(&**ns, Namespace::none());
401                assert_eq!(&**name, "elem");
402            }
403            ref other => panic!("unexected item in position 0: {:?}", other),
404        };
405        match items[1] {
406            Item::ElementHeadEnd => (),
407            ref other => panic!("unexected item in position 1: {:?}", other),
408        };
409        match items[2] {
410            Item::Text(ref value) => {
411                assert_eq!(value, "Hello World!");
412            }
413            ref other => panic!("unexected item in position 2: {:?}", other),
414        };
415        match items[3] {
416            Item::ElementFoot => (),
417            ref other => panic!("unexected item in position 3: {:?}", other),
418        };
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    fn items_to_events<'x, I: IntoIterator<Item = Item<'x>>>(
427        items: I,
428    ) -> Result<Vec<Event>, crate::error::Error> {
429        let iter = ItemToEvent {
430            inner: items.into_iter().map(|x| Ok(x)),
431            event_buffer: None,
432            elem_buffer: None,
433        };
434        let mut result = Vec::new();
435        for ev in iter {
436            let ev = ev?;
437            result.push(ev);
438        }
439        Ok(result)
440    }
441
442    #[test]
443    fn item_to_event_xml_decl() {
444        let items = vec![Item::XmlDeclaration(XmlVersion::V1_0)];
445        let events = items_to_events(items).expect("item conversion");
446        assert_eq!(events.len(), 1);
447        match events[0] {
448            Event::XmlDeclaration(_, XmlVersion::V1_0) => (),
449            ref other => panic!("unexected event in position 0: {:?}", other),
450        };
451    }
452
453    #[test]
454    fn item_to_event_simple_empty_element() {
455        let items = vec![
456            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
457            Item::ElementHeadEnd,
458            Item::ElementFoot,
459        ];
460        let events = items_to_events(items).expect("item conversion");
461        assert_eq!(events.len(), 2);
462        match events[0] {
463            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
464                assert_eq!(attrs.len(), 0);
465                assert_eq!(ns, Namespace::none());
466                assert_eq!(name, "elem");
467            }
468            ref other => panic!("unexected event in position 0: {:?}", other),
469        };
470        match events[1] {
471            Event::EndElement(_) => (),
472            ref other => panic!("unexected event in position 1: {:?}", other),
473        };
474    }
475
476    #[test]
477    fn item_to_event_short_empty_element() {
478        let items = vec![
479            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
480            Item::ElementFoot,
481        ];
482        let events = items_to_events(items).expect("item conversion");
483        assert_eq!(events.len(), 2);
484        match events[0] {
485            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
486                assert_eq!(attrs.len(), 0);
487                assert_eq!(ns, Namespace::none());
488                assert_eq!(name, "elem");
489            }
490            ref other => panic!("unexected event in position 0: {:?}", other),
491        };
492        match events[1] {
493            Event::EndElement(_) => (),
494            ref other => panic!("unexected event in position 1: {:?}", other),
495        };
496    }
497
498    #[test]
499    fn item_to_event_element_with_text_content() {
500        let items = vec![
501            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
502            Item::ElementHeadEnd,
503            Item::Text(Cow::Borrowed("Hello World!")),
504            Item::ElementFoot,
505        ];
506        let events = items_to_events(items).expect("item conversion");
507        assert_eq!(events.len(), 3);
508        match events[0] {
509            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
510                assert_eq!(attrs.len(), 0);
511                assert_eq!(ns, Namespace::none());
512                assert_eq!(name, "elem");
513            }
514            ref other => panic!("unexected event in position 0: {:?}", other),
515        };
516        match events[1] {
517            Event::Text(_, ref value) => {
518                assert_eq!(value, "Hello World!");
519            }
520            ref other => panic!("unexected event in position 1: {:?}", other),
521        };
522        match events[2] {
523            Event::EndElement(_) => (),
524            ref other => panic!("unexected event in position 2: {:?}", other),
525        };
526    }
527
528    #[test]
529    fn item_to_event_element_with_attributes() {
530        let items = vec![
531            Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
532            Item::Attribute(
533                Namespace::NONE,
534                Cow::Borrowed("attr".try_into().unwrap()),
535                Cow::Borrowed("value"),
536            ),
537            Item::ElementHeadEnd,
538            Item::ElementFoot,
539        ];
540        let events = items_to_events(items).expect("item conversion");
541        assert_eq!(events.len(), 2);
542        match events[0] {
543            Event::StartElement(_, (ref ns, ref name), ref attrs) => {
544                assert_eq!(ns, Namespace::none());
545                assert_eq!(name, "elem");
546                assert_eq!(attrs.len(), 1);
547                assert_eq!(
548                    attrs.get(Namespace::none(), "attr").map(|x| x.as_str()),
549                    Some("value")
550                );
551            }
552            ref other => panic!("unexected event in position 0: {:?}", other),
553        };
554        match events[1] {
555            Event::EndElement(_) => (),
556            ref other => panic!("unexected event in position 2: {:?}", other),
557        };
558    }
559}