easyofd_core/
xml_element.rs1use std::fmt::Write as _;
12
13#[derive(Debug, Clone, PartialEq)]
18pub struct XmlNode {
19 pub name: String,
21 pub attrs: Vec<(String, String)>,
23 pub children: Vec<XmlNode>,
25 pub text: Option<String>,
27}
28
29impl XmlNode {
30 #[must_use]
32 pub fn element(name: impl Into<String>) -> Self {
33 Self {
34 name: name.into(),
35 attrs: Vec::new(),
36 children: Vec::new(),
37 text: None,
38 }
39 }
40
41 #[must_use]
43 pub fn text_node(text: impl Into<String>) -> Self {
44 Self {
45 name: String::new(),
46 attrs: Vec::new(),
47 children: Vec::new(),
48 text: Some(text.into()),
49 }
50 }
51
52 #[must_use]
54 pub fn attr(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
55 self.attrs.push((key.into(), value.into()));
56 self
57 }
58
59 pub fn push_child(&mut self, child: XmlNode) {
61 self.children.push(child);
62 }
63
64 #[must_use]
66 pub fn get_attr(&self, key: &str) -> Option<&str> {
67 self.attrs
68 .iter()
69 .find(|(k, _)| k == key)
70 .map(|(_, v)| v.as_str())
71 }
72
73 #[must_use]
75 pub fn child(&self, name: &str) -> Option<&XmlNode> {
76 self.children.iter().find(|c| c.name == name)
77 }
78
79 pub fn children_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a XmlNode> {
81 self.children.iter().filter(move |c| c.name == name)
82 }
83
84 #[must_use]
88 pub fn to_xml_string(&self) -> String {
89 let mut out = String::new();
90 self.write_self_xml(&mut out);
91 out
92 }
93}
94
95pub trait XmlElement {
101 fn element_name(&self) -> &'static str;
103
104 fn attributes(&self) -> Vec<(String, String)>;
106
107 fn child_nodes(&self) -> Vec<XmlNode> {
109 Vec::new()
110 }
111
112 fn text_content(&self) -> Option<&str> {
114 None
115 }
116
117 fn to_xml(&self) -> String {
119 let mut out = String::new();
120 self.write_xml(&mut out);
121 out
122 }
123
124 fn write_xml(&self, out: &mut String) {
126 let name = self.element_name();
127 out.push('<');
128 out.push_str(name);
129 for (k, v) in self.attributes() {
130 out.push(' ');
131 out.push_str(&k);
132 out.push_str("=\"");
133 out.push_str(&xml_escape(&v));
134 out.push('"');
135 }
136 let children = self.child_nodes();
137 let text = self.text_content();
138 if children.is_empty() && text.is_none_or(|s| s.is_empty()) {
139 out.push_str("/>");
140 return;
141 }
142 out.push('>');
143 if let Some(t) = text {
144 out.push_str(&xml_escape(t));
145 }
146 for child in children {
147 child.write_self_xml(out);
148 }
149 out.push_str("</");
150 out.push_str(name);
151 out.push('>');
152 }
153
154 fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError>
160 where
161 Self: Sized;
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct XmlElementError(pub String);
167
168impl std::fmt::Display for XmlElementError {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 f.write_str(&self.0)
171 }
172}
173
174impl std::error::Error for XmlElementError {}
175
176impl XmlNode {
177 fn write_self_xml(&self, out: &mut String) {
182 if let Some(text) = &self.text {
183 out.push_str(&xml_escape(text));
184 return;
185 }
186 let _ = write!(out, "<{}", self.name);
187 for (k, v) in &self.attrs {
188 let _ = write!(out, " {k}=\"{}\"", xml_escape(v));
189 }
190 let is_empty = self.children.is_empty()
192 || (self.children.len() == 1
193 && self.children[0].name.is_empty()
194 && self.children[0].text.as_deref().is_some_and(str::is_empty));
195 if is_empty {
196 out.push_str("/>");
197 return;
198 }
199 out.push('>');
200 for child in &self.children {
201 child.write_self_xml(out);
202 }
203 let _ = write!(out, "</{}>", self.name);
204 }
205}
206
207#[must_use]
209pub fn xml_escape(s: &str) -> String {
210 let mut out = String::with_capacity(s.len());
211 for c in s.chars() {
212 match c {
213 '&' => out.push_str("&"),
214 '<' => out.push_str("<"),
215 '>' => out.push_str(">"),
216 '"' => out.push_str("""),
217 '\'' => out.push_str("'"),
218 _ => out.push(c),
219 }
220 }
221 out
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 struct SampleElement {
229 id: String,
230 name: String,
231 }
232
233 impl XmlElement for SampleElement {
234 fn element_name(&self) -> &'static str {
235 "Sample"
236 }
237
238 fn attributes(&self) -> Vec<(String, String)> {
239 vec![("ID".to_string(), self.id.clone())]
240 }
241
242 fn text_content(&self) -> Option<&str> {
243 Some(&self.name)
244 }
245
246 fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
247 Ok(Self {
248 id: node.get_attr("ID").unwrap_or_default().to_string(),
249 name: node.text.clone().unwrap_or_default(),
250 })
251 }
252 }
253
254 #[test]
255 fn test_to_xml_simple() {
256 let el = SampleElement {
257 id: "1".to_string(),
258 name: "测试 & 数据".to_string(),
259 };
260 let xml = el.to_xml();
261 assert_eq!(xml, r#"<Sample ID="1">测试 & 数据</Sample>"#);
262 }
263
264 #[test]
265 fn test_node_roundtrip() {
266 let mut node = XmlNode::element("Page")
267 .attr("ID", "5")
268 .attr("BaseLoc", "Pages/Page_0/Content.xml");
269 node.push_child(XmlNode::element("Layer"));
270 assert_eq!(node.get_attr("ID"), Some("5"));
271 assert_eq!(node.child("Layer").unwrap().name, "Layer");
272
273 let mut out = String::new();
274 node.write_self_xml(&mut out);
275 let xml = out;
276 assert!(xml.starts_with(r#"<Page ID="5" BaseLoc="Pages/Page_0/Content.xml">"#));
277 assert!(xml.contains("<Layer/>"));
278 }
279
280 #[test]
281 fn test_xml_escape() {
282 assert_eq!(xml_escape("<a&b\"c'>"), "<a&b"c'>");
283 }
284}