facet_xml_legacy/
serialize.rs

1//! XML serialization implementation.
2
3use std::collections::HashMap;
4use std::io::Write;
5
6use base64::Engine;
7use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
8use facet_core::{Def, Facet, Field, Shape, StructKind};
9use facet_reflect::{HasFields, Peek, is_spanned_shape};
10
11use crate::annotation::{XmlAnnotationPhase, fields_missing_xml_annotations};
12use crate::deserialize::{XmlFieldExt, XmlShapeExt};
13use crate::error::{MissingAnnotationPhase, XmlError, XmlErrorKind};
14
15/// A function that formats a floating-point number to a writer.
16///
17/// This is used to customize how `f32` and `f64` values are serialized to XML.
18/// The function receives the value (as `f64`, with `f32` values upcast) and
19/// a writer to write the formatted output to.
20pub type FloatFormatter = fn(f64, &mut dyn Write) -> std::io::Result<()>;
21
22/// Options for XML serialization.
23#[derive(Clone)]
24pub struct SerializeOptions {
25    /// Whether to pretty-print with indentation (default: false)
26    pub pretty: bool,
27    /// Indentation string for pretty-printing (default: "  ")
28    pub indent: &'static str,
29    /// Custom formatter for floating-point numbers (f32 and f64).
30    /// If `None`, uses the default `Display` implementation.
31    pub float_formatter: Option<FloatFormatter>,
32    /// Whether to preserve entity references (like `&sup1;`, `&#92;`, `&#x5C;`) in string values.
33    ///
34    /// When `true`, entity references in strings are not escaped - the `&` in entity references
35    /// is left as-is instead of being escaped to `&amp;`. This is useful when serializing
36    /// content that already contains entity references (like HTML entities in SVG).
37    ///
38    /// Default: `false` (all `&` characters are escaped to `&amp;`).
39    ///
40    /// # Example
41    ///
42    /// ```
43    /// # use facet::Facet;
44    /// # use facet_xml_legacy as xml;
45    /// # use facet_xml_legacy::{to_string_with_options, SerializeOptions};
46    ///
47    /// #[derive(Facet)]
48    /// struct Text {
49    ///     #[facet(xml::attribute)]
50    ///     content: String,
51    /// }
52    ///
53    /// let text = Text { content: ".end&sup1;".to_string() };
54    ///
55    /// // Without preserve_entities: &sup1; becomes &amp;sup1;
56    /// let xml = xml::to_string(&text).unwrap();
57    /// assert!(xml.contains("&amp;sup1;"));
58    ///
59    /// // With preserve_entities: &sup1; is preserved
60    /// let options = SerializeOptions::new().preserve_entities(true);
61    /// let xml = to_string_with_options(&text, &options).unwrap();
62    /// assert!(xml.contains("&sup1;"));
63    /// ```
64    pub preserve_entities: bool,
65}
66
67impl Default for SerializeOptions {
68    fn default() -> Self {
69        Self {
70            pretty: false,
71            indent: "  ",
72            float_formatter: None,
73            preserve_entities: false,
74        }
75    }
76}
77
78impl std::fmt::Debug for SerializeOptions {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("SerializeOptions")
81            .field("pretty", &self.pretty)
82            .field("indent", &self.indent)
83            .field("float_formatter", &self.float_formatter.map(|_| "..."))
84            .field("preserve_entities", &self.preserve_entities)
85            .finish()
86    }
87}
88
89impl SerializeOptions {
90    /// Create new default options (compact output).
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Enable pretty-printing with default indentation.
96    pub fn pretty(mut self) -> Self {
97        self.pretty = true;
98        self
99    }
100
101    /// Set a custom indentation string (implies pretty-printing).
102    pub fn indent(mut self, indent: &'static str) -> Self {
103        self.indent = indent;
104        self.pretty = true;
105        self
106    }
107
108    /// Get the indent string if pretty-printing is enabled, otherwise None.
109    fn indent_str(&self) -> Option<&str> {
110        if self.pretty { Some(self.indent) } else { None }
111    }
112
113    /// Set a custom formatter for floating-point numbers (f32 and f64).
114    ///
115    /// The formatter function receives the value as `f64` (f32 values are upcast)
116    /// and writes the formatted output to the provided writer.
117    ///
118    /// # Example
119    ///
120    /// ```
121    /// # use facet::Facet;
122    /// # use facet_xml_legacy as xml;
123    /// # use facet_xml_legacy::{to_string_with_options, SerializeOptions};
124    /// # use std::io::Write;
125    /// fn fmt_g(value: f64, w: &mut dyn Write) -> std::io::Result<()> {
126    ///     // Format like C's %g: 6 significant digits, trim trailing zeros
127    ///     let s = format!("{:.6}", value);
128    ///     let s = s.trim_end_matches('0').trim_end_matches('.');
129    ///     write!(w, "{}", s)
130    /// }
131    ///
132    /// #[derive(Facet)]
133    /// struct Point {
134    ///     #[facet(xml::attribute)]
135    ///     x: f64,
136    ///     #[facet(xml::attribute)]
137    ///     y: f64,
138    /// }
139    ///
140    /// let point = Point { x: 1.5, y: 2.0 };
141    /// let options = SerializeOptions::new().float_formatter(fmt_g);
142    /// let xml = to_string_with_options(&point, &options).unwrap();
143    /// assert_eq!(xml, r#"<Point x="1.5" y="2"/>"#);
144    /// ```
145    pub fn float_formatter(mut self, formatter: FloatFormatter) -> Self {
146        self.float_formatter = Some(formatter);
147        self
148    }
149
150    /// Enable preservation of entity references in string values.
151    ///
152    /// When enabled, entity references like `&sup1;`, `&#92;`, `&#x5C;` are not escaped.
153    /// The `&` in recognized entity patterns is left as-is instead of being escaped to `&amp;`.
154    ///
155    /// This is useful when serializing content that already contains entity references,
156    /// such as HTML entities in SVG content.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// # use facet::Facet;
162    /// # use facet_xml_legacy as xml;
163    /// # use facet_xml_legacy::{to_string_with_options, SerializeOptions};
164    ///
165    /// #[derive(Facet)]
166    /// struct Text {
167    ///     #[facet(xml::attribute)]
168    ///     content: String,
169    /// }
170    ///
171    /// let text = Text { content: ".end&sup1;".to_string() };
172    /// let options = SerializeOptions::new().preserve_entities(true);
173    /// let xml = to_string_with_options(&text, &options).unwrap();
174    /// assert!(xml.contains("&sup1;"));
175    /// ```
176    pub fn preserve_entities(mut self, preserve: bool) -> Self {
177        self.preserve_entities = preserve;
178        self
179    }
180}
181
182/// Well-known XML namespace URIs and their conventional prefixes.
183const WELL_KNOWN_NAMESPACES: &[(&str, &str)] = &[
184    ("http://www.w3.org/2001/XMLSchema-instance", "xsi"),
185    ("http://www.w3.org/2001/XMLSchema", "xs"),
186    ("http://www.w3.org/XML/1998/namespace", "xml"),
187    ("http://www.w3.org/1999/xlink", "xlink"),
188    ("http://www.w3.org/2000/svg", "svg"),
189    ("http://www.w3.org/1999/xhtml", "xhtml"),
190    ("http://schemas.xmlsoap.org/soap/envelope/", "soap"),
191    ("http://www.w3.org/2003/05/soap-envelope", "soap12"),
192    ("http://schemas.android.com/apk/res/android", "android"),
193];
194
195pub(crate) type Result<T> = std::result::Result<T, XmlError>;
196
197/// Serialize a value of type `T` to an XML string.
198///
199/// The type `T` must be a struct where fields are marked with XML attributes like
200/// `#[facet(xml::element)]`, `#[facet(xml::attribute)]`, or `#[facet(xml::text)]`.
201///
202/// # Example
203/// ```
204/// # use facet::Facet;
205/// # use facet_xml_legacy as xml;
206/// # use facet_xml_legacy::to_string;
207/// #[derive(Facet)]
208/// struct Person {
209///     #[facet(xml::attribute)]
210///     id: u32,
211///     #[facet(xml::element)]
212///     name: String,
213/// }
214///
215/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
216/// let person = Person { id: 42, name: "Alice".into() };
217/// let xml = to_string(&person)?;
218/// assert_eq!(xml, r#"<Person id="42"><name>Alice</name></Person>"#);
219/// # Ok(())
220/// # }
221/// ```
222pub fn to_string<T: Facet<'static>>(value: &T) -> Result<String> {
223    to_string_with_options(value, &SerializeOptions::default())
224}
225
226/// Serialize a value of type `T` to a pretty-printed XML string.
227///
228/// This is a convenience function that enables pretty-printing with default indentation.
229///
230/// # Example
231/// ```
232/// # use facet::Facet;
233/// # use facet_xml_legacy as xml;
234/// # use facet_xml_legacy::to_string_pretty;
235/// #[derive(Facet)]
236/// struct Person {
237///     #[facet(xml::attribute)]
238///     id: u32,
239///     #[facet(xml::element)]
240///     name: String,
241/// }
242///
243/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
244/// let person = Person { id: 42, name: "Alice".into() };
245/// let xml = to_string_pretty(&person)?;
246/// // Output will have newlines and indentation
247/// # Ok(())
248/// # }
249/// ```
250pub fn to_string_pretty<T: Facet<'static>>(value: &T) -> Result<String> {
251    to_string_with_options(value, &SerializeOptions::default().pretty())
252}
253
254/// Serialize a value of type `T` to an XML string with custom options.
255///
256/// # Example
257///
258/// ```
259/// # use facet::Facet;
260/// # use facet_xml_legacy as xml;
261/// # use facet_xml_legacy::{to_string_with_options, SerializeOptions};
262/// #[derive(Facet)]
263/// struct Person {
264///     #[facet(xml::attribute)]
265///     id: u32,
266///     #[facet(xml::element)]
267///     name: String,
268/// }
269///
270/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
271/// let person = Person { id: 42, name: "Alice".into() };
272///
273/// // Compact output
274/// let xml = to_string_with_options(&person, &SerializeOptions::default())?;
275/// assert_eq!(xml, r#"<Person id="42"><name>Alice</name></Person>"#);
276///
277/// // Pretty output with tabs
278/// let xml = to_string_with_options(&person, &SerializeOptions::default().indent("\t"))?;
279/// # Ok(())
280/// # }
281/// ```
282pub fn to_string_with_options<T: Facet<'static>>(
283    value: &T,
284    options: &SerializeOptions,
285) -> Result<String> {
286    let mut output = Vec::new();
287    to_writer_with_options(&mut output, value, options)?;
288    Ok(String::from_utf8(output).expect("XML output should be valid UTF-8"))
289}
290
291/// Serialize a value of type `T` to a writer as XML.
292///
293/// This is the streaming version of [`to_string`] - it writes directly to any
294/// type implementing [`std::io::Write`].
295///
296/// # Example
297///
298/// Writing to a `Vec<u8>` buffer:
299/// ```
300/// # use facet::Facet;
301/// # use facet_xml_legacy as xml;
302/// # use facet_xml_legacy::to_writer;
303/// #[derive(Facet)]
304/// struct Person {
305///     #[facet(xml::attribute)]
306///     id: u32,
307///     #[facet(xml::element)]
308///     name: String,
309/// }
310///
311/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
312/// let person = Person { id: 42, name: "Alice".into() };
313/// let mut buffer = Vec::new();
314/// to_writer(&mut buffer, &person)?;
315/// let xml = String::from_utf8(buffer).unwrap();
316/// assert_eq!(xml, r#"<Person id="42"><name>Alice</name></Person>"#);
317/// # Ok(())
318/// # }
319/// ```
320pub fn to_writer<W: Write, T: Facet<'static>>(writer: &mut W, value: &T) -> Result<()> {
321    to_writer_with_options(writer, value, &SerializeOptions::default())
322}
323
324/// Serialize a value of type `T` to a writer as pretty-printed XML.
325///
326/// This is a convenience function that enables pretty-printing with default indentation.
327///
328/// # Example
329///
330/// ```
331/// # use facet::Facet;
332/// # use facet_xml_legacy as xml;
333/// # use facet_xml_legacy::to_writer_pretty;
334/// #[derive(Facet)]
335/// struct Person {
336///     #[facet(xml::attribute)]
337///     id: u32,
338///     #[facet(xml::element)]
339///     name: String,
340/// }
341///
342/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
343/// let person = Person { id: 42, name: "Alice".into() };
344/// let mut buffer = Vec::new();
345/// to_writer_pretty(&mut buffer, &person)?;
346/// // Output will have newlines and indentation
347/// # Ok(())
348/// # }
349/// ```
350pub fn to_writer_pretty<W: Write, T: Facet<'static>>(writer: &mut W, value: &T) -> Result<()> {
351    to_writer_with_options(writer, value, &SerializeOptions::default().pretty())
352}
353
354/// Serialize a value of type `T` to a writer as XML with custom options.
355///
356/// # Example
357///
358/// ```
359/// # use facet::Facet;
360/// # use facet_xml_legacy as xml;
361/// # use facet_xml_legacy::{to_writer_with_options, SerializeOptions};
362/// #[derive(Facet)]
363/// struct Person {
364///     #[facet(xml::attribute)]
365///     id: u32,
366///     #[facet(xml::element)]
367///     name: String,
368/// }
369///
370/// # fn main() -> Result<(), facet_xml_legacy::XmlError> {
371/// let person = Person { id: 42, name: "Alice".into() };
372///
373/// // Compact output (default)
374/// let mut buffer = Vec::new();
375/// to_writer_with_options(&mut buffer, &person, &SerializeOptions::default())?;
376/// assert_eq!(buffer, br#"<Person id="42"><name>Alice</name></Person>"#);
377///
378/// // Pretty output with default indent
379/// let mut buffer = Vec::new();
380/// to_writer_with_options(&mut buffer, &person, &SerializeOptions::default().pretty())?;
381///
382/// // Pretty output with custom indent (tabs)
383/// let mut buffer = Vec::new();
384/// to_writer_with_options(&mut buffer, &person, &SerializeOptions::default().indent("\t"))?;
385/// # Ok(())
386/// # }
387/// ```
388pub fn to_writer_with_options<W: Write, T: Facet<'static>>(
389    writer: &mut W,
390    value: &T,
391    options: &SerializeOptions,
392) -> Result<()> {
393    let peek = Peek::new(value);
394    let mut serializer = XmlSerializer::new(
395        writer,
396        options.indent_str(),
397        options.float_formatter,
398        options.preserve_entities,
399    );
400
401    // Get the type name for the root element, respecting `rename` attribute
402    let type_name = crate::deserialize::get_shape_display_name(peek.shape());
403    serializer.serialize_element(type_name, peek)
404}
405
406struct XmlSerializer<'a, W> {
407    writer: W,
408    /// Namespace URI -> prefix mapping for already-declared namespaces.
409    declared_namespaces: HashMap<String, String>,
410    /// Counter for auto-generating namespace prefixes (ns0, ns1, ...).
411    next_ns_index: usize,
412    /// Indentation string for pretty-printing (None for compact output).
413    indent: Option<&'a str>,
414    /// Current indentation depth.
415    depth: usize,
416    /// The currently active default namespace (from xmlns="..." on an ancestor).
417    /// Child elements in this namespace don't need to re-declare it.
418    current_default_ns: Option<String>,
419    /// Custom formatter for floating-point numbers.
420    float_formatter: Option<FloatFormatter>,
421    /// Whether to preserve entity references in string values.
422    preserve_entities: bool,
423}
424
425impl<'a, W: Write> XmlSerializer<'a, W> {
426    fn new(
427        writer: W,
428        indent: Option<&'a str>,
429        float_formatter: Option<FloatFormatter>,
430        preserve_entities: bool,
431    ) -> Self {
432        Self {
433            writer,
434            declared_namespaces: HashMap::new(),
435            next_ns_index: 0,
436            indent,
437            depth: 0,
438            current_default_ns: None,
439            float_formatter,
440            preserve_entities,
441        }
442    }
443
444    /// Write indentation for the current depth.
445    fn write_indent(&mut self) -> Result<()> {
446        if let Some(indent_str) = self.indent {
447            for _ in 0..self.depth {
448                self.writer
449                    .write_all(indent_str.as_bytes())
450                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
451            }
452        }
453        Ok(())
454    }
455
456    /// Write a newline if pretty-printing is enabled.
457    fn write_newline(&mut self) -> Result<()> {
458        if self.indent.is_some() {
459            self.writer
460                .write_all(b"\n")
461                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
462        }
463        Ok(())
464    }
465
466    /// Get or create a prefix for the given namespace URI.
467    /// Returns the prefix (without colon).
468    ///
469    /// Note: We always need to emit xmlns declarations on each element that uses a prefix,
470    /// because XML namespace declarations are scoped to the element and its descendants.
471    /// A declaration on a sibling or earlier element doesn't apply.
472    fn get_or_create_prefix(&mut self, namespace_uri: &str) -> String {
473        // Check if we've already assigned a prefix to this URI
474        if let Some(prefix) = self.declared_namespaces.get(namespace_uri) {
475            return prefix.clone();
476        }
477
478        // Try well-known namespaces
479        let prefix = WELL_KNOWN_NAMESPACES
480            .iter()
481            .find(|(uri, _)| *uri == namespace_uri)
482            .map(|(_, prefix)| (*prefix).to_string())
483            .unwrap_or_else(|| {
484                // Auto-generate a prefix
485                let prefix = format!("ns{}", self.next_ns_index);
486                self.next_ns_index += 1;
487                prefix
488            });
489
490        // Ensure the prefix isn't already in use for a different namespace
491        let final_prefix = if self.declared_namespaces.values().any(|p| p == &prefix) {
492            // Conflict! Generate a new one
493            let prefix = format!("ns{}", self.next_ns_index);
494            self.next_ns_index += 1;
495            prefix
496        } else {
497            prefix
498        };
499
500        self.declared_namespaces
501            .insert(namespace_uri.to_string(), final_prefix.clone());
502        final_prefix
503    }
504
505    /// Get the effective namespace for a field, considering field-level xml::ns
506    /// and container-level xml::ns_all.
507    ///
508    /// For attributes: `ns_all` is NOT applied because unprefixed attributes in XML
509    /// are always in "no namespace", regardless of any default xmlns declaration.
510    /// Only explicit `xml::ns` on the attribute field is used.
511    ///
512    /// For elements: Both `xml::ns` and `ns_all` are considered.
513    fn get_field_namespace(
514        field: &Field,
515        ns_all: Option<&'static str>,
516        is_attribute: bool,
517    ) -> Option<&'static str> {
518        if is_attribute {
519            // Attributes only use explicit xml::ns, not ns_all
520            // Per XML spec: unprefixed attributes are in "no namespace"
521            field.xml_ns()
522        } else {
523            // Elements use xml::ns or fall back to ns_all
524            field.xml_ns().or(ns_all)
525        }
526    }
527
528    fn serialize_element<'mem, 'facet>(
529        &mut self,
530        element_name: &str,
531        peek: Peek<'mem, 'facet>,
532    ) -> Result<()> {
533        // Handle Option<T> - skip if None
534        if let Ok(opt_peek) = peek.into_option() {
535            if opt_peek.is_none() {
536                return Ok(());
537            }
538            if let Some(inner) = opt_peek.value() {
539                return self.serialize_element(element_name, inner);
540            }
541            return Ok(());
542        }
543
544        // Handle Spanned<T> - unwrap to the inner value
545        if is_spanned_shape(peek.shape())
546            && let Ok(struct_peek) = peek.into_struct()
547            && let Ok(value_field) = struct_peek.field_by_name("value")
548        {
549            return self.serialize_element(element_name, value_field);
550        }
551
552        // Check if this is a struct
553        let shape = peek.shape();
554        if let Ok(struct_peek) = peek.into_struct() {
555            return self.serialize_struct_as_element(element_name, struct_peek, shape);
556        }
557
558        // Check if this is an enum
559        if let Ok(enum_peek) = peek.into_enum() {
560            return self.serialize_enum_as_element(element_name, enum_peek);
561        }
562
563        // Check if this is a byte slice/array - serialize as base64
564        if self.try_serialize_bytes_as_element(element_name, peek)? {
565            return Ok(());
566        }
567
568        // Check if this is a list-like type (Vec, array, set)
569        if let Ok(list_peek) = peek.into_list_like() {
570            return self.serialize_list_as_element(element_name, list_peek);
571        }
572
573        // Check if this is a map
574        if let Ok(map_peek) = peek.into_map() {
575            return self.serialize_map_as_element(element_name, map_peek);
576        }
577
578        // For scalars/primitives, serialize as element with text content
579        write!(self.writer, "<{}>", escape_element_name(element_name))
580            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
581        self.serialize_value(peek)?;
582        write!(self.writer, "</{}>", escape_element_name(element_name))
583            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
584
585        Ok(())
586    }
587
588    fn serialize_enum_as_element<'mem, 'facet>(
589        &mut self,
590        element_name: &str,
591        enum_peek: facet_reflect::PeekEnum<'mem, 'facet>,
592    ) -> Result<()> {
593        let shape = enum_peek.shape();
594        let variant_name = enum_peek
595            .variant_name_active()
596            .map_err(|_| XmlErrorKind::SerializeUnknownElementType)?;
597
598        let fields: Vec<_> = enum_peek.fields_for_serialize().collect();
599
600        // Determine enum tagging strategy
601        let is_untagged = shape.is_untagged();
602        let tag_attr = shape.get_tag_attr();
603        let content_attr = shape.get_content_attr();
604
605        if is_untagged {
606            // Untagged: serialize content directly with element name
607            self.serialize_enum_content(element_name, variant_name, &fields)?;
608        } else if let Some(tag) = tag_attr {
609            if let Some(content) = content_attr {
610                // Adjacently tagged: <Element tag="Variant"><content>...</content></Element>
611                write!(
612                    self.writer,
613                    "<{} {}=\"{}\">",
614                    escape_element_name(element_name),
615                    escape_element_name(tag),
616                    escape_attribute_value(variant_name, self.preserve_entities)
617                )
618                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
619
620                if !fields.is_empty() {
621                    // Wrap content in the content element
622                    write!(self.writer, "<{}>", escape_element_name(content))
623                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
624                    self.serialize_variant_fields(&fields)?;
625                    write!(self.writer, "</{}>", escape_element_name(content))
626                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
627                }
628
629                write!(self.writer, "</{}>", escape_element_name(element_name))
630                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
631            } else {
632                // Internally tagged: <Element tag="Variant">...fields...</Element>
633                write!(
634                    self.writer,
635                    "<{} {}=\"{}\">",
636                    escape_element_name(element_name),
637                    escape_element_name(tag),
638                    escape_attribute_value(variant_name, self.preserve_entities)
639                )
640                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
641
642                // Serialize fields directly (not wrapped in variant element)
643                self.serialize_variant_fields(&fields)?;
644
645                write!(self.writer, "</{}>", escape_element_name(element_name))
646                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
647            }
648        } else {
649            // Externally tagged (default): <Element><Variant>...</Variant></Element>
650            write!(self.writer, "<{}>", escape_element_name(element_name))
651                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
652
653            if fields.is_empty() {
654                // Unit variant - just the variant name as an empty element
655                write!(self.writer, "<{}/>", escape_element_name(variant_name))
656                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
657            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
658                // Newtype variant - serialize the inner value with variant name
659                self.serialize_element(variant_name, fields[0].1)?;
660            } else {
661                // Struct-like variant
662                write!(self.writer, "<{}>", escape_element_name(variant_name))
663                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
664
665                for (field_item, field_peek) in fields {
666                    self.serialize_element(&field_item.name, field_peek)?;
667                }
668
669                write!(self.writer, "</{}>", escape_element_name(variant_name))
670                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
671            }
672
673            write!(self.writer, "</{}>", escape_element_name(element_name))
674                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
675        }
676
677        Ok(())
678    }
679
680    /// Serialize enum content for untagged enums
681    fn serialize_enum_content<'mem, 'facet>(
682        &mut self,
683        element_name: &str,
684        variant_name: &str,
685        fields: &[(facet_reflect::FieldItem, Peek<'mem, 'facet>)],
686    ) -> Result<()> {
687        if fields.is_empty() {
688            // Unit variant - empty element
689            write!(self.writer, "<{}/>", escape_element_name(element_name))
690                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
691        } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
692            // Newtype variant - serialize inner directly
693            self.serialize_element(element_name, fields[0].1)?;
694        } else {
695            // Struct-like variant - serialize as struct
696            write!(self.writer, "<{}>", escape_element_name(element_name))
697                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
698
699            for (field_item, field_peek) in fields {
700                // Use variant_name as hint for structs in untagged context
701                let _ = variant_name; // Available if needed for disambiguation
702                self.serialize_element(&field_item.name, *field_peek)?;
703            }
704
705            write!(self.writer, "</{}>", escape_element_name(element_name))
706                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
707        }
708        Ok(())
709    }
710
711    /// Serialize variant fields without wrapper
712    fn serialize_variant_fields<'mem, 'facet>(
713        &mut self,
714        fields: &[(facet_reflect::FieldItem, Peek<'mem, 'facet>)],
715    ) -> Result<()> {
716        if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
717            // Single tuple field - serialize value directly
718            self.serialize_value(fields[0].1)?;
719        } else {
720            for (field_item, field_peek) in fields {
721                self.serialize_element(&field_item.name, *field_peek)?;
722            }
723        }
724        Ok(())
725    }
726
727    fn serialize_list_as_element<'mem, 'facet>(
728        &mut self,
729        element_name: &str,
730        list_peek: facet_reflect::PeekListLike<'mem, 'facet>,
731    ) -> Result<()> {
732        write!(self.writer, "<{}>", escape_element_name(element_name))
733            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
734
735        let has_items = list_peek.iter().next().is_some();
736        if has_items {
737            self.depth += 1;
738        }
739
740        for item in list_peek.iter() {
741            self.write_newline()?;
742            self.write_indent()?;
743            self.serialize_list_item_element(item)?;
744        }
745
746        if has_items {
747            self.depth -= 1;
748            self.write_newline()?;
749            self.write_indent()?;
750        }
751
752        write!(self.writer, "</{}>", escape_element_name(element_name))
753            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
754
755        Ok(())
756    }
757
758    fn serialize_map_as_element<'mem, 'facet>(
759        &mut self,
760        element_name: &str,
761        map_peek: facet_reflect::PeekMap<'mem, 'facet>,
762    ) -> Result<()> {
763        write!(self.writer, "<{}>", escape_element_name(element_name))
764            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
765
766        let has_items = map_peek.iter().next().is_some();
767        if has_items {
768            self.depth += 1;
769        }
770
771        for (key, value) in map_peek.iter() {
772            self.write_newline()?;
773            self.write_indent()?;
774            // Use the key as the element name
775            if let Some(key_str) = key.as_str() {
776                self.serialize_element(key_str, value)?;
777            } else if let Some(key_val) = value_to_string(key, self.float_formatter) {
778                self.serialize_element(&key_val, value)?;
779            } else {
780                // Fallback: use "entry" as element name with key as text
781                write!(self.writer, "<entry>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
782                self.serialize_value(key)?;
783                self.serialize_value(value)?;
784                write!(self.writer, "</entry>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
785            }
786        }
787
788        if has_items {
789            self.depth -= 1;
790            self.write_newline()?;
791            self.write_indent()?;
792        }
793
794        write!(self.writer, "</{}>", escape_element_name(element_name))
795            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
796
797        Ok(())
798    }
799
800    /// Try to serialize bytes (`Vec<u8>`, `&[u8]`, `[u8; N]`) as base64-encoded element.
801    /// Returns Ok(true) if bytes were handled, Ok(false) if not bytes.
802    fn try_serialize_bytes_as_element<'mem, 'facet>(
803        &mut self,
804        element_name: &str,
805        peek: Peek<'mem, 'facet>,
806    ) -> Result<bool> {
807        let shape = peek.shape();
808
809        // Check for Vec<u8>
810        if let Def::List(ld) = &shape.def
811            && ld.t().is_type::<u8>()
812            && let Some(bytes) = peek.as_bytes()
813        {
814            let encoded = BASE64_STANDARD.encode(bytes);
815            write!(self.writer, "<{}>", escape_element_name(element_name))
816                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
817            write!(
818                self.writer,
819                "{}",
820                escape_text(&encoded, self.preserve_entities)
821            )
822            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
823            write!(self.writer, "</{}>", escape_element_name(element_name))
824                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
825            return Ok(true);
826        }
827
828        // Check for [u8; N]
829        if let Def::Array(ad) = &shape.def
830            && ad.t().is_type::<u8>()
831        {
832            // Collect bytes from the array
833            if let Ok(list_peek) = peek.into_list_like() {
834                let bytes: Vec<u8> = list_peek
835                    .iter()
836                    .filter_map(|p| p.get::<u8>().ok().copied())
837                    .collect();
838                let encoded = BASE64_STANDARD.encode(&bytes);
839                write!(self.writer, "<{}>", escape_element_name(element_name))
840                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
841                write!(
842                    self.writer,
843                    "{}",
844                    escape_text(&encoded, self.preserve_entities)
845                )
846                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
847                write!(self.writer, "</{}>", escape_element_name(element_name))
848                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
849                return Ok(true);
850            }
851        }
852
853        // Check for &[u8]
854        if let Def::Slice(sd) = &shape.def
855            && sd.t().is_type::<u8>()
856            && let Some(bytes) = peek.as_bytes()
857        {
858            let encoded = BASE64_STANDARD.encode(bytes);
859            write!(self.writer, "<{}>", escape_element_name(element_name))
860                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
861            write!(
862                self.writer,
863                "{}",
864                escape_text(&encoded, self.preserve_entities)
865            )
866            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
867            write!(self.writer, "</{}>", escape_element_name(element_name))
868                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
869            return Ok(true);
870        }
871
872        Ok(false)
873    }
874
875    fn serialize_struct_as_element<'mem, 'facet>(
876        &mut self,
877        element_name: &str,
878        struct_peek: facet_reflect::PeekStruct<'mem, 'facet>,
879        shape: &'static Shape,
880    ) -> Result<()> {
881        let struct_ty = struct_peek.ty();
882
883        match struct_ty.kind {
884            StructKind::Unit => {
885                // Unit struct - just output empty element
886                write!(self.writer, "<{}/>", escape_element_name(element_name))
887                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
888                return Ok(());
889            }
890            StructKind::Tuple | StructKind::TupleStruct => {
891                // Tuple struct - serialize fields in order as child elements
892                let fields: Vec<_> = struct_peek.fields_for_serialize().collect();
893                if fields.is_empty() {
894                    write!(self.writer, "<{}/>", escape_element_name(element_name))
895                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
896                    return Ok(());
897                }
898
899                write!(self.writer, "<{}>", escape_element_name(element_name))
900                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
901
902                for (i, (_, field_peek)) in fields.into_iter().enumerate() {
903                    // Use indexed element names for tuple fields
904                    let field_name = format!("_{i}");
905                    self.serialize_element(&field_name, field_peek)?;
906                }
907
908                write!(self.writer, "</{}>", escape_element_name(element_name))
909                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
910                return Ok(());
911            }
912            StructKind::Struct => {
913                // Named struct - fall through to normal handling
914            }
915        }
916
917        let fields = struct_ty.fields;
918        let missing = fields_missing_xml_annotations(fields, XmlAnnotationPhase::Serialize);
919        if !missing.is_empty() {
920            let field_info = missing
921                .into_iter()
922                .map(|field| (field.name, field.shape().type_identifier))
923                .collect();
924            return Err(XmlError::new(XmlErrorKind::MissingXmlAnnotations {
925                type_name: shape.type_identifier,
926                phase: MissingAnnotationPhase::Serialize,
927                fields: field_info,
928            }));
929        }
930
931        // Get container-level namespace default
932        let ns_all = shape.xml_ns_all();
933
934        // Collect attributes (with field info for namespace), elements, and text content
935        struct AttrInfo {
936            name: String,
937            value: String,
938            namespace: Option<&'static str>,
939        }
940        let mut attributes: Vec<AttrInfo> = Vec::new();
941        let mut elements: Vec<(facet_reflect::FieldItem, Peek<'mem, 'facet>)> = Vec::new();
942        let mut elements_list: Vec<Peek<'mem, 'facet>> = Vec::new();
943        let mut text_content: Option<Peek<'mem, 'facet>> = None;
944
945        for (field_item, field_peek) in struct_peek.fields_for_serialize() {
946            // Skip flattened map entries (field is None) - legacy serializer doesn't support them
947            let Some(field) = field_item.field else {
948                continue;
949            };
950
951            // Handle custom serialization for attributes - get value immediately
952            if field.is_xml_attribute() {
953                let value = if field.proxy_convert_out_fn().is_some() {
954                    // Get the intermediate representation for serialization
955                    if let Ok(owned) = field_peek.custom_serialization(field) {
956                        value_to_string(owned.as_peek(), self.float_formatter)
957                    } else {
958                        value_to_string(field_peek, self.float_formatter)
959                    }
960                } else {
961                    value_to_string(field_peek, self.float_formatter)
962                };
963                if let Some(value) = value {
964                    // Pass is_attribute=true so ns_all is NOT applied
965                    let namespace = Self::get_field_namespace(&field, ns_all, true);
966                    attributes.push(AttrInfo {
967                        name: field_item.name.into_owned(),
968                        value,
969                        namespace,
970                    });
971                }
972            } else if field.is_xml_element() {
973                elements.push((field_item, field_peek));
974            } else if field.is_xml_elements() {
975                elements_list.push(field_peek);
976            } else if field.is_xml_text() {
977                text_content = Some(field_peek);
978            }
979        }
980
981        // Determine if we need content
982        let has_content =
983            !elements.is_empty() || !elements_list.is_empty() || text_content.is_some();
984
985        // Collect xmlns declarations needed for attributes on this element
986        // We always emit xmlns declarations on the element that uses them, because
987        // XML namespace scope is limited to an element and its descendants.
988        let mut xmlns_decls: Vec<(String, String)> = Vec::new(); // (prefix, uri)
989        let mut attr_prefixes: Vec<Option<String>> = Vec::new(); // prefix for each attribute
990
991        for attr in &attributes {
992            if let Some(ns_uri) = attr.namespace {
993                let prefix = self.get_or_create_prefix(ns_uri);
994                // Always emit xmlns declaration on this element
995                if !xmlns_decls.iter().any(|(_, u)| u == ns_uri) {
996                    xmlns_decls.push((prefix.clone(), ns_uri.to_string()));
997                }
998                attr_prefixes.push(Some(prefix));
999            } else {
1000                attr_prefixes.push(None);
1001            }
1002        }
1003
1004        // Write opening tag
1005        write!(self.writer, "<{}", escape_element_name(element_name))
1006            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1007
1008        // If ns_all is set and differs from the current default namespace,
1009        // emit a default namespace declaration (xmlns="...").
1010        // Child elements with the same namespace will be unprefixed and inherit it.
1011        let emitting_new_default_ns = if let Some(ns_uri) = ns_all {
1012            let dominated = self
1013                .current_default_ns
1014                .as_ref()
1015                .is_some_and(|current| current == ns_uri);
1016            if !dominated {
1017                write!(
1018                    self.writer,
1019                    " xmlns=\"{}\"",
1020                    escape_attribute_value(ns_uri, self.preserve_entities)
1021                )
1022                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1023                true
1024            } else {
1025                false
1026            }
1027        } else {
1028            false
1029        };
1030
1031        // Write xmlns declarations for attributes (only for explicitly namespaced attributes)
1032        for (prefix, uri) in &xmlns_decls {
1033            write!(
1034                self.writer,
1035                " xmlns:{}=\"{}\"",
1036                escape_element_name(prefix),
1037                escape_attribute_value(uri, self.preserve_entities)
1038            )
1039            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1040        }
1041
1042        // Write attributes (with prefix if namespaced)
1043        for (attr, prefix) in attributes.iter().zip(attr_prefixes.iter()) {
1044            let attr_name = if let Some(p) = prefix {
1045                format!("{p}:{}", attr.name)
1046            } else {
1047                attr.name.to_string()
1048            };
1049            write!(
1050                self.writer,
1051                " {}=\"{}\"",
1052                escape_element_name(&attr_name),
1053                escape_attribute_value(&attr.value, self.preserve_entities)
1054            )
1055            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1056        }
1057
1058        if !has_content {
1059            // Self-closing tag
1060            write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1061            return Ok(());
1062        }
1063
1064        write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1065
1066        // Save and update the default namespace for children
1067        let old_default_ns = if emitting_new_default_ns {
1068            let old = self.current_default_ns.take();
1069            self.current_default_ns = ns_all.map(|s| s.to_string());
1070            old
1071        } else {
1072            None
1073        };
1074
1075        // Write text content if present (no indentation for text content)
1076        if let Some(text_peek) = text_content {
1077            self.serialize_text_value(text_peek)?;
1078        }
1079
1080        // Check if we have child elements (for indentation purposes)
1081        let has_child_elements = !elements.is_empty() || !elements_list.is_empty();
1082
1083        // Write child elements (with namespace support)
1084        // Pass ns_all as the default namespace so child elements with matching
1085        // namespace use unprefixed form (they inherit the default xmlns).
1086        if has_child_elements {
1087            self.depth += 1;
1088        }
1089
1090        for (field_item, field_peek) in elements {
1091            // Skip flattened map entries
1092            let Some(field) = field_item.field else {
1093                continue;
1094            };
1095            // Pass is_attribute=false for elements
1096            let field_ns = Self::get_field_namespace(&field, ns_all, false);
1097
1098            self.write_newline()?;
1099            self.write_indent()?;
1100
1101            // Handle custom serialization for elements
1102            if field.proxy_convert_out_fn().is_some() {
1103                if let Ok(owned) = field_peek.custom_serialization(field) {
1104                    self.serialize_namespaced_element(
1105                        &field_item.name,
1106                        owned.as_peek(),
1107                        field_ns,
1108                        ns_all,
1109                    )?;
1110                } else {
1111                    self.serialize_namespaced_element(
1112                        &field_item.name,
1113                        field_peek,
1114                        field_ns,
1115                        ns_all,
1116                    )?;
1117                }
1118            } else {
1119                self.serialize_namespaced_element(&field_item.name, field_peek, field_ns, ns_all)?;
1120            }
1121        }
1122
1123        // Write elements lists
1124        for field_peek in elements_list {
1125            self.serialize_elements_list(field_peek)?;
1126        }
1127
1128        if has_child_elements {
1129            self.depth -= 1;
1130            self.write_newline()?;
1131            self.write_indent()?;
1132        }
1133
1134        // Restore the old default namespace
1135        if emitting_new_default_ns {
1136            self.current_default_ns = old_default_ns;
1137        }
1138
1139        // Write closing tag
1140        write!(self.writer, "</{}>", escape_element_name(element_name))
1141            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1142
1143        Ok(())
1144    }
1145
1146    /// Serialize an element with optional namespace.
1147    ///
1148    /// - `namespace`: The namespace this element should be in
1149    /// - `default_ns`: The currently active default namespace (from parent's xmlns="...")
1150    ///
1151    /// If the element's namespace matches the default namespace, we use an unprefixed
1152    /// element name (the element inherits the default namespace).
1153    /// Otherwise, we use a prefix and emit an xmlns:prefix declaration.
1154    fn serialize_namespaced_element<'mem, 'facet>(
1155        &mut self,
1156        element_name: &str,
1157        peek: Peek<'mem, 'facet>,
1158        namespace: Option<&str>,
1159        default_ns: Option<&str>,
1160    ) -> Result<()> {
1161        // Handle Option<T> - skip if None
1162        if let Ok(opt_peek) = peek.into_option() {
1163            if opt_peek.is_none() {
1164                return Ok(());
1165            }
1166            if let Some(inner) = opt_peek.value() {
1167                return self.serialize_namespaced_element(
1168                    element_name,
1169                    inner,
1170                    namespace,
1171                    default_ns,
1172                );
1173            }
1174            return Ok(());
1175        }
1176
1177        // Handle Spanned<T> - unwrap to the inner value
1178        if is_spanned_shape(peek.shape())
1179            && let Ok(struct_peek) = peek.into_struct()
1180            && let Ok(value_field) = struct_peek.field_by_name("value")
1181        {
1182            return self.serialize_namespaced_element(
1183                element_name,
1184                value_field,
1185                namespace,
1186                default_ns,
1187            );
1188        }
1189
1190        // Determine element name and xmlns declaration
1191        // If namespace matches the current default, use unprefixed form (inherit default).
1192        // Otherwise, use a prefix and emit xmlns:prefix declaration.
1193        let (final_name, xmlns_decl) = if let Some(ns_uri) = namespace {
1194            if default_ns == Some(ns_uri) {
1195                // Element is in the default namespace - use unprefixed form
1196                (element_name.to_string(), None)
1197            } else {
1198                // Element is in a different namespace - use prefix
1199                let prefix = self.get_or_create_prefix(ns_uri);
1200                let prefixed = format!("{prefix}:{element_name}");
1201                (prefixed, Some((prefix, ns_uri.to_string())))
1202            }
1203        } else {
1204            (element_name.to_string(), None)
1205        };
1206
1207        // Check if this is a struct - handle specially for proper namespace propagation
1208        let shape = peek.shape();
1209        if let Ok(struct_peek) = peek.into_struct() {
1210            return self.serialize_struct_as_namespaced_element(
1211                &final_name,
1212                struct_peek,
1213                xmlns_decl,
1214                shape,
1215                default_ns,
1216            );
1217        }
1218
1219        // Check if this is an enum
1220        if let Ok(enum_peek) = peek.into_enum() {
1221            return self.serialize_enum_as_namespaced_element(&final_name, enum_peek, xmlns_decl);
1222        }
1223
1224        // For scalars/primitives, serialize as element with text content
1225        write!(self.writer, "<{}", escape_element_name(&final_name))
1226            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1227
1228        // Add xmlns declaration if needed
1229        if let Some((prefix, uri)) = xmlns_decl {
1230            write!(
1231                self.writer,
1232                " xmlns:{}=\"{}\"",
1233                escape_element_name(&prefix),
1234                escape_attribute_value(&uri, self.preserve_entities)
1235            )
1236            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1237        }
1238
1239        write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1240        self.serialize_value(peek)?;
1241        write!(self.writer, "</{}>", escape_element_name(&final_name))
1242            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1243
1244        Ok(())
1245    }
1246
1247    /// Serialize a struct as an element with optional xmlns declaration on the opening tag.
1248    ///
1249    /// - `parent_default_ns`: The default namespace inherited from the parent element
1250    fn serialize_struct_as_namespaced_element<'mem, 'facet>(
1251        &mut self,
1252        element_name: &str,
1253        struct_peek: facet_reflect::PeekStruct<'mem, 'facet>,
1254        xmlns_decl: Option<(String, String)>,
1255        shape: &'static Shape,
1256        parent_default_ns: Option<&str>,
1257    ) -> Result<()> {
1258        match struct_peek.ty().kind {
1259            StructKind::Unit => {
1260                // Unit struct - just output empty element with xmlns if needed
1261                write!(self.writer, "<{}", escape_element_name(element_name))
1262                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1263                if let Some((prefix, uri)) = xmlns_decl {
1264                    write!(
1265                        self.writer,
1266                        " xmlns:{}=\"{}\"",
1267                        escape_element_name(&prefix),
1268                        escape_attribute_value(&uri, self.preserve_entities)
1269                    )
1270                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1271                }
1272                write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1273                return Ok(());
1274            }
1275            StructKind::Tuple | StructKind::TupleStruct => {
1276                // Tuple struct - serialize fields in order as child elements
1277                let fields: Vec<_> = struct_peek.fields_for_serialize().collect();
1278                if fields.is_empty() {
1279                    write!(self.writer, "<{}", escape_element_name(element_name))
1280                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1281                    if let Some((prefix, uri)) = xmlns_decl {
1282                        write!(
1283                            self.writer,
1284                            " xmlns:{}=\"{}\"",
1285                            escape_element_name(&prefix),
1286                            escape_attribute_value(&uri, self.preserve_entities)
1287                        )
1288                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1289                    }
1290                    write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1291                    return Ok(());
1292                }
1293
1294                write!(self.writer, "<{}", escape_element_name(element_name))
1295                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1296                if let Some((prefix, uri)) = xmlns_decl {
1297                    write!(
1298                        self.writer,
1299                        " xmlns:{}=\"{}\"",
1300                        escape_element_name(&prefix),
1301                        escape_attribute_value(&uri, self.preserve_entities)
1302                    )
1303                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1304                }
1305                write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1306
1307                for (i, (_, field_peek)) in fields.into_iter().enumerate() {
1308                    let field_name = format!("_{i}");
1309                    self.serialize_element(&field_name, field_peek)?;
1310                }
1311
1312                write!(self.writer, "</{}>", escape_element_name(element_name))
1313                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1314                return Ok(());
1315            }
1316            StructKind::Struct => {
1317                // Named struct - fall through to normal handling
1318            }
1319        }
1320
1321        // Get container-level namespace default for the nested struct
1322        let ns_all = shape.xml_ns_all();
1323
1324        // The actual default namespace in the XML is inherited from parent.
1325        // We don't emit xmlns="..." on nested elements because that would
1326        // change the element's own namespace. Child elements in a different
1327        // namespace will use prefixed form.
1328        let effective_default_ns: Option<&str> = parent_default_ns;
1329
1330        // Collect attributes, elements, and text content
1331        struct AttrInfo {
1332            name: String,
1333            value: String,
1334            namespace: Option<&'static str>,
1335        }
1336        let mut attributes: Vec<AttrInfo> = Vec::new();
1337        let mut elements: Vec<(facet_reflect::FieldItem, Peek<'mem, 'facet>)> = Vec::new();
1338        let mut elements_list: Vec<Peek<'mem, 'facet>> = Vec::new();
1339        let mut text_content: Option<Peek<'mem, 'facet>> = None;
1340
1341        for (field_item, field_peek) in struct_peek.fields_for_serialize() {
1342            // Skip flattened map entries (field is None) - legacy serializer doesn't support them
1343            let Some(field) = field_item.field else {
1344                continue;
1345            };
1346
1347            if field.is_xml_attribute() {
1348                let value = if field.proxy_convert_out_fn().is_some() {
1349                    if let Ok(owned) = field_peek.custom_serialization(field) {
1350                        value_to_string(owned.as_peek(), self.float_formatter)
1351                    } else {
1352                        value_to_string(field_peek, self.float_formatter)
1353                    }
1354                } else {
1355                    value_to_string(field_peek, self.float_formatter)
1356                };
1357                if let Some(value) = value {
1358                    // Pass is_attribute=true so ns_all is NOT applied
1359                    let namespace = Self::get_field_namespace(&field, ns_all, true);
1360                    attributes.push(AttrInfo {
1361                        name: field_item.name.into_owned(),
1362                        value,
1363                        namespace,
1364                    });
1365                }
1366            } else if field.is_xml_element() {
1367                elements.push((field_item, field_peek));
1368            } else if field.is_xml_elements() {
1369                elements_list.push(field_peek);
1370            } else if field.is_xml_text() {
1371                text_content = Some(field_peek);
1372            }
1373        }
1374
1375        let has_content =
1376            !elements.is_empty() || !elements_list.is_empty() || text_content.is_some();
1377
1378        // Collect xmlns declarations needed for attributes
1379        // We always emit xmlns declarations on each element that uses them.
1380        let mut xmlns_decls: Vec<(String, String)> = Vec::new();
1381        let mut attr_prefixes: Vec<Option<String>> = Vec::new();
1382
1383        // Start with the element's own xmlns declaration if any (from prefixed form)
1384        if let Some((prefix, uri)) = xmlns_decl {
1385            xmlns_decls.push((prefix, uri));
1386        }
1387
1388        for attr in &attributes {
1389            if let Some(ns_uri) = attr.namespace {
1390                let prefix = self.get_or_create_prefix(ns_uri);
1391                // Always emit xmlns declaration on this element (if not already)
1392                if !xmlns_decls.iter().any(|(_, u)| u == ns_uri) {
1393                    xmlns_decls.push((prefix.clone(), ns_uri.to_string()));
1394                }
1395                attr_prefixes.push(Some(prefix));
1396            } else {
1397                attr_prefixes.push(None);
1398            }
1399        }
1400
1401        // Write opening tag
1402        write!(self.writer, "<{}", escape_element_name(element_name))
1403            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1404
1405        // NOTE: We intentionally do NOT emit xmlns="..." on nested struct elements here.
1406        // The element itself is in the parent's namespace (determined by the context
1407        // where it's used). Only the struct's CHILD elements are affected by ns_all.
1408        // Child elements in a different namespace will use prefixed form.
1409
1410        // Write prefixed xmlns declarations (for explicitly namespaced attributes)
1411        for (prefix, uri) in &xmlns_decls {
1412            write!(
1413                self.writer,
1414                " xmlns:{}=\"{}\"",
1415                escape_element_name(prefix),
1416                escape_attribute_value(uri, self.preserve_entities)
1417            )
1418            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1419        }
1420
1421        // Write attributes
1422        for (attr, prefix) in attributes.iter().zip(attr_prefixes.iter()) {
1423            let attr_name = if let Some(p) = prefix {
1424                format!("{p}:{}", attr.name)
1425            } else {
1426                attr.name.to_string()
1427            };
1428            write!(
1429                self.writer,
1430                " {}=\"{}\"",
1431                escape_element_name(&attr_name),
1432                escape_attribute_value(&attr.value, self.preserve_entities)
1433            )
1434            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1435        }
1436
1437        if !has_content {
1438            write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1439            return Ok(());
1440        }
1441
1442        write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1443
1444        if let Some(text_peek) = text_content {
1445            self.serialize_text_value(text_peek)?;
1446        }
1447
1448        // Check if we have child elements (for indentation purposes)
1449        let has_child_elements = !elements.is_empty() || !elements_list.is_empty();
1450
1451        if has_child_elements {
1452            self.depth += 1;
1453        }
1454
1455        for (field_item, field_peek) in elements {
1456            // Skip flattened map entries
1457            let Some(field) = field_item.field else {
1458                continue;
1459            };
1460            // Pass is_attribute=false for elements
1461            let field_ns = Self::get_field_namespace(&field, ns_all, false);
1462
1463            self.write_newline()?;
1464            self.write_indent()?;
1465
1466            if field.proxy_convert_out_fn().is_some() {
1467                if let Ok(owned) = field_peek.custom_serialization(field) {
1468                    self.serialize_namespaced_element(
1469                        &field_item.name,
1470                        owned.as_peek(),
1471                        field_ns,
1472                        effective_default_ns,
1473                    )?;
1474                } else {
1475                    self.serialize_namespaced_element(
1476                        &field_item.name,
1477                        field_peek,
1478                        field_ns,
1479                        effective_default_ns,
1480                    )?;
1481                }
1482            } else {
1483                self.serialize_namespaced_element(
1484                    &field_item.name,
1485                    field_peek,
1486                    field_ns,
1487                    effective_default_ns,
1488                )?;
1489            }
1490        }
1491
1492        for field_peek in elements_list {
1493            self.serialize_elements_list(field_peek)?;
1494        }
1495
1496        if has_child_elements {
1497            self.depth -= 1;
1498            self.write_newline()?;
1499            self.write_indent()?;
1500        }
1501
1502        write!(self.writer, "</{}>", escape_element_name(element_name))
1503            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1504
1505        Ok(())
1506    }
1507
1508    /// Serialize an enum as an element with optional xmlns declaration.
1509    fn serialize_enum_as_namespaced_element<'mem, 'facet>(
1510        &mut self,
1511        prefixed_element_name: &str,
1512        enum_peek: facet_reflect::PeekEnum<'mem, 'facet>,
1513        xmlns_decl: Option<(String, String)>,
1514    ) -> Result<()> {
1515        let shape = enum_peek.shape();
1516        let variant_name = enum_peek
1517            .variant_name_active()
1518            .map_err(|_| XmlErrorKind::SerializeUnknownElementType)?;
1519
1520        let fields: Vec<_> = enum_peek.fields_for_serialize().collect();
1521
1522        let is_untagged = shape.is_untagged();
1523        let tag_attr = shape.get_tag_attr();
1524        let content_attr = shape.get_content_attr();
1525
1526        // Helper to write opening tag with optional xmlns
1527        let write_open_tag =
1528            |writer: &mut W, name: &str, xmlns: &Option<(String, String)>| -> Result<()> {
1529                write!(writer, "<{}", escape_element_name(name))
1530                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1531                if let Some((prefix, uri)) = xmlns {
1532                    write!(
1533                        writer,
1534                        " xmlns:{}=\"{}\"",
1535                        escape_element_name(prefix),
1536                        escape_attribute_value(uri, self.preserve_entities)
1537                    )
1538                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1539                }
1540                Ok(())
1541            };
1542
1543        if is_untagged {
1544            // Untagged: serialize content directly
1545            if fields.is_empty() {
1546                write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1547                write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1548            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
1549                // Newtype variant
1550                write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1551                write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1552                self.serialize_value(fields[0].1)?;
1553                write!(
1554                    self.writer,
1555                    "</{}>",
1556                    escape_element_name(prefixed_element_name)
1557                )
1558                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1559            } else {
1560                write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1561                write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1562                for (field_item, field_peek) in fields {
1563                    self.serialize_element(&field_item.name, field_peek)?;
1564                }
1565                write!(
1566                    self.writer,
1567                    "</{}>",
1568                    escape_element_name(prefixed_element_name)
1569                )
1570                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1571            }
1572        } else if let Some(tag) = tag_attr {
1573            if let Some(content) = content_attr {
1574                // Adjacently tagged
1575                write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1576                write!(
1577                    self.writer,
1578                    " {}=\"{}\">",
1579                    escape_element_name(tag),
1580                    escape_attribute_value(variant_name, self.preserve_entities)
1581                )
1582                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1583
1584                if !fields.is_empty() {
1585                    write!(self.writer, "<{}>", escape_element_name(content))
1586                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1587                    self.serialize_variant_fields(&fields)?;
1588                    write!(self.writer, "</{}>", escape_element_name(content))
1589                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1590                }
1591
1592                write!(
1593                    self.writer,
1594                    "</{}>",
1595                    escape_element_name(prefixed_element_name)
1596                )
1597                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1598            } else {
1599                // Internally tagged
1600                write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1601                write!(
1602                    self.writer,
1603                    " {}=\"{}\">",
1604                    escape_element_name(tag),
1605                    escape_attribute_value(variant_name, self.preserve_entities)
1606                )
1607                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1608                self.serialize_variant_fields(&fields)?;
1609                write!(
1610                    self.writer,
1611                    "</{}>",
1612                    escape_element_name(prefixed_element_name)
1613                )
1614                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1615            }
1616        } else {
1617            // Externally tagged (default)
1618            write_open_tag(&mut self.writer, prefixed_element_name, &xmlns_decl)?;
1619            write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1620
1621            if fields.is_empty() {
1622                write!(self.writer, "<{}/>", escape_element_name(variant_name))
1623                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1624            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
1625                self.serialize_element(variant_name, fields[0].1)?;
1626            } else {
1627                write!(self.writer, "<{}>", escape_element_name(variant_name))
1628                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1629                for (field_item, field_peek) in fields {
1630                    self.serialize_element(&field_item.name, field_peek)?;
1631                }
1632                write!(self.writer, "</{}>", escape_element_name(variant_name))
1633                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1634            }
1635
1636            write!(
1637                self.writer,
1638                "</{}>",
1639                escape_element_name(prefixed_element_name)
1640            )
1641            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1642        }
1643
1644        Ok(())
1645    }
1646
1647    fn serialize_elements_list<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
1648        let list_peek = peek
1649            .into_list()
1650            .map_err(|_| XmlErrorKind::SerializeNotList)?;
1651
1652        for item_peek in list_peek.iter() {
1653            self.write_newline()?;
1654            self.write_indent()?;
1655            self.serialize_list_item_element(item_peek)?;
1656        }
1657
1658        Ok(())
1659    }
1660
1661    fn serialize_list_item_element<'mem, 'facet>(
1662        &mut self,
1663        peek: Peek<'mem, 'facet>,
1664    ) -> Result<()> {
1665        // For enums, use variant name as element name
1666        if let Ok(enum_peek) = peek.into_enum() {
1667            let variant_name = enum_peek
1668                .variant_name_active()
1669                .map_err(|_| XmlErrorKind::SerializeUnknownElementType)?;
1670
1671            // Get the variant's fields (respecting skip_serializing)
1672            let fields: Vec<_> = enum_peek.fields_for_serialize().collect();
1673
1674            if fields.is_empty() {
1675                // Unit variant - empty element
1676                write!(self.writer, "<{}/>", escape_element_name(variant_name))
1677                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1678            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
1679                // Tuple variant with single field - serialize the inner value
1680                self.serialize_element(variant_name, fields[0].1)?;
1681            } else {
1682                // Struct-like variant
1683                write!(self.writer, "<{}>", escape_element_name(variant_name))
1684                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1685
1686                for (field_item, field_peek) in fields {
1687                    self.serialize_element(&field_item.name, field_peek)?;
1688                }
1689
1690                write!(self.writer, "</{}>", escape_element_name(variant_name))
1691                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1692            }
1693            return Ok(());
1694        }
1695
1696        // For structs, use type name as element name
1697        let type_name = peek.shape().type_identifier;
1698        self.serialize_element(type_name, peek)
1699    }
1700
1701    fn serialize_text_value<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
1702        // Handle Option<T>
1703        if let Ok(opt_peek) = peek.into_option() {
1704            if opt_peek.is_none() {
1705                return Ok(());
1706            }
1707            if let Some(inner) = opt_peek.value() {
1708                return self.serialize_text_value(inner);
1709            }
1710            return Ok(());
1711        }
1712
1713        // Handle Spanned<T>
1714        if is_spanned_shape(peek.shape())
1715            && let Ok(struct_peek) = peek.into_struct()
1716            && let Ok(value_peek) = struct_peek.field_by_name("value")
1717        {
1718            return self.serialize_text_value(value_peek);
1719        }
1720
1721        self.serialize_value(peek)
1722    }
1723
1724    fn serialize_value<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
1725        // Handle Option<T>
1726        if let Ok(opt_peek) = peek.into_option() {
1727            if opt_peek.is_none() {
1728                return Ok(());
1729            }
1730            if let Some(inner) = opt_peek.value() {
1731                return self.serialize_value(inner);
1732            }
1733            return Ok(());
1734        }
1735
1736        // Handle Spanned<T>
1737        if is_spanned_shape(peek.shape())
1738            && let Ok(struct_peek) = peek.into_struct()
1739            && let Ok(value_peek) = struct_peek.field_by_name("value")
1740        {
1741            return self.serialize_value(value_peek);
1742        }
1743
1744        // Unwrap transparent wrappers
1745        let peek = peek.innermost_peek();
1746
1747        // Try string first
1748        if let Some(s) = peek.as_str() {
1749            write!(self.writer, "{}", escape_text(s, self.preserve_entities))
1750                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1751            return Ok(());
1752        }
1753
1754        // Try various types
1755        if let Ok(v) = peek.get::<bool>() {
1756            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1757            return Ok(());
1758        }
1759
1760        if let Ok(v) = peek.get::<i8>() {
1761            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1762            return Ok(());
1763        }
1764        if let Ok(v) = peek.get::<i16>() {
1765            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1766            return Ok(());
1767        }
1768        if let Ok(v) = peek.get::<i32>() {
1769            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1770            return Ok(());
1771        }
1772        if let Ok(v) = peek.get::<i64>() {
1773            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1774            return Ok(());
1775        }
1776
1777        if let Ok(v) = peek.get::<u8>() {
1778            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1779            return Ok(());
1780        }
1781        if let Ok(v) = peek.get::<u16>() {
1782            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1783            return Ok(());
1784        }
1785        if let Ok(v) = peek.get::<u32>() {
1786            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1787            return Ok(());
1788        }
1789        if let Ok(v) = peek.get::<u64>() {
1790            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1791            return Ok(());
1792        }
1793
1794        if let Ok(v) = peek.get::<f32>() {
1795            if let Some(fmt) = self.float_formatter {
1796                fmt(f64::from(*v), &mut self.writer)
1797                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1798            } else {
1799                write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1800            }
1801            return Ok(());
1802        }
1803        if let Ok(v) = peek.get::<f64>() {
1804            if let Some(fmt) = self.float_formatter {
1805                fmt(*v, &mut self.writer).map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1806            } else {
1807                write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1808            }
1809            return Ok(());
1810        }
1811
1812        if let Ok(v) = peek.get::<char>() {
1813            write!(
1814                self.writer,
1815                "{}",
1816                escape_text(&v.to_string(), self.preserve_entities)
1817            )
1818            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
1819            return Ok(());
1820        }
1821
1822        Err(XmlErrorKind::SerializeUnknownValueType.into())
1823    }
1824}
1825
1826/// Convert a Peek value to a string representation, handling Options, Spanned, and transparent wrappers.
1827fn value_to_string<'mem, 'facet>(
1828    peek: Peek<'mem, 'facet>,
1829    float_formatter: Option<FloatFormatter>,
1830) -> Option<String> {
1831    // Handle Option<T>
1832    if let Ok(opt_peek) = peek.into_option() {
1833        if opt_peek.is_none() {
1834            return None;
1835        }
1836        if let Some(inner) = opt_peek.value() {
1837            return value_to_string(inner, float_formatter);
1838        }
1839        return None;
1840    }
1841
1842    // Handle Spanned<T>
1843    if is_spanned_shape(peek.shape())
1844        && let Ok(struct_peek) = peek.into_struct()
1845        && let Ok(value_peek) = struct_peek.field_by_name("value")
1846    {
1847        return value_to_string(value_peek, float_formatter);
1848    }
1849
1850    // Unwrap transparent wrappers
1851    let peek = peek.innermost_peek();
1852
1853    // Try string first
1854    if let Some(s) = peek.as_str() {
1855        return Some(s.to_string());
1856    }
1857
1858    // Try various types
1859    if let Ok(v) = peek.get::<bool>() {
1860        return Some(v.to_string());
1861    }
1862
1863    if let Ok(v) = peek.get::<i8>() {
1864        return Some(v.to_string());
1865    }
1866    if let Ok(v) = peek.get::<i16>() {
1867        return Some(v.to_string());
1868    }
1869    if let Ok(v) = peek.get::<i32>() {
1870        return Some(v.to_string());
1871    }
1872    if let Ok(v) = peek.get::<i64>() {
1873        return Some(v.to_string());
1874    }
1875
1876    if let Ok(v) = peek.get::<u8>() {
1877        return Some(v.to_string());
1878    }
1879    if let Ok(v) = peek.get::<u16>() {
1880        return Some(v.to_string());
1881    }
1882    if let Ok(v) = peek.get::<u32>() {
1883        return Some(v.to_string());
1884    }
1885    if let Ok(v) = peek.get::<u64>() {
1886        return Some(v.to_string());
1887    }
1888
1889    if let Ok(v) = peek.get::<f32>() {
1890        if let Some(fmt) = float_formatter {
1891            let mut buf = Vec::new();
1892            if fmt(f64::from(*v), &mut buf).is_ok() {
1893                return String::from_utf8(buf).ok();
1894            }
1895        }
1896        return Some(v.to_string());
1897    }
1898    if let Ok(v) = peek.get::<f64>() {
1899        if let Some(fmt) = float_formatter {
1900            let mut buf = Vec::new();
1901            if fmt(*v, &mut buf).is_ok() {
1902                return String::from_utf8(buf).ok();
1903            }
1904        }
1905        return Some(v.to_string());
1906    }
1907
1908    if let Ok(v) = peek.get::<char>() {
1909        return Some(v.to_string());
1910    }
1911
1912    None
1913}
1914
1915/// Escape special characters in XML text content.
1916fn escape_text(s: &str, preserve_entities: bool) -> String {
1917    if preserve_entities {
1918        escape_preserving_entities(s, false)
1919    } else {
1920        let mut result = String::with_capacity(s.len());
1921        for c in s.chars() {
1922            match c {
1923                '<' => result.push_str("&lt;"),
1924                '>' => result.push_str("&gt;"),
1925                '&' => result.push_str("&amp;"),
1926                _ => result.push(c),
1927            }
1928        }
1929        result
1930    }
1931}
1932
1933/// Escape special characters in XML attribute values.
1934fn escape_attribute_value(s: &str, preserve_entities: bool) -> String {
1935    if preserve_entities {
1936        escape_preserving_entities(s, true)
1937    } else {
1938        let mut result = String::with_capacity(s.len());
1939        for c in s.chars() {
1940            match c {
1941                '<' => result.push_str("&lt;"),
1942                '>' => result.push_str("&gt;"),
1943                '&' => result.push_str("&amp;"),
1944                '"' => result.push_str("&quot;"),
1945                '\'' => result.push_str("&apos;"),
1946                _ => result.push(c),
1947            }
1948        }
1949        result
1950    }
1951}
1952
1953/// Escape special characters while preserving entity references.
1954///
1955/// Recognizes entity reference patterns:
1956/// - Named entities: `&name;` (alphanumeric name)
1957/// - Decimal numeric entities: `&#digits;`
1958/// - Hexadecimal numeric entities: `&#xhex;` or `&#Xhex;`
1959fn escape_preserving_entities(s: &str, is_attribute: bool) -> String {
1960    let mut result = String::with_capacity(s.len());
1961    let chars: Vec<char> = s.chars().collect();
1962    let mut i = 0;
1963
1964    while i < chars.len() {
1965        let c = chars[i];
1966        match c {
1967            '<' => result.push_str("&lt;"),
1968            '>' => result.push_str("&gt;"),
1969            '"' if is_attribute => result.push_str("&quot;"),
1970            '\'' if is_attribute => result.push_str("&apos;"),
1971            '&' => {
1972                // Check if this is the start of an entity reference
1973                if let Some(entity_len) = try_parse_entity_reference(&chars[i..]) {
1974                    // It's a valid entity reference - copy it as-is
1975                    for j in 0..entity_len {
1976                        result.push(chars[i + j]);
1977                    }
1978                    i += entity_len;
1979                    continue;
1980                } else {
1981                    // Not a valid entity reference - escape the ampersand
1982                    result.push_str("&amp;");
1983                }
1984            }
1985            _ => result.push(c),
1986        }
1987        i += 1;
1988    }
1989
1990    result
1991}
1992
1993/// Try to parse an entity reference starting at the given position.
1994/// Returns the length of the entity reference if valid, or None if not.
1995///
1996/// Valid patterns:
1997/// - `&name;` where name is one or more alphanumeric characters
1998/// - `&#digits;` where digits are decimal digits
1999/// - `&#xhex;` or `&#Xhex;` where hex is hexadecimal digits
2000fn try_parse_entity_reference(chars: &[char]) -> Option<usize> {
2001    if chars.is_empty() || chars[0] != '&' {
2002        return None;
2003    }
2004
2005    // Need at least `&x;` (3 chars minimum)
2006    if chars.len() < 3 {
2007        return None;
2008    }
2009
2010    let mut len = 1; // Start after '&'
2011
2012    if chars[1] == '#' {
2013        // Numeric entity reference
2014        len = 2;
2015
2016        if len < chars.len() && (chars[len] == 'x' || chars[len] == 'X') {
2017            // Hexadecimal: &#xHEX;
2018            len += 1;
2019            let start = len;
2020            while len < chars.len() && chars[len].is_ascii_hexdigit() {
2021                len += 1;
2022            }
2023            // Need at least one hex digit
2024            if len == start {
2025                return None;
2026            }
2027        } else {
2028            // Decimal: &#DIGITS;
2029            let start = len;
2030            while len < chars.len() && chars[len].is_ascii_digit() {
2031                len += 1;
2032            }
2033            // Need at least one digit
2034            if len == start {
2035                return None;
2036            }
2037        }
2038    } else {
2039        // Named entity reference: &NAME;
2040        // Name must start with a letter or underscore, then letters, digits, underscores, hyphens, periods
2041        if !chars[len].is_ascii_alphabetic() && chars[len] != '_' {
2042            return None;
2043        }
2044        len += 1;
2045        while len < chars.len()
2046            && (chars[len].is_ascii_alphanumeric()
2047                || chars[len] == '_'
2048                || chars[len] == '-'
2049                || chars[len] == '.')
2050        {
2051            len += 1;
2052        }
2053    }
2054
2055    // Must end with ';'
2056    if len >= chars.len() || chars[len] != ';' {
2057        return None;
2058    }
2059
2060    Some(len + 1) // Include the semicolon
2061}
2062
2063/// Escape element name (for now, assume valid XML names).
2064fn escape_element_name(name: &str) -> &str {
2065    name
2066}