Skip to main content

instant_xml/
any_element.rs

1use std::borrow::Cow;
2
3use crate::de::Node;
4use crate::{Deserializer, Error, FromXml, Id, Kind};
5
6/// A dynamically captured XML element.
7///
8/// The [`AnyElement`] type captures an arbitrary XML element tree at runtime,
9/// preserving its namespace, name, attributes, text
10/// content, and nested children. This is useful when the XML schema allows
11/// arbitrary content (e.g. `<xs:any namespace="##any" processContents="skip" />`)
12/// or when the element structure is not known at compile time.
13///
14/// String values are borrowed from the input XML where possible via [`Cow`].
15///
16/// # Example
17///
18/// As root:
19/// ```
20/// use instant_xml::{from_str, AnyElement};
21///
22/// let xml = r#"<item xmlns="http://example.com" key="val">hello</item>"#;
23/// let elem: AnyElement<'_> = from_str(xml).unwrap();
24///
25/// assert_eq!(elem.name, "item");
26/// assert_eq!(elem.ns, "http://example.com");
27/// assert_eq!(elem.text.as_deref(), Some("hello"));
28/// assert_eq!(elem.attributes.len(), 1);
29/// ```
30///
31/// As child (borrowing from the input):
32/// ```
33/// use instant_xml::{from_str, FromXml, AnyElement};
34///
35/// #[derive(Debug, FromXml, PartialEq)]
36/// #[xml(ns("http://example.com"))]
37/// struct Wrapper<'a> {
38///     #[xml(borrow)]
39///     inner: AnyElement<'a>,
40/// }
41///
42/// let xml = r#"<Wrapper xmlns="http://example.com"><item>text</item></Wrapper>"#;
43/// let parsed: Wrapper<'_> = from_str(xml).unwrap();
44///
45/// assert_eq!(parsed.inner.name, "item");
46/// assert_eq!(parsed.inner.ns, "http://example.com");
47/// assert_eq!(parsed.inner.text.as_deref(), Some("text"));
48/// ```
49///
50/// **Note:** When using `AnyElement` as a field in a derived struct, add
51/// `#[xml(borrow)]` so the derive macro generates the correct lifetime bounds.
52/// Use [`into_owned()`](Self::into_owned) to convert to `AnyElement<'static>`
53/// when you need to decouple from the input lifetime.
54///
55/// # Matching behavior
56///
57/// `AnyElement` matches any XML element regardless of namespace or name. When
58/// used as a field in a derived struct, it will capture whichever element appears
59/// in that position. For capturing multiple arbitrary children, use
60/// `Vec<AnyElement>`.
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct AnyElement<'xml> {
63    /// XML namespace URI of this element.
64    pub ns: Cow<'xml, str>,
65    /// Local element name.
66    pub name: Cow<'xml, str>,
67    /// Attributes on this element.
68    pub attributes: Vec<AnyAttribute<'xml>>,
69    /// Text content of this element, if any.
70    pub text: Option<Cow<'xml, str>>,
71    /// Nested child elements.
72    pub children: Vec<AnyElement<'xml>>,
73}
74
75impl<'a> AnyElement<'a> {
76    fn deserialize<'xml: 'a>(
77        deserializer: &mut Deserializer<'_, 'xml>,
78        id: Id<'xml>,
79    ) -> Result<Self, Error> {
80        let mut elem = Self {
81            ns: Cow::Borrowed(id.ns),
82            name: Cow::Borrowed(id.name),
83            attributes: Vec::new(),
84            text: None,
85            children: Vec::new(),
86        };
87
88        loop {
89            match deserializer.next() {
90                Some(Ok(Node::Attribute(attr))) => {
91                    // Namespace declarations (`xmlns:prefix="uri"`) are consumed by the
92                    // deserializer to resolve prefixes, so only regular attributes arrive
93                    // here. Resolve the prefix to a namespace URI immediately.
94                    let id = deserializer.attribute_id(&attr)?;
95
96                    elem.attributes.push(AnyAttribute {
97                        ns: Cow::Borrowed(id.ns),
98                        name: Cow::Borrowed(id.name),
99                        value: attr.value,
100                    });
101                }
102                Some(Ok(Node::Open(element))) => {
103                    let child_id = deserializer.element_id(&element)?;
104                    let mut nested = deserializer.nested(element);
105                    elem.children
106                        .push(Self::deserialize(&mut nested, child_id)?);
107                }
108                Some(Ok(Node::Text(text))) => elem.text = Some(text),
109                Some(Ok(Node::Close { .. })) => break,
110                Some(Ok(_)) => continue,
111                Some(Err(e)) => return Err(e),
112                None => break,
113            }
114        }
115
116        Ok(elem)
117    }
118
119    /// Converts this element into an owned version with `'static` lifetime.
120    ///
121    /// This recursively converts all borrowed strings into owned copies,
122    /// decoupling the result from the original XML input.
123    pub fn into_owned(self) -> AnyElement<'static> {
124        AnyElement {
125            ns: Cow::Owned(self.ns.into_owned()),
126            name: Cow::Owned(self.name.into_owned()),
127            attributes: self
128                .attributes
129                .into_iter()
130                .map(|a| a.into_owned())
131                .collect(),
132            text: self.text.map(|t| Cow::Owned(t.into_owned())),
133            children: self.children.into_iter().map(|c| c.into_owned()).collect(),
134        }
135    }
136}
137
138impl<'xml, 'a> FromXml<'xml> for AnyElement<'a>
139where
140    'xml: 'a,
141{
142    /// Matches any element regardless of namespace or name.
143    fn matches(_id: Id<'_>, _field: Option<Id<'_>>) -> bool {
144        true
145    }
146
147    fn deserialize<'cx>(
148        into: &mut Self::Accumulator,
149        _field: &'static str,
150        deserializer: &mut Deserializer<'cx, 'xml>,
151    ) -> Result<(), Error> {
152        let id = deserializer.parent();
153        *into = Some(Self::deserialize(deserializer, id)?);
154        Ok(())
155    }
156
157    type Accumulator = Option<Self>;
158    const KIND: Kind = Kind::Element;
159}
160
161/// An XML attribute with a resolved namespace URI.
162#[derive(Clone, Debug, Eq, PartialEq)]
163pub struct AnyAttribute<'xml> {
164    /// Namespace URI of this attribute (empty string for unprefixed attributes).
165    pub ns: Cow<'xml, str>,
166    /// Local attribute name.
167    pub name: Cow<'xml, str>,
168    /// Attribute value.
169    pub value: Cow<'xml, str>,
170}
171
172impl<'a> AnyAttribute<'a> {
173    /// Converts this attribute into an owned version with `'static` lifetime.
174    pub fn into_owned(self) -> AnyAttribute<'static> {
175        AnyAttribute {
176            ns: Cow::Owned(self.ns.into_owned()),
177            name: Cow::Owned(self.name.into_owned()),
178            value: Cow::Owned(self.value.into_owned()),
179        }
180    }
181}