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