Port everything over to AsXml

Jonas SchΓ€fer created

Change summary

parsers/src/attention.rs           |   4 
parsers/src/avatar.rs              |   4 
parsers/src/blocking.rs            |   6 
parsers/src/bob.rs                 |  18 +
parsers/src/bookmarks.rs           |   4 
parsers/src/carbons.rs             |   8 
parsers/src/cert_management.rs     |   6 
parsers/src/csi.rs                 |   8 
parsers/src/data_forms_validate.rs |   4 
parsers/src/date.rs                |   9 
parsers/src/delay.rs               |   4 
parsers/src/disco.rs               |   8 
parsers/src/eme.rs                 |   4 
parsers/src/extdisco.rs            |   4 
parsers/src/fast.rs                |  10 
parsers/src/hashes.rs              |  26 +-
parsers/src/http_upload.rs         |   6 
parsers/src/ibb.rs                 |   6 
parsers/src/idle.rs                |   4 
parsers/src/jid_prep.rs            |   6 
parsers/src/jingle_ft.rs           |   4 
parsers/src/jingle_grouping.rs     |   4 
parsers/src/jingle_ice_udp.rs      |   4 
parsers/src/jingle_raw_udp.rs      |   4 
parsers/src/jingle_rtcp_fb.rs      |   4 
parsers/src/jingle_rtp.rs          |   6 
parsers/src/jingle_ssma.rs         |   4 
parsers/src/jingle_thumnails.rs    |   4 
parsers/src/legacy_omemo.rs        |  18 +-
parsers/src/mam.rs                 |   8 
parsers/src/message_correct.rs     |   4 
parsers/src/mix.rs                 |  10 
parsers/src/muc/muc.rs             |   4 
parsers/src/muc/user.rs            |   4 
parsers/src/occupant_id.rs         |   4 
parsers/src/openpgp.rs             |   6 
parsers/src/ping.rs                |   4 
parsers/src/pubsub/owner.rs        |  10 
parsers/src/pubsub/pubsub.rs       |  10 
parsers/src/reactions.rs           |   4 
parsers/src/receipts.rs            |   6 
parsers/src/rtt.rs                 |   6 
parsers/src/sasl.rs                |  12 
parsers/src/sm.rs                  |  12 
parsers/src/stanza_id.rs           |   6 
parsers/src/stream.rs              |   4 
parsers/src/time.rs                |   4 
parsers/src/util/macro_tests.rs    |  34 ++--
parsers/src/util/macros.rs         |  46 ++--
parsers/src/vcard.rs               |   4 
parsers/src/version.rs             |   4 
parsers/src/websocket.rs           |   4 
xso-proc/Cargo.toml                |   2 
xso-proc/src/compound.rs           |  89 ++++++---
xso-proc/src/field.rs              |  36 +--
xso-proc/src/lib.rs                |  31 +-
xso-proc/src/scope.rs              |  21 +
xso-proc/src/state.rs              |  69 ++++----
xso-proc/src/structs.rs            |  74 ++++++--
xso-proc/src/types.rs              | 151 ++++++++++++++++-
xso/src/from_xml_doc.md            |  10 
xso/src/lib.rs                     |  30 ++
xso/src/rxml_util.rs               | 267 +++++++++++++++++++++++++++++++
xso/src/text.rs                    |  28 +-
64 files changed, 848 insertions(+), 371 deletions(-)

Detailed changes

parsers/src/attention.rs πŸ”—

@@ -3,13 +3,13 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 
 /// Requests the attention of the recipient.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::ATTENTION, name = "attention")]
 pub struct Attention;
 

parsers/src/avatar.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::hashes::Sha1HexAttribute;
 use crate::ns;
@@ -23,7 +23,7 @@ generate_element!(
 impl PubSubPayload for Metadata {}
 
 /// Communicates avatar metadata.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::AVATAR_METADATA, name = "info")]
 pub struct Info {
     /// The size of the image data in bytes.

parsers/src/blocking.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
@@ -14,7 +14,7 @@ use xso::error::FromElementError;
 
 /// The element requesting the blocklist, the result iq will contain a
 /// [BlocklistResult].
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::BLOCKING, name = "blocklist")]
 pub struct BlocklistRequest;
 
@@ -88,7 +88,7 @@ generate_blocking_element!(
 impl IqSetPayload for Unblock {}
 
 /// The application-specific error condition when a message is blocked.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::BLOCKING_ERRORS, name = "blocked")]
 pub struct Blocked;
 

parsers/src/bob.rs πŸ”—

@@ -4,12 +4,14 @@
 // 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 xso::{error::Error, text::Base64, FromXml, FromXmlText, IntoXml, IntoXmlText};
+use std::borrow::Cow;
+use std::str::FromStr;
+
+use xso::{error::Error, text::Base64, AsXml, AsXmlText, FromXml, FromXmlText};
 
 use crate::hashes::{Algo, Hash};
 use crate::ns;
 use minidom::IntoAttributeValue;
-use std::str::FromStr;
 
 /// A Content-ID, as defined in RFC2111.
 ///
@@ -56,14 +58,18 @@ impl FromXmlText for ContentId {
     }
 }
 
-impl IntoXmlText for ContentId {
-    fn into_xml_text(self) -> Result<String, Error> {
+impl AsXmlText for ContentId {
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
         let algo = match self.hash.algo {
             Algo::Sha_1 => "sha1",
             Algo::Sha_256 => "sha256",
             _ => unimplemented!(),
         };
-        Ok(format!("{}+{}@bob.xmpp.org", algo, self.hash.to_hex()))
+        Ok(Cow::Owned(format!(
+            "{}+{}@bob.xmpp.org",
+            algo,
+            self.hash.to_hex()
+        )))
     }
 }
 
@@ -79,7 +85,7 @@ impl IntoAttributeValue for ContentId {
 }
 
 /// Request for an uncached cid file.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::BOB, name = "data")]
 pub struct Data {
     /// The cid in question.

parsers/src/bookmarks.rs πŸ”—

@@ -16,7 +16,7 @@
 //!
 //! The [`Conference`][crate::bookmarks::Conference] struct used in [`private::Query`][`crate::private::Query`] is the one from this module. Only the querying mechanism changes from a legacy PubSub implementation here, to a legacy Private XML Query implementation in that other module. The [`Conference`][crate::bookmarks2::Conference] element from the [`bookmarks2`][crate::bookmarks2] module is a different structure, but conversion is possible from [`bookmarks::Conference`][crate::bookmarks::Conference] to [`bookmarks2::Conference`][crate::bookmarks2::Conference] via the [`Conference::into_bookmarks2`][crate::bookmarks::Conference::into_bookmarks2] method.
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use jid::BareJid;
 
@@ -63,7 +63,7 @@ impl Conference {
 }
 
 /// An URL bookmark.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::BOOKMARKS, name = "url")]
 pub struct Url {
     /// A user-defined name for this URL.

parsers/src/carbons.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::forwarding::Forwarded;
 use crate::iq::IqSetPayload;
@@ -12,14 +12,14 @@ use crate::message::MessagePayload;
 use crate::ns;
 
 /// Enable carbons for this session.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CARBONS, name = "enable")]
 pub struct Enable;
 
 impl IqSetPayload for Enable {}
 
 /// Disable a previously-enabled carbons.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CARBONS, name = "disable")]
 pub struct Disable;
 
@@ -27,7 +27,7 @@ impl IqSetPayload for Disable {}
 
 /// Request the enclosing message to not be copied to other carbons-enabled
 /// resources of the user.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CARBONS, name = "private")]
 pub struct Private;
 

parsers/src/cert_management.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
+use xso::{text::Base64, AsXml, FromXml};
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
@@ -17,7 +17,7 @@ generate_elem_id!(
 );
 
 /// An X.509 certificate.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL_CERT, name = "x509cert")]
 pub struct Cert {
     /// The BER X.509 data.
@@ -43,7 +43,7 @@ generate_element!(
 impl IqSetPayload for Append {}
 
 /// Client requests the current list of X.509 certificates.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL_CERT, name = "items")]
 pub struct ListCertsQuery;
 

parsers/src/csi.rs πŸ”—

@@ -4,22 +4,22 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::ns;
 
 /// Stream:feature sent by the server to advertise it supports CSI.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CSI, name = "csi")]
 pub struct Feature;
 
 /// Client indicates it is inactive.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CSI, name = "inactive")]
 pub struct Inactive;
 
 /// Client indicates it is active again.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::CSI, name = "active")]
 pub struct Active;
 

parsers/src/data_forms_validate.rs πŸ”—

@@ -8,7 +8,7 @@ use std::fmt::{Display, Formatter};
 use std::str::FromStr;
 
 use minidom::{Element, IntoAttributeValue};
-use xso::{error::FromElementError, FromXml, IntoXml};
+use xso::{error::FromElementError, AsXml, FromXml};
 
 use crate::ns::{self, XDATA_VALIDATE};
 use crate::Error;
@@ -67,7 +67,7 @@ pub enum Method {
 }
 
 /// Selection Ranges in "list-multi"
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::XDATA_VALIDATE, name = "list-range")]
 pub struct ListRange {
     /// The 'min' attribute specifies the minimum allowable number of selected/entered values.

parsers/src/date.rs πŸ”—

@@ -4,9 +4,10 @@
 // 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::str::FromStr;
 
-use xso::{error::Error, FromXmlText, IntoXmlText};
+use xso::{error::Error, AsXmlText, FromXmlText};
 
 use chrono::{DateTime as ChronoDateTime, FixedOffset};
 use minidom::{IntoAttributeValue, Node};
@@ -48,9 +49,9 @@ impl FromXmlText for DateTime {
     }
 }
 
-impl IntoXmlText for DateTime {
-    fn into_xml_text(self) -> Result<String, Error> {
-        Ok(self.0.to_rfc3339())
+impl AsXmlText for DateTime {
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
+        Ok(Cow::Owned(self.0.to_rfc3339()))
     }
 }
 

parsers/src/delay.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{text::EmptyAsNone, FromXml, IntoXml};
+use xso::{text::EmptyAsNone, AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::message::MessagePayload;
@@ -13,7 +13,7 @@ use crate::presence::PresencePayload;
 use jid::Jid;
 
 /// Notes when and by whom a message got stored for later delivery.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::DELAY, name = "delay")]
 pub struct Delay {
     /// The entity which delayed this message.

parsers/src/disco.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::data_forms::{DataForm, DataFormType};
@@ -20,7 +20,7 @@ use jid::Jid;
 ///
 /// It should only be used in an `<iq type='get'/>`, as it can only represent
 /// the request, and not a result.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::DISCO_INFO, name = "query")]
 pub struct DiscoInfoQuery {
     /// Node on which we are doing the discovery.
@@ -31,7 +31,7 @@ pub struct DiscoInfoQuery {
 impl IqGetPayload for DiscoInfoQuery {}
 
 /// Structure representing a `<feature xmlns='http://jabber.org/protocol/disco#info'/>` element.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq, Eq, Hash)]
 #[xml(namespace = ns::DISCO_INFO, name = "feature")]
 pub struct Feature {
     /// Namespace of the feature we want to represent.
@@ -203,7 +203,7 @@ children: [
 impl IqGetPayload for DiscoItemsQuery {}
 
 /// Structure representing an `<item xmlns='http://jabber.org/protocol/disco#items'/>` element.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::DISCO_ITEMS, name = "item")]
 pub struct Item {
     /// JID of the entity pointed by this item.

parsers/src/eme.rs πŸ”—

@@ -4,13 +4,13 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 
 /// Structure representing an `<encryption xmlns='urn:xmpp:eme:0'/>` element.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::EME, name = "encryption")]
 pub struct ExplicitMessageEncryption {
     /// Namespace of the encryption scheme used.

parsers/src/extdisco.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::data_forms::DataForm;
 use crate::date::DateTime;
@@ -101,7 +101,7 @@ generate_element!(
 impl IqGetPayload for Service {}
 
 /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::EXT_DISCO, name = "services")]
 pub struct ServicesQuery {
     /// TODO

parsers/src/fast.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::ns;
@@ -14,7 +14,7 @@ generate_elem_id!(
     Mechanism, "mechanism", FAST
 );
 
-// TODO: Replace this with a proper bool once we can derive FromXml and IntoXml on FastQuery.
+// TODO: Replace this with a proper bool once we can derive FromXml and AsXml on FastQuery.
 generate_attribute!(
     /// Whether TLS zero-roundtrip is possible.
     Tls0Rtt, "tls-0rtt", bool
@@ -34,7 +34,7 @@ children: [
 );
 
 /// This is the `<fast/>` element the client MUST include within its SASL2 authentication request.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::FAST, name = "fast")]
 pub struct FastResponse {
     /// Servers MUST reject any authentication requests received via TLS 0-RTT payloads that do not
@@ -51,7 +51,7 @@ pub struct FastResponse {
 }
 
 /// This is the `<request-token/>` element sent by the client in the SASL2 authenticate step.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::FAST, name = "request-token")]
 pub struct RequestToken {
     /// This element MUST contain a 'mechanism' attribute, the value of which MUST be one of the
@@ -62,7 +62,7 @@ pub struct RequestToken {
 
 /// This is the `<token/>` element sent by the server on successful SASL2 authentication containing
 /// a `<request-token/>` element.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::FAST, name = "token")]
 pub struct Token {
     /// The secret token to be used for subsequent authentications, as generated by the server.

parsers/src/hashes.rs πŸ”—

@@ -4,14 +4,16 @@
 // 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 xso::{error::Error, text::Base64, FromXml, FromXmlText, IntoXml, IntoXmlText};
-
-use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
-use minidom::IntoAttributeValue;
+use std::borrow::Cow;
 use std::num::ParseIntError;
 use std::ops::{Deref, DerefMut};
 use std::str::FromStr;
 
+use xso::{error::Error, text::Base64, AsXml, AsXmlText, FromXml, FromXmlText};
+
+use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
+use minidom::IntoAttributeValue;
+
 use crate::ns;
 
 /// List of the algorithms we support, or Unknown.
@@ -97,9 +99,9 @@ impl FromXmlText for Algo {
     }
 }
 
-impl IntoXmlText for Algo {
-    fn into_xml_text(self) -> Result<String, Error> {
-        Ok(String::from(match self {
+impl AsXmlText for Algo {
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
+        Ok(Cow::Borrowed(match self {
             Algo::Sha_1 => "sha-1",
             Algo::Sha_256 => "sha-256",
             Algo::Sha_512 => "sha-512",
@@ -107,7 +109,7 @@ impl IntoXmlText for Algo {
             Algo::Sha3_512 => "sha3-512",
             Algo::Blake2b_256 => "blake2b-256",
             Algo::Blake2b_512 => "blake2b-512",
-            Algo::Unknown(text) => return Ok(text),
+            Algo::Unknown(text) => text.as_str(),
         }))
     }
 }
@@ -120,7 +122,7 @@ impl IntoAttributeValue for Algo {
 
 /// This element represents a hash of some data, defined by the hash
 /// algorithm used and the computed value.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::HASHES, name = "hash")]
 pub struct Hash {
     /// The algorithm used to create this hash.
@@ -213,9 +215,9 @@ impl FromXmlText for Sha1HexAttribute {
     }
 }
 
-impl IntoXmlText for Sha1HexAttribute {
-    fn into_xml_text(self) -> Result<String, xso::error::Error> {
-        Ok(self.to_hex())
+impl AsXmlText for Sha1HexAttribute {
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, xso::error::Error> {
+        Ok(Cow::Owned(self.to_hex()))
     }
 }
 

parsers/src/http_upload.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::iq::{IqGetPayload, IqResultPayload};
@@ -14,7 +14,7 @@ use crate::ns;
 use crate::Element;
 
 /// Requesting a slot
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::HTTP_UPLOAD, name = "request")]
 pub struct SlotRequest {
     /// The filename to be uploaded.
@@ -97,7 +97,7 @@ generate_element!(
 );
 
 /// Get URL
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::HTTP_UPLOAD, name = "get")]
 pub struct Get {
     /// URL

parsers/src/ibb.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
+use xso::{text::Base64, AsXml, FromXml};
 
 use crate::iq::IqSetPayload;
 use crate::ns;
@@ -44,7 +44,7 @@ attributes: [
 impl IqSetPayload for Open {}
 
 /// Exchange a chunk of data in an open stream.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::IBB, name = "data")]
 pub struct Data {
     /// Sequence number of this chunk, must wraparound after 65535.
@@ -63,7 +63,7 @@ pub struct Data {
 impl IqSetPayload for Data {}
 
 /// Close an open stream.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::IBB, name = "close")]
 pub struct Close {
     /// The identifier of the stream to be closed.

parsers/src/idle.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::ns;
 use crate::presence::PresencePayload;
 
 /// Represents the last time the user interacted with their system.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::IDLE, name = "idle")]
 pub struct Idle {
     /// The time at which the user stopped interacting.

parsers/src/jid_prep.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use jid::Jid;
 
@@ -12,7 +12,7 @@ use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
 
 /// Request from a client to stringprep/PRECIS a string into a JID.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JID_PREP, name = "jid")]
 pub struct JidPrepQuery {
     /// The potential JID.
@@ -30,7 +30,7 @@ impl JidPrepQuery {
 }
 
 /// Response from the server with the stringprep’d/PRECIS’d JID.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JID_PREP, name = "jid")]
 pub struct JidPrepResponse {
     /// The JID.

parsers/src/jingle_ft.rs πŸ”—

@@ -13,7 +13,7 @@ use std::collections::BTreeMap;
 use std::str::FromStr;
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 generate_element!(
@@ -323,7 +323,7 @@ impl From<Checksum> for Element {
 }
 
 /// A notice that the file transfer has been completed.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_FT, name = "received")]
 pub struct Received {
     /// The content identifier of this Jingle session.

parsers/src/jingle_grouping.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::jingle::ContentId;
 use crate::ns;
@@ -21,7 +21,7 @@ generate_attribute!(
 );
 
 /// Describes a content that should be grouped with other ones.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_GROUPING, name = "content")]
 pub struct Content {
     /// The name of the matching [`Content`](crate::jingle::Content).

parsers/src/jingle_ice_udp.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use std::net::IpAddr;
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::jingle_dtls_srtp::Fingerprint;
 use crate::ns;
@@ -68,7 +68,7 @@ generate_attribute!(
 );
 
 /// A candidate for an ICE-UDP session.
-#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
 #[xml(namespace = ns::JINGLE_ICE_UDP, name = "candidate")]
 pub struct Candidate {
     /// A Component ID as defined in ICE-CORE.

parsers/src/jingle_raw_udp.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use std::net::IpAddr;
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::jingle_ice_udp::Type;
 use crate::ns;
@@ -35,7 +35,7 @@ impl Transport {
 }
 
 /// A candidate for an ICE-UDP session.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_RAW_UDP, name = "candidate")]
 pub struct Candidate {
     /// A Component ID as defined in ICE-CORE.

parsers/src/jingle_rtcp_fb.rs πŸ”—

@@ -4,12 +4,12 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::ns;
 
 /// Wrapper element for a rtcp-fb.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_RTCP_FB, name = "rtcp-fb")]
 pub struct RtcpFb {
     /// Type of this rtcp-fb.

parsers/src/jingle_rtp.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::jingle_rtcp_fb::RtcpFb;
 use crate::jingle_rtp_hdrext::RtpHdrext;
@@ -13,7 +13,7 @@ use crate::ns;
 
 /// Specifies the ability to multiplex RTP Data and Control Packets on a single port as
 /// described in RFCΒ 5761.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_RTP, name = "rtcp-mux")]
 pub struct RtcpMux;
 
@@ -138,7 +138,7 @@ impl PayloadType {
 }
 
 /// Parameter related to a payload.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_RTP, name = "parameter")]
 pub struct Parameter {
     /// The name of the parameter, from the list at

parsers/src/jingle_ssma.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::ns;
 
@@ -32,7 +32,7 @@ impl Source {
 }
 
 /// Parameter associated with a ssrc.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_SSMA, name = "parameter")]
 pub struct Parameter {
     /// The name of the parameter.

parsers/src/jingle_thumnails.rs πŸ”—

@@ -6,12 +6,12 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::ns;
 
 /// A Jingle thumbnail.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::JINGLE_THUMBNAILS, name = "thumbnail")]
 pub struct Thumbnail {
     /// The URI of the thumbnail.

parsers/src/legacy_omemo.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
+use xso::{text::Base64, AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 use crate::pubsub::PubSubPayload;
 
 /// Element of the device list
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "device")]
 pub struct Device {
     /// Device id
@@ -33,7 +33,7 @@ impl PubSubPayload for DeviceList {}
 
 /// SignedPreKey public key
 /// Part of a device's bundle
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "signedPreKeyPublic")]
 pub struct SignedPreKeyPublic {
     /// SignedPreKey id
@@ -47,7 +47,7 @@ pub struct SignedPreKeyPublic {
 
 /// SignedPreKey signature
 /// Part of a device's bundle
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "signedPreKeySignature")]
 pub struct SignedPreKeySignature {
     /// Signature bytes
@@ -56,7 +56,7 @@ pub struct SignedPreKeySignature {
 }
 
 /// Part of a device's bundle
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "identityKey")]
 pub struct IdentityKey {
     /// Serialized PublicKey
@@ -76,7 +76,7 @@ generate_element!(
 
 /// PreKey public key
 /// Part of a device's bundle
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "preKeyPublic")]
 pub struct PreKeyPublic {
     /// PreKey id
@@ -123,7 +123,7 @@ generate_element!(
 );
 
 /// IV used for payload encryption
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "iv")]
 pub struct IV {
     /// IV bytes
@@ -139,7 +139,7 @@ generate_attribute!(
 );
 
 /// Part of the OMEMO element header
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "key")]
 pub struct Key {
     /// The device id this key is encrypted for.
@@ -159,7 +159,7 @@ pub struct Key {
 }
 
 /// The encrypted message body
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::LEGACY_OMEMO, name = "payload")]
 pub struct Payload {
     /// Encrypted with AES-128 in Galois/Counter Mode (GCM)

parsers/src/mam.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::data_forms::DataForm;
@@ -165,7 +165,7 @@ generate_element!(
 impl IqResultPayload for Fin {}
 
 /// Metadata of the first message in the archive.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::MAM, name = "start")]
 pub struct Start {
     /// The id of the first message in the archive.
@@ -178,7 +178,7 @@ pub struct Start {
 }
 
 /// Metadata of the last message in the archive.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::MAM, name = "end")]
 pub struct End {
     /// The id of the last message in the archive.
@@ -191,7 +191,7 @@ pub struct End {
 }
 
 /// Request an archive for its metadata.
-#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 #[xml(namespace = ns::MAM, name = "metadata")]
 pub struct MetadataQuery;
 

parsers/src/message_correct.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 
 /// Defines that the message containing this payload should replace a
 /// previous message, identified by the id.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::MESSAGE_CORRECT, name = "replace")]
 pub struct Replace {
     /// The 'id' attribute of the message getting corrected.

parsers/src/mix.rs πŸ”—

@@ -7,7 +7,7 @@
 // TODO: validate nicks by applying the β€œnickname” profile of the PRECIS OpaqueString class, as
 // defined in RFC 7700.
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::iq::{IqResultPayload, IqSetPayload};
 use crate::message::MessagePayload;
@@ -59,7 +59,7 @@ impl Participant {
 }
 
 /// A node to subscribe to.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::MIX_CORE, name = "subscribe")]
 pub struct Subscribe {
     /// The PubSub node to subscribe to.
@@ -151,7 +151,7 @@ impl UpdateSubscription {
 
 /// Request to leave a given MIX channel.  It will automatically unsubscribe the user from all
 /// nodes on this channel.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::MIX_CORE, name = "leave")]
 pub struct Leave;
 
@@ -204,7 +204,7 @@ impl Mix {
 }
 
 /// Create a new MIX channel.
-#[derive(FromXml, IntoXml, PartialEq, Clone, Debug, Default)]
+#[derive(FromXml, AsXml, PartialEq, Clone, Debug, Default)]
 #[xml(namespace = ns::MIX_CORE, name = "create")]
 pub struct Create {
     /// The requested channel identifier.
@@ -230,7 +230,7 @@ impl Create {
 }
 
 /// Destroy a given MIX channel.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::MIX_CORE, name = "destroy")]
 pub struct Destroy {
     /// The channel identifier to be destroyed.

parsers/src/muc/muc.rs πŸ”—

@@ -5,14 +5,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::ns;
 use crate::presence::PresencePayload;
 
 /// Represents the query for messages before our join.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone, Default)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
 #[xml(namespace = ns::MUC, name = "history")]
 pub struct History {
     /// How many characters of history to send, in XML characters.

parsers/src/muc/user.rs πŸ”—

@@ -7,7 +7,7 @@
 
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::message::MessagePayload;
@@ -131,7 +131,7 @@ impl From<Actor> for Element {
 
 /// Used to continue a one-to-one discussion in a room, with more than one
 /// participant.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::MUC_USER, name = "continue")]
 pub struct Continue {
     /// The thread to continue in this room.

parsers/src/occupant_id.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
@@ -14,7 +14,7 @@ use crate::presence::PresencePayload;
 ///
 /// It allows clients to identify a MUC participant across reconnects and
 /// renames. It thus prevents impersonification of anonymous users.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::OID, name = "occupant-id")]
 pub struct OccupantId {
     /// The id associated to the sending user by the MUC service.

parsers/src/openpgp.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
+use xso::{text::Base64, AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::ns;
@@ -12,7 +12,7 @@ use crate::pubsub::PubSubPayload;
 
 /// Data contained in the PubKey element
 // TODO: Merge this container with the PubKey struct
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::OX, name = "data")]
 pub struct PubKeyData {
     /// Base64 data
@@ -36,7 +36,7 @@ generate_element!(
 impl PubSubPayload for PubKey {}
 
 /// Public key metadata
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::OX, name = "pubkey-metadata")]
 pub struct PubKeyMeta {
     /// OpenPGP v4 fingerprint

parsers/src/ping.rs πŸ”—

@@ -5,14 +5,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::iq::IqGetPayload;
 use crate::ns;
 
 /// Represents a ping to the recipient, which must be answered with an
 /// empty `<iq/>` or with an error.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PING, name = "ping")]
 pub struct Ping;
 

parsers/src/pubsub/owner.rs πŸ”—

@@ -5,7 +5,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::data_forms::DataForm;
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
@@ -29,7 +29,7 @@ generate_element!(
 );
 
 /// An affiliation element.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB_OWNER, name = "affiliation")]
 pub struct Affiliation {
     /// The node this affiliation pertains to.
@@ -77,7 +77,7 @@ generate_element!(
 );
 
 /// A redirect element.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB_OWNER, name = "redirect")]
 pub struct Redirect {
     /// The node this node will be redirected to.
@@ -86,7 +86,7 @@ pub struct Redirect {
 }
 
 /// Request to clear a node.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB_OWNER, name = "purge")]
 pub struct Purge {
     /// The node to be cleared.
@@ -108,7 +108,7 @@ generate_element!(
 );
 
 /// A subscription element, describing the state of a subscription.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB_OWNER, name = "subscription")]
 pub struct SubscriptionElem {
     /// The JID affected by this subscription.

parsers/src/pubsub/pubsub.rs πŸ”—

@@ -6,7 +6,7 @@
 
 use xso::{
     error::{Error, FromElementError},
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::data_forms::DataForm;
@@ -34,7 +34,7 @@ generate_element!(
 );
 
 /// An affiliation element.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB, name = "affiliation")]
 pub struct Affiliation {
     /// The node this affiliation pertains to.
@@ -56,7 +56,7 @@ generate_element!(
 );
 
 /// Request to create a new node.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::PUBSUB, name = "create")]
 pub struct Create {
     /// The node name to create, if `None` the service will generate one.
@@ -222,7 +222,7 @@ impl From<SubscribeOptions> for Element {
 }
 
 /// A request to subscribe a JID to a node.
-#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
 #[xml(namespace = ns::PUBSUB, name = "subscribe")]
 pub struct Subscribe {
     /// The JID being subscribed.
@@ -270,7 +270,7 @@ generate_element!(
 );
 
 /// An unsubscribe request.
-#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
+#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
 #[xml(namespace = ns::PUBSUB, name = "unsubscribe")]
 pub struct Unsubscribe {
     /// The JID affected by this request.

parsers/src/reactions.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
@@ -25,7 +25,7 @@ generate_element!(
 impl MessagePayload for Reactions {}
 
 /// One emoji reaction.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::REACTIONS, name = "reaction")]
 pub struct Reaction {
     /// The text of this reaction.

parsers/src/receipts.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 
 /// Requests that this message is acked by the final recipient once
 /// received.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::RECEIPTS, name = "request")]
 pub struct Request;
 
@@ -19,7 +19,7 @@ impl MessagePayload for Request {}
 
 /// Notes that a previous message has correctly been received, it is
 /// referenced by its 'id' attribute.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::RECEIPTS, name = "received")]
 pub struct Received {
     /// The 'id' attribute of the received message.

parsers/src/rtt.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{text::EmptyAsNone, FromXml, IntoXml};
+use xso::{text::EmptyAsNone, AsXml, FromXml};
 
 use crate::ns;
 use crate::Element;
@@ -31,7 +31,7 @@ generate_attribute!(
 );
 
 /// Supports the transmission of text, including key presses, and text block inserts.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::RTT, name = "t")]
 pub struct Insert {
     /// Position in the message to start inserting from.  If None, this means to start from the
@@ -113,7 +113,7 @@ impl TryFrom<Action> for Erase {
 
 /// Allow for the transmission of intervals, between real-time text actions, to recreate the
 /// pauses between key presses.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::RTT, name = "w")]
 pub struct Wait {
     /// Amount of milliseconds to wait before the next action.

parsers/src/sasl.rs πŸ”—

@@ -7,7 +7,7 @@
 use xso::{
     error::{Error, FromElementError},
     text::Base64,
-    FromXml, IntoXml,
+    AsXml, FromXml,
 };
 
 use crate::ns;
@@ -48,7 +48,7 @@ generate_attribute!(
 
 /// The first step of the SASL process, selecting the mechanism and sending
 /// the first part of the handshake.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL, name = "auth")]
 pub struct Auth {
     /// The mechanism used.
@@ -63,7 +63,7 @@ pub struct Auth {
 /// In case the mechanism selected at the [auth](struct.Auth.html) step
 /// requires a second step, the server sends this element with additional
 /// data.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL, name = "challenge")]
 pub struct Challenge {
     /// The challenge data.
@@ -74,7 +74,7 @@ pub struct Challenge {
 /// In case the mechanism selected at the [auth](struct.Auth.html) step
 /// requires a second step, this contains the client’s response to the
 /// server’s [challenge](struct.Challenge.html).
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL, name = "response")]
 pub struct Response {
     /// The response data.
@@ -84,12 +84,12 @@ pub struct Response {
 
 /// Sent by the client at any point after [auth](struct.Auth.html) if it
 /// wants to cancel the current authentication process.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL, name = "abort")]
 pub struct Abort;
 
 /// Sent by the server on SASL success.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SASL, name = "success")]
 pub struct Success {
     /// Possible data sent on success.

parsers/src/sm.rs πŸ”—

@@ -4,13 +4,13 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::ns;
 use crate::stanza_error::DefinedCondition;
 
 /// Acknowledgement of the currently received stanzas.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SM, name = "a")]
 pub struct A {
     /// The last handled stanza.
@@ -105,12 +105,12 @@ generate_element!(
 );
 
 /// Requests the currently received stanzas by the other party.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SM, name = "r")]
 pub struct R;
 
 /// Requests a stream resumption.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SM, name = "resume")]
 pub struct Resume {
     /// The last handled stanza.
@@ -124,7 +124,7 @@ pub struct Resume {
 }
 
 /// The response by the server for a successfully resumed stream.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SM, name = "resumed")]
 pub struct Resumed {
     /// The last handled stanza.
@@ -139,7 +139,7 @@ pub struct Resumed {
 
 // TODO: add support for optional and required.
 /// Represents availability of Stream Management in `<stream:features/>`.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SM, name = "sm")]
 pub struct StreamManagement;
 

parsers/src/stanza_id.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
@@ -12,7 +12,7 @@ use jid::Jid;
 
 /// Gives the identifier a service has stamped on this stanza, often in
 /// order to identify it inside of [an archive](../mam/index.html).
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SID, name = "stanza-id")]
 pub struct StanzaId {
     /// The id associated to this stanza by another entity.
@@ -28,7 +28,7 @@ impl MessagePayload for StanzaId {}
 
 /// A hack for MUC before version 1.31 to track a message which may have
 /// its 'id' attribute changed.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::SID, name = "origin-id")]
 pub struct OriginId {
     /// The id this client set for this stanza.

parsers/src/stream.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use jid::BareJid;
 
 use crate::ns;
 
 /// The stream opening for client-server communications.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::STREAM, name = "stream")]
 pub struct Stream {
     /// The JID of the entity opening this stream.

parsers/src/time.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::date::DateTime;
 use crate::iq::{IqGetPayload, IqResultPayload};
@@ -15,7 +15,7 @@ use std::str::FromStr;
 use xso::error::{Error, FromElementError};
 
 /// An entity time query.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::TIME, name = "time")]
 pub struct TimeQuery;
 

parsers/src/util/macro_tests.rs πŸ”—

@@ -19,9 +19,9 @@ mod helpers {
     // this is to ensure that the macros do not have hidden dependencies on
     // any specific names being imported.
     use minidom::Element;
-    use xso::{error::FromElementError, transform, try_from_element, FromXml, IntoXml};
+    use xso::{error::FromElementError, transform, try_from_element, AsXml, FromXml};
 
-    pub(super) fn roundtrip_full<T: IntoXml + FromXml + PartialEq + std::fmt::Debug + Clone>(
+    pub(super) fn roundtrip_full<T: AsXml + FromXml + PartialEq + std::fmt::Debug + Clone>(
         s: &str,
     ) {
         let initial: Element = s.parse().unwrap();
@@ -47,7 +47,7 @@ mod helpers {
 
 use self::helpers::{parse_str, roundtrip_full};
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 // these are adverserial local names in order to trigger any issues with
 // unqualified names in the macro expansions.
@@ -81,7 +81,7 @@ static BAR_NAME: &::xso::exports::rxml::strings::NcNameStr = {
     }
 };
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "foo")]
 struct Empty;
 
@@ -164,7 +164,7 @@ fn empty_qname_check_has_precedence_over_attr_check() {
     }
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = BAR_NAME)]
 struct NamePath;
 
@@ -178,7 +178,7 @@ fn name_path_roundtrip() {
     roundtrip_full::<NamePath>("<bar xmlns='urn:example:ns1'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = "urn:example:ns2", name = "baz")]
 struct NamespaceLit;
 
@@ -192,7 +192,7 @@ fn namespace_lit_roundtrip() {
     roundtrip_full::<NamespaceLit>("<baz xmlns='urn:example:ns2'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct RequiredAttribute {
     #[xml(attribute)]
@@ -237,7 +237,7 @@ fn required_attribute_missing() {
     }
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct RenamedAttribute {
     #[xml(attribute = "a1")]
@@ -256,7 +256,7 @@ fn renamed_attribute_roundtrip() {
     roundtrip_full::<RenamedAttribute>("<attr xmlns='urn:example:ns1' a1='bar' bar='baz'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct NamespacedAttribute {
     #[xml(attribute(namespace = "urn:example:ns1", name = FOO_NAME))]
@@ -297,7 +297,7 @@ fn namespaced_attribute_roundtrip_b() {
     );
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct PrefixedAttribute {
     #[xml(attribute = "xml:lang")]
@@ -314,7 +314,7 @@ fn prefixed_attribute_roundtrip() {
     roundtrip_full::<PrefixedAttribute>("<attr xmlns='urn:example:ns1' xml:lang='foo'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct RequiredNonStringAttribute {
     #[xml(attribute)]
@@ -331,7 +331,7 @@ fn required_non_string_attribute_roundtrip() {
     roundtrip_full::<RequiredNonStringAttribute>("<attr xmlns='urn:example:ns1' foo='-16'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "attr")]
 struct DefaultAttribute {
     #[xml(attribute(default))]
@@ -381,7 +381,7 @@ fn default_attribute_roundtrip_pp() {
     roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz' bar='16'/>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "text")]
 struct TextString {
     #[xml(text)]
@@ -409,7 +409,7 @@ fn text_string_positive_preserves_whitespace() {
     assert_eq!(el.text, " \t\n");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "text")]
 struct TextNonString {
     #[xml(text)]
@@ -426,7 +426,7 @@ fn text_non_string_roundtrip() {
     roundtrip_full::<TextNonString>("<text xmlns='urn:example:ns1'>123456</text>");
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "elem")]
 struct IgnoresWhitespaceWithoutTextConsumer;
 
@@ -443,7 +443,7 @@ fn ignores_whitespace_without_text_consumer_positive() {
     .unwrap();
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "elem")]
 struct FailsTextWithoutTextConsumer;
 
@@ -465,7 +465,7 @@ fn fails_text_without_text_consumer_positive() {
     }
 }
 
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "text")]
 struct TextWithCodec {
     #[xml(text(codec = xso::text::EmptyAsNone))]

parsers/src/util/macros.rs πŸ”—

@@ -94,9 +94,13 @@ macro_rules! generate_attribute {
                 })
             }
         }
-        impl ::xso::IntoXmlText for $elem {
-            fn into_xml_text(self) -> Result<String, xso::error::Error> {
-                Ok(self.to_string())
+        impl ::xso::AsXmlText for $elem {
+            fn as_xml_text(&self) -> Result<::std::borrow::Cow<'_, str>, xso::error::Error> {
+                match self {
+                    $(
+                        $elem::$a => Ok(::std::borrow::Cow::Borrowed($b))
+                    ),+
+                }
             }
         }
         impl ::minidom::IntoAttributeValue for $elem {
@@ -130,16 +134,16 @@ macro_rules! generate_attribute {
                 s.parse().map_err(xso::error::Error::text_parse_error)
             }
         }
-        impl ::xso::IntoXmlText for $elem {
-            fn into_xml_text(self) -> Result<String, xso::error::Error> {
-                Ok(String::from(match self {
+        impl ::xso::AsXmlText for $elem {
+            fn as_xml_text(&self) -> Result<std::borrow::Cow<'_, str>, xso::error::Error> {
+                Ok(std::borrow::Cow::Borrowed(match self {
                     $($elem::$a => $b),+
                 }))
             }
 
             #[allow(unreachable_patterns)]
-            fn into_optional_xml_text(self) -> Result<Option<String>, xso::error::Error> {
-                Ok(Some(String::from(match self {
+            fn as_optional_xml_text(&self) -> Result<Option<std::borrow::Cow<'_, str>>, xso::error::Error> {
+                Ok(Some(std::borrow::Cow::Borrowed(match self {
                     $elem::$default => return Ok(None),
                     $($elem::$a => $b),+
                 })))
@@ -219,17 +223,17 @@ macro_rules! generate_attribute {
                 }
             }
         }
-        impl ::xso::IntoXmlText for $elem {
-            fn into_xml_text(self) -> Result<String, xso::error::Error> {
+        impl ::xso::AsXmlText for $elem {
+            fn as_xml_text(&self) -> Result<::std::borrow::Cow<'_, str>, xso::error::Error> {
                 match self {
-                    Self::True => Ok("true".to_owned()),
-                    Self::False => Ok("false".to_owned()),
+                    Self::True => Ok(::std::borrow::Cow::Borrowed("true")),
+                    Self::False => Ok(::std::borrow::Cow::Borrowed("false")),
                 }
             }
 
-            fn into_optional_xml_text(self) -> Result<Option<String>, xso::error::Error> {
+            fn as_optional_xml_text(&self) -> Result<Option<::std::borrow::Cow<'_, str>>, xso::error::Error> {
                 match self {
-                    Self::True => Ok(Some("true".to_owned())),
+                    Self::True => Ok(Some(::std::borrow::Cow::Borrowed("true"))),
                     Self::False => Ok(None),
                 }
             }
@@ -435,9 +439,9 @@ macro_rules! generate_id {
                 Ok(Self(s))
             }
         }
-        impl ::xso::IntoXmlText for $elem {
-            fn into_xml_text(self) ->Result<String, xso::error::Error> {
-                Ok(self.0)
+        impl ::xso::AsXmlText for $elem {
+            fn as_xml_text(&self) ->Result<::std::borrow::Cow<'_, str>, xso::error::Error> {
+                Ok(::std::borrow::Cow::Borrowed(self.0.as_str()))
             }
         }
         impl ::minidom::IntoAttributeValue for $elem {
@@ -803,11 +807,11 @@ macro_rules! generate_element {
             }
         }
 
-        impl ::xso::IntoXml for $elem {
-            type EventIter = ::xso::minidom_compat::IntoEventsViaElement;
+        impl ::xso::AsXml for $elem {
+            type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
 
-            fn into_event_iter(self) -> Result<Self::EventIter, ::xso::error::Error> {
-                Self::EventIter::new(self)
+            fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
+                ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
             }
         }
     );

parsers/src/vcard.rs πŸ”—

@@ -13,7 +13,7 @@
 //! For vCard updates defined in [XEP-0153](https://xmpp.org/extensions/xep-0153.html),
 //! see [`vcard_update`][crate::vcard_update] module.
 
-use xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::util::text_node_codecs::{Codec, WhitespaceAwareBase64};
@@ -33,7 +33,7 @@ generate_element!(
 );
 
 /// The type of the photo.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::VCARD, name = "TYPE")]
 pub struct Type {
     /// The type as a plain text string; at least "image/jpeg", "image/gif" and "image/png" SHOULD be supported.

parsers/src/version.rs πŸ”—

@@ -4,7 +4,7 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
@@ -13,7 +13,7 @@ use crate::ns;
 ///
 /// It should only be used in an `<iq type='get'/>`, as it can only
 /// represent the request, and not a result.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::VERSION, name = "query")]
 pub struct VersionQuery;
 

parsers/src/websocket.rs πŸ”—

@@ -4,14 +4,14 @@
 // 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 xso::{FromXml, IntoXml};
+use xso::{AsXml, FromXml};
 
 use jid::BareJid;
 
 use crate::ns;
 
 /// The stream opening for WebSocket.
-#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = ns::WEBSOCKET, name = "open")]
 pub struct Open {
     /// The JID of the entity opening this stream.

xso-proc/Cargo.toml πŸ”—

@@ -4,7 +4,7 @@ version = "0.0.2"
 authors = [
   "Jonas SchΓ€fer <jonas@zombofant.net>",
 ]
-description = "Macro implementation of #[derive(FromXml, IntoXml)]"
+description = "Macro implementation of #[derive(FromXml, AsXml)]"
 homepage = "https://xmpp.rs"
 repository = "https://gitlab.com/xmpp-rs/xmpp-rs"
 keywords = ["xso", "derive", "serialization"]

xso-proc/src/compound.rs πŸ”—

@@ -12,9 +12,9 @@ use syn::{spanned::Spanned, *};
 
 use crate::error_message::ParentRef;
 use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit};
-use crate::scope::{mangle_member, FromEventsScope, IntoEventsScope};
-use crate::state::{FromEventsSubmachine, IntoEventsSubmachine, State};
-use crate::types::qname_ty;
+use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
+use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
+use crate::types::{namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
 
 /// A struct or enum variant's contents.
 pub(crate) struct Compound {
@@ -230,58 +230,86 @@ impl Compound {
     /// **Important:** The returned submachine is not in functional state!
     /// It's `init` must be modified so that a variable called `name` of type
     /// `rxml::QName` is in scope.
-    pub(crate) fn make_into_event_iter_statemachine(
+    pub(crate) fn make_as_item_iter_statemachine(
         &self,
         input_name: &Path,
         state_prefix: &str,
-    ) -> Result<IntoEventsSubmachine> {
-        let scope = IntoEventsScope::new();
-        let IntoEventsScope { ref attrs, .. } = scope;
-
-        let start_element_state_ident = quote::format_ident!("{}StartElement", state_prefix);
-        let end_element_state_ident = quote::format_ident!("{}EndElement", state_prefix);
+        lifetime: &Lifetime,
+    ) -> Result<AsItemsSubmachine> {
+        let scope = AsItemsScope::new(lifetime);
+
+        let element_head_start_state_ident =
+            quote::format_ident!("{}ElementHeadStart", state_prefix);
+        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
+        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
         let name_ident = quote::format_ident!("name");
+        let ns_ident = quote::format_ident!("ns");
+        let dummy_ident = quote::format_ident!("dummy");
         let mut states = Vec::new();
 
-        let mut init_body = TokenStream::default();
         let mut destructure = TokenStream::default();
         let mut start_init = TokenStream::default();
 
         states.push(
-            State::new(start_element_state_ident.clone())
-                .with_field(&name_ident, &qname_ty(Span::call_site())),
+            State::new(element_head_start_state_ident.clone())
+                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
+                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
+                .with_field(
+                    &name_ident,
+                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
+                ),
+        );
+
+        let mut element_head_end_idx = states.len();
+        states.push(
+            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
+                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
+            }),
         );
 
         for (i, field) in self.fields.iter().enumerate() {
             let member = field.member();
             let bound_name = mangle_member(member);
-            let part = field.make_iterator_part(&scope, &bound_name)?;
+            let part = field.make_iterator_part(&bound_name)?;
             let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
+            let ty = scope.borrow(field.ty().clone());
 
             match part {
-                FieldIteratorPart::Header { setter } => {
+                FieldIteratorPart::Header { generator } => {
+                    // we have to make sure that we carry our data around in
+                    // all the previous states.
+                    for state in &mut states[..element_head_end_idx] {
+                        state.add_field(&bound_name, &ty);
+                    }
+                    states.insert(
+                        element_head_end_idx,
+                        State::new(state_name)
+                            .with_field(&bound_name, &ty)
+                            .with_impl(quote! {
+                                #generator
+                            }),
+                    );
+                    element_head_end_idx += 1;
+
                     destructure.extend(quote! {
-                        #member: #bound_name,
+                        #member: ref #bound_name,
                     });
-                    init_body.extend(setter);
                     start_init.extend(quote! {
                         #bound_name,
                     });
-                    states[0].add_field(&bound_name, field.ty());
                 }
 
                 FieldIteratorPart::Text { generator } => {
                     // we have to make sure that we carry our data around in
                     // all the previous states.
                     for state in states.iter_mut() {
-                        state.add_field(&bound_name, field.ty());
+                        state.add_field(&bound_name, &ty);
                     }
                     states.push(
                         State::new(state_name)
-                            .with_field(&bound_name, field.ty())
+                            .with_field(&bound_name, &ty)
                             .with_impl(quote! {
-                                #generator.map(|value| ::xso::exports::rxml::Event::Text(
-                                    ::xso::exports::rxml::parser::EventMetrics::zero(),
+                                #generator.map(|value| ::xso::Item::Text(
                                     value,
                                 ))
                             }),
@@ -298,32 +326,27 @@ impl Compound {
 
         states[0].set_impl(quote! {
             {
-                let mut #attrs = ::xso::exports::rxml::AttrMap::new();
-                #init_body
-                ::core::option::Option::Some(::xso::exports::rxml::Event::StartElement(
-                    ::xso::exports::rxml::parser::EventMetrics::zero(),
+                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
+                    #ns_ident,
                     #name_ident,
-                    #attrs,
                 ))
             }
         });
 
         states.push(
-            State::new(end_element_state_ident.clone()).with_impl(quote! {
-                ::core::option::Option::Some(::xso::exports::rxml::Event::EndElement(
-                    ::xso::exports::rxml::parser::EventMetrics::zero(),
-                ))
+            State::new(element_foot_state_ident.clone()).with_impl(quote! {
+                ::core::option::Option::Some(::xso::Item::ElementFoot)
             }),
         );
 
-        Ok(IntoEventsSubmachine {
+        Ok(AsItemsSubmachine {
             defs: TokenStream::default(),
             states,
             destructure: quote! {
                 #input_name { #destructure }
             },
             init: quote! {
-                Self::#start_element_state_ident { #name_ident, #start_init }
+                Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
             },
         })
     }

xso-proc/src/field.rs πŸ”—

@@ -14,9 +14,9 @@ use rxml_validation::NcName;
 
 use crate::error_message::{self, ParentRef};
 use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
-use crate::scope::{FromEventsScope, IntoEventsScope};
+use crate::scope::FromEventsScope;
 use crate::types::{
-    default_fn, from_xml_text_fn, into_optional_xml_text_fn, into_xml_text_fn, string_ty,
+    as_optional_xml_text_fn, as_xml_text_fn, default_fn, from_xml_text_fn, string_ty,
     text_codec_decode_fn, text_codec_encode_fn,
 };
 
@@ -69,13 +69,12 @@ pub(crate) enum FieldBuilderPart {
 pub(crate) enum FieldIteratorPart {
     /// The field is emitted as part of StartElement.
     Header {
-        /// A sequence of statements which updates the temporary variables
-        /// during the StartElement event's construction, consuming the
-        /// field's value.
-        setter: TokenStream,
+        /// An expression which consumes the field's value and returns a
+        /// `Item`.
+        generator: TokenStream,
     },
 
-    /// The field is emitted as text event.
+    /// The field is emitted as text item.
     Text {
         /// An expression which consumes the field's value and returns a
         /// String, which is then emitted as text data.
@@ -295,19 +294,13 @@ impl FieldDef {
     ///
     /// `bound_name` must be the name to which the field's value is bound in
     /// the iterator code.
-    pub(crate) fn make_iterator_part(
-        &self,
-        scope: &IntoEventsScope,
-        bound_name: &Ident,
-    ) -> Result<FieldIteratorPart> {
+    pub(crate) fn make_iterator_part(&self, bound_name: &Ident) -> Result<FieldIteratorPart> {
         match self.kind {
             FieldKind::Attribute {
                 ref xml_name,
                 ref xml_namespace,
                 ..
             } => {
-                let IntoEventsScope { ref attrs, .. } = scope;
-
                 let xml_namespace = match xml_namespace {
                     Some(v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
                     None => quote! {
@@ -315,16 +308,13 @@ impl FieldDef {
                     },
                 };
 
-                let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
+                let as_optional_xml_text = as_optional_xml_text_fn(self.ty.clone());
 
                 Ok(FieldIteratorPart::Header {
-                    // This is a neat little trick:
-                    // Option::from(x) converts x to an Option<T> *unless* it
-                    // already is an Option<_>.
-                    setter: quote! {
-                        #into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert(
+                    generator: quote! {
+                        #as_optional_xml_text(#bound_name)?.map(|#bound_name| ::xso::Item::Attribute(
                             #xml_namespace,
-                            #xml_name.to_owned(),
+                            ::std::borrow::Cow::Borrowed(#xml_name),
                             #bound_name,
                         ));
                     },
@@ -338,8 +328,8 @@ impl FieldDef {
                         quote! { #encode(#bound_name)? }
                     }
                     None => {
-                        let into_xml_text = into_xml_text_fn(self.ty.clone());
-                        quote! { ::core::option::Option::Some(#into_xml_text(#bound_name)?) }
+                        let as_xml_text = as_xml_text_fn(self.ty.clone());
+                        quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) }
                     }
                 };
 

xso-proc/src/lib.rs πŸ”—

@@ -111,26 +111,27 @@ pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
     }
 }
 
-/// Generate a `xso::IntoXml` implementation for the given item, or fail with
+/// Generate a `xso::AsXml` implementation for the given item, or fail with
 /// a proper compiler error.
-fn into_xml_impl(input: Item) -> Result<TokenStream> {
+fn as_xml_impl(input: Item) -> Result<TokenStream> {
     let (vis, ident, def) = parse_struct(input)?;
 
-    let structs::IntoXmlParts {
+    let structs::AsXmlParts {
         defs,
-        into_event_iter_body,
-        event_iter_ty_ident,
-    } = def.make_into_event_iter(&vis)?;
+        as_xml_iter_body,
+        item_iter_ty_lifetime,
+        item_iter_ty,
+    } = def.make_as_xml_iter(&vis)?;
 
     #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
     let mut result = quote! {
         #defs
 
-        impl ::xso::IntoXml for #ident {
-            type EventIter = #event_iter_ty_ident;
+        impl ::xso::AsXml for #ident {
+            type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty;
 
-            fn into_event_iter(self) -> ::core::result::Result<Self::EventIter, ::xso::error::Error> {
-                #into_event_iter_body
+            fn as_xml_iter(&self) -> ::core::result::Result<Self::ItemIter<'_>, ::xso::error::Error> {
+                #as_xml_iter_body
             }
         }
     };
@@ -162,15 +163,15 @@ fn into_xml_impl(input: Item) -> Result<TokenStream> {
     Ok(result)
 }
 
-/// Macro to derive a `xso::IntoXml` implementation on a type.
+/// Macro to derive a `xso::AsXml` implementation on a type.
 ///
 /// The user-facing documentation for this macro lives in the `xso` crate.
-#[proc_macro_derive(IntoXml, attributes(xml))]
-pub fn into_xml(input: RawTokenStream) -> RawTokenStream {
-    // Shim wrapper around `into_xml_impl` which converts any errors into
+#[proc_macro_derive(AsXml, attributes(xml))]
+pub fn as_xml(input: RawTokenStream) -> RawTokenStream {
+    // Shim wrapper around `as_xml_impl` which converts any errors into
     // actual compiler errors within the resulting token stream.
     let item = syn::parse_macro_input!(input as Item);
-    match into_xml_impl(item) {
+    match as_xml_impl(item) {
         Ok(v) => v.into(),
         Err(e) => e.into_compile_error().into(),
     }

xso-proc/src/scope.rs πŸ”—

@@ -9,6 +9,8 @@
 use proc_macro2::Span;
 use syn::*;
 
+use crate::types::ref_ty;
+
 /// Container struct for various identifiers used throughout the parser code.
 ///
 /// This struct is passed around from the [`crate::compound::Compound`]
@@ -80,19 +82,24 @@ impl FromEventsScope {
 /// same page about which identifiers are used for what.
 ///
 /// See [`FromEventsScope`] for recommendations on the usage.
-pub(crate) struct IntoEventsScope {
-    /// Accesses the `AttrMap` from code in
-    /// [`crate::field::FieldIteratorPart::Header`].
-    pub(crate) attrs: Ident,
+pub(crate) struct AsItemsScope {
+    /// Lifetime for data borrowed by the implementation.
+    pub(crate) lifetime: Lifetime,
 }
 
-impl IntoEventsScope {
+impl AsItemsScope {
     /// Create a fresh scope with all necessary identifiers.
-    pub(crate) fn new() -> Self {
+    pub(crate) fn new(lifetime: &Lifetime) -> Self {
         Self {
-            attrs: Ident::new("attrs", Span::call_site()),
+            lifetime: lifetime.clone(),
         }
     }
+
+    /// Create a reference to `ty`, borrowed for the lifetime of the AsXml
+    /// impl.
+    pub(crate) fn borrow(&self, ty: Type) -> Type {
+        ref_ty(ty, self.lifetime.clone())
+    }
 }
 
 pub(crate) fn mangle_member(member: &Member) -> Ident {

xso-proc/src/state.rs πŸ”—

@@ -82,7 +82,7 @@ impl State {
     /// the `advance` implementation of the state machine.
     ///
     /// See [`FromEventsStateMachine::advance_match_arms`] and
-    /// [`IntoEventsSubmachine::compile`] for the respective
+    /// [`AsItemsSubmachine::compile`] for the respective
     /// requirements on the implementations.
     pub(crate) fn with_impl(mut self, body: TokenStream) -> Self {
         self.advance_body = body;
@@ -171,12 +171,12 @@ impl FromEventsSubmachine {
     }
 }
 
-/// A partial [`IntoEventsStateMachine`] which only covers the builder for a
+/// A partial [`AsItemsStateMachine`] which only covers the builder for a
 /// single compound.
 ///
-/// See [`IntoEventsStateMachine`] for more information on the state machines
+/// See [`AsItemsStateMachine`] for more information on the state machines
 /// in general.
-pub(crate) struct IntoEventsSubmachine {
+pub(crate) struct AsItemsSubmachine {
     /// Additional items necessary for the statemachine.
     pub(crate) defs: TokenStream,
 
@@ -194,7 +194,7 @@ pub(crate) struct IntoEventsSubmachine {
     pub(crate) init: TokenStream,
 }
 
-impl IntoEventsSubmachine {
+impl AsItemsSubmachine {
     /// Convert a partial state machine into a full state machine.
     ///
     /// This converts the abstract [`State`] items into token
@@ -202,13 +202,13 @@ impl IntoEventsSubmachine {
     /// definitions and the match arms), rendering them effectively immutable.
     ///
     /// This requires that the [`State::advance_body`] token streams evaluate
-    /// to an `Option<rxml::Event>`. If it evaluates to `Some(.)`, that is
+    /// to an `Option<Item>`. If it evaluates to `Some(.)`, that is
     /// emitted from the iterator. If it evaluates to `None`, the `advance`
     /// implementation is called again.
     ///
     /// Each state implementation is augmented to also enter the next state,
     /// causing the iterator to terminate eventually.
-    pub(crate) fn compile(self) -> IntoEventsStateMachine {
+    pub(crate) fn compile(self) -> AsItemsStateMachine {
         let mut state_defs = TokenStream::default();
         let mut advance_match_arms = TokenStream::default();
 
@@ -227,13 +227,13 @@ impl IntoEventsSubmachine {
                     ..
                 }) => {
                     quote! {
-                        ::core::result::Result::Ok((::core::option::Option::Some(Self::#next_name { #construct_next }), event))
+                        ::core::result::Result::Ok((::core::option::Option::Some(Self::#next_name { #construct_next }), item))
                     }
                 }
                 // final state -> exit the state machine
                 None => {
                     quote! {
-                        ::core::result::Result::Ok((::core::option::Option::None, event))
+                        ::core::result::Result::Ok((::core::option::Option::None, item))
                     }
                 }
             };
@@ -244,17 +244,17 @@ impl IntoEventsSubmachine {
 
             advance_match_arms.extend(quote! {
                 Self::#name { #destructure } => {
-                    let event = #advance_body;
+                    let item = #advance_body;
                     #footer
                 }
             });
         }
 
-        IntoEventsStateMachine {
+        AsItemsStateMachine {
             defs: self.defs,
             state_defs,
             advance_match_arms,
-            variants: vec![IntoEventsEntryPoint {
+            variants: vec![AsItemsEntryPoint {
                 init: self.init,
                 destructure: self.destructure,
             }],
@@ -281,7 +281,7 @@ pub(crate) struct FromEventsEntryPoint {
 }
 
 /// A single variant's entrypoint into the event iterator.
-pub(crate) struct IntoEventsEntryPoint {
+pub(crate) struct AsItemsEntryPoint {
     /// A pattern match which destructures the target type into its parts, for
     /// use by `init`.
     destructure: TokenStream,
@@ -468,7 +468,7 @@ impl FromEventsStateMachine {
 /// method. That method consumes the enum value and returns either a new enum
 /// value, an error, or the output type of the state machine.
 #[derive(Default)]
-pub(crate) struct IntoEventsStateMachine {
+pub(crate) struct AsItemsStateMachine {
     /// Extra items which are needed for the state machine implementation.
     defs: TokenStream,
 
@@ -480,7 +480,7 @@ pub(crate) struct IntoEventsStateMachine {
     /// enumeration type.
     ///
     /// Each match arm must either diverge or evaluate to a
-    /// `Result<(Option<State>, Option<Event>), xso::error::Error>`, where
+    /// `Result<(Option<State>, Option<Item>), xso::error::Error>`, where
     /// where `State` is the state enumeration.
     ///
     /// If `Some(.)` is returned for the event, that event is emitted. If
@@ -489,7 +489,7 @@ pub(crate) struct IntoEventsStateMachine {
     /// field.
     ///
     /// If `None` is returned for the `Option<State>`, the iterator
-    /// terminates yielding the `Option<Event>` value directly (even if it is
+    /// terminates yielding the `Option<Item>` value directly (even if it is
     /// `None`). After the iterator has terminated, it yields `None`
     /// indefinitely.
     advance_match_arms: TokenStream,
@@ -498,10 +498,10 @@ pub(crate) struct IntoEventsStateMachine {
     ///
     /// This may only contain more than one element if an enumeration is being
     /// serialised by the resulting state machine.
-    variants: Vec<IntoEventsEntryPoint>,
+    variants: Vec<AsItemsEntryPoint>,
 }
 
-impl IntoEventsStateMachine {
+impl AsItemsStateMachine {
     /// Render the state machine as a token stream.
     ///
     /// The token stream contains the following pieces:
@@ -515,7 +515,8 @@ impl IntoEventsStateMachine {
         vis: &Visibility,
         input_ty: &Type,
         state_ty_ident: &Ident,
-        event_iter_ty_ident: &Ident,
+        item_iter_ty_lifetime: &Lifetime,
+        item_iter_ty: &Type,
     ) -> Result<TokenStream> {
         let Self {
             defs,
@@ -525,10 +526,10 @@ impl IntoEventsStateMachine {
         } = self;
 
         let input_ty_ref = make_ty_ref(input_ty);
-        let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::IntoXml`] derive macro and implements [`std::iter:Iterator`] for {0}.", input_ty_ref);
+        let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::AsXml`] derive macro and implements [`std::iter:Iterator`] for {0}.", input_ty_ref);
 
         let init_body = if variants.len() == 1 {
-            let IntoEventsEntryPoint { destructure, init } = variants.remove(0);
+            let AsItemsEntryPoint { destructure, init } = variants.remove(0);
             quote! {
                 {
                     let #destructure = value;
@@ -537,7 +538,7 @@ impl IntoEventsStateMachine {
             }
         } else {
             let mut match_arms = TokenStream::default();
-            for IntoEventsEntryPoint { destructure, init } in variants {
+            for AsItemsEntryPoint { destructure, init } in variants {
                 match_arms.extend(quote! {
                     #destructure => #init,
                 });
@@ -553,40 +554,40 @@ impl IntoEventsStateMachine {
         Ok(quote! {
             #defs
 
-            enum #state_ty_ident {
+            enum #state_ty_ident<#item_iter_ty_lifetime> {
                 #state_defs
             }
 
-            impl #state_ty_ident {
-                fn advance(mut self) -> ::core::result::Result<(::core::option::Option<Self>, ::core::option::Option<::xso::exports::rxml::Event>), ::xso::error::Error> {
+            impl<#item_iter_ty_lifetime> #state_ty_ident<#item_iter_ty_lifetime> {
+                fn advance(mut self) -> ::core::result::Result<(::core::option::Option<Self>, ::core::option::Option<::xso::Item<#item_iter_ty_lifetime>>), ::xso::error::Error> {
                     match self {
                         #advance_match_arms
                     }
                 }
 
                 fn new(
-                    value: #input_ty,
+                    value: &#item_iter_ty_lifetime #input_ty,
                 ) -> ::core::result::Result<Self, ::xso::error::Error> {
                     ::core::result::Result::Ok(#init_body)
                 }
             }
 
             #[doc = #docstr]
-            #vis struct #event_iter_ty_ident(::core::option::Option<#state_ty_ident>);
+            #vis struct #item_iter_ty(::core::option::Option<#state_ty_ident<#item_iter_ty_lifetime>>);
 
-            impl ::std::iter::Iterator for #event_iter_ty_ident {
-                type Item = ::core::result::Result<::xso::exports::rxml::Event, ::xso::error::Error>;
+            impl<#item_iter_ty_lifetime> ::std::iter::Iterator for #item_iter_ty {
+                type Item = ::core::result::Result<::xso::Item<#item_iter_ty_lifetime>, ::xso::error::Error>;
 
                 fn next(&mut self) -> ::core::option::Option<Self::Item> {
                     let mut state = self.0.take()?;
                     loop {
-                        let (next_state, ev) = match state.advance() {
+                        let (next_state, item) = match state.advance() {
                             ::core::result::Result::Ok(v) => v,
                             ::core::result::Result::Err(e) => return ::core::option::Option::Some(::core::result::Result::Err(e)),
                         };
-                        if let ::core::option::Option::Some(ev) = ev {
+                        if let ::core::option::Option::Some(item) = item {
                             self.0 = next_state;
-                            return ::core::option::Option::Some(::core::result::Result::Ok(ev));
+                            return ::core::option::Option::Some(::core::result::Result::Ok(item));
                         }
                         // no event, do we have a state?
                         if let ::core::option::Option::Some(st) = next_state {
@@ -602,8 +603,8 @@ impl IntoEventsStateMachine {
                 }
             }
 
-            impl #event_iter_ty_ident {
-                fn new(value: #input_ty) -> ::core::result::Result<Self, ::xso::error::Error> {
+            impl<#item_iter_ty_lifetime> #item_iter_ty {
+                fn new(value: &#item_iter_ty_lifetime #input_ty) -> ::core::result::Result<Self, ::xso::error::Error> {
                     #state_ty_ident::new(value).map(|ok| Self(::core::option::Option::Some(ok)))
                 }
             }

xso-proc/src/structs.rs πŸ”—

@@ -6,7 +6,7 @@
 
 //! Handling of structs
 
-use proc_macro2::TokenStream;
+use proc_macro2::{Span, TokenStream};
 use quote::quote;
 use syn::*;
 
@@ -25,16 +25,19 @@ pub(crate) struct FromXmlParts {
     pub(crate) builder_ty_ident: Ident,
 }
 
-/// Parts necessary to construct a `::xso::IntoXml` implementation.
-pub(crate) struct IntoXmlParts {
+/// Parts necessary to construct a `::xso::AsXml` implementation.
+pub(crate) struct AsXmlParts {
     /// Additional items necessary for the implementation.
     pub(crate) defs: TokenStream,
 
-    /// The body of the `::xso::IntoXml::into_event_iter` function.
-    pub(crate) into_event_iter_body: TokenStream,
+    /// The body of the `::xso::AsXml::as_xml_iter` function.
+    pub(crate) as_xml_iter_body: TokenStream,
 
-    /// The name of the type which is the `::xso::IntoXml::EventIter`.
-    pub(crate) event_iter_ty_ident: Ident,
+    /// The type which is the `::xso::AsXml::ItemIter`.
+    pub(crate) item_iter_ty: Type,
+
+    /// The lifetime name used in `item_iter_ty`.
+    pub(crate) item_iter_ty_lifetime: Lifetime,
 }
 
 /// Definition of a struct and how to parse it.
@@ -55,7 +58,7 @@ pub(crate) struct StructDef {
     builder_ty_ident: Ident,
 
     /// Name of the iterator type.
-    event_iter_ty_ident: Ident,
+    item_iter_ty_ident: Ident,
 
     /// Flag whether debug mode is enabled.
     debug: bool,
@@ -78,7 +81,7 @@ impl StructDef {
             inner: Compound::from_fields(fields)?,
             target_ty_ident: ident.clone(),
             builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
-            event_iter_ty_ident: quote::format_ident!("{}IntoXmlIterator", ident),
+            item_iter_ty_ident: quote::format_ident!("{}AsXmlIterator", ident),
             debug: meta.debug.is_set(),
         })
     }
@@ -136,22 +139,53 @@ impl StructDef {
         })
     }
 
-    pub(crate) fn make_into_event_iter(&self, vis: &Visibility) -> Result<IntoXmlParts> {
+    pub(crate) fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
         let xml_namespace = &self.namespace;
         let xml_name = &self.name;
 
         let target_ty_ident = &self.target_ty_ident;
-        let event_iter_ty_ident = &self.event_iter_ty_ident;
-        let state_ty_ident = quote::format_ident!("{}State", event_iter_ty_ident);
+        let item_iter_ty_ident = &self.item_iter_ty_ident;
+        let item_iter_ty_lifetime = Lifetime {
+            apostrophe: Span::call_site(),
+            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
+        };
+        let item_iter_ty = Type::Path(TypePath {
+            qself: None,
+            path: Path {
+                leading_colon: None,
+                segments: [PathSegment {
+                    ident: item_iter_ty_ident.clone(),
+                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                        colon2_token: None,
+                        lt_token: token::Lt {
+                            spans: [Span::call_site()],
+                        },
+                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
+                            .into_iter()
+                            .collect(),
+                        gt_token: token::Gt {
+                            spans: [Span::call_site()],
+                        },
+                    }),
+                }]
+                .into_iter()
+                .collect(),
+            },
+        });
+        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
 
         let defs = self
             .inner
-            .make_into_event_iter_statemachine(&target_ty_ident.clone().into(), "Struct")?
+            .make_as_item_iter_statemachine(
+                &target_ty_ident.clone().into(),
+                "Struct",
+                &item_iter_ty_lifetime,
+            )?
             .with_augmented_init(|init| {
                 quote! {
                     let name = (
                         ::xso::exports::rxml::Namespace::from(#xml_namespace),
-                        #xml_name.into(),
+                        ::std::borrow::Cow::Borrowed(#xml_name),
                     );
                     #init
                 }
@@ -165,15 +199,17 @@ impl StructDef {
                 }
                 .into(),
                 &state_ty_ident,
-                event_iter_ty_ident,
+                &item_iter_ty_lifetime,
+                &item_iter_ty,
             )?;
 
-        Ok(IntoXmlParts {
+        Ok(AsXmlParts {
             defs,
-            into_event_iter_body: quote! {
-                #event_iter_ty_ident::new(self)
+            as_xml_iter_body: quote! {
+                #item_iter_ty_ident::new(self)
             },
-            event_iter_ty_ident: event_iter_ty_ident.clone(),
+            item_iter_ty,
+            item_iter_ty_lifetime,
         })
     }
 

xso-proc/src/types.rs πŸ”—

@@ -9,8 +9,8 @@
 use proc_macro2::Span;
 use syn::{spanned::Spanned, *};
 
-/// Construct a [`syn::Type`] referring to `::xso::exports::rxml::QName`.
-pub(crate) fn qname_ty(span: Span) -> Type {
+/// Construct a [`syn::Type`] referring to `::xso::exports::rxml::Namespace`.
+pub(crate) fn namespace_ty(span: Span) -> Type {
     Type::Path(TypePath {
         qself: None,
         path: Path {
@@ -31,7 +31,7 @@ pub(crate) fn qname_ty(span: Span) -> Type {
                     arguments: PathArguments::None,
                 },
                 PathSegment {
-                    ident: Ident::new("QName", span),
+                    ident: Ident::new("Namespace", span),
                     arguments: PathArguments::None,
                 },
             ]
@@ -41,6 +41,83 @@ pub(crate) fn qname_ty(span: Span) -> Type {
     })
 }
 
+/// Construct a [`syn::Type`] referring to `::xso::exports::rxml::NcNameStr`.
+pub(crate) fn ncnamestr_ty(span: Span) -> Type {
+    Type::Path(TypePath {
+        qself: None,
+        path: Path {
+            leading_colon: Some(syn::token::PathSep {
+                spans: [span, span],
+            }),
+            segments: [
+                PathSegment {
+                    ident: Ident::new("xso", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("exports", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("rxml", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("NcNameStr", span),
+                    arguments: PathArguments::None,
+                },
+            ]
+            .into_iter()
+            .collect(),
+        },
+    })
+}
+
+/// Construct a [`syn::Type`] referring to `Cow<#lifetime, #ty>`.
+pub(crate) fn cow_ty(ty: Type, lifetime: Lifetime) -> Type {
+    let span = ty.span();
+    Type::Path(TypePath {
+        qself: None,
+        path: Path {
+            leading_colon: Some(syn::token::PathSep {
+                spans: [span, span],
+            }),
+            segments: [
+                PathSegment {
+                    ident: Ident::new("std", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("borrow", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("Cow", span),
+                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                        colon2_token: None,
+                        lt_token: token::Lt { spans: [span] },
+                        args: [
+                            GenericArgument::Lifetime(lifetime),
+                            GenericArgument::Type(ty),
+                        ]
+                        .into_iter()
+                        .collect(),
+                        gt_token: token::Gt { spans: [span] },
+                    }),
+                },
+            ]
+            .into_iter()
+            .collect(),
+        },
+    })
+}
+
+/// Construct a [`syn::Type`] referring to
+/// `Cow<#lifetime, ::rxml::NcNameStr>`.
+pub(crate) fn ncnamestr_cow_ty(ty_span: Span, lifetime: Lifetime) -> Type {
+    cow_ty(ncnamestr_ty(ty_span), lifetime)
+}
+
 /// Construct a [`syn::Expr`] referring to
 /// `<#ty as ::xso::FromXmlText>::from_xml_text`.
 pub(crate) fn from_xml_text_fn(ty: Type) -> Expr {
@@ -79,8 +156,8 @@ pub(crate) fn from_xml_text_fn(ty: Type) -> Expr {
 }
 
 /// Construct a [`syn::Expr`] referring to
-/// `<#ty as ::xso::IntoOptionalXmlText>::into_optional_xml_text`.
-pub(crate) fn into_optional_xml_text_fn(ty: Type) -> Expr {
+/// `<#ty as ::xso::AsOptionalXmlText>::as_optional_xml_text`.
+pub(crate) fn as_optional_xml_text_fn(ty: Type) -> Expr {
     let span = ty.span();
     Expr::Path(ExprPath {
         attrs: Vec::new(),
@@ -101,11 +178,11 @@ pub(crate) fn into_optional_xml_text_fn(ty: Type) -> Expr {
                     arguments: PathArguments::None,
                 },
                 PathSegment {
-                    ident: Ident::new("IntoOptionalXmlText", span),
+                    ident: Ident::new("AsOptionalXmlText", span),
                     arguments: PathArguments::None,
                 },
                 PathSegment {
-                    ident: Ident::new("into_optional_xml_text", span),
+                    ident: Ident::new("as_optional_xml_text", span),
                     arguments: PathArguments::None,
                 },
             ]
@@ -185,8 +262,8 @@ pub(crate) fn string_ty(span: Span) -> Type {
 }
 
 /// Construct a [`syn::Expr`] referring to
-/// `<#ty as ::xso::IntoXmlText>::into_xml_text`.
-pub(crate) fn into_xml_text_fn(ty: Type) -> Expr {
+/// `<#ty as ::xso::AsXmlText>::as_xml_text`.
+pub(crate) fn as_xml_text_fn(ty: Type) -> Expr {
     let span = ty.span();
     Expr::Path(ExprPath {
         attrs: Vec::new(),
@@ -207,11 +284,11 @@ pub(crate) fn into_xml_text_fn(ty: Type) -> Expr {
                     arguments: PathArguments::None,
                 },
                 PathSegment {
-                    ident: Ident::new("IntoXmlText", span),
+                    ident: Ident::new("AsXmlText", span),
                     arguments: PathArguments::None,
                 },
                 PathSegment {
-                    ident: Ident::new("into_xml_text", span),
+                    ident: Ident::new("as_xml_text", span),
                     arguments: PathArguments::None,
                 },
             ]
@@ -293,3 +370,55 @@ pub(crate) fn text_codec_decode_fn(codec_ty: Type, for_ty: Type) -> Expr {
         path: ty.path,
     })
 }
+
+/// Construct a [`syn::Type`] for `&#lifetime #ty`.
+pub(crate) fn ref_ty(ty: Type, lifetime: Lifetime) -> Type {
+    let span = ty.span();
+    Type::Reference(TypeReference {
+        and_token: token::And { spans: [span] },
+        lifetime: Some(lifetime),
+        mutability: None,
+        elem: Box::new(ty),
+    })
+}
+
+/// Construct a [`syn::Type`] referring to
+/// `::std::marker::PhantomData<&#lifetime ()>`.
+pub(crate) fn phantom_lifetime_ty(lifetime: Lifetime) -> Type {
+    let span = lifetime.span();
+    let dummy = Type::Tuple(TypeTuple {
+        paren_token: token::Paren::default(),
+        elems: punctuated::Punctuated::default(),
+    });
+    Type::Path(TypePath {
+        qself: None,
+        path: Path {
+            leading_colon: Some(syn::token::PathSep {
+                spans: [span, span],
+            }),
+            segments: [
+                PathSegment {
+                    ident: Ident::new("std", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("marker", span),
+                    arguments: PathArguments::None,
+                },
+                PathSegment {
+                    ident: Ident::new("PhantomData", span),
+                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                        colon2_token: None,
+                        lt_token: token::Lt { spans: [span] },
+                        args: [GenericArgument::Type(ref_ty(dummy, lifetime))]
+                            .into_iter()
+                            .collect(),
+                        gt_token: token::Gt { spans: [span] },
+                    }),
+                },
+            ]
+            .into_iter()
+            .collect(),
+        },
+    })
+}

xso/src/from_xml_doc.md πŸ”—

@@ -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

xso/src/lib.rs πŸ”—

@@ -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)? {

xso/src/rxml_util.rs πŸ”—

@@ -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),
+        };
+    }
+}

xso/src/text.rs πŸ”—

@@ -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)
     }
 }