1pub fn escape_xml(s: &str) -> String {
7 s.replace('&', "&")
8 .replace('<', "<")
9 .replace('>', ">")
10 .replace('"', """)
11 .replace('\'', "'")
12}
13
14#[allow(dead_code)]
16pub struct XmlWriter {
17 buffer: String,
18 indent_level: usize,
19 indent_str: &'static str,
20}
21
22impl XmlWriter {
23 pub fn new() -> Self {
24 Self {
25 buffer: String::new(),
26 indent_level: 0,
27 indent_str: " ",
28 }
29 }
30
31 pub fn with_capacity(capacity: usize) -> Self {
32 Self {
33 buffer: String::with_capacity(capacity),
34 indent_level: 0,
35 indent_str: " ",
36 }
37 }
38
39 pub fn xml_declaration(&mut self) -> &mut Self {
41 self.buffer
42 .push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
43 self.buffer.push('\n');
44 self
45 }
46
47 pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
49 self.buffer.push('<');
50 self.buffer.push_str(name);
51 for (key, value) in attrs {
52 self.buffer.push(' ');
53 self.buffer.push_str(key);
54 self.buffer.push_str("=\"");
55 self.buffer.push_str(&escape_xml(value));
56 self.buffer.push('"');
57 }
58 self.buffer.push('>');
59 self.indent_level += 1;
60 self
61 }
62
63 pub fn end_element(&mut self, name: &str) -> &mut Self {
65 self.indent_level = self.indent_level.saturating_sub(1);
66 self.buffer.push_str("</");
67 self.buffer.push_str(name);
68 self.buffer.push('>');
69 self
70 }
71
72 pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
74 self.buffer.push('<');
75 self.buffer.push_str(name);
76 for (key, value) in attrs {
77 self.buffer.push(' ');
78 self.buffer.push_str(key);
79 self.buffer.push_str("=\"");
80 self.buffer.push_str(&escape_xml(value));
81 self.buffer.push('"');
82 }
83 self.buffer.push_str("/>");
84 self
85 }
86
87 pub fn text(&mut self, content: &str) -> &mut Self {
89 self.buffer.push_str(&escape_xml(content));
90 self
91 }
92
93 pub fn raw(&mut self, xml: &str) -> &mut Self {
95 self.buffer.push_str(xml);
96 self
97 }
98
99 pub fn finish(self) -> String {
101 self.buffer
102 }
103
104 pub fn as_str(&self) -> &str {
106 &self.buffer
107 }
108}
109
110impl Default for XmlWriter {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_escape_xml() {
122 assert_eq!(escape_xml("a & b"), "a & b");
123 assert_eq!(escape_xml("<tag>"), "<tag>");
124 assert_eq!(escape_xml("\"quoted\""), ""quoted"");
125 }
126
127 #[test]
128 fn test_xml_writer() {
129 let mut writer = XmlWriter::new();
130 writer
131 .start_element("root", &[("attr", "value")])
132 .text("content")
133 .end_element("root");
134 assert_eq!(writer.finish(), r#"<root attr="value">content</root>"#);
135 }
136
137 #[test]
138 fn test_xml_writer_empty_element() {
139 let mut writer = XmlWriter::new();
140 writer.empty_element("br", &[]);
141 assert_eq!(writer.finish(), "<br/>");
142 }
143}