@@ -14,6 +14,14 @@ rxml = { version = "0.11.0", default-features = false }
minidom = { version = "^0.15" }
xso_proc = { version = "0.0.2", optional = true }
+# optional dependencies to provide text conversion to/from types from these crates
+# NOTE: because we don't have public/private dependencies yet and cargo
+# defaults to picking the highest matching version by default, the only
+# sensible thing we can do here is to depend on the least version of the most
+# recent semver of each crate.
+jid = { version = "^0.10", optional = true }
+uuid = { version = "^1", optional = true }
+
[features]
macros = [ "dep:xso_proc" ]
minidom = [ "xso_proc/minidom"]
@@ -1,3 +1,4 @@
+#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
/*!
@@ -22,6 +23,7 @@ use of this library in parsing XML streams like specified in RFC 6120.
pub mod error;
#[cfg(feature = "minidom")]
pub mod minidom_compat;
+mod text;
#[doc(hidden)]
pub mod exports {
@@ -30,6 +32,8 @@ pub mod exports {
pub use rxml;
}
+use std::borrow::Cow;
+
#[doc = include_str!("from_xml_doc.md")]
#[doc(inline)]
#[cfg(feature = "macros")]
@@ -130,6 +134,126 @@ pub trait FromXml {
) -> Result<Self::Builder, self::error::FromEventsError>;
}
+/// Trait allowing to convert XML text to a value.
+///
+/// This trait is similar to [`std::str::FromStr`], however, due to
+/// restrictions imposed by the orphan rule, a separate trait is needed.
+/// Implementations for many standard library types are available. In
+/// addition, the following feature flags can enable more implementations:
+///
+/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
+/// - `uuid`: `uuid::Uuid`
+///
+/// Because of this unfortunate situation, we are **extremely liberal** with
+/// accepting optional dependencies for this purpose. You are very welcome to
+/// make merge requests against this crate adding support for parsing
+/// third-party crates.
+pub trait FromXmlText: Sized {
+ /// Convert the given XML text to a value.
+ fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
+}
+
+impl FromXmlText for String {
+ fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
+ Ok(data)
+ }
+}
+
+impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
+ fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
+ Ok(Cow::Owned(T::from_xml_text(data)?))
+ }
+}
+
+impl<T: FromXmlText> FromXmlText for Option<T> {
+ fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
+ Ok(Some(T::from_xml_text(data)?))
+ }
+}
+
+impl<T: FromXmlText> FromXmlText for Box<T> {
+ fn from_xml_text(data: String) -> Result<Self, self::error::Error> {
+ Ok(Box::new(T::from_xml_text(data)?))
+ }
+}
+
+/// Trait to convert a value to an XML text string.
+///
+/// This trait is implemented for many standard library types implementing
+/// [`std::fmt::Display`]. In addition, the following feature flags can enable
+/// more implementations:
+///
+/// - `jid`: `jid::Jid`, `jid::BareJid`, `jid::FullJid`
+/// - `uuid`: `uuid::Uuid`
+///
+/// Because of the unfortunate situation as described in [`FromXmlText`], we
+/// are **extremely liberal** with accepting optional dependencies for this
+/// purpose. You are very welcome to make merge requests against this crate
+/// adding support for parsing third-party crates.
+pub trait IntoXmlText: Sized {
+ /// Convert the value to an XML string in a context where an absent value
+ /// cannot be represented.
+ fn into_xml_text(self) -> Result<String, self::error::Error>;
+
+ /// Convert the value to an XML string in a context where an absent value
+ /// can be represented.
+ ///
+ /// The provided implementation will always return the result of
+ /// [`Self::into_xml_text`] wrapped into `Some(.)`. By re-implementing
+ /// this method, implementors can customize the behaviour for certain
+ /// values.
+ fn into_optional_xml_text(self) -> Result<Option<String>, self::error::Error> {
+ Ok(Some(self.into_xml_text()?))
+ }
+}
+
+impl IntoXmlText for String {
+ fn into_xml_text(self) -> Result<String, self::error::Error> {
+ Ok(self)
+ }
+}
+
+impl<T: IntoXmlText> IntoXmlText for Box<T> {
+ fn into_xml_text(self) -> Result<String, self::error::Error> {
+ T::into_xml_text(*self)
+ }
+}
+
+impl<T: IntoXmlText, B: ToOwned<Owned = T>> IntoXmlText for Cow<'_, B> {
+ fn into_xml_text(self) -> Result<String, self::error::Error> {
+ T::into_xml_text(self.into_owned())
+ }
+}
+
+/// Specialized variant of [`IntoXmlText`].
+///
+/// Do **not** implement this unless you cannot implement [`IntoXmlText`]:
+/// implementing [`IntoXmlText`] is more versatile and an
+/// [`IntoOptionalXmlText`] implementation is automatically provided.
+///
+/// If you need to customize the behaviour of the [`IntoOptionalXmlText`]
+/// blanket implementation, implement a custom
+/// [`IntoXmlText::into_optional_xml_text`] instead.
+pub trait IntoOptionalXmlText {
+ /// Convert the value to an XML string in a context where an absent value
+ /// can be represented.
+ fn into_optional_xml_text(self) -> Result<Option<String>, self::error::Error>;
+}
+
+impl<T: IntoXmlText> IntoOptionalXmlText for T {
+ fn into_optional_xml_text(self) -> Result<Option<String>, self::error::Error> {
+ <Self as IntoXmlText>::into_optional_xml_text(self)
+ }
+}
+
+impl<T: IntoOptionalXmlText> IntoOptionalXmlText for Option<T> {
+ fn into_optional_xml_text(self) -> Result<Option<String>, self::error::Error> {
+ self.map(T::into_optional_xml_text)
+ .transpose()
+ .map(Option::flatten)
+ }
+}
+
/// Attempt to transform a type implementing [`IntoXml`] into another
/// type which implements [`FromXml`].
pub fn transform<T: FromXml, F: IntoXml>(from: F) -> Result<T, self::error::Error> {
@@ -0,0 +1,105 @@
+// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
+//
+// 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/.
+
+//! Module containing implementations for conversions to/from XML text.
+
+use crate::{error::Error, FromXmlText, IntoXmlText};
+
+#[cfg(feature = "jid")]
+use jid;
+#[cfg(feature = "uuid")]
+use uuid;
+
+macro_rules! convert_via_fromstr_and_display {
+ ($($(#[cfg(feature = $feature:literal)])?$t:ty,)+) => {
+ $(
+ $(
+ #[cfg(feature = $feature)]
+ #[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
+ )?
+ impl FromXmlText for $t {
+ fn from_xml_text(s: String) -> Result<Self, Error> {
+ s.parse().map_err(Error::text_parse_error)
+ }
+ }
+
+ $(
+ #[cfg(feature = $feature)]
+ #[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
+ )?
+ impl IntoXmlText for $t {
+ fn into_xml_text(self) -> Result<String, Error> {
+ Ok(self.to_string())
+ }
+ }
+ )+
+ }
+}
+
+/// This provides an implementation compliant with xsd::bool.
+impl FromXmlText for bool {
+ fn from_xml_text(s: String) -> Result<Self, Error> {
+ match s.as_str() {
+ "1" => "true",
+ "0" => "false",
+ other => other,
+ }
+ .parse()
+ .map_err(Error::text_parse_error)
+ }
+}
+
+/// This provides an implementation compliant with xsd::bool.
+impl IntoXmlText for bool {
+ fn into_xml_text(self) -> Result<String, Error> {
+ Ok(self.to_string())
+ }
+}
+
+convert_via_fromstr_and_display! {
+ u8,
+ u16,
+ u32,
+ u64,
+ u128,
+ usize,
+ i8,
+ i16,
+ i32,
+ i64,
+ i128,
+ isize,
+ f32,
+ f64,
+ std::net::IpAddr,
+ std::net::Ipv4Addr,
+ std::net::Ipv6Addr,
+ std::net::SocketAddr,
+ std::net::SocketAddrV4,
+ std::net::SocketAddrV6,
+ std::num::NonZeroU8,
+ std::num::NonZeroU16,
+ std::num::NonZeroU32,
+ std::num::NonZeroU64,
+ std::num::NonZeroU128,
+ std::num::NonZeroUsize,
+ std::num::NonZeroI8,
+ std::num::NonZeroI16,
+ std::num::NonZeroI32,
+ std::num::NonZeroI64,
+ std::num::NonZeroI128,
+ std::num::NonZeroIsize,
+
+ #[cfg(feature = "uuid")]
+ uuid::Uuid,
+
+ #[cfg(feature = "jid")]
+ jid::Jid,
+ #[cfg(feature = "jid")]
+ jid::FullJid,
+ #[cfg(feature = "jid")]
+ jid::BareJid,
+}