Port everything over to AsXml
This commit is contained in:
parent
4910b01244
commit
ccf38cdf9b
64 changed files with 848 additions and 371 deletions
|
|
@ -1,7 +1,7 @@
|
|||
# Make a struct or enum parseable from XML
|
||||
|
||||
This derives the [`FromXml`] trait on a struct or enum. It is the counterpart
|
||||
to [`macro@IntoXml`].
|
||||
to [`macro@AsXml`].
|
||||
|
||||
## Example
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ The following mapping types are defined:
|
|||
|
||||
The `attribute` meta causes the field to be mapped to an XML attribute of the
|
||||
same name. For `FromXml`, the field's type must implement [`FromXmlText`] and
|
||||
for `IntoXml`, the field's type must implement [`IntoOptionalXmlText`].
|
||||
for `AsXml`, the field's type must implement [`AsOptionalXmlText`].
|
||||
|
||||
The following keys can be used inside the `#[xml(attribute(..))]` meta:
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ otherwise).
|
|||
If `default` is specified and the attribute is absent in the source, the value
|
||||
is generated using [`std::default::Default`], requiring the field type to
|
||||
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
||||
influence on `IntoXml`.
|
||||
influence on `AsXml`.
|
||||
|
||||
##### Example
|
||||
|
||||
|
|
@ -148,14 +148,14 @@ If `codec` is given, the given `codec` must implement
|
|||
[`TextCodec<T>`][`TextCodec`] where `T` is the type of the field.
|
||||
|
||||
If `codec` is *not* given, the field's type must implement [`FromXmlText`] for
|
||||
`FromXml` and for `IntoXml`, the field's type must implement [`IntoXmlText`].
|
||||
`FromXml` and for `AsXml`, the field's type must implement [`AsXmlText`].
|
||||
|
||||
The `text` meta also supports a shorthand syntax, `#[xml(text = ..)]`, where
|
||||
the value is treated as the value for the `codec` key (with optional prefix as
|
||||
described above, and unnamespaced otherwise).
|
||||
|
||||
Only a single field per struct may be annotated with `#[xml(text)]` at a time,
|
||||
to avoid parsing ambiguities. This is also true if only `IntoXml` is derived on
|
||||
to avoid parsing ambiguities. This is also true if only `AsXml` is derived on
|
||||
a field, for consistency.
|
||||
|
||||
##### Example without codec
|
||||
|
|
|
|||
|
|
@ -49,14 +49,14 @@ pub use xso_proc::FromXml;
|
|||
|
||||
/// # Make a struct or enum serialisable to XML
|
||||
///
|
||||
/// This derives the [`IntoXml`] trait on a struct or enum. It is the
|
||||
/// This derives the [`AsXml`] trait on a struct or enum. It is the
|
||||
/// counterpart to [`macro@FromXml`].
|
||||
///
|
||||
/// The attributes necessary and available for the derivation to work are
|
||||
/// documented on [`macro@FromXml`].
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "macros")]
|
||||
pub use xso_proc::IntoXml;
|
||||
pub use xso_proc::AsXml;
|
||||
|
||||
/// Trait allowing to consume a struct and iterate its contents as
|
||||
/// serialisable [`rxml::Event`] items.
|
||||
|
|
@ -369,10 +369,10 @@ impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempt to transform a type implementing [`IntoXml`] into another
|
||||
/// Attempt to transform a type implementing [`AsXml`] into another
|
||||
/// type which implements [`FromXml`].
|
||||
pub fn transform<T: FromXml, F: IntoXml>(from: F) -> Result<T, self::error::Error> {
|
||||
let mut iter = from.into_event_iter()?;
|
||||
pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {
|
||||
let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
|
||||
let (qname, attrs) = match iter.next() {
|
||||
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
|
||||
Some(Err(e)) => return Err(e),
|
||||
|
|
@ -418,8 +418,24 @@ pub fn try_from_element<T: FromXml>(
|
|||
}
|
||||
};
|
||||
|
||||
let mut iter = from.into_event_iter()?;
|
||||
iter.next().expect("first event from minidom::Element")?;
|
||||
let mut iter = from.as_xml_iter()?;
|
||||
// consume the element header
|
||||
for item in &mut iter {
|
||||
let item = item?;
|
||||
match item {
|
||||
// discard the element header
|
||||
Item::XmlDeclaration(..) => (),
|
||||
Item::ElementHeadStart(..) => (),
|
||||
Item::Attribute(..) => (),
|
||||
Item::ElementHeadEnd => {
|
||||
// now that the element header is over, we break out
|
||||
break;
|
||||
}
|
||||
Item::Text(..) => panic!("text before end of element header"),
|
||||
Item::ElementFoot => panic!("element foot before end of element header"),
|
||||
}
|
||||
}
|
||||
let iter = self::rxml_util::ItemToEvent::new(iter);
|
||||
for event in iter {
|
||||
let event = event?;
|
||||
if let Some(v) = sink.feed(event)? {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rxml::{Namespace, NcNameStr, XmlVersion};
|
||||
#[cfg(feature = "minidom")]
|
||||
use rxml::Event;
|
||||
use rxml::{parser::EventMetrics, AttrMap, Event, Namespace, NcName, NcNameStr, XmlVersion};
|
||||
|
||||
/// An encodable item.
|
||||
///
|
||||
|
|
@ -164,12 +162,130 @@ impl<I: Iterator<Item = Result<Event, crate::error::Error>>> Iterator for EventT
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterator adapter which converts an iterator over [`Item`] to
|
||||
/// an iterator over [`Event`][`crate::Event`].
|
||||
///
|
||||
/// As `Event` does not support borrowing data, this iterator copies the data
|
||||
/// from the items on the fly.
|
||||
pub(crate) struct ItemToEvent<I> {
|
||||
inner: I,
|
||||
event_buffer: Option<Event>,
|
||||
elem_buffer: Option<(Namespace, NcName, AttrMap)>,
|
||||
}
|
||||
|
||||
impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> ItemToEvent<I> {
|
||||
/// Create a new adapter with `inner` as the source iterator.
|
||||
pub(crate) fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
event_buffer: None,
|
||||
elem_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ItemToEvent<I> {
|
||||
fn update<'x>(&mut self, item: Item<'x>) -> Result<Option<Event>, crate::error::Error> {
|
||||
assert!(self.event_buffer.is_none());
|
||||
match item {
|
||||
Item::XmlDeclaration(v) => {
|
||||
assert!(self.elem_buffer.is_none());
|
||||
Ok(Some(Event::XmlDeclaration(EventMetrics::zero(), v)))
|
||||
}
|
||||
Item::ElementHeadStart(ns, name) => {
|
||||
if self.elem_buffer.is_some() {
|
||||
// this is only used with AsXml implementations, so
|
||||
// triggering this is always a coding failure instead of a
|
||||
// runtime error.
|
||||
panic!("got a second ElementHeadStart items without ElementHeadEnd inbetween: ns={:?} name={:?} (state={:?})", ns, name, self.elem_buffer);
|
||||
}
|
||||
self.elem_buffer = Some((ns.to_owned(), name.into_owned(), AttrMap::new()));
|
||||
Ok(None)
|
||||
}
|
||||
Item::Attribute(ns, name, value) => {
|
||||
let Some((_, _, attrs)) = self.elem_buffer.as_mut() else {
|
||||
// this is only used with AsXml implementations, so
|
||||
// triggering this is always a coding failure instead of a
|
||||
// runtime error.
|
||||
panic!(
|
||||
"got a second Attribute item without ElementHeadStart: ns={:?}, name={:?}",
|
||||
ns, name
|
||||
);
|
||||
};
|
||||
attrs.insert(ns, name.into_owned(), value.into_owned());
|
||||
Ok(None)
|
||||
}
|
||||
Item::ElementHeadEnd => {
|
||||
let Some((ns, name, attrs)) = self.elem_buffer.take() else {
|
||||
// this is only used with AsXml implementations, so
|
||||
// triggering this is always a coding failure instead of a
|
||||
// runtime error.
|
||||
panic!(
|
||||
"got ElementHeadEnd item without ElementHeadStart: {:?}",
|
||||
item
|
||||
);
|
||||
};
|
||||
Ok(Some(Event::StartElement(
|
||||
EventMetrics::zero(),
|
||||
(ns, name),
|
||||
attrs,
|
||||
)))
|
||||
}
|
||||
Item::Text(value) => {
|
||||
if let Some(elem_buffer) = self.elem_buffer.as_ref() {
|
||||
// this is only used with AsXml implementations, so
|
||||
// triggering this is always a coding failure instead of a
|
||||
// runtime error.
|
||||
panic!("got Text after ElementHeadStart but before ElementHeadEnd: Text({:?}) (state = {:?})", value, elem_buffer);
|
||||
}
|
||||
Ok(Some(Event::Text(EventMetrics::zero(), value.into_owned())))
|
||||
}
|
||||
Item::ElementFoot => {
|
||||
let end_ev = Event::EndElement(EventMetrics::zero());
|
||||
let result = if let Some((ns, name, attrs)) = self.elem_buffer.take() {
|
||||
// content-less element
|
||||
self.event_buffer = Some(end_ev);
|
||||
Event::StartElement(EventMetrics::zero(), (ns, name), attrs)
|
||||
} else {
|
||||
end_ev
|
||||
};
|
||||
Ok(Some(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> Iterator for ItemToEvent<I> {
|
||||
type Item = Result<Event, crate::error::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(event) = self.event_buffer.take() {
|
||||
return Some(Ok(event));
|
||||
}
|
||||
loop {
|
||||
let item = match self.inner.next() {
|
||||
Some(Ok(v)) => v,
|
||||
Some(Err(e)) => return Some(Err(e)),
|
||||
None => return None,
|
||||
};
|
||||
match self.update(item).transpose() {
|
||||
Some(v) => return Some(v),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
// 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<I: Iterator<Item = Event>>(events: I) -> Vec<Item<'static>> {
|
||||
|
|
@ -307,3 +423,144 @@ mod tests_minidom {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn items_to_events<'x, I: IntoIterator<Item = Item<'x>>>(
|
||||
items: I,
|
||||
) -> Result<Vec<Event>, crate::error::Error> {
|
||||
let iter = ItemToEvent {
|
||||
inner: items.into_iter().map(|x| Ok(x)),
|
||||
event_buffer: None,
|
||||
elem_buffer: None,
|
||||
};
|
||||
let mut result = Vec::new();
|
||||
for ev in iter {
|
||||
let ev = ev?;
|
||||
result.push(ev);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_to_event_xml_decl() {
|
||||
let items = vec![Item::XmlDeclaration(XmlVersion::V1_0)];
|
||||
let events = items_to_events(items).expect("item conversion");
|
||||
assert_eq!(events.len(), 1);
|
||||
match events[0] {
|
||||
Event::XmlDeclaration(_, XmlVersion::V1_0) => (),
|
||||
ref other => panic!("unexected event in position 0: {:?}", other),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_to_event_simple_empty_element() {
|
||||
let items = vec![
|
||||
Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
|
||||
Item::ElementHeadEnd,
|
||||
Item::ElementFoot,
|
||||
];
|
||||
let events = items_to_events(items).expect("item conversion");
|
||||
assert_eq!(events.len(), 2);
|
||||
match events[0] {
|
||||
Event::StartElement(_, (ref ns, ref name), ref attrs) => {
|
||||
assert_eq!(attrs.len(), 0);
|
||||
assert_eq!(ns, Namespace::none());
|
||||
assert_eq!(name, "elem");
|
||||
}
|
||||
ref other => panic!("unexected event in position 0: {:?}", other),
|
||||
};
|
||||
match events[1] {
|
||||
Event::EndElement(_) => (),
|
||||
ref other => panic!("unexected event in position 1: {:?}", other),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_to_event_short_empty_element() {
|
||||
let items = vec![
|
||||
Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
|
||||
Item::ElementFoot,
|
||||
];
|
||||
let events = items_to_events(items).expect("item conversion");
|
||||
assert_eq!(events.len(), 2);
|
||||
match events[0] {
|
||||
Event::StartElement(_, (ref ns, ref name), ref attrs) => {
|
||||
assert_eq!(attrs.len(), 0);
|
||||
assert_eq!(ns, Namespace::none());
|
||||
assert_eq!(name, "elem");
|
||||
}
|
||||
ref other => panic!("unexected event in position 0: {:?}", other),
|
||||
};
|
||||
match events[1] {
|
||||
Event::EndElement(_) => (),
|
||||
ref other => panic!("unexected event in position 1: {:?}", other),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_to_event_element_with_text_content() {
|
||||
let items = vec![
|
||||
Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
|
||||
Item::ElementHeadEnd,
|
||||
Item::Text(Cow::Borrowed("Hello World!")),
|
||||
Item::ElementFoot,
|
||||
];
|
||||
let events = items_to_events(items).expect("item conversion");
|
||||
assert_eq!(events.len(), 3);
|
||||
match events[0] {
|
||||
Event::StartElement(_, (ref ns, ref name), ref attrs) => {
|
||||
assert_eq!(attrs.len(), 0);
|
||||
assert_eq!(ns, Namespace::none());
|
||||
assert_eq!(name, "elem");
|
||||
}
|
||||
ref other => panic!("unexected event in position 0: {:?}", other),
|
||||
};
|
||||
match events[1] {
|
||||
Event::Text(_, ref value) => {
|
||||
assert_eq!(value, "Hello World!");
|
||||
}
|
||||
ref other => panic!("unexected event in position 1: {:?}", other),
|
||||
};
|
||||
match events[2] {
|
||||
Event::EndElement(_) => (),
|
||||
ref other => panic!("unexected event in position 2: {:?}", other),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_to_event_element_with_attributes() {
|
||||
let items = vec![
|
||||
Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
|
||||
Item::Attribute(
|
||||
Namespace::NONE,
|
||||
Cow::Borrowed("attr".try_into().unwrap()),
|
||||
Cow::Borrowed("value"),
|
||||
),
|
||||
Item::ElementHeadEnd,
|
||||
Item::ElementFoot,
|
||||
];
|
||||
let events = items_to_events(items).expect("item conversion");
|
||||
assert_eq!(events.len(), 2);
|
||||
match events[0] {
|
||||
Event::StartElement(_, (ref ns, ref name), ref attrs) => {
|
||||
assert_eq!(ns, Namespace::none());
|
||||
assert_eq!(name, "elem");
|
||||
assert_eq!(attrs.len(), 1);
|
||||
assert_eq!(
|
||||
attrs.get(Namespace::none(), "attr").map(|x| x.as_str()),
|
||||
Some("value")
|
||||
);
|
||||
}
|
||||
ref other => panic!("unexected event in position 0: {:?}", other),
|
||||
};
|
||||
match events[1] {
|
||||
Event::EndElement(_) => (),
|
||||
ref other => panic!("unexected event in position 2: {:?}", other),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ convert_via_fromstr_and_display! {
|
|||
/// Represent a way to encode/decode text data into a Rust type.
|
||||
///
|
||||
/// This trait can be used in scenarios where implementing [`FromXmlText`]
|
||||
/// and/or [`IntoXmlText`] on a type is not feasible or sensible, such as the
|
||||
/// and/or [`AsXmlText`] on a type is not feasible or sensible, such as the
|
||||
/// following:
|
||||
///
|
||||
/// 1. The type originates in a foreign crate, preventing the implementation
|
||||
|
|
@ -143,7 +143,7 @@ convert_via_fromstr_and_display! {
|
|||
/// 2. There is more than one way to convert a value to/from XML.
|
||||
///
|
||||
/// The codec to use for a text can be specified in the attributes understood
|
||||
/// by `FromXml` and `IntoXml` derive macros. See the documentation of the
|
||||
/// by `FromXml` and `AsXml` derive macros. See the documentation of the
|
||||
/// [`FromXml`][`macro@crate::FromXml`] derive macro for details.
|
||||
pub trait TextCodec<T> {
|
||||
/// Decode a string value into the type.
|
||||
|
|
@ -152,7 +152,7 @@ pub trait TextCodec<T> {
|
|||
/// Encode the type as string value.
|
||||
///
|
||||
/// If this returns `None`, the string value is not emitted at all.
|
||||
fn encode(value: T) -> Result<Option<String>, Error>;
|
||||
fn encode(value: &T) -> Result<Option<Cow<'_, str>>, Error>;
|
||||
}
|
||||
|
||||
/// Text codec which does no transform.
|
||||
|
|
@ -163,8 +163,8 @@ impl TextCodec<String> for Plain {
|
|||
Ok(s)
|
||||
}
|
||||
|
||||
fn encode(value: String) -> Result<Option<String>, Error> {
|
||||
Ok(Some(value))
|
||||
fn encode(value: &String) -> Result<Option<Cow<'_, str>>, Error> {
|
||||
Ok(Some(Cow::Borrowed(value.as_str())))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,9 +180,9 @@ impl TextCodec<Option<String>> for EmptyAsNone {
|
|||
}
|
||||
}
|
||||
|
||||
fn encode(value: Option<String>) -> Result<Option<String>, Error> {
|
||||
Ok(match value {
|
||||
Some(v) if !v.is_empty() => Some(v),
|
||||
fn encode(value: &Option<String>) -> Result<Option<Cow<'_, str>>, Error> {
|
||||
Ok(match value.as_ref() {
|
||||
Some(v) if !v.is_empty() => Some(Cow::Borrowed(v.as_str())),
|
||||
Some(_) | None => None,
|
||||
})
|
||||
}
|
||||
|
|
@ -237,8 +237,8 @@ impl<Filter: TextFilter> TextCodec<Vec<u8>> for Base64<Filter> {
|
|||
.map_err(Error::text_parse_error)
|
||||
}
|
||||
|
||||
fn encode(value: Vec<u8>) -> Result<Option<String>, Error> {
|
||||
Ok(Some(StandardBase64Engine.encode(&value)))
|
||||
fn encode(value: &Vec<u8>) -> Result<Option<Cow<'_, str>>, Error> {
|
||||
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,11 @@ impl<Filter: TextFilter> TextCodec<Option<Vec<u8>>> for Base64<Filter> {
|
|||
Ok(Some(Self::decode(s)?))
|
||||
}
|
||||
|
||||
fn encode(decoded: Option<Vec<u8>>) -> Result<Option<String>, Error> {
|
||||
decoded.map(Self::encode).transpose().map(Option::flatten)
|
||||
fn encode(decoded: &Option<Vec<u8>>) -> Result<Option<Cow<'_, str>>, Error> {
|
||||
decoded
|
||||
.as_ref()
|
||||
.map(Self::encode)
|
||||
.transpose()
|
||||
.map(Option::flatten)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue