1mod parser;
4
5use facet_xml as xml;
6use std::collections::HashMap;
7
8pub use parser::{
9 ElementParseError, ElementParser, ElementSerializeError, ElementSerializer, from_element,
10 to_element,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum PathError {
16 EmptyPath { path: Vec<usize> },
18 IndexOutOfBounds {
20 path: Vec<usize>,
21 index: usize,
22 len: usize,
23 },
24 TextNodeHasNoChildren { path: Vec<usize> },
26}
27
28impl std::fmt::Display for PathError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 PathError::EmptyPath { path } => write!(f, "empty path: {path:?}"),
32 PathError::IndexOutOfBounds { path, index, len } => {
33 write!(
34 f,
35 "index {index} out of bounds (len={len}) at path {path:?}"
36 )
37 }
38 PathError::TextNodeHasNoChildren { path } => {
39 write!(f, "text node has no children at path {path:?}")
40 }
41 }
42 }
43}
44
45impl std::error::Error for PathError {}
46
47#[derive(Debug, Clone, PartialEq, Eq, facet::Facet)]
49#[repr(u8)]
50pub enum Content {
51 #[facet(xml::text)]
53 Text(String),
54 #[facet(xml::custom_element)]
56 Element(Element),
57}
58
59impl Content {
60 pub fn as_text(&self) -> Option<&str> {
62 match self {
63 Content::Text(t) => Some(t),
64 _ => None,
65 }
66 }
67
68 pub fn as_element(&self) -> Option<&Element> {
70 match self {
71 Content::Element(e) => Some(e),
72 _ => None,
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Default, facet::Facet)]
82pub struct Element {
83 #[facet(xml::tag, default)]
85 pub tag: String,
86
87 #[facet(flatten, default)]
89 pub attrs: HashMap<String, String>,
90
91 #[facet(flatten, default)]
93 #[facet(recursive_type)]
94 pub children: Vec<Content>,
95}
96
97impl Element {
98 pub fn new(tag: impl Into<String>) -> Self {
100 Self {
101 tag: tag.into(),
102 attrs: HashMap::new(),
103 children: Vec::new(),
104 }
105 }
106
107 pub fn with_attr(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
109 self.attrs.insert(name.into(), value.into());
110 self
111 }
112
113 pub fn with_child(mut self, child: Element) -> Self {
115 self.children.push(Content::Element(child));
116 self
117 }
118
119 pub fn with_text(mut self, text: impl Into<String>) -> Self {
121 self.children.push(Content::Text(text.into()));
122 self
123 }
124
125 pub fn get_attr(&self, name: &str) -> Option<&str> {
127 self.attrs.get(name).map(|s| s.as_str())
128 }
129
130 pub fn child_elements(&self) -> impl Iterator<Item = &Element> {
132 self.children.iter().filter_map(|c| c.as_element())
133 }
134
135 pub fn text_content(&self) -> String {
137 let mut result = String::new();
138 for child in &self.children {
139 match child {
140 Content::Text(t) => result.push_str(t),
141 Content::Element(e) => result.push_str(&e.text_content()),
142 }
143 }
144 result
145 }
146
147 pub fn get_content_mut(&mut self, path: &[usize]) -> Result<&mut Content, PathError> {
150 if path.is_empty() {
151 return Err(PathError::EmptyPath { path: vec![] });
152 }
153
154 let idx = path[0];
155 let len = self.children.len();
156 let child = self
157 .children
158 .get_mut(idx)
159 .ok_or_else(|| PathError::IndexOutOfBounds {
160 path: path.to_vec(),
161 index: idx,
162 len,
163 })?;
164
165 if path.len() == 1 {
166 return Ok(child);
167 }
168
169 match child {
170 Content::Element(e) => e.get_content_mut(&path[1..]),
171 Content::Text(_) => Err(PathError::TextNodeHasNoChildren {
172 path: path.to_vec(),
173 }),
174 }
175 }
176
177 pub fn children_mut(&mut self, path: &[usize]) -> Result<&mut Vec<Content>, PathError> {
179 if path.is_empty() {
180 return Ok(&mut self.children);
181 }
182 match self.get_content_mut(path)? {
183 Content::Element(e) => Ok(&mut e.children),
184 Content::Text(_) => Err(PathError::TextNodeHasNoChildren {
185 path: path.to_vec(),
186 }),
187 }
188 }
189
190 pub fn attrs_mut(&mut self, path: &[usize]) -> Result<&mut HashMap<String, String>, PathError> {
192 if path.is_empty() {
193 return Ok(&mut self.attrs);
194 }
195 match self.get_content_mut(path)? {
196 Content::Element(e) => Ok(&mut e.attrs),
197 Content::Text(_) => Err(PathError::TextNodeHasNoChildren {
198 path: path.to_vec(),
199 }),
200 }
201 }
202
203 pub fn to_html(&self) -> String {
205 let mut out = String::new();
206 self.write_html(&mut out);
207 out
208 }
209
210 pub fn write_html(&self, out: &mut String) {
212 out.push('<');
213 out.push_str(&self.tag);
214 let mut attr_list: Vec<_> = self.attrs.iter().collect();
216 attr_list.sort_by_key(|(k, _)| *k);
217 for (k, v) in attr_list {
218 out.push(' ');
219 out.push_str(k);
220 out.push_str("=\"");
221 out.push_str(&html_escape(v));
222 out.push('"');
223 }
224 out.push('>');
225 for child in &self.children {
226 match child {
227 Content::Text(s) => out.push_str(s),
228 Content::Element(e) => e.write_html(out),
229 }
230 }
231 out.push_str("</");
232 out.push_str(&self.tag);
233 out.push('>');
234 }
235}
236
237fn html_escape(s: &str) -> String {
238 s.replace('&', "&")
239 .replace('<', "<")
240 .replace('>', ">")
241 .replace('"', """)
242}
243
244impl From<Element> for Content {
245 fn from(e: Element) -> Self {
246 Content::Element(e)
247 }
248}
249
250impl From<String> for Content {
251 fn from(s: String) -> Self {
252 Content::Text(s)
253 }
254}
255
256impl From<&str> for Content {
257 fn from(s: &str) -> Self {
258 Content::Text(s.to_owned())
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn element_builder_api() {
268 let elem = Element::new("root")
269 .with_attr("id", "123")
270 .with_child(Element::new("child").with_text("hello world"));
271
272 assert_eq!(elem.tag, "root");
273 assert_eq!(elem.get_attr("id"), Some("123"));
274 assert_eq!(elem.children.len(), 1);
275
276 let child = elem.child_elements().next().unwrap();
277 assert_eq!(child.tag, "child");
278 assert_eq!(child.text_content(), "hello world");
279 }
280
281 #[test]
282 fn parse_simple_xml() {
283 let xml = r#"<root><child>hello</child></root>"#;
284 let elem: Element = facet_xml::from_str(xml).unwrap();
285
286 assert_eq!(elem.tag, "root");
287 assert_eq!(elem.children.len(), 1);
288
289 let child = elem.child_elements().next().unwrap();
290 assert_eq!(child.tag, "child");
291 assert_eq!(child.text_content(), "hello");
292 }
293
294 #[test]
295 fn parse_with_attributes() {
296 let xml = r#"<root id="123" class="test"><child name="foo">bar</child></root>"#;
297 let elem: Element = facet_xml::from_str(xml).unwrap();
298
299 assert_eq!(elem.tag, "root");
300 assert_eq!(elem.get_attr("id"), Some("123"));
301 assert_eq!(elem.get_attr("class"), Some("test"));
302
303 let child = elem.child_elements().next().unwrap();
304 assert_eq!(child.get_attr("name"), Some("foo"));
305 assert_eq!(child.text_content(), "bar");
306 }
307
308 #[test]
309 fn parse_mixed_content() {
310 let xml = r#"<p>Hello <b>world</b>!</p>"#;
311 let elem: Element = facet_xml::from_str(xml).unwrap();
312
313 assert_eq!(elem.tag, "p");
314 assert_eq!(elem.children.len(), 3);
315 assert_eq!(elem.children[0].as_text(), Some("Hello"));
317 assert_eq!(elem.children[1].as_element().unwrap().tag, "b");
318 assert_eq!(elem.children[2].as_text(), Some("!"));
319 assert_eq!(elem.text_content(), "Helloworld!");
320 }
321
322 #[test]
323 fn from_element_to_struct() {
324 #[derive(facet::Facet, Debug, PartialEq)]
325 struct Person {
326 name: String,
327 age: u32,
328 }
329
330 let elem = Element::new("person")
331 .with_child(Element::new("name").with_text("Alice"))
332 .with_child(Element::new("age").with_text("30"));
333
334 let person: Person = from_element(&elem).unwrap();
335 assert_eq!(person.name, "Alice");
336 assert_eq!(person.age, 30);
337 }
338
339 #[test]
340 fn from_element_with_attrs() {
341 #[derive(facet::Facet, Debug, PartialEq)]
342 struct Item {
343 #[facet(xml::attribute)]
344 id: String,
345 value: String,
346 }
347
348 let elem = Element::new("item")
349 .with_attr("id", "123")
350 .with_child(Element::new("value").with_text("hello"));
351
352 let item: Item = from_element(&elem).unwrap();
353 assert_eq!(item.id, "123");
354 assert_eq!(item.value, "hello");
355 }
356
357 #[test]
358 fn to_element_simple() {
359 #[derive(facet::Facet, Debug, PartialEq)]
360 struct Person {
361 name: String,
362 age: u32,
363 }
364
365 let person = Person {
366 name: "Alice".to_string(),
367 age: 30,
368 };
369
370 let elem = to_element(&person).unwrap();
371 assert_eq!(elem.tag, "person");
372 assert_eq!(elem.children.len(), 2);
373
374 let name_child = elem.child_elements().find(|e| e.tag == "name").unwrap();
375 assert_eq!(name_child.text_content(), "Alice");
376
377 let age_child = elem.child_elements().find(|e| e.tag == "age").unwrap();
378 assert_eq!(age_child.text_content(), "30");
379 }
380
381 #[test]
382 fn to_element_with_attrs() {
383 #[derive(facet::Facet, Debug, PartialEq)]
384 struct Item {
385 #[facet(xml::attribute)]
386 id: String,
387 value: String,
388 }
389
390 let item = Item {
391 id: "123".to_string(),
392 value: "hello".to_string(),
393 };
394
395 let elem = to_element(&item).unwrap();
396 assert_eq!(elem.tag, "item");
397 assert_eq!(elem.get_attr("id"), Some("123"));
398
399 let value_child = elem.child_elements().find(|e| e.tag == "value").unwrap();
400 assert_eq!(value_child.text_content(), "hello");
401 }
402
403 #[test]
404 fn roundtrip_simple() {
405 #[derive(facet::Facet, Debug, PartialEq)]
406 struct Person {
407 name: String,
408 age: u32,
409 }
410
411 let original = Person {
412 name: "Bob".to_string(),
413 age: 42,
414 };
415
416 let elem = to_element(&original).unwrap();
417 let roundtripped: Person = from_element(&elem).unwrap();
418
419 assert_eq!(original, roundtripped);
420 }
421
422 #[test]
423 fn roundtrip_with_attrs() {
424 #[derive(facet::Facet, Debug, PartialEq)]
425 struct Item {
426 #[facet(xml::attribute)]
427 id: String,
428 #[facet(xml::attribute)]
429 version: u32,
430 value: String,
431 }
432
433 let original = Item {
434 id: "test-123".to_string(),
435 version: 5,
436 value: "content".to_string(),
437 };
438
439 let elem = to_element(&original).unwrap();
440 let roundtripped: Item = from_element(&elem).unwrap();
441
442 assert_eq!(original, roundtripped);
443 }
444}