Skip to main content

instant_xml/
ser.rs

1use std::collections::hash_map::Entry;
2use std::collections::HashMap;
3use std::fmt::{self};
4use std::mem;
5
6use super::Error;
7use crate::ToXml;
8
9/// XML serializer for writing structured XML output
10pub struct Serializer<'xml, W: fmt::Write + ?Sized> {
11    output: &'xml mut W,
12    /// Map namespace keys to prefixes.
13    ///
14    /// The prefix map is updated using `Context` types that are held on the stack in the relevant
15    /// `ToXml` implementation. If a prefix is already defined for a given namespace, we don't
16    /// update the set with the new prefix.
17    prefixes: HashMap<&'static str, &'static str>,
18    default_ns: &'static str,
19    state: State,
20}
21
22impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
23    /// Create a new serializer writing to the given output
24    pub fn new(output: &'xml mut W) -> Self {
25        Self {
26            output,
27            prefixes: HashMap::new(),
28            default_ns: "",
29            state: State::Element,
30        }
31    }
32
33    /// Write the opening tag for an element
34    ///
35    /// Returns the namespace prefix if one was used.
36    pub fn write_start(&mut self, name: &str, ns: &str) -> Result<Option<&'static str>, Error> {
37        if self.state != State::Element {
38            return Err(Error::UnexpectedState("invalid state for element start"));
39        }
40
41        let prefix = match (ns == self.default_ns, self.prefixes.get(ns)) {
42            (true, _) => {
43                self.output.write_fmt(format_args!("<{name}"))?;
44                None
45            }
46            (false, Some(prefix)) => {
47                self.output.write_fmt(format_args!("<{prefix}:{name}"))?;
48                Some(*prefix)
49            }
50            _ => {
51                self.output
52                    .write_fmt(format_args!("<{name} xmlns=\"{ns}\""))?;
53                None
54            }
55        };
56
57        self.state = State::Attribute;
58        Ok(prefix)
59    }
60
61    /// Write an attribute with the given name and value
62    pub fn write_attr<V: ToXml + ?Sized>(
63        &mut self,
64        name: &str,
65        ns: &str,
66        value: &V,
67    ) -> Result<(), Error> {
68        if self.state != State::Attribute {
69            return Err(Error::UnexpectedState("invalid state for attribute"));
70        }
71
72        match ns == self.default_ns {
73            true => self.output.write_fmt(format_args!(" {name}=\""))?,
74            false => {
75                let prefix = self
76                    .prefixes
77                    .get(ns)
78                    .ok_or(Error::UnexpectedState("unknown prefix"))?;
79                self.output.write_fmt(format_args!(" {prefix}:{name}=\""))?;
80            }
81        }
82
83        self.state = State::Scalar;
84        value.serialize(None, self)?;
85        self.state = State::Attribute;
86        self.output.write_char('"')?;
87        Ok(())
88    }
89
90    /// Write a string value (text content or attribute value)
91    pub fn write_str<V: fmt::Display + ?Sized>(&mut self, value: &V) -> Result<(), Error> {
92        if !matches!(self.state, State::Element | State::Scalar) {
93            return Err(Error::UnexpectedState("invalid state for scalar"));
94        }
95
96        self.output.write_fmt(format_args!("{value}"))?;
97        self.state = State::Element;
98        Ok(())
99    }
100
101    /// Complete the opening tag and transition to element content
102    pub fn end_start(&mut self) -> Result<(), Error> {
103        if self.state != State::Attribute {
104            return Err(Error::UnexpectedState("invalid state for element end"));
105        }
106
107        self.output.write_char('>')?;
108        self.state = State::Element;
109        Ok(())
110    }
111
112    /// Close an empty element (self-closing tag)
113    pub fn end_empty(&mut self) -> Result<(), Error> {
114        if self.state != State::Attribute {
115            return Err(Error::UnexpectedState("invalid state for element end"));
116        }
117
118        self.output.write_str(" />")?;
119        self.state = State::Element;
120        Ok(())
121    }
122
123    /// Write the closing tag for an element
124    pub fn write_close(&mut self, prefix: Option<&str>, name: &str) -> Result<(), Error> {
125        if self.state != State::Element {
126            return Err(Error::UnexpectedState("invalid state for close element"));
127        }
128
129        match prefix {
130            Some(prefix) => self.output.write_fmt(format_args!("</{prefix}:{name}>"))?,
131            None => self.output.write_fmt(format_args!("</{name}>"))?,
132        }
133
134        Ok(())
135    }
136
137    /// Push a namespace context onto the stack
138    ///
139    /// Returns the previous context to be restored later with `pop`.
140    pub fn push<const N: usize>(&mut self, new: Context<N>) -> Result<Context<N>, Error> {
141        if self.state != State::Attribute {
142            return Err(Error::UnexpectedState("invalid state for attribute"));
143        }
144
145        let mut old = Context::default();
146        let prev = mem::replace(&mut self.default_ns, new.default_ns);
147        let _ = mem::replace(&mut old.default_ns, prev);
148
149        let mut used = 0;
150        for prefix in new.prefixes.into_iter() {
151            if prefix.prefix.is_empty() {
152                continue;
153            }
154
155            if self.prefixes.contains_key(prefix.ns) {
156                continue;
157            }
158
159            self.output
160                .write_fmt(format_args!(" xmlns:{}=\"{}\"", prefix.prefix, prefix.ns))?;
161
162            let prev = match self.prefixes.entry(prefix.ns) {
163                Entry::Occupied(mut entry) => mem::replace(entry.get_mut(), prefix.prefix),
164                Entry::Vacant(entry) => {
165                    entry.insert(prefix.prefix);
166                    ""
167                }
168            };
169
170            old.prefixes[used] = Prefix {
171                ns: prefix.ns,
172                prefix: prev,
173            };
174            used += 1;
175        }
176
177        Ok(old)
178    }
179
180    /// Pop a namespace context from the stack, restoring the previous context
181    pub fn pop<const N: usize>(&mut self, old: Context<N>) {
182        let _ = mem::replace(&mut self.default_ns, old.default_ns);
183        for prefix in old.prefixes.into_iter() {
184            if prefix.ns.is_empty() && prefix.prefix.is_empty() {
185                continue;
186            }
187
188            let mut entry = match self.prefixes.entry(prefix.ns) {
189                Entry::Occupied(entry) => entry,
190                Entry::Vacant(_) => unreachable!(),
191            };
192
193            match prefix.prefix {
194                "" => {
195                    entry.remove();
196                }
197                prev => {
198                    let _ = mem::replace(entry.get_mut(), prev);
199                }
200            }
201        }
202    }
203
204    /// Get the prefix for a namespace URI, if any
205    pub fn prefix(&self, ns: &str) -> Option<&'static str> {
206        self.prefixes.get(ns).copied()
207    }
208
209    /// Get the current default namespace URI
210    pub fn default_ns(&self) -> &'static str {
211        self.default_ns
212    }
213}
214
215/// Namespace context for serialization
216#[derive(Debug)]
217pub struct Context<const N: usize> {
218    /// The default namespace URI
219    pub default_ns: &'static str,
220    /// Array of namespace prefix mappings
221    pub prefixes: [Prefix; N],
222}
223
224impl<const N: usize> Default for Context<N> {
225    fn default() -> Self {
226        Self {
227            default_ns: Default::default(),
228            prefixes: [Prefix { prefix: "", ns: "" }; N],
229        }
230    }
231}
232
233/// A namespace prefix mapping
234#[derive(Clone, Copy, Debug, Default)]
235pub struct Prefix {
236    /// The namespace prefix
237    pub prefix: &'static str,
238    /// The namespace URI
239    pub ns: &'static str,
240}
241
242#[derive(Debug, Eq, PartialEq)]
243enum State {
244    Attribute,
245    Element,
246    Scalar,
247}