xso: implement AsXml for minidom::Element

This commit is contained in:
Jonas Schäfer 2024-07-09 16:46:11 +02:00
commit d12b6c31ea

View file

@ -5,6 +5,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::borrow::Cow;
use std::marker::PhantomData;
use std::vec::IntoIter;
@ -13,12 +14,13 @@ use minidom::{Element, Node};
use rxml::{
parser::EventMetrics,
writer::{SimpleNamespaces, TrackNamespace},
AttrMap, Event, Name, Namespace, NcName,
AttrMap, Event, Name, NameStr, Namespace, NcName, NcNameStr,
};
use crate::{
error::{Error, FromEventsError},
FromEventsBuilder, FromXml, IntoXml,
rxml_util::Item,
AsXml, FromEventsBuilder, FromXml, IntoXml,
};
/// State machine for converting a minidom Element into rxml events.
@ -152,6 +154,153 @@ impl IntoXml for Element {
}
}
enum AsXmlState<'a> {
/// Element header: we need to generate the
/// [`Item::ElementHeadStart`] item from the namespace and name.
Header { element: &'a Element },
/// Element header: we now generate the attributes.
Attributes {
/// The element (needed for the contents later and to access the
/// prefix mapping).
element: &'a Element,
/// Attribute iterator.
attributes: minidom::element::Attrs<'a>,
},
/// Content: The contents of the element are streamed as events.
Nodes {
/// Remaining child nodes (text and/or children) to emit.
nodes: minidom::element::Nodes<'a>,
/// When emitting a child element, this is a nested [`IntoEvents`]
/// instance for that child element.
nested: Option<Box<ElementAsXml<'a>>>,
},
}
/// Convert a [`minidom::Element`] to [`Item`][`crate::rxml_util::Item`]s.
///
/// This can be constructed from the
/// [`AsXml::as_xml_iter`][`crate::AsXml::as_xml_iter`]
/// implementation on [`minidom::Element`].
pub struct ElementAsXml<'a>(Option<AsXmlState<'a>>);
impl<'a> Iterator for ElementAsXml<'a> {
type Item = Result<Item<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
None => None,
Some(AsXmlState::Header { ref element }) => {
let item = Item::ElementHeadStart(
Namespace::from(element.ns()),
Cow::Borrowed(match <&NcNameStr>::try_from(element.name()) {
Ok(v) => v,
Err(e) => {
self.0 = None;
return Some(Err(e.into()));
}
}),
);
self.0 = Some(AsXmlState::Attributes {
element,
attributes: element.attrs(),
});
Some(Ok(item))
}
Some(AsXmlState::Attributes {
ref mut attributes,
ref element,
}) => {
if let Some((name, value)) = attributes.next() {
let name = match <&NameStr>::try_from(name) {
Ok(v) => v,
Err(e) => {
self.0 = None;
return Some(Err(e.into()));
}
};
let (prefix, name) = match name.split_name() {
Ok(v) => v,
Err(e) => {
self.0 = None;
return Some(Err(e.into()));
}
};
let namespace = if let Some(prefix) = prefix {
if prefix == "xml" {
Namespace::XML
} else {
let ns = match element.prefixes.get(&Some(prefix.as_str().to_owned())) {
Some(v) => v,
None => {
panic!("undeclared xml namespace prefix in minidom::Element")
}
};
Namespace::from(ns.to_owned())
}
} else {
Namespace::NONE
};
Some(Ok(Item::Attribute(
namespace,
Cow::Borrowed(name),
Cow::Borrowed(value),
)))
} else {
self.0 = Some(AsXmlState::Nodes {
nodes: element.nodes(),
nested: None,
});
Some(Ok(Item::ElementHeadEnd))
}
}
Some(AsXmlState::Nodes {
ref mut nodes,
ref mut nested,
}) => {
if let Some(nested) = nested.as_mut() {
if let Some(next) = nested.next() {
return Some(next);
}
}
*nested = None;
match nodes.next() {
None => {
self.0 = None;
Some(Ok(Item::ElementFoot))
}
Some(minidom::Node::Text(ref text)) => {
Some(Ok(Item::Text(Cow::Borrowed(text))))
}
Some(minidom::Node::Element(ref element)) => {
let mut iter = match element.as_xml_iter() {
Ok(v) => v,
Err(e) => {
self.0 = None;
return Some(Err(e.into()));
}
};
let item = iter.next().unwrap();
*nested = Some(Box::new(iter));
Some(item)
}
}
}
}
}
}
impl AsXml for minidom::Element {
type ItemIter<'a> = ElementAsXml<'a>;
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, Error> {
Ok(ElementAsXml(Some(AsXmlState::Header { element: self })))
}
}
/// Construct a [`minidom::Element`] from [`rxml::Event`]s
///
/// This can be constructed from the