diff --git a/xso/src/minidom_compat.rs b/xso/src/minidom_compat.rs index f27a9828..5ac68adb 100644 --- a/xso/src/minidom_compat.rs +++ b/xso/src/minidom_compat.rs @@ -19,7 +19,7 @@ use rxml::{ use crate::{ error::{Error, FromEventsError}, - rxml_util::Item, + rxml_util::{EventToItem, Item}, AsXml, FromEventsBuilder, FromXml, IntoXml, }; @@ -453,6 +453,35 @@ impl Iterator for IntoEventsViaElement { } } +/// Helper struct to stream a struct which implements conversion +/// to [`minidom::Element`]. +pub struct AsItemsViaElement<'x> { + iter: EventToItem, + lifetime_binding: PhantomData>, +} + +impl<'x> AsItemsViaElement<'x> { + /// Create a new streaming parser for `T`. + pub fn new(value: T) -> Result + where + Error: From, + minidom::Element: TryFrom, + { + Ok(Self { + iter: EventToItem::new(IntoEventsViaElement::new(value)?), + lifetime_binding: PhantomData, + }) + } +} + +impl<'x> Iterator for AsItemsViaElement<'x> { + type Item = Result, Error>; + + fn next(&mut self) -> Option { + self.iter.next().map(|x| x.map(Item::into_owned)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/xso/src/rxml_util.rs b/xso/src/rxml_util.rs index dd64e51a..36f95646 100644 --- a/xso/src/rxml_util.rs +++ b/xso/src/rxml_util.rs @@ -9,6 +9,8 @@ use std::borrow::Cow; use rxml::{Namespace, NcNameStr, XmlVersion}; +#[cfg(feature = "minidom")] +use rxml::Event; /// An encodable item. /// @@ -90,3 +92,218 @@ impl Item<'_> { } } } + +/// Iterator adapter which converts an iterator over [`Event`][`rxml::Event`] +/// to an iterator over [`Item<'static>`][`Item`]. +/// +/// This iterator consumes the events and returns items which contain the data +/// in an owned fashion. +#[cfg(feature = "minidom")] +pub(crate) struct EventToItem { + inner: I, + attributes: Option>, +} + +#[cfg(feature = "minidom")] +impl EventToItem { + pub(crate) fn new(inner: I) -> Self { + Self { + inner, + attributes: None, + } + } + + fn drain(&mut self) -> Option> { + match self.attributes { + Some(ref mut attrs) => { + if let Some(((ns, name), value)) = attrs.next() { + Some(Item::Attribute(ns, Cow::Owned(name), Cow::Owned(value))) + } else { + self.attributes = None; + Some(Item::ElementHeadEnd) + } + } + None => None, + } + } + + fn update(&mut self, ev: Event) -> Item<'static> { + assert!(self.attributes.is_none()); + match ev { + Event::XmlDeclaration(_, v) => Item::XmlDeclaration(v), + Event::StartElement(_, (ns, name), attrs) => { + self.attributes = Some(attrs.into_iter()); + Item::ElementHeadStart(ns, Cow::Owned(name)) + } + Event::Text(_, value) => Item::Text(Cow::Owned(value)), + Event::EndElement(_) => Item::ElementFoot, + } + } +} + +#[cfg(feature = "minidom")] +impl>> Iterator for EventToItem { + type Item = Result, crate::error::Error>; + + fn next(&mut self) -> Option { + if let Some(item) = self.drain() { + return Some(Ok(item)); + } + let next = match self.inner.next() { + Some(Ok(v)) => v, + Some(Err(e)) => return Some(Err(e)), + None => return None, + }; + Some(Ok(self.update(next))) + } + + fn size_hint(&self) -> (usize, Option) { + // we may create an indefinte amount of items for a single event, + // so we cannot provide a reasonable upper bound. + (self.inner.size_hint().0, None) + } +} + +#[cfg(all(test, feature = "minidom"))] +mod tests_minidom { + use std::convert::TryInto; + + use rxml::{parser::EventMetrics, AttrMap}; + + use super::*; + + fn events_to_items>(events: I) -> Vec> { + let iter = EventToItem { + inner: events.map(|ev| Ok(ev)), + attributes: None, + }; + let mut result = Vec::new(); + for item in iter { + let item = item.unwrap(); + result.push(item); + } + result + } + + #[test] + fn event_to_item_xml_declaration() { + let events = vec![Event::XmlDeclaration( + EventMetrics::zero(), + XmlVersion::V1_0, + )]; + let items = events_to_items(events.into_iter()); + assert_eq!(items.len(), 1); + match items[0] { + Item::XmlDeclaration(XmlVersion::V1_0) => (), + ref other => panic!("unexected item in position 0: {:?}", other), + }; + } + + #[test] + fn event_to_item_empty_element() { + let events = vec![ + Event::StartElement( + EventMetrics::zero(), + (Namespace::NONE, "elem".try_into().unwrap()), + AttrMap::new(), + ), + Event::EndElement(EventMetrics::zero()), + ]; + let items = events_to_items(events.into_iter()); + assert_eq!(items.len(), 3); + match items[0] { + Item::ElementHeadStart(ref ns, ref name) => { + assert_eq!(&**ns, Namespace::none()); + assert_eq!(&**name, "elem"); + } + ref other => panic!("unexected item in position 0: {:?}", other), + }; + match items[1] { + Item::ElementHeadEnd => (), + ref other => panic!("unexected item in position 1: {:?}", other), + }; + match items[2] { + Item::ElementFoot => (), + ref other => panic!("unexected item in position 2: {:?}", other), + }; + } + + #[test] + fn event_to_item_element_with_attributes() { + let mut attrs = AttrMap::new(); + attrs.insert( + Namespace::NONE, + "attr".try_into().unwrap(), + "value".to_string(), + ); + let events = vec![ + Event::StartElement( + EventMetrics::zero(), + (Namespace::NONE, "elem".try_into().unwrap()), + attrs, + ), + Event::EndElement(EventMetrics::zero()), + ]; + let items = events_to_items(events.into_iter()); + assert_eq!(items.len(), 4); + match items[0] { + Item::ElementHeadStart(ref ns, ref name) => { + assert_eq!(&**ns, Namespace::none()); + assert_eq!(&**name, "elem"); + } + ref other => panic!("unexected item in position 0: {:?}", other), + }; + match items[1] { + Item::Attribute(ref ns, ref name, ref value) => { + assert_eq!(&**ns, Namespace::none()); + assert_eq!(&**name, "attr"); + assert_eq!(&**value, "value"); + } + ref other => panic!("unexected item in position 1: {:?}", other), + }; + match items[2] { + Item::ElementHeadEnd => (), + ref other => panic!("unexected item in position 2: {:?}", other), + }; + match items[3] { + Item::ElementFoot => (), + ref other => panic!("unexected item in position 3: {:?}", other), + }; + } + + #[test] + fn event_to_item_element_with_text() { + let events = vec![ + Event::StartElement( + EventMetrics::zero(), + (Namespace::NONE, "elem".try_into().unwrap()), + AttrMap::new(), + ), + Event::Text(EventMetrics::zero(), "Hello World!".to_owned()), + Event::EndElement(EventMetrics::zero()), + ]; + let items = events_to_items(events.into_iter()); + assert_eq!(items.len(), 4); + match items[0] { + Item::ElementHeadStart(ref ns, ref name) => { + assert_eq!(&**ns, Namespace::none()); + assert_eq!(&**name, "elem"); + } + ref other => panic!("unexected item in position 0: {:?}", other), + }; + match items[1] { + Item::ElementHeadEnd => (), + ref other => panic!("unexected item in position 1: {:?}", other), + }; + match items[2] { + Item::Text(ref value) => { + assert_eq!(value, "Hello World!"); + } + ref other => panic!("unexected item in position 2: {:?}", other), + }; + match items[3] { + Item::ElementFoot => (), + ref other => panic!("unexected item in position 3: {:?}", other), + }; + } +}