facet_xml/
serializer.rs

1extern crate alloc;
2
3use alloc::{borrow::Cow, format, string::String, vec::Vec};
4use std::{collections::HashMap, io::Write};
5
6use facet_core::Facet;
7use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
8use facet_reflect::Peek;
9
10/// A function that formats a floating-point number to a writer.
11///
12/// This is used to customize how `f32` and `f64` values are serialized to XML.
13/// The function receives the value (as `f64`, with `f32` values upcast) and
14/// a writer to write the formatted output to.
15pub type FloatFormatter = fn(f64, &mut dyn Write) -> std::io::Result<()>;
16
17/// Options for XML serialization.
18#[derive(Clone)]
19pub struct SerializeOptions {
20    /// Whether to pretty-print with indentation (default: false)
21    pub pretty: bool,
22    /// Indentation string for pretty-printing (default: "  ")
23    pub indent: Cow<'static, str>,
24    /// Custom formatter for floating-point numbers (f32 and f64).
25    /// If `None`, uses the default `Display` implementation.
26    pub float_formatter: Option<FloatFormatter>,
27    /// Whether to preserve entity references (like `&sup1;`, `&#92;`, `&#x5C;`) in string values.
28    ///
29    /// When `true`, entity references in strings are not escaped - the `&` in entity references
30    /// is left as-is instead of being escaped to `&amp;`. This is useful when serializing
31    /// content that already contains entity references (like HTML entities in SVG).
32    ///
33    /// Default: `false` (all `&` characters are escaped to `&amp;`).
34    pub preserve_entities: bool,
35}
36
37impl Default for SerializeOptions {
38    fn default() -> Self {
39        Self {
40            pretty: false,
41            indent: Cow::Borrowed("  "),
42            float_formatter: None,
43            preserve_entities: false,
44        }
45    }
46}
47
48impl core::fmt::Debug for SerializeOptions {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        f.debug_struct("SerializeOptions")
51            .field("pretty", &self.pretty)
52            .field("indent", &self.indent)
53            .field("float_formatter", &self.float_formatter.map(|_| "..."))
54            .field("preserve_entities", &self.preserve_entities)
55            .finish()
56    }
57}
58
59impl SerializeOptions {
60    /// Create new default options (compact output).
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Enable pretty-printing with default indentation.
66    pub fn pretty(mut self) -> Self {
67        self.pretty = true;
68        self
69    }
70
71    /// Set a custom indentation string (implies pretty-printing).
72    pub fn indent(mut self, indent: impl Into<Cow<'static, str>>) -> Self {
73        self.indent = indent.into();
74        self.pretty = true;
75        self
76    }
77
78    /// Set a custom formatter for floating-point numbers (f32 and f64).
79    ///
80    /// The formatter function receives the value as `f64` (f32 values are upcast)
81    /// and writes the formatted output to the provided writer.
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// # use facet::Facet;
87    /// # use facet_xml as xml;
88    /// # use facet_xml::{to_string_with_options, SerializeOptions};
89    /// # use std::io::Write;
90    /// fn fmt_g(value: f64, w: &mut dyn Write) -> std::io::Result<()> {
91    ///     // Format like C's %g: 6 significant digits, trim trailing zeros
92    ///     let s = format!("{:.6}", value);
93    ///     let s = s.trim_end_matches('0').trim_end_matches('.');
94    ///     write!(w, "{}", s)
95    /// }
96    ///
97    /// #[derive(Facet)]
98    /// struct Point {
99    ///     #[facet(xml::attribute)]
100    ///     x: f64,
101    ///     #[facet(xml::attribute)]
102    ///     y: f64,
103    /// }
104    ///
105    /// let point = Point { x: 1.5, y: 2.0 };
106    /// let options = SerializeOptions::new().float_formatter(fmt_g);
107    /// let xml = to_string_with_options(&point, &options).unwrap();
108    /// assert_eq!(xml, r#"<Point x="1.5" y="2"></Point>"#);
109    /// ```
110    pub fn float_formatter(mut self, formatter: FloatFormatter) -> Self {
111        self.float_formatter = Some(formatter);
112        self
113    }
114
115    /// Enable preservation of entity references in string values.
116    ///
117    /// When enabled, entity references like `&sup1;`, `&#92;`, `&#x5C;` are not escaped.
118    /// The `&` in recognized entity patterns is left as-is instead of being escaped to `&amp;`.
119    ///
120    /// This is useful when serializing content that already contains entity references,
121    /// such as HTML entities in SVG content.
122    pub fn preserve_entities(mut self, preserve: bool) -> Self {
123        self.preserve_entities = preserve;
124        self
125    }
126}
127
128/// Well-known XML namespace URIs and their conventional prefixes.
129#[allow(dead_code)] // Used in Phase 4 namespace serialization (partial implementation)
130const WELL_KNOWN_NAMESPACES: &[(&str, &str)] = &[
131    ("http://www.w3.org/2001/XMLSchema-instance", "xsi"),
132    ("http://www.w3.org/2001/XMLSchema", "xs"),
133    ("http://www.w3.org/XML/1998/namespace", "xml"),
134    ("http://www.w3.org/1999/xlink", "xlink"),
135    ("http://www.w3.org/2000/svg", "svg"),
136    ("http://www.w3.org/1999/xhtml", "xhtml"),
137    ("http://schemas.xmlsoap.org/soap/envelope/", "soap"),
138    ("http://www.w3.org/2003/05/soap-envelope", "soap12"),
139    ("http://schemas.android.com/apk/res/android", "android"),
140];
141
142#[derive(Debug)]
143pub struct XmlSerializeError {
144    msg: &'static str,
145}
146
147impl core::fmt::Display for XmlSerializeError {
148    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149        f.write_str(self.msg)
150    }
151}
152
153impl std::error::Error for XmlSerializeError {}
154
155#[derive(Debug)]
156enum Ctx {
157    Root { kind: Option<Kind> },
158    Struct { close: Option<String> },
159    Seq { close: Option<String> },
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163enum Kind {
164    Struct,
165    Seq,
166}
167
168/// XML serializer with configurable output options.
169///
170/// The output is designed to round-trip through `facet-xml`'s parser:
171/// - structs are elements whose children are field elements
172/// - sequences are elements whose children are repeated `<item>` elements
173/// - element names are treated as map keys; the root element name is ignored
174pub struct XmlSerializer {
175    out: Vec<u8>,
176    stack: Vec<Ctx>,
177    pending_field: Option<String>,
178    /// Pending namespace for the next field to be serialized
179    pending_namespace: Option<String>,
180    /// True if the current field is an attribute (vs element)
181    pending_is_attribute: bool,
182    /// True if the current field is text content (xml::text)
183    pending_is_text: bool,
184    /// True if the current field is an xml::elements list (no wrapper element)
185    pending_is_elements: bool,
186    /// Container-level default namespace (from xml::ns_all) for current struct
187    current_ns_all: Option<String>,
188    /// Buffered attributes for the current element (name, value, namespace_opt)
189    pending_attributes: Vec<(String, String, Option<String>)>,
190    item_tag: &'static str,
191    /// Namespace URI -> prefix mapping for already-declared namespaces.
192    declared_namespaces: HashMap<String, String>,
193    /// Counter for auto-generating namespace prefixes (ns0, ns1, ...).
194    next_ns_index: usize,
195    /// The currently active default namespace (from xmlns="..." on an ancestor).
196    /// When set, elements in this namespace use unprefixed names.
197    current_default_ns: Option<String>,
198    /// True if we've written the opening `<root>` tag
199    root_tag_written: bool,
200    /// Name to use for the root element (from struct's rename attribute)
201    root_element_name: Option<String>,
202    /// Deferred element tag - we wait to write the opening tag until we've collected all attributes.
203    /// Format: (element_name, namespace, close_name)
204    /// When Some, we haven't written `<tag ...>` yet; attributes are being collected in pending_attributes.
205    deferred_open_tag: Option<(String, Option<String>, String)>,
206    /// Stack of xml::elements state - when true, we're inside an xml::elements list
207    /// and should not emit a wrapper element for the list items.
208    elements_stack: Vec<bool>,
209    /// When set, we're about to serialize an externally-tagged enum inside xml::elements.
210    /// The next begin_struct() should be skipped (it's the wrapper struct), and the
211    /// following field_key(variant_name) should also be skipped because variant_metadata
212    /// already set up pending_field with the variant name.
213    skip_enum_wrapper: Option<String>,
214    /// Serialization options (pretty-printing, float formatting, etc.)
215    options: SerializeOptions,
216    /// Current indentation depth for pretty-printing
217    depth: usize,
218}
219
220impl XmlSerializer {
221    /// Create a new XML serializer with default options.
222    pub fn new() -> Self {
223        Self::with_options(SerializeOptions::default())
224    }
225
226    /// Create a new XML serializer with the given options.
227    pub fn with_options(options: SerializeOptions) -> Self {
228        Self {
229            out: Vec::new(),
230            stack: vec![Ctx::Root { kind: None }],
231            pending_field: None,
232            pending_namespace: None,
233            pending_is_attribute: false,
234            pending_is_text: false,
235            pending_is_elements: false,
236            current_ns_all: None,
237            pending_attributes: Vec::new(),
238            item_tag: "item",
239            declared_namespaces: HashMap::new(),
240            next_ns_index: 0,
241            current_default_ns: None,
242            root_tag_written: false,
243            root_element_name: None,
244            deferred_open_tag: None,
245            elements_stack: Vec::new(),
246            skip_enum_wrapper: None,
247            options,
248            depth: 0,
249        }
250    }
251
252    pub fn finish(mut self) -> Vec<u8> {
253        // Ensure root tag is written (even if struct is empty)
254        self.ensure_root_tag_written();
255
256        // Close any remaining non-root elements.
257        while let Some(ctx) = self.stack.pop() {
258            match ctx {
259                Ctx::Root { .. } => break,
260                Ctx::Struct { close } | Ctx::Seq { close } => {
261                    if let Some(name) = close {
262                        self.write_close_tag(&name, true);
263                    }
264                }
265            }
266        }
267        // Write root closing tag
268        self.depth = self.depth.saturating_sub(1);
269        self.write_indent();
270        let root_name = self.root_element_name.as_deref().unwrap_or("root");
271        self.out.extend_from_slice(b"</");
272        self.out.extend_from_slice(root_name.as_bytes());
273        self.out.push(b'>');
274        self.out
275    }
276
277    /// Flush any deferred opening tag (writing `<tag attrs>`) before we need to write content.
278    /// This is called when we encounter a non-attribute field or element content.
279    fn flush_deferred_open_tag(&mut self) {
280        if let Some((element_name, element_ns, _close_name)) = self.deferred_open_tag.take() {
281            self.write_indent();
282            self.out.push(b'<');
283
284            // Handle namespace for element
285            if let Some(ns_uri) = element_ns {
286                if self.current_default_ns.as_deref() == Some(&ns_uri) {
287                    // Element is in the default namespace - use unprefixed form
288                    self.out.extend_from_slice(element_name.as_bytes());
289                } else {
290                    // Get or create a prefix for this namespace
291                    let prefix = self.get_or_create_prefix(&ns_uri);
292                    self.out.extend_from_slice(prefix.as_bytes());
293                    self.out.push(b':');
294                    self.out.extend_from_slice(element_name.as_bytes());
295                    // Write xmlns declaration
296                    self.out.extend_from_slice(b" xmlns:");
297                    self.out.extend_from_slice(prefix.as_bytes());
298                    self.out.extend_from_slice(b"=\"");
299                    self.out.extend_from_slice(ns_uri.as_bytes());
300                    self.out.push(b'"');
301                }
302            } else {
303                self.out.extend_from_slice(element_name.as_bytes());
304            }
305
306            // Write buffered attributes
307            let attrs: Vec<_> = self.pending_attributes.drain(..).collect();
308            let mut attrs_with_prefixes = Vec::new();
309            for (name, value, ns) in attrs {
310                let prefix = ns.as_ref().map(|uri| self.get_or_create_prefix(uri));
311                attrs_with_prefixes.push((name, value, ns, prefix));
312            }
313
314            for (attr_name, attr_value, attr_ns, prefix_opt) in attrs_with_prefixes {
315                self.out.push(b' ');
316                if let (Some(ns_uri), Some(prefix)) = (attr_ns, prefix_opt) {
317                    // Namespaced attribute - write xmlns declaration first
318                    self.out.extend_from_slice(b"xmlns:");
319                    self.out.extend_from_slice(prefix.as_bytes());
320                    self.out.extend_from_slice(b"=\"");
321                    self.out.extend_from_slice(ns_uri.as_bytes());
322                    self.out.extend_from_slice(b"\" ");
323                    // Now write the prefixed attribute
324                    self.out.extend_from_slice(prefix.as_bytes());
325                    self.out.push(b':');
326                }
327                self.out.extend_from_slice(attr_name.as_bytes());
328                self.out.extend_from_slice(b"=\"");
329                // Escape attribute value
330                for b in attr_value.as_bytes() {
331                    match *b {
332                        b'&' => self.out.extend_from_slice(b"&amp;"),
333                        b'<' => self.out.extend_from_slice(b"&lt;"),
334                        b'>' => self.out.extend_from_slice(b"&gt;"),
335                        b'"' => self.out.extend_from_slice(b"&quot;"),
336                        _ => self.out.push(*b),
337                    }
338                }
339                self.out.push(b'"');
340            }
341
342            self.out.push(b'>');
343            self.write_newline();
344            self.depth += 1;
345        }
346    }
347
348    fn write_open_tag(&mut self, name: &str) {
349        self.write_indent();
350        self.out.push(b'<');
351
352        // Check if we have a pending namespace for this field
353        if let Some(ns_uri) = self.pending_namespace.take() {
354            // Check if this namespace matches the current default namespace
355            // If so, we can use an unprefixed element name (it inherits the default)
356            if self.current_default_ns.as_deref() == Some(&ns_uri) {
357                // Element is in the default namespace - use unprefixed form
358                self.out.extend_from_slice(name.as_bytes());
359            } else {
360                // Get or create a prefix for this namespace
361                let prefix = self.get_or_create_prefix(&ns_uri);
362
363                // Write prefixed element name
364                self.out.extend_from_slice(prefix.as_bytes());
365                self.out.push(b':');
366                self.out.extend_from_slice(name.as_bytes());
367
368                // Write xmlns declaration
369                self.out.extend_from_slice(b" xmlns:");
370                self.out.extend_from_slice(prefix.as_bytes());
371                self.out.extend_from_slice(b"=\"");
372                self.out.extend_from_slice(ns_uri.as_bytes());
373                self.out.push(b'"');
374            }
375        } else {
376            // No namespace - just write the element name
377            self.out.extend_from_slice(name.as_bytes());
378        }
379
380        // Write buffered attributes
381        // Drain attributes first to avoid borrow checker issues
382        let attrs: Vec<_> = self.pending_attributes.drain(..).collect();
383
384        // Now resolve prefixes for namespaced attributes
385        let mut attrs_with_prefixes = Vec::new();
386        for (name, value, ns) in attrs {
387            let prefix = ns.as_ref().map(|uri| self.get_or_create_prefix(uri));
388            attrs_with_prefixes.push((name, value, ns, prefix));
389        }
390
391        for (attr_name, attr_value, attr_ns, prefix_opt) in attrs_with_prefixes {
392            self.out.push(b' ');
393
394            if let (Some(ns_uri), Some(prefix)) = (attr_ns, prefix_opt) {
395                // Namespaced attribute - write xmlns declaration first
396                self.out.extend_from_slice(b"xmlns:");
397                self.out.extend_from_slice(prefix.as_bytes());
398                self.out.extend_from_slice(b"=\"");
399                self.out.extend_from_slice(ns_uri.as_bytes());
400                self.out.extend_from_slice(b"\" ");
401
402                // Now write the prefixed attribute
403                self.out.extend_from_slice(prefix.as_bytes());
404                self.out.push(b':');
405            }
406
407            self.out.extend_from_slice(attr_name.as_bytes());
408            self.out.extend_from_slice(b"=\"");
409            // Escape attribute value
410            for b in attr_value.as_bytes() {
411                match *b {
412                    b'&' => self.out.extend_from_slice(b"&amp;"),
413                    b'<' => self.out.extend_from_slice(b"&lt;"),
414                    b'>' => self.out.extend_from_slice(b"&gt;"),
415                    b'"' => self.out.extend_from_slice(b"&quot;"),
416                    _ => self.out.push(*b),
417                }
418            }
419            self.out.push(b'"');
420        }
421
422        self.out.push(b'>');
423    }
424
425    /// Write a closing tag.
426    /// If `block` is true, decrement depth and add indentation (for container elements).
427    /// If `block` is false, write inline (for scalar elements where content preceded this).
428    fn write_close_tag(&mut self, name: &str, block: bool) {
429        if block {
430            self.depth = self.depth.saturating_sub(1);
431            self.write_indent();
432        }
433        self.out.extend_from_slice(b"</");
434        self.out.extend_from_slice(name.as_bytes());
435        self.out.push(b'>');
436        if block {
437            self.write_newline();
438        }
439    }
440
441    fn write_text_escaped(&mut self, text: &str) {
442        if self.options.preserve_entities {
443            let escaped = escape_preserving_entities(text, false);
444            self.out.extend_from_slice(escaped.as_bytes());
445        } else {
446            for b in text.as_bytes() {
447                match *b {
448                    b'&' => self.out.extend_from_slice(b"&amp;"),
449                    b'<' => self.out.extend_from_slice(b"&lt;"),
450                    b'>' => self.out.extend_from_slice(b"&gt;"),
451                    _ => self.out.push(*b),
452                }
453            }
454        }
455    }
456
457    /// Format a float value using the custom formatter if set, otherwise default.
458    fn format_float(&self, v: f64) -> String {
459        if let Some(fmt) = self.options.float_formatter {
460            let mut buf = Vec::new();
461            if fmt(v, &mut buf).is_ok()
462                && let Ok(s) = String::from_utf8(buf)
463            {
464                return s;
465            }
466        }
467        #[cfg(feature = "fast")]
468        return zmij::Buffer::new().format(v).to_string();
469        #[cfg(not(feature = "fast"))]
470        v.to_string()
471    }
472
473    /// Write indentation for the current depth (if pretty-printing is enabled).
474    fn write_indent(&mut self) {
475        if self.options.pretty {
476            for _ in 0..self.depth {
477                self.out.extend_from_slice(self.options.indent.as_bytes());
478            }
479        }
480    }
481
482    /// Write a newline (if pretty-printing is enabled).
483    fn write_newline(&mut self) {
484        if self.options.pretty {
485            self.out.push(b'\n');
486        }
487    }
488
489    fn ensure_root_tag_written(&mut self) {
490        if !self.root_tag_written {
491            let root_name = self.root_element_name.as_deref().unwrap_or("root");
492            self.out.push(b'<');
493            self.out.extend_from_slice(root_name.as_bytes());
494
495            // If ns_all is set, emit a default namespace declaration (xmlns="...")
496            // and set current_default_ns so child elements can use unprefixed form
497            if let Some(ns_all) = &self.current_ns_all {
498                self.out.extend_from_slice(b" xmlns=\"");
499                self.out.extend_from_slice(ns_all.as_bytes());
500                self.out.push(b'"');
501                self.current_default_ns = Some(ns_all.clone());
502            }
503
504            // Write buffered attributes if any (for root-level attributes)
505            let attrs: Vec<_> = self.pending_attributes.drain(..).collect();
506            let mut attrs_with_prefixes = Vec::new();
507            for (name, value, ns) in attrs {
508                let prefix = ns.as_ref().map(|uri| self.get_or_create_prefix(uri));
509                attrs_with_prefixes.push((name, value, ns, prefix));
510            }
511
512            for (attr_name, attr_value, attr_ns, prefix_opt) in attrs_with_prefixes {
513                self.out.push(b' ');
514
515                if let (Some(ns_uri), Some(prefix)) = (attr_ns, prefix_opt) {
516                    // Namespaced attribute - write xmlns declaration first
517                    self.out.extend_from_slice(b"xmlns:");
518                    self.out.extend_from_slice(prefix.as_bytes());
519                    self.out.extend_from_slice(b"=\"");
520                    self.out.extend_from_slice(ns_uri.as_bytes());
521                    self.out.extend_from_slice(b"\" ");
522
523                    // Now write the prefixed attribute
524                    self.out.extend_from_slice(prefix.as_bytes());
525                    self.out.push(b':');
526                }
527
528                self.out.extend_from_slice(attr_name.as_bytes());
529                self.out.extend_from_slice(b"=\"");
530                // Escape attribute value
531                for b in attr_value.as_bytes() {
532                    match *b {
533                        b'&' => self.out.extend_from_slice(b"&amp;"),
534                        b'<' => self.out.extend_from_slice(b"&lt;"),
535                        b'>' => self.out.extend_from_slice(b"&gt;"),
536                        b'"' => self.out.extend_from_slice(b"&quot;"),
537                        _ => self.out.push(*b),
538                    }
539                }
540                self.out.push(b'"');
541            }
542
543            self.out.push(b'>');
544            self.write_newline();
545            self.depth += 1;
546            self.root_tag_written = true;
547        }
548    }
549
550    fn open_value_element_if_needed(&mut self) -> Result<Option<String>, XmlSerializeError> {
551        // Flush any deferred tag before opening a new element
552        self.flush_deferred_open_tag();
553        self.ensure_root_tag_written();
554        match self.stack.last() {
555            Some(Ctx::Root { .. }) => Ok(None),
556            Some(Ctx::Struct { .. }) => {
557                let Some(name) = self.pending_field.take() else {
558                    return Err(XmlSerializeError {
559                        msg: "value emitted in struct without field key",
560                    });
561                };
562
563                // Compute the full tag name (with prefix if namespaced) for closing
564                // If namespace matches current default, use unprefixed
565                let full_name = if let Some(ns_uri) = self.pending_namespace.clone() {
566                    if self.current_default_ns.as_deref() == Some(&ns_uri) {
567                        // Element is in the default namespace - use unprefixed form
568                        name.clone()
569                    } else {
570                        let prefix = self.get_or_create_prefix(&ns_uri);
571                        format!("{}:{}", prefix, name)
572                    }
573                } else {
574                    name.clone()
575                };
576
577                self.write_open_tag(&name);
578                Ok(Some(full_name))
579            }
580            Some(Ctx::Seq { .. }) => {
581                let name = self.item_tag.to_string();
582                self.write_open_tag(&name);
583                Ok(Some(name))
584            }
585            None => Err(XmlSerializeError {
586                msg: "serializer state missing root context",
587            }),
588        }
589    }
590
591    /// Like `open_value_element_if_needed`, but defers writing the opening tag
592    /// until we've collected all attributes. Returns the close tag name.
593    fn defer_value_element_if_needed(&mut self) -> Result<Option<String>, XmlSerializeError> {
594        self.ensure_root_tag_written();
595        match self.stack.last() {
596            Some(Ctx::Root { .. }) => Ok(None),
597            Some(Ctx::Struct { .. }) => {
598                let Some(name) = self.pending_field.take() else {
599                    return Err(XmlSerializeError {
600                        msg: "value emitted in struct without field key",
601                    });
602                };
603
604                // Compute the full tag name (with prefix if namespaced) for closing
605                let (close_name, element_ns) = if let Some(ns_uri) = self.pending_namespace.clone()
606                {
607                    if self.current_default_ns.as_deref() == Some(&ns_uri) {
608                        (name.clone(), Some(ns_uri))
609                    } else {
610                        let prefix = self.get_or_create_prefix(&ns_uri);
611                        (format!("{}:{}", prefix, name), Some(ns_uri))
612                    }
613                } else {
614                    (name.clone(), None)
615                };
616
617                // Store the deferred tag info instead of writing it
618                self.deferred_open_tag = Some((name, element_ns, close_name.clone()));
619                self.pending_namespace = None;
620                Ok(Some(close_name))
621            }
622            Some(Ctx::Seq { .. }) => {
623                // For sequences, check if we have a pending field name (from xml::elements)
624                // or fall back to item_tag for regular sequences
625                if let Some(name) = self.pending_field.take() {
626                    // xml::elements case - use the item's type name and defer the tag
627                    let (close_name, element_ns) =
628                        if let Some(ns_uri) = self.pending_namespace.clone() {
629                            if self.current_default_ns.as_deref() == Some(&ns_uri) {
630                                (name.clone(), Some(ns_uri))
631                            } else {
632                                let prefix = self.get_or_create_prefix(&ns_uri);
633                                (format!("{}:{}", prefix, name), Some(ns_uri))
634                            }
635                        } else {
636                            (name.clone(), None)
637                        };
638                    self.deferred_open_tag = Some((name, element_ns, close_name.clone()));
639                    self.pending_namespace = None;
640                    Ok(Some(close_name))
641                } else {
642                    // Regular sequence - use item_tag and write immediately
643                    let name = self.item_tag.to_string();
644                    self.write_open_tag(&name);
645                    Ok(Some(name))
646                }
647            }
648            None => Err(XmlSerializeError {
649                msg: "serializer state missing root context",
650            }),
651        }
652    }
653
654    fn enter_struct_root(&mut self) {
655        if let Some(Ctx::Root { kind }) = self.stack.last_mut() {
656            *kind = Some(Kind::Struct);
657        }
658        self.stack.push(Ctx::Struct { close: None });
659    }
660
661    fn enter_seq_root(&mut self) {
662        if let Some(Ctx::Root { kind }) = self.stack.last_mut() {
663            *kind = Some(Kind::Seq);
664        }
665        self.stack.push(Ctx::Seq { close: None });
666    }
667
668    /// Get or create a prefix for the given namespace URI.
669    /// Returns the prefix (without colon).
670    fn get_or_create_prefix(&mut self, namespace_uri: &str) -> String {
671        // Check if we've already assigned a prefix to this URI
672        if let Some(prefix) = self.declared_namespaces.get(namespace_uri) {
673            return prefix.clone();
674        }
675
676        // Try well-known namespaces
677        let prefix = WELL_KNOWN_NAMESPACES
678            .iter()
679            .find(|(uri, _)| *uri == namespace_uri)
680            .map(|(_, prefix)| (*prefix).to_string())
681            .unwrap_or_else(|| {
682                // Auto-generate a prefix
683                let prefix = format!("ns{}", self.next_ns_index);
684                self.next_ns_index += 1;
685                prefix
686            });
687
688        // Ensure the prefix isn't already in use for a different namespace
689        let final_prefix = if self.declared_namespaces.values().any(|p| p == &prefix) {
690            // Conflict! Generate a new one
691            let prefix = format!("ns{}", self.next_ns_index);
692            self.next_ns_index += 1;
693            prefix
694        } else {
695            prefix
696        };
697
698        self.declared_namespaces
699            .insert(namespace_uri.to_string(), final_prefix.clone());
700        final_prefix
701    }
702}
703
704impl Default for XmlSerializer {
705    fn default() -> Self {
706        Self::new()
707    }
708}
709
710impl FormatSerializer for XmlSerializer {
711    type Error = XmlSerializeError;
712
713    fn begin_struct(&mut self) -> Result<(), Self::Error> {
714        // Flush any deferred tag from parent before starting a new struct
715        self.flush_deferred_open_tag();
716
717        // If we're skipping the enum wrapper struct (for xml::elements enum serialization),
718        // just push a struct context without creating any element
719        if self.skip_enum_wrapper.is_some() {
720            self.stack.push(Ctx::Struct { close: None });
721            return Ok(());
722        }
723
724        match self.stack.last() {
725            Some(Ctx::Root { kind: None }) => {
726                self.enter_struct_root();
727                Ok(())
728            }
729            Some(Ctx::Root {
730                kind: Some(Kind::Struct),
731            }) => Err(XmlSerializeError {
732                msg: "multiple root values are not supported",
733            }),
734            Some(Ctx::Root {
735                kind: Some(Kind::Seq),
736            })
737            | Some(Ctx::Seq { .. })
738            | Some(Ctx::Struct { .. }) => {
739                // For nested structs, defer the opening tag until we've collected all attributes
740                let close = self.defer_value_element_if_needed()?;
741                self.stack.push(Ctx::Struct { close });
742                Ok(())
743            }
744            None => Err(XmlSerializeError {
745                msg: "serializer state missing root context",
746            }),
747        }
748    }
749
750    fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
751        // If we're skipping the enum wrapper, check if this is the variant name field_key
752        // that we should skip (variant_metadata already set up pending_field)
753        if let Some(ref variant_name) = self.skip_enum_wrapper
754            && key == variant_name
755        {
756            // Clear the skip flag - the wrapper struct's field_key is now consumed
757            // The next begin_struct will be the actual content struct
758            self.skip_enum_wrapper = None;
759            return Ok(());
760        }
761        self.pending_field = Some(key.to_string());
762        Ok(())
763    }
764
765    fn end_struct(&mut self) -> Result<(), Self::Error> {
766        // Flush any deferred opening tag before closing
767        self.flush_deferred_open_tag();
768
769        match self.stack.pop() {
770            Some(Ctx::Struct { close }) => {
771                if let Some(name) = close {
772                    self.write_close_tag(&name, true);
773                }
774                Ok(())
775            }
776            _ => Err(XmlSerializeError {
777                msg: "end_struct called without matching begin_struct",
778            }),
779        }
780    }
781
782    fn begin_seq(&mut self) -> Result<(), Self::Error> {
783        // Track if this is an xml::elements sequence (no wrapper element)
784        let is_elements = self.pending_is_elements;
785        self.pending_is_elements = false;
786        self.elements_stack.push(is_elements);
787
788        match self.stack.last() {
789            Some(Ctx::Root { kind: None }) => {
790                self.enter_seq_root();
791                Ok(())
792            }
793            Some(Ctx::Root {
794                kind: Some(Kind::Seq),
795            }) => Err(XmlSerializeError {
796                msg: "multiple root values are not supported",
797            }),
798            Some(Ctx::Root {
799                kind: Some(Kind::Struct),
800            })
801            | Some(Ctx::Seq { .. })
802            | Some(Ctx::Struct { .. }) => {
803                // For xml::elements, don't create a wrapper element - items go directly as children
804                if is_elements {
805                    self.pending_field = None;
806                    self.pending_namespace = None;
807                    self.stack.push(Ctx::Seq { close: None });
808                } else {
809                    let close = self.open_value_element_if_needed()?;
810                    self.stack.push(Ctx::Seq { close });
811                }
812                Ok(())
813            }
814            None => Err(XmlSerializeError {
815                msg: "serializer state missing root context",
816            }),
817        }
818    }
819
820    fn end_seq(&mut self) -> Result<(), Self::Error> {
821        // Pop the xml::elements state
822        self.elements_stack.pop();
823
824        match self.stack.pop() {
825            Some(Ctx::Seq { close }) => {
826                if let Some(name) = close {
827                    self.write_close_tag(&name, true);
828                }
829                Ok(())
830            }
831            _ => Err(XmlSerializeError {
832                msg: "end_seq called without matching begin_seq",
833            }),
834        }
835    }
836
837    fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
838        // If this is an attribute, buffer it instead of writing as a child element
839        if self.pending_is_attribute {
840            let name = self.pending_field.take().ok_or(XmlSerializeError {
841                msg: "attribute value without field name",
842            })?;
843            let namespace = self.pending_namespace.take();
844
845            // Convert scalar to string for attribute value
846            let value = match scalar {
847                ScalarValue::Null => "null".to_string(),
848                ScalarValue::Bool(v) => if v { "true" } else { "false" }.to_string(),
849                ScalarValue::Char(c) => c.to_string(),
850                ScalarValue::I64(v) => v.to_string(),
851                ScalarValue::U64(v) => v.to_string(),
852                ScalarValue::I128(v) => v.to_string(),
853                ScalarValue::U128(v) => v.to_string(),
854                ScalarValue::F64(v) => self.format_float(v),
855                ScalarValue::Str(s) | ScalarValue::StringlyTyped(s) => s.into_owned(),
856                ScalarValue::Bytes(_) => {
857                    return Err(XmlSerializeError {
858                        msg: "bytes serialization unsupported for xml",
859                    });
860                }
861            };
862
863            self.pending_attributes.push((name, value, namespace));
864            self.pending_is_attribute = false;
865            return Ok(());
866        }
867
868        // If this is text content (xml::text), write it directly without element wrapper
869        if self.pending_is_text {
870            // Clear pending field - we're writing text content, not an element
871            self.pending_field = None;
872            self.pending_namespace = None;
873            self.pending_is_text = false;
874
875            // Flush any deferred opening tag first
876            self.flush_deferred_open_tag();
877            self.ensure_root_tag_written();
878
879            // Write the text content directly
880            match scalar {
881                ScalarValue::Null => self.write_text_escaped("null"),
882                ScalarValue::Bool(v) => self.write_text_escaped(if v { "true" } else { "false" }),
883                ScalarValue::Char(c) => {
884                    let mut buf = [0u8; 4];
885                    self.write_text_escaped(c.encode_utf8(&mut buf));
886                }
887                ScalarValue::I64(v) => self.write_text_escaped(&v.to_string()),
888                ScalarValue::U64(v) => self.write_text_escaped(&v.to_string()),
889                ScalarValue::I128(v) => self.write_text_escaped(&v.to_string()),
890                ScalarValue::U128(v) => self.write_text_escaped(&v.to_string()),
891                ScalarValue::F64(v) => {
892                    let formatted = self.format_float(v);
893                    self.write_text_escaped(&formatted);
894                }
895                ScalarValue::Str(s) | ScalarValue::StringlyTyped(s) => self.write_text_escaped(&s),
896                ScalarValue::Bytes(_) => {
897                    return Err(XmlSerializeError {
898                        msg: "bytes serialization unsupported for xml",
899                    });
900                }
901            }
902            return Ok(());
903        }
904
905        // Regular child element
906        let close = self.open_value_element_if_needed()?;
907
908        match scalar {
909            ScalarValue::Null => {
910                // Encode as the literal "null" to round-trip through parse_scalar.
911                self.write_text_escaped("null");
912            }
913            ScalarValue::Bool(v) => self.write_text_escaped(if v { "true" } else { "false" }),
914            ScalarValue::Char(c) => {
915                let mut buf = [0u8; 4];
916                self.write_text_escaped(c.encode_utf8(&mut buf));
917            }
918            ScalarValue::I64(v) => self.write_text_escaped(&v.to_string()),
919            ScalarValue::U64(v) => self.write_text_escaped(&v.to_string()),
920            ScalarValue::I128(v) => self.write_text_escaped(&v.to_string()),
921            ScalarValue::U128(v) => self.write_text_escaped(&v.to_string()),
922            ScalarValue::F64(v) => {
923                let formatted = self.format_float(v);
924                self.write_text_escaped(&formatted);
925            }
926            ScalarValue::Str(s) | ScalarValue::StringlyTyped(s) => self.write_text_escaped(&s),
927            ScalarValue::Bytes(_) => {
928                return Err(XmlSerializeError {
929                    msg: "bytes serialization unsupported for xml",
930                });
931            }
932        }
933
934        if let Some(name) = close {
935            // Scalar close is inline (no indent), then newline
936            self.write_close_tag(&name, false);
937            self.write_newline();
938        }
939
940        Ok(())
941    }
942
943    fn field_metadata(&mut self, field: &facet_reflect::FieldItem) -> Result<(), Self::Error> {
944        // For flattened map entries, treat them as attributes
945        let Some(field_def) = field.field else {
946            self.pending_is_attribute = true;
947            self.pending_is_text = false;
948            self.pending_is_elements = false;
949            return Ok(());
950        };
951
952        // Check if this field is an attribute
953        self.pending_is_attribute = field_def.get_attr(Some("xml"), "attribute").is_some();
954        // Check if this field is text content
955        self.pending_is_text = field_def.get_attr(Some("xml"), "text").is_some();
956        // Check if this field is an xml::elements list (no wrapper element)
957        self.pending_is_elements = field_def.get_attr(Some("xml"), "elements").is_some();
958
959        // Extract xml::ns attribute from the field
960        if let Some(ns_attr) = field_def.get_attr(Some("xml"), "ns")
961            && let Some(ns_uri) = ns_attr.get_as::<&str>().copied()
962        {
963            self.pending_namespace = Some(ns_uri.to_string());
964            return Ok(());
965        }
966
967        // If field doesn't have explicit xml::ns, check for container-level xml::ns_all
968        // Only apply ns_all to elements, not attributes or text content (per XML spec)
969        if !self.pending_is_attribute
970            && !self.pending_is_text
971            && let Some(ns_all) = &self.current_ns_all
972        {
973            self.pending_namespace = Some(ns_all.clone());
974        }
975
976        Ok(())
977    }
978
979    fn struct_metadata(&mut self, shape: &facet_core::Shape) -> Result<(), Self::Error> {
980        // Extract xml::ns_all attribute from the struct
981        self.current_ns_all = shape
982            .attributes
983            .iter()
984            .find(|attr| attr.ns == Some("xml") && attr.key == "ns_all")
985            .and_then(|attr| attr.get_as::<&str>().copied())
986            .map(String::from);
987
988        // Get the element name from the shape (respecting rename attribute)
989        let element_name = shape
990            .get_builtin_attr_value::<&str>("rename")
991            .unwrap_or(shape.type_identifier);
992
993        // If this is the root element (stack only has Root context), save the name
994        if matches!(self.stack.last(), Some(Ctx::Root { kind: None })) {
995            self.root_element_name = Some(element_name.to_string());
996        }
997
998        // If we're inside an xml::elements list, use the shape's element name
999        if self.elements_stack.last() == Some(&true) && self.pending_field.is_none() {
1000            self.pending_field = Some(element_name.to_string());
1001
1002            // Also apply xml::ns_all if set
1003            if let Some(ns_all) = &self.current_ns_all {
1004                self.pending_namespace = Some(ns_all.clone());
1005            }
1006        }
1007
1008        Ok(())
1009    }
1010
1011    fn variant_metadata(
1012        &mut self,
1013        variant: &'static facet_core::Variant,
1014    ) -> Result<(), Self::Error> {
1015        // If we're inside an xml::elements list, set the pending field to the variant name
1016        // and mark that we should skip the externally-tagged wrapper struct.
1017        //
1018        // For externally-tagged enums, the serialization flow is:
1019        //   1. variant_metadata(variant) - we're here
1020        //   2. begin_struct() - creates wrapper struct (we want to SKIP this)
1021        //   3. field_key(variant.name) - sets field name (we want to SKIP this)
1022        //   4. shared_serialize(inner) - serializes the actual content
1023        //
1024        // We set pending_field to the variant name, and skip_enum_wrapper to tell
1025        // begin_struct() to not create an element, and field_key() to not override
1026        // the pending_field we just set.
1027        if self.elements_stack.last() == Some(&true) {
1028            // Get the element name from the variant (respecting rename attribute)
1029            let element_name = variant
1030                .get_builtin_attr("rename")
1031                .and_then(|attr| attr.get_as::<&str>().copied())
1032                .unwrap_or(variant.name);
1033            self.pending_field = Some(element_name.to_string());
1034            // Set the skip flag with the variant name so field_key knows what to skip
1035            self.skip_enum_wrapper = Some(variant.name.to_string());
1036        }
1037        Ok(())
1038    }
1039
1040    /// For XML, `None` values should not emit any content.
1041    /// We skip emitting an element entirely rather than writing `<field>null</field>`.
1042    fn serialize_none(&mut self) -> Result<(), Self::Error> {
1043        // Clear pending field state - we're skipping this value
1044        self.pending_field = None;
1045        self.pending_namespace = None;
1046        self.pending_is_attribute = false;
1047        self.pending_is_text = false;
1048        // Do nothing - don't emit anything for None
1049        Ok(())
1050    }
1051
1052    fn preferred_field_order(&self) -> facet_format::FieldOrdering {
1053        facet_format::FieldOrdering::AttributesFirst
1054    }
1055}
1056
1057/// Serialize a value to XML bytes with default options.
1058pub fn to_vec<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<XmlSerializeError>>
1059where
1060    T: Facet<'facet> + ?Sized,
1061{
1062    to_vec_with_options(value, &SerializeOptions::default())
1063}
1064
1065/// Serialize a value to XML bytes with custom options.
1066pub fn to_vec_with_options<'facet, T>(
1067    value: &'_ T,
1068    options: &SerializeOptions,
1069) -> Result<Vec<u8>, SerializeError<XmlSerializeError>>
1070where
1071    T: Facet<'facet> + ?Sized,
1072{
1073    let mut serializer = XmlSerializer::with_options(options.clone());
1074    serialize_root(&mut serializer, Peek::new(value))?;
1075    Ok(serializer.finish())
1076}
1077
1078/// Serialize a value to an XML string with default options.
1079pub fn to_string<'facet, T>(value: &'_ T) -> Result<String, SerializeError<XmlSerializeError>>
1080where
1081    T: Facet<'facet> + ?Sized,
1082{
1083    let bytes = to_vec(value)?;
1084    // SAFETY: XmlSerializer produces valid UTF-8
1085    Ok(String::from_utf8(bytes).expect("XmlSerializer produces valid UTF-8"))
1086}
1087
1088/// Serialize a value to a pretty-printed XML string with default indentation.
1089pub fn to_string_pretty<'facet, T>(
1090    value: &'_ T,
1091) -> Result<String, SerializeError<XmlSerializeError>>
1092where
1093    T: Facet<'facet> + ?Sized,
1094{
1095    to_string_with_options(value, &SerializeOptions::default().pretty())
1096}
1097
1098/// Serialize a value to an XML string with custom options.
1099pub fn to_string_with_options<'facet, T>(
1100    value: &'_ T,
1101    options: &SerializeOptions,
1102) -> Result<String, SerializeError<XmlSerializeError>>
1103where
1104    T: Facet<'facet> + ?Sized,
1105{
1106    let bytes = to_vec_with_options(value, options)?;
1107    // SAFETY: XmlSerializer produces valid UTF-8
1108    Ok(String::from_utf8(bytes).expect("XmlSerializer produces valid UTF-8"))
1109}
1110
1111/// Escape special characters while preserving entity references.
1112///
1113/// Recognizes entity reference patterns:
1114/// - Named entities: `&name;` (alphanumeric name)
1115/// - Decimal numeric entities: `&#digits;`
1116/// - Hexadecimal numeric entities: `&#xhex;` or `&#Xhex;`
1117fn escape_preserving_entities(s: &str, is_attribute: bool) -> String {
1118    let mut result = String::with_capacity(s.len());
1119    let chars: Vec<char> = s.chars().collect();
1120    let mut i = 0;
1121
1122    while i < chars.len() {
1123        let c = chars[i];
1124        match c {
1125            '<' => result.push_str("&lt;"),
1126            '>' => result.push_str("&gt;"),
1127            '"' if is_attribute => result.push_str("&quot;"),
1128            '&' => {
1129                // Check if this is the start of an entity reference
1130                if let Some(entity_len) = try_parse_entity_reference(&chars[i..]) {
1131                    // It's a valid entity reference - copy it as-is
1132                    for j in 0..entity_len {
1133                        result.push(chars[i + j]);
1134                    }
1135                    i += entity_len;
1136                    continue;
1137                } else {
1138                    // Not a valid entity reference - escape the ampersand
1139                    result.push_str("&amp;");
1140                }
1141            }
1142            _ => result.push(c),
1143        }
1144        i += 1;
1145    }
1146
1147    result
1148}
1149
1150/// Try to parse an entity reference starting at the given position.
1151/// Returns the length of the entity reference if valid, or None if not.
1152///
1153/// Valid patterns:
1154/// - `&name;` where name is one or more alphanumeric characters
1155/// - `&#digits;` where digits are decimal digits
1156/// - `&#xhex;` or `&#Xhex;` where hex is hexadecimal digits
1157fn try_parse_entity_reference(chars: &[char]) -> Option<usize> {
1158    if chars.is_empty() || chars[0] != '&' {
1159        return None;
1160    }
1161
1162    // Need at least `&x;` (3 chars minimum)
1163    if chars.len() < 3 {
1164        return None;
1165    }
1166
1167    let mut len = 1; // Start after '&'
1168
1169    if chars[1] == '#' {
1170        // Numeric entity reference
1171        len = 2;
1172
1173        if len < chars.len() && (chars[len] == 'x' || chars[len] == 'X') {
1174            // Hexadecimal: &#xHEX;
1175            len += 1;
1176            let start = len;
1177            while len < chars.len() && chars[len].is_ascii_hexdigit() {
1178                len += 1;
1179            }
1180            // Need at least one hex digit
1181            if len == start {
1182                return None;
1183            }
1184        } else {
1185            // Decimal: &#DIGITS;
1186            let start = len;
1187            while len < chars.len() && chars[len].is_ascii_digit() {
1188                len += 1;
1189            }
1190            // Need at least one digit
1191            if len == start {
1192                return None;
1193            }
1194        }
1195    } else {
1196        // Named entity reference: &NAME;
1197        // Name must start with a letter or underscore, then letters, digits, underscores, hyphens, periods
1198        if !chars[len].is_ascii_alphabetic() && chars[len] != '_' {
1199            return None;
1200        }
1201        len += 1;
1202        while len < chars.len()
1203            && (chars[len].is_ascii_alphanumeric()
1204                || chars[len] == '_'
1205                || chars[len] == '-'
1206                || chars[len] == '.')
1207        {
1208            len += 1;
1209        }
1210    }
1211
1212    // Must end with ';'
1213    if len >= chars.len() || chars[len] != ';' {
1214        return None;
1215    }
1216
1217    Some(len + 1) // Include the semicolon
1218}