wacore_binary/
node.rs

1use crate::attrs::{AttrParser, AttrParserRef};
2use crate::jid::{Jid, JidRef};
3use indexmap::IndexMap;
4use std::borrow::Cow;
5
6pub type Attrs = IndexMap<String, String>;
7pub type AttrsRef<'a> = Vec<(Cow<'a, str>, ValueRef<'a>)>;
8
9/// A decoded attribute value that can be either a string or a structured JID.
10/// This avoids string allocation when decoding JID tokens - the JidRef is returned
11/// directly and only converted to a string when actually needed.
12#[derive(Debug, Clone, PartialEq)]
13pub enum ValueRef<'a> {
14    String(Cow<'a, str>),
15    Jid(JidRef<'a>),
16}
17
18impl<'a> ValueRef<'a> {
19    /// Get the value as a string slice, if it's a string variant.
20    pub fn as_str(&self) -> Option<&str> {
21        match self {
22            ValueRef::String(s) => Some(s.as_ref()),
23            ValueRef::Jid(_) => None,
24        }
25    }
26
27    /// Get the value as a JidRef, if it's a JID variant.
28    pub fn as_jid(&self) -> Option<&JidRef<'a>> {
29        match self {
30            ValueRef::Jid(j) => Some(j),
31            ValueRef::String(_) => None,
32        }
33    }
34
35    /// Convert to an owned Jid, parsing from string if necessary.
36    pub fn to_jid(&self) -> Option<Jid> {
37        match self {
38            ValueRef::Jid(j) => Some(j.to_owned()),
39            ValueRef::String(s) => Jid::from_str(s.as_ref()).ok(),
40        }
41    }
42
43    /// Convert to a string, formatting the JID if necessary.
44    /// Returns a Cow to avoid allocation when the value is already a string.
45    pub fn to_string_cow(&self) -> Cow<'a, str> {
46        match self {
47            ValueRef::String(s) => s.clone(),
48            ValueRef::Jid(j) => Cow::Owned(j.to_string()),
49        }
50    }
51}
52
53use std::fmt;
54use std::str::FromStr;
55
56impl<'a> fmt::Display for ValueRef<'a> {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            ValueRef::String(s) => write!(f, "{}", s),
60            ValueRef::Jid(j) => write!(f, "{}", j),
61        }
62    }
63}
64
65pub type NodeVec<'a> = Vec<NodeRef<'a>>;
66
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[derive(Debug, Clone, PartialEq)]
69pub enum NodeContent {
70    Bytes(Vec<u8>),
71    String(String),
72    Nodes(Vec<Node>),
73}
74
75#[derive(Debug, Clone, PartialEq)]
76pub enum NodeContentRef<'a> {
77    Bytes(Cow<'a, [u8]>),
78    String(Cow<'a, str>),
79    Nodes(Box<NodeVec<'a>>),
80}
81
82impl NodeContent {
83    /// Convert an owned NodeContent to a borrowed NodeContentRef.
84    pub fn as_content_ref(&self) -> NodeContentRef<'_> {
85        match self {
86            NodeContent::Bytes(b) => NodeContentRef::Bytes(Cow::Borrowed(b)),
87            NodeContent::String(s) => NodeContentRef::String(Cow::Borrowed(s)),
88            NodeContent::Nodes(nodes) => {
89                NodeContentRef::Nodes(Box::new(nodes.iter().map(|n| n.as_node_ref()).collect()))
90            }
91        }
92    }
93}
94
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96#[derive(Debug, Clone, PartialEq, Default)]
97pub struct Node {
98    pub tag: String,
99    pub attrs: Attrs,
100    pub content: Option<NodeContent>,
101}
102
103#[derive(Debug, Clone, PartialEq)]
104pub struct NodeRef<'a> {
105    pub tag: Cow<'a, str>,
106    pub attrs: AttrsRef<'a>,
107    pub content: Option<Box<NodeContentRef<'a>>>,
108}
109
110impl Node {
111    pub fn new(tag: &str, attrs: Attrs, content: Option<NodeContent>) -> Self {
112        Self {
113            tag: tag.to_string(),
114            attrs,
115            content,
116        }
117    }
118
119    /// Convert an owned Node to a borrowed NodeRef.
120    /// The returned NodeRef borrows from self.
121    pub fn as_node_ref(&self) -> NodeRef<'_> {
122        NodeRef {
123            tag: Cow::Borrowed(&self.tag),
124            attrs: self
125                .attrs
126                .iter()
127                .map(|(k, v)| {
128                    (
129                        Cow::Borrowed(k.as_str()),
130                        ValueRef::String(Cow::Borrowed(v.as_str())),
131                    )
132                })
133                .collect(),
134            content: self.content.as_ref().map(|c| Box::new(c.as_content_ref())),
135        }
136    }
137
138    pub fn children(&self) -> Option<&[Node]> {
139        match &self.content {
140            Some(NodeContent::Nodes(nodes)) => Some(nodes),
141            _ => None,
142        }
143    }
144
145    pub fn attrs(&self) -> AttrParser<'_> {
146        AttrParser::new(self)
147    }
148
149    pub fn get_optional_child_by_tag<'a>(&'a self, tags: &[&str]) -> Option<&'a Node> {
150        let mut current_node = self;
151        for &tag in tags {
152            if let Some(children) = current_node.children() {
153                if let Some(found) = children.iter().find(|c| c.tag == tag) {
154                    current_node = found;
155                } else {
156                    return None;
157                }
158            } else {
159                return None;
160            }
161        }
162        Some(current_node)
163    }
164
165    pub fn get_children_by_tag(&self, tag: &str) -> Vec<&Node> {
166        if let Some(children) = self.children() {
167            children.iter().filter(|c| c.tag == tag).collect()
168        } else {
169            Vec::new()
170        }
171    }
172
173    pub fn get_optional_child(&self, tag: &str) -> Option<&Node> {
174        self.children()
175            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
176    }
177}
178
179impl<'a> NodeRef<'a> {
180    pub fn new(
181        tag: Cow<'a, str>,
182        attrs: AttrsRef<'a>,
183        content: Option<NodeContentRef<'a>>,
184    ) -> Self {
185        Self {
186            tag,
187            attrs,
188            content: content.map(Box::new),
189        }
190    }
191
192    pub fn attr_parser(&'a self) -> AttrParserRef<'a> {
193        AttrParserRef::new(self)
194    }
195
196    pub fn children(&self) -> Option<&[NodeRef<'a>]> {
197        match self.content.as_deref() {
198            Some(NodeContentRef::Nodes(nodes)) => Some(nodes.as_slice()),
199            _ => None,
200        }
201    }
202
203    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'a>> {
204        self.attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
205    }
206
207    pub fn attrs_iter(&self) -> impl Iterator<Item = (&Cow<'a, str>, &ValueRef<'a>)> {
208        self.attrs.iter().map(|(k, v)| (k, v))
209    }
210
211    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'a>> {
212        let mut current_node = self;
213        for &tag in tags {
214            if let Some(children) = current_node.children() {
215                if let Some(found) = children.iter().find(|c| c.tag == tag) {
216                    current_node = found;
217                } else {
218                    return None;
219                }
220            } else {
221                return None;
222            }
223        }
224        Some(current_node)
225    }
226
227    pub fn get_children_by_tag(&self, tag: &str) -> Vec<&NodeRef<'a>> {
228        if let Some(children) = self.children() {
229            children.iter().filter(|c| c.tag == tag).collect()
230        } else {
231            Vec::new()
232        }
233    }
234
235    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'a>> {
236        self.children()
237            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
238    }
239
240    pub fn to_owned(&self) -> Node {
241        Node {
242            tag: self.tag.to_string(),
243            attrs: self
244                .attrs
245                .iter()
246                .map(|(k, v)| (k.to_string(), v.to_string_cow().into_owned()))
247                .collect::<IndexMap<String, String>>(),
248            content: self.content.as_deref().map(|c| match c {
249                NodeContentRef::Bytes(b) => NodeContent::Bytes(b.to_vec()),
250                NodeContentRef::String(s) => NodeContent::String(s.to_string()),
251                NodeContentRef::Nodes(nodes) => {
252                    NodeContent::Nodes(nodes.iter().map(|n| n.to_owned()).collect())
253                }
254            }),
255        }
256    }
257}