Skip to main content

ppt_rs/core/
xml_utils.rs

1//! XML utility functions
2//!
3//! Centralized XML utilities to avoid duplication across modules.
4
5/// Escape special XML characters
6pub fn escape_xml(s: &str) -> String {
7    s.replace('&', "&")
8        .replace('<', "&lt;")
9        .replace('>', "&gt;")
10        .replace('"', "&quot;")
11        .replace('\'', "&apos;")
12}
13
14/// Normalize color string (remove # prefix, uppercase)
15#[inline]
16#[allow(dead_code)]
17pub fn normalize_color(color: &str) -> String {
18    color.trim_start_matches('#').to_uppercase()
19}
20
21/// XML writer helper for building XML strings efficiently
22#[allow(dead_code)]
23pub struct XmlWriter {
24    buffer: String,
25    indent_level: usize,
26    indent_str: &'static str,
27}
28
29impl XmlWriter {
30    pub fn new() -> Self {
31        Self {
32            buffer: String::new(),
33            indent_level: 0,
34            indent_str: "  ",
35        }
36    }
37
38    pub fn with_capacity(capacity: usize) -> Self {
39        Self {
40            buffer: String::with_capacity(capacity),
41            indent_level: 0,
42            indent_str: "  ",
43        }
44    }
45
46    /// Write XML declaration
47    pub fn xml_declaration(&mut self) -> &mut Self {
48        self.buffer.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
49        self.buffer.push('\n');
50        self
51    }
52
53    /// Start an element with attributes
54    pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
55        self.buffer.push('<');
56        self.buffer.push_str(name);
57        for (key, value) in attrs {
58            self.buffer.push(' ');
59            self.buffer.push_str(key);
60            self.buffer.push_str("=\"");
61            self.buffer.push_str(&escape_xml(value));
62            self.buffer.push('"');
63        }
64        self.buffer.push('>');
65        self.indent_level += 1;
66        self
67    }
68
69    /// End an element
70    pub fn end_element(&mut self, name: &str) -> &mut Self {
71        self.indent_level = self.indent_level.saturating_sub(1);
72        self.buffer.push_str("</");
73        self.buffer.push_str(name);
74        self.buffer.push('>');
75        self
76    }
77
78    /// Write a self-closing element
79    pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
80        self.buffer.push('<');
81        self.buffer.push_str(name);
82        for (key, value) in attrs {
83            self.buffer.push(' ');
84            self.buffer.push_str(key);
85            self.buffer.push_str("=\"");
86            self.buffer.push_str(&escape_xml(value));
87            self.buffer.push('"');
88        }
89        self.buffer.push_str("/>");
90        self
91    }
92
93    /// Write text content
94    pub fn text(&mut self, content: &str) -> &mut Self {
95        self.buffer.push_str(&escape_xml(content));
96        self
97    }
98
99    /// Write raw XML (no escaping)
100    pub fn raw(&mut self, xml: &str) -> &mut Self {
101        self.buffer.push_str(xml);
102        self
103    }
104
105    /// Get the built XML string
106    pub fn finish(self) -> String {
107        self.buffer
108    }
109
110    /// Get a reference to the buffer
111    pub fn as_str(&self) -> &str {
112        &self.buffer
113    }
114}
115
116impl Default for XmlWriter {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_escape_xml() {
128        assert_eq!(escape_xml("a & b"), "a &amp; b");
129        assert_eq!(escape_xml("<tag>"), "&lt;tag&gt;");
130        assert_eq!(escape_xml("\"quoted\""), "&quot;quoted&quot;");
131    }
132
133    #[test]
134    fn test_normalize_color() {
135        assert_eq!(normalize_color("#ff0000"), "FF0000");
136        assert_eq!(normalize_color("FF0000"), "FF0000");
137        assert_eq!(normalize_color("#abc"), "ABC");
138    }
139
140    #[test]
141    fn test_xml_writer() {
142        let mut writer = XmlWriter::new();
143        writer
144            .start_element("root", &[("attr", "value")])
145            .text("content")
146            .end_element("root");
147        assert_eq!(writer.finish(), r#"<root attr="value">content</root>"#);
148    }
149
150    #[test]
151    fn test_xml_writer_empty_element() {
152        let mut writer = XmlWriter::new();
153        writer.empty_element("br", &[]);
154        assert_eq!(writer.finish(), "<br/>");
155    }
156}