Skip to main content

lib3mf_core/writer/
xml_writer.rs

1use crate::error::{Lib3mfError, Result};
2use quick_xml::Writer;
3use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
4use std::io::Write;
5
6/// A low-level XML writer providing indented output for 3MF XML serialization.
7pub struct XmlWriter<W: Write> {
8    writer: Writer<W>,
9}
10
11impl<W: Write> XmlWriter<W> {
12    /// Creates a new `XmlWriter` wrapping the given writer with 2-space indentation.
13    pub fn new(inner: W) -> Self {
14        Self {
15            writer: Writer::new_with_indent(inner, b' ', 2),
16        }
17    }
18
19    /// Writes the XML declaration (`<?xml version="1.0" encoding="UTF-8"?>`).
20    pub fn write_declaration(&mut self) -> Result<()> {
21        let decl = BytesDecl::new("1.0", Some("UTF-8"), None);
22        self.writer
23            .write_event(Event::Decl(decl))
24            .map_err(|e| Lib3mfError::Validation(e.to_string()))
25    }
26
27    /// Returns an `ElementBuilder` for constructing and writing a start or empty element.
28    pub fn start_element(&mut self, name: &str) -> ElementBuilder<'_, W> {
29        ElementBuilder {
30            writer: self,
31            name: name.to_string(),
32            attributes: Vec::new(),
33        }
34    }
35
36    /// Writes a closing tag for the given element name.
37    pub fn end_element(&mut self, name: &str) -> Result<()> {
38        self.writer
39            .write_event(Event::End(BytesEnd::new(name)))
40            .map_err(|e| Lib3mfError::Validation(e.to_string()))
41    }
42
43    /// Writes a text node with the given content.
44    pub fn write_text(&mut self, text: &str) -> Result<()> {
45        self.writer
46            .write_event(Event::Text(BytesText::new(text)))
47            .map_err(|e| Lib3mfError::Validation(e.to_string()))
48    }
49}
50
51/// A builder for constructing XML elements with attributes before writing.
52pub struct ElementBuilder<'a, W: Write> {
53    writer: &'a mut XmlWriter<W>,
54    name: String,
55    attributes: Vec<(String, String)>,
56}
57
58impl<'a, W: Write> ElementBuilder<'a, W> {
59    /// Adds a required attribute to the element.
60    pub fn attr(mut self, key: &str, value: &str) -> Self {
61        self.attributes.push((key.to_string(), value.to_string()));
62        self
63    }
64
65    /// Adds an optional attribute to the element, only if `value` is `Some`.
66    pub fn optional_attr(mut self, key: &str, value: Option<&str>) -> Self {
67        if let Some(v) = value {
68            self.attributes.push((key.to_string(), v.to_string()));
69        }
70        self
71    }
72
73    /// Writes the element as a self-closing empty element (e.g., `<vertex x="1" />`).
74    pub fn write_empty(self) -> Result<()> {
75        let mut elem = BytesStart::new(&self.name);
76        for (k, v) in &self.attributes {
77            elem.push_attribute((k.as_str(), v.as_str()));
78        }
79        self.writer
80            .writer
81            .write_event(Event::Empty(elem))
82            .map_err(|e| Lib3mfError::Validation(e.to_string()))
83    }
84
85    /// Writes the element as an opening tag (e.g., `<model xmlns="...">`) with child content to follow.
86    pub fn write_start(self) -> Result<()> {
87        let mut elem = BytesStart::new(&self.name);
88        for (k, v) in &self.attributes {
89            elem.push_attribute((k.as_str(), v.as_str()));
90        }
91        self.writer
92            .writer
93            .write_event(Event::Start(elem))
94            .map_err(|e| Lib3mfError::Validation(e.to_string()))
95    }
96}