1pub fn escape_xml(s: &str) -> String {
7 s.replace('&', "&")
8 .replace('<', "<")
9 .replace('>', ">")
10 .replace('"', """)
11 .replace('\'', "'")
12}
13
14#[inline]
16#[allow(dead_code)]
17pub fn normalize_color(color: &str) -> String {
18 color.trim_start_matches('#').to_uppercase()
19}
20
21#[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 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 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 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 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 pub fn text(&mut self, content: &str) -> &mut Self {
95 self.buffer.push_str(&escape_xml(content));
96 self
97 }
98
99 pub fn raw(&mut self, xml: &str) -> &mut Self {
101 self.buffer.push_str(xml);
102 self
103 }
104
105 pub fn finish(self) -> String {
107 self.buffer
108 }
109
110 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 & b");
129 assert_eq!(escape_xml("<tag>"), "<tag>");
130 assert_eq!(escape_xml("\"quoted\""), ""quoted"");
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}