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::I64(v) => v.to_string(),
850                ScalarValue::U64(v) => v.to_string(),
851                ScalarValue::I128(v) => v.to_string(),
852                ScalarValue::U128(v) => v.to_string(),
853                ScalarValue::F64(v) => self.format_float(v),
854                ScalarValue::Str(s) => s.into_owned(),
855                ScalarValue::Bytes(_) => {
856                    return Err(XmlSerializeError {
857                        msg: "bytes serialization unsupported for xml",
858                    });
859                }
860            };
861
862            self.pending_attributes.push((name, value, namespace));
863            self.pending_is_attribute = false;
864            return Ok(());
865        }
866
867        // If this is text content (xml::text), write it directly without element wrapper
868        if self.pending_is_text {
869            // Clear pending field - we're writing text content, not an element
870            self.pending_field = None;
871            self.pending_namespace = None;
872            self.pending_is_text = false;
873
874            // Flush any deferred opening tag first
875            self.flush_deferred_open_tag();
876            self.ensure_root_tag_written();
877
878            // Write the text content directly
879            match scalar {
880                ScalarValue::Null => self.write_text_escaped("null"),
881                ScalarValue::Bool(v) => self.write_text_escaped(if v { "true" } else { "false" }),
882                ScalarValue::I64(v) => self.write_text_escaped(&v.to_string()),
883                ScalarValue::U64(v) => self.write_text_escaped(&v.to_string()),
884                ScalarValue::I128(v) => self.write_text_escaped(&v.to_string()),
885                ScalarValue::U128(v) => self.write_text_escaped(&v.to_string()),
886                ScalarValue::F64(v) => {
887                    let formatted = self.format_float(v);
888                    self.write_text_escaped(&formatted);
889                }
890                ScalarValue::Str(s) => self.write_text_escaped(&s),
891                ScalarValue::Bytes(_) => {
892                    return Err(XmlSerializeError {
893                        msg: "bytes serialization unsupported for xml",
894                    });
895                }
896            }
897            return Ok(());
898        }
899
900        // Regular child element
901        let close = self.open_value_element_if_needed()?;
902
903        match scalar {
904            ScalarValue::Null => {
905                // Encode as the literal "null" to round-trip through parse_scalar.
906                self.write_text_escaped("null");
907            }
908            ScalarValue::Bool(v) => self.write_text_escaped(if v { "true" } else { "false" }),
909            ScalarValue::I64(v) => self.write_text_escaped(&v.to_string()),
910            ScalarValue::U64(v) => self.write_text_escaped(&v.to_string()),
911            ScalarValue::I128(v) => self.write_text_escaped(&v.to_string()),
912            ScalarValue::U128(v) => self.write_text_escaped(&v.to_string()),
913            ScalarValue::F64(v) => {
914                let formatted = self.format_float(v);
915                self.write_text_escaped(&formatted);
916            }
917            ScalarValue::Str(s) => self.write_text_escaped(&s),
918            ScalarValue::Bytes(_) => {
919                return Err(XmlSerializeError {
920                    msg: "bytes serialization unsupported for xml",
921                });
922            }
923        }
924
925        if let Some(name) = close {
926            // Scalar close is inline (no indent), then newline
927            self.write_close_tag(&name, false);
928            self.write_newline();
929        }
930
931        Ok(())
932    }
933
934    fn field_metadata(&mut self, field: &facet_reflect::FieldItem) -> Result<(), Self::Error> {
935        // For flattened map entries, treat them as attributes
936        let Some(field_def) = field.field else {
937            self.pending_is_attribute = true;
938            self.pending_is_text = false;
939            self.pending_is_elements = false;
940            return Ok(());
941        };
942
943        // Check if this field is an attribute
944        self.pending_is_attribute = field_def.get_attr(Some("xml"), "attribute").is_some();
945        // Check if this field is text content
946        self.pending_is_text = field_def.get_attr(Some("xml"), "text").is_some();
947        // Check if this field is an xml::elements list (no wrapper element)
948        self.pending_is_elements = field_def.get_attr(Some("xml"), "elements").is_some();
949
950        // Extract xml::ns attribute from the field
951        if let Some(ns_attr) = field_def.get_attr(Some("xml"), "ns")
952            && let Some(ns_uri) = ns_attr.get_as::<&str>().copied()
953        {
954            self.pending_namespace = Some(ns_uri.to_string());
955            return Ok(());
956        }
957
958        // If field doesn't have explicit xml::ns, check for container-level xml::ns_all
959        // Only apply ns_all to elements, not attributes or text content (per XML spec)
960        if !self.pending_is_attribute
961            && !self.pending_is_text
962            && let Some(ns_all) = &self.current_ns_all
963        {
964            self.pending_namespace = Some(ns_all.clone());
965        }
966
967        Ok(())
968    }
969
970    fn struct_metadata(&mut self, shape: &facet_core::Shape) -> Result<(), Self::Error> {
971        // Extract xml::ns_all attribute from the struct
972        self.current_ns_all = shape
973            .attributes
974            .iter()
975            .find(|attr| attr.ns == Some("xml") && attr.key == "ns_all")
976            .and_then(|attr| attr.get_as::<&str>().copied())
977            .map(String::from);
978
979        // Get the element name from the shape (respecting rename attribute)
980        let element_name = shape
981            .get_builtin_attr_value::<&str>("rename")
982            .unwrap_or(shape.type_identifier);
983
984        // If this is the root element (stack only has Root context), save the name
985        if matches!(self.stack.last(), Some(Ctx::Root { kind: None })) {
986            self.root_element_name = Some(element_name.to_string());
987        }
988
989        // If we're inside an xml::elements list, use the shape's element name
990        if self.elements_stack.last() == Some(&true) && self.pending_field.is_none() {
991            self.pending_field = Some(element_name.to_string());
992
993            // Also apply xml::ns_all if set
994            if let Some(ns_all) = &self.current_ns_all {
995                self.pending_namespace = Some(ns_all.clone());
996            }
997        }
998
999        Ok(())
1000    }
1001
1002    fn variant_metadata(
1003        &mut self,
1004        variant: &'static facet_core::Variant,
1005    ) -> Result<(), Self::Error> {
1006        // If we're inside an xml::elements list, set the pending field to the variant name
1007        // and mark that we should skip the externally-tagged wrapper struct.
1008        //
1009        // For externally-tagged enums, the serialization flow is:
1010        //   1. variant_metadata(variant) - we're here
1011        //   2. begin_struct() - creates wrapper struct (we want to SKIP this)
1012        //   3. field_key(variant.name) - sets field name (we want to SKIP this)
1013        //   4. shared_serialize(inner) - serializes the actual content
1014        //
1015        // We set pending_field to the variant name, and skip_enum_wrapper to tell
1016        // begin_struct() to not create an element, and field_key() to not override
1017        // the pending_field we just set.
1018        if self.elements_stack.last() == Some(&true) {
1019            // Get the element name from the variant (respecting rename attribute)
1020            let element_name = variant
1021                .get_builtin_attr("rename")
1022                .and_then(|attr| attr.get_as::<&str>().copied())
1023                .unwrap_or(variant.name);
1024            self.pending_field = Some(element_name.to_string());
1025            // Set the skip flag with the variant name so field_key knows what to skip
1026            self.skip_enum_wrapper = Some(variant.name.to_string());
1027        }
1028        Ok(())
1029    }
1030
1031    /// For XML, `None` values should not emit any content.
1032    /// We skip emitting an element entirely rather than writing `<field>null</field>`.
1033    fn serialize_none(&mut self) -> Result<(), Self::Error> {
1034        // Clear pending field state - we're skipping this value
1035        self.pending_field = None;
1036        self.pending_namespace = None;
1037        self.pending_is_attribute = false;
1038        self.pending_is_text = false;
1039        // Do nothing - don't emit anything for None
1040        Ok(())
1041    }
1042
1043    fn preferred_field_order(&self) -> facet_format::FieldOrdering {
1044        facet_format::FieldOrdering::AttributesFirst
1045    }
1046}
1047
1048/// Serialize a value to XML bytes with default options.
1049pub fn to_vec<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<XmlSerializeError>>
1050where
1051    T: Facet<'facet> + ?Sized,
1052{
1053    to_vec_with_options(value, &SerializeOptions::default())
1054}
1055
1056/// Serialize a value to XML bytes with custom options.
1057pub fn to_vec_with_options<'facet, T>(
1058    value: &'_ T,
1059    options: &SerializeOptions,
1060) -> Result<Vec<u8>, SerializeError<XmlSerializeError>>
1061where
1062    T: Facet<'facet> + ?Sized,
1063{
1064    let mut serializer = XmlSerializer::with_options(options.clone());
1065    serialize_root(&mut serializer, Peek::new(value))?;
1066    Ok(serializer.finish())
1067}
1068
1069/// Serialize a value to an XML string with default options.
1070pub fn to_string<'facet, T>(value: &'_ T) -> Result<String, SerializeError<XmlSerializeError>>
1071where
1072    T: Facet<'facet> + ?Sized,
1073{
1074    let bytes = to_vec(value)?;
1075    // SAFETY: XmlSerializer produces valid UTF-8
1076    Ok(String::from_utf8(bytes).expect("XmlSerializer produces valid UTF-8"))
1077}
1078
1079/// Serialize a value to a pretty-printed XML string with default indentation.
1080pub fn to_string_pretty<'facet, T>(
1081    value: &'_ T,
1082) -> Result<String, SerializeError<XmlSerializeError>>
1083where
1084    T: Facet<'facet> + ?Sized,
1085{
1086    to_string_with_options(value, &SerializeOptions::default().pretty())
1087}
1088
1089/// Serialize a value to an XML string with custom options.
1090pub fn to_string_with_options<'facet, T>(
1091    value: &'_ T,
1092    options: &SerializeOptions,
1093) -> Result<String, SerializeError<XmlSerializeError>>
1094where
1095    T: Facet<'facet> + ?Sized,
1096{
1097    let bytes = to_vec_with_options(value, options)?;
1098    // SAFETY: XmlSerializer produces valid UTF-8
1099    Ok(String::from_utf8(bytes).expect("XmlSerializer produces valid UTF-8"))
1100}
1101
1102/// Escape special characters while preserving entity references.
1103///
1104/// Recognizes entity reference patterns:
1105/// - Named entities: `&name;` (alphanumeric name)
1106/// - Decimal numeric entities: `&#digits;`
1107/// - Hexadecimal numeric entities: `&#xhex;` or `&#Xhex;`
1108fn escape_preserving_entities(s: &str, is_attribute: bool) -> String {
1109    let mut result = String::with_capacity(s.len());
1110    let chars: Vec<char> = s.chars().collect();
1111    let mut i = 0;
1112
1113    while i < chars.len() {
1114        let c = chars[i];
1115        match c {
1116            '<' => result.push_str("&lt;"),
1117            '>' => result.push_str("&gt;"),
1118            '"' if is_attribute => result.push_str("&quot;"),
1119            '&' => {
1120                // Check if this is the start of an entity reference
1121                if let Some(entity_len) = try_parse_entity_reference(&chars[i..]) {
1122                    // It's a valid entity reference - copy it as-is
1123                    for j in 0..entity_len {
1124                        result.push(chars[i + j]);
1125                    }
1126                    i += entity_len;
1127                    continue;
1128                } else {
1129                    // Not a valid entity reference - escape the ampersand
1130                    result.push_str("&amp;");
1131                }
1132            }
1133            _ => result.push(c),
1134        }
1135        i += 1;
1136    }
1137
1138    result
1139}
1140
1141/// Try to parse an entity reference starting at the given position.
1142/// Returns the length of the entity reference if valid, or None if not.
1143///
1144/// Valid patterns:
1145/// - `&name;` where name is one or more alphanumeric characters
1146/// - `&#digits;` where digits are decimal digits
1147/// - `&#xhex;` or `&#Xhex;` where hex is hexadecimal digits
1148fn try_parse_entity_reference(chars: &[char]) -> Option<usize> {
1149    if chars.is_empty() || chars[0] != '&' {
1150        return None;
1151    }
1152
1153    // Need at least `&x;` (3 chars minimum)
1154    if chars.len() < 3 {
1155        return None;
1156    }
1157
1158    let mut len = 1; // Start after '&'
1159
1160    if chars[1] == '#' {
1161        // Numeric entity reference
1162        len = 2;
1163
1164        if len < chars.len() && (chars[len] == 'x' || chars[len] == 'X') {
1165            // Hexadecimal: &#xHEX;
1166            len += 1;
1167            let start = len;
1168            while len < chars.len() && chars[len].is_ascii_hexdigit() {
1169                len += 1;
1170            }
1171            // Need at least one hex digit
1172            if len == start {
1173                return None;
1174            }
1175        } else {
1176            // Decimal: &#DIGITS;
1177            let start = len;
1178            while len < chars.len() && chars[len].is_ascii_digit() {
1179                len += 1;
1180            }
1181            // Need at least one digit
1182            if len == start {
1183                return None;
1184            }
1185        }
1186    } else {
1187        // Named entity reference: &NAME;
1188        // Name must start with a letter or underscore, then letters, digits, underscores, hyphens, periods
1189        if !chars[len].is_ascii_alphabetic() && chars[len] != '_' {
1190            return None;
1191        }
1192        len += 1;
1193        while len < chars.len()
1194            && (chars[len].is_ascii_alphanumeric()
1195                || chars[len] == '_'
1196                || chars[len] == '-'
1197                || chars[len] == '.')
1198        {
1199            len += 1;
1200        }
1201    }
1202
1203    // Must end with ';'
1204    if len >= chars.len() || chars[len] != ';' {
1205        return None;
1206    }
1207
1208    Some(len + 1) // Include the semicolon
1209}