Skip to main content

lsl_core/
xml_dom.rs

1//! Mutable XML DOM for stream descriptions.
2//! Provides a pugixml-compatible tree structure used by StreamInfo's `<desc>` element.
3
4use parking_lot::Mutex;
5use std::sync::Arc;
6
7/// Internal node data
8#[derive(Debug)]
9struct NodeData {
10    name: String,
11    value: String,
12    children: Vec<XmlNode>,
13    parent: Option<XmlNodeWeak>,
14}
15
16type XmlNodeInner = Arc<Mutex<NodeData>>;
17type XmlNodeWeak = std::sync::Weak<Mutex<NodeData>>;
18
19/// An XML element node handle. Cloning is cheap (Arc).
20#[derive(Debug, Clone)]
21pub struct XmlNode {
22    inner: XmlNodeInner,
23}
24
25/// A null/empty sentinel node
26static EMPTY_NODE: once_cell::sync::Lazy<XmlNode> = once_cell::sync::Lazy::new(|| XmlNode {
27    inner: Arc::new(Mutex::new(NodeData {
28        name: String::new(),
29        value: String::new(),
30        children: Vec::new(),
31        parent: None,
32    })),
33});
34
35impl XmlNode {
36    /// Create a new named node
37    pub fn new(name: &str) -> Self {
38        XmlNode {
39            inner: Arc::new(Mutex::new(NodeData {
40                name: name.to_string(),
41                value: String::new(),
42                children: Vec::new(),
43                parent: None,
44            })),
45        }
46    }
47
48    /// Create an empty/null node
49    pub fn empty() -> Self {
50        EMPTY_NODE.clone()
51    }
52
53    /// Check if this is the empty sentinel
54    pub fn is_empty(&self) -> bool {
55        self.inner.lock().name.is_empty()
56    }
57
58    /// Get the name of this node
59    pub fn name(&self) -> String {
60        self.inner.lock().name.clone()
61    }
62
63    /// Get the text value of this node
64    pub fn value(&self) -> String {
65        self.inner.lock().value.clone()
66    }
67
68    /// Set the name
69    pub fn set_name(&self, name: &str) {
70        self.inner.lock().name = name.to_string();
71    }
72
73    /// Set the text value
74    pub fn set_value(&self, value: &str) {
75        self.inner.lock().value = value.to_string();
76    }
77
78    /// Append a child element with the given name, return the new child
79    pub fn append_child(&self, name: &str) -> XmlNode {
80        let child = XmlNode::new(name);
81        {
82            child.inner.lock().parent = Some(Arc::downgrade(&self.inner));
83        }
84        self.inner.lock().children.push(child.clone());
85        child
86    }
87
88    /// Prepend a child element
89    pub fn prepend_child(&self, name: &str) -> XmlNode {
90        let child = XmlNode::new(name);
91        {
92            child.inner.lock().parent = Some(Arc::downgrade(&self.inner));
93        }
94        self.inner.lock().children.insert(0, child.clone());
95        child
96    }
97
98    /// Append a child element with name and text value
99    pub fn append_child_value(&self, name: &str, value: &str) -> XmlNode {
100        let child = self.append_child(name);
101        child.set_value(value);
102        child
103    }
104
105    /// Prepend a child element with name and text value
106    pub fn prepend_child_value(&self, name: &str, value: &str) -> XmlNode {
107        let child = self.prepend_child(name);
108        child.set_value(value);
109        child
110    }
111
112    /// Set the value of a named child (creating it if needed)
113    pub fn set_child_value(&self, name: &str, value: &str) -> bool {
114        let data = self.inner.lock();
115        for child in &data.children {
116            if child.name() == name {
117                child.set_value(value);
118                return true;
119            }
120        }
121        // create it
122        drop(data);
123        self.append_child_value(name, value);
124        true
125    }
126
127    /// Get a child by name
128    pub fn child(&self, name: &str) -> XmlNode {
129        let data = self.inner.lock();
130        for child in &data.children {
131            if child.name() == name {
132                return child.clone();
133            }
134        }
135        XmlNode::empty()
136    }
137
138    /// Get the text value of a named child
139    pub fn child_value(&self, name: &str) -> String {
140        let ch = self.child(name);
141        if ch.is_empty() {
142            String::new()
143        } else {
144            ch.value()
145        }
146    }
147
148    /// Get the text content (value of first text child or self value)
149    pub fn child_value_self(&self) -> String {
150        self.value()
151    }
152
153    /// Get the first child
154    pub fn first_child(&self) -> XmlNode {
155        let data = self.inner.lock();
156        data.children
157            .first()
158            .cloned()
159            .unwrap_or_else(XmlNode::empty)
160    }
161
162    /// Get the last child
163    pub fn last_child(&self) -> XmlNode {
164        let data = self.inner.lock();
165        data.children.last().cloned().unwrap_or_else(XmlNode::empty)
166    }
167
168    /// Get next sibling (requires parent)
169    pub fn next_sibling(&self) -> XmlNode {
170        self.sibling_offset(1, None)
171    }
172
173    /// Get next sibling with a given name
174    pub fn next_sibling_named(&self, name: &str) -> XmlNode {
175        self.sibling_offset(1, Some(name))
176    }
177
178    /// Get previous sibling
179    pub fn previous_sibling(&self) -> XmlNode {
180        self.sibling_offset(-1, None)
181    }
182
183    /// Get previous sibling with a given name
184    pub fn previous_sibling_named(&self, name: &str) -> XmlNode {
185        self.sibling_offset(-1, Some(name))
186    }
187
188    fn sibling_offset(&self, direction: i32, name_filter: Option<&str>) -> XmlNode {
189        let parent_weak = {
190            let data = self.inner.lock();
191            match &data.parent {
192                Some(w) => w.clone(),
193                None => return XmlNode::empty(),
194            }
195        };
196        let parent = match parent_weak.upgrade() {
197            Some(p) => p,
198            None => return XmlNode::empty(),
199        };
200        let parent_data = parent.lock();
201        let self_ptr = Arc::as_ptr(&self.inner);
202        let mut found_self = false;
203        let iter: Box<dyn Iterator<Item = &XmlNode>> = if direction > 0 {
204            Box::new(parent_data.children.iter())
205        } else {
206            Box::new(parent_data.children.iter().rev())
207        };
208        for child in iter {
209            if found_self {
210                if let Some(name) = name_filter {
211                    if child.name() == name {
212                        return child.clone();
213                    }
214                } else {
215                    return child.clone();
216                }
217            }
218            if Arc::as_ptr(&child.inner) == self_ptr {
219                found_self = true;
220            }
221        }
222        XmlNode::empty()
223    }
224
225    /// Get parent
226    pub fn parent(&self) -> XmlNode {
227        let data = self.inner.lock();
228        match &data.parent {
229            Some(w) => match w.upgrade() {
230                Some(p) => XmlNode { inner: p },
231                None => XmlNode::empty(),
232            },
233            None => XmlNode::empty(),
234        }
235    }
236
237    /// Remove a child by name
238    pub fn remove_child_named(&self, name: &str) {
239        let mut data = self.inner.lock();
240        data.children.retain(|c| c.name() != name);
241    }
242
243    /// Remove a specific child node
244    pub fn remove_child(&self, child: &XmlNode) {
245        let child_ptr = Arc::as_ptr(&child.inner);
246        let mut data = self.inner.lock();
247        data.children.retain(|c| Arc::as_ptr(&c.inner) != child_ptr);
248    }
249
250    /// Deep clone (copy) of this subtree
251    pub fn deep_clone(&self) -> XmlNode {
252        let data = self.inner.lock();
253        let new_node = XmlNode::new(&data.name);
254        new_node.set_value(&data.value);
255        for child in &data.children {
256            let child_clone = child.deep_clone();
257            child_clone.inner.lock().parent = Some(Arc::downgrade(&new_node.inner));
258            new_node.inner.lock().children.push(child_clone);
259        }
260        new_node
261    }
262
263    /// Append a copy of another subtree
264    pub fn append_copy(&self, other: &XmlNode) -> XmlNode {
265        let copy = other.deep_clone();
266        copy.inner.lock().parent = Some(Arc::downgrade(&self.inner));
267        self.inner.lock().children.push(copy.clone());
268        copy
269    }
270
271    /// Prepend a copy of another subtree
272    pub fn prepend_copy(&self, other: &XmlNode) -> XmlNode {
273        let copy = other.deep_clone();
274        copy.inner.lock().parent = Some(Arc::downgrade(&self.inner));
275        self.inner.lock().children.insert(0, copy.clone());
276        copy
277    }
278
279    /// Serialize this subtree to XML string
280    pub fn to_xml(&self) -> String {
281        let mut out = String::new();
282        self.write_xml(&mut out, 0);
283        out
284    }
285
286    fn write_xml(&self, out: &mut String, _depth: usize) {
287        let data = self.inner.lock();
288        if data.name.is_empty() {
289            return;
290        }
291        out.push('<');
292        out.push_str(&data.name);
293        out.push('>');
294        if !data.value.is_empty() {
295            xml_escape_into(out, &data.value);
296        }
297        for child in &data.children {
298            child.write_xml(out, _depth + 1);
299        }
300        out.push_str("</");
301        out.push_str(&data.name);
302        out.push('>');
303    }
304
305    /// Check pointer identity
306    pub fn same_as(&self, other: &XmlNode) -> bool {
307        Arc::ptr_eq(&self.inner, &other.inner)
308    }
309}
310
311fn xml_escape_into(out: &mut String, s: &str) {
312    for c in s.chars() {
313        match c {
314            '&' => out.push_str("&amp;"),
315            '<' => out.push_str("&lt;"),
316            '>' => out.push_str("&gt;"),
317            '"' => out.push_str("&quot;"),
318            '\'' => out.push_str("&apos;"),
319            _ => out.push(c),
320        }
321    }
322}
323
324pub fn xml_escape(s: &str) -> String {
325    let mut out = String::new();
326    xml_escape_into(&mut out, s);
327    out
328}
329
330pub fn xml_unescape(s: &str) -> String {
331    s.replace("&amp;", "&")
332        .replace("&lt;", "<")
333        .replace("&gt;", ">")
334        .replace("&quot;", "\"")
335        .replace("&apos;", "'")
336}