diff --git a/xso/src/lib.rs b/xso/src/lib.rs index 6f08d02dea1c95c07c1c5f3addeb90a1773b1d4a..ca4d2924c9c859e186bba0c7904c5da9d3bc8739 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -24,6 +24,7 @@ pub mod error; #[cfg(feature = "minidom")] #[cfg_attr(docsrs, doc(cfg(feature = "minidom")))] pub mod minidom_compat; +mod rxml_util; pub mod text; #[doc(hidden)] @@ -38,6 +39,9 @@ use std::borrow::Cow; #[doc(inline)] pub use text::TextCodec; +#[doc(inline)] +pub use rxml_util::Item; + #[doc = include_str!("from_xml_doc.md")] #[doc(inline)] #[cfg(feature = "macros")] @@ -75,6 +79,29 @@ pub trait IntoXml { fn into_event_iter(self) -> Result; } +/// Trait allowing to iterate a struct's contents as serialisable +/// [`Item`]s. +/// +/// **Important:** Changing the [`ItemIter`][`Self::ItemIter`] associated +/// type is considered a non-breaking change for any given implementation of +/// this trait. Always refer to a type's iterator type using fully-qualified +/// notation, for example: `::ItemIter`. +pub trait AsXml { + /// The iterator type. + /// + /// **Important:** Changing this type is considered a non-breaking change + /// for any given implementation of this trait. Always refer to a type's + /// iterator type using fully-qualified notation, for example: + /// `::ItemIter`. + type ItemIter<'x>: Iterator, self::error::Error>> + where + Self: 'x; + + /// Return an iterator which emits the contents of the struct or enum as + /// serialisable [`Item`] items. + fn as_xml_iter(&self) -> Result, self::error::Error>; +} + /// Trait for a temporary object allowing to construct a struct from /// [`rxml::Event`] items. /// diff --git a/xso/src/rxml_util.rs b/xso/src/rxml_util.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd64e51a378c3a7b58b1ddcfe4c2cdd87800209d --- /dev/null +++ b/xso/src/rxml_util.rs @@ -0,0 +1,92 @@ +// Copyright (c) 2024 Jonas Schäfer +// +// 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/. + +//! Utilities which may eventually move upstream to the `rxml` crate. + +use std::borrow::Cow; + +use rxml::{Namespace, NcNameStr, XmlVersion}; + +/// An encodable item. +/// +/// Unlike [`rxml::Item`], the contents of this item may either be owned or +/// borrowed, individually. This enables the use in an [`crate::AsXml`] trait +/// even if data needs to be generated during serialisation. +#[derive(Debug)] +pub enum Item<'x> { + /// XML declaration + XmlDeclaration(XmlVersion), + + /// Start of an element header + ElementHeadStart( + /// Namespace name + Namespace, + /// Local name of the attribute + Cow<'x, NcNameStr>, + ), + + /// An attribute key/value pair + Attribute( + /// Namespace name + Namespace, + /// Local name of the attribute + Cow<'x, NcNameStr>, + /// Value of the attribute + Cow<'x, str>, + ), + + /// End of an element header + ElementHeadEnd, + + /// A piece of text (in element content, not attributes) + Text(Cow<'x, str>), + + /// Footer of an element + /// + /// This can be used either in places where [`Text`] could be used to + /// close the most recently opened unclosed element, or it can be used + /// instead of [`ElementHeadEnd`] to close the element using `/>`, without + /// any child content. + /// + /// [`Text`]: Self::Text + /// [`ElementHeadEnd`]: Self::ElementHeadEnd + ElementFoot, +} + +impl Item<'_> { + /// Exchange all borrowed pieces inside this item for owned items, cloning + /// them if necessary. + pub fn into_owned(self) -> Item<'static> { + match self { + Self::XmlDeclaration(v) => Item::XmlDeclaration(v), + Self::ElementHeadStart(ns, name) => { + Item::ElementHeadStart(ns, Cow::Owned(name.into_owned())) + } + Self::Attribute(ns, name, value) => Item::Attribute( + ns, + Cow::Owned(name.into_owned()), + Cow::Owned(value.into_owned()), + ), + Self::ElementHeadEnd => Item::ElementHeadEnd, + Self::Text(value) => Item::Text(Cow::Owned(value.into_owned())), + Self::ElementFoot => Item::ElementFoot, + } + } + + /// Return an [`rxml::Item`], which borrows data from this item. + pub fn as_rxml_item<'x>(&'x self) -> rxml::Item<'x> { + match self { + Self::XmlDeclaration(ref v) => rxml::Item::XmlDeclaration(*v), + Self::ElementHeadStart(ref ns, ref name) => rxml::Item::ElementHeadStart(ns, &**name), + Self::Attribute(ref ns, ref name, ref value) => { + rxml::Item::Attribute(ns, &**name, &**value) + } + Self::ElementHeadEnd => rxml::Item::ElementHeadEnd, + Self::Text(ref value) => rxml::Item::Text(&**value), + Self::ElementFoot => rxml::Item::ElementFoot, + } + } +}