Skip to main content

libxml_rs/xml/writer/
mod.rs

1//! XML Writer API (§30, §85 Phase 7).
2//!
3//! Streaming XML writer with indentation, encoding, escaping, document lifecycle.
4//!
5//! Provides the `xmlTextWriter*` family of functions that allow constructing
6//! XML documents in a streaming fashion — start/end element, write attributes,
7//! text content, CDATA, comments, processing instructions, and DTD declarations.
8//!
9//! # UPSTREAM-PARITY
10//!
11//! This module mirrors libxml2's `xmlTextWriter` API defined in `xmlwriter.h`.
12//! The writer maintains a state machine that tracks whether we are inside an
13//! element start tag (attributes can be written), inside an attribute value,
14//! inside a CDATA section, etc.
15
16#![allow(
17    missing_docs,
18    non_snake_case,
19    non_camel_case_types,
20    non_upper_case_globals
21)]
22
23use core::ffi::c_void;
24use core::ptr;
25use std::os::raw::{c_char, c_int, c_uint};
26
27use crate::abi::allocator;
28
29use crate::abi::structs::*;
30use crate::abi::types::*;
31use crate::xml::io;
32use crate::xml::tree;
33
34// ═══════════════════════════════════════════════════════════════════════════════
35// Constants
36// ═══════════════════════════════════════════════════════════════════════════════
37
38// ═══════════════════════════════════════════════════════════════════════════════
39// Writer state enumeration
40// ═══════════════════════════════════════════════════════════════════════════════
41
42/// Writer state — tracks what kind of content we are currently inside.
43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44enum WriterState {
45    /// Initial / idle state — no element open.
46    None,
47    /// Inside an element start tag (attributes may be written).
48    Element,
49    /// Inside an attribute value.
50    Attribute,
51    /// Inside a CDATA section.
52    CData,
53    /// Inside a comment.
54    Comment,
55    /// Inside a processing instruction.
56    PI,
57    /// Inside a DTD declaration (bracket not yet written).
58    DTD,
59    /// Inside a DTD declaration after the internal subset bracket.
60    DTDText,
61    /// Inside a DTD element declaration.
62    DTDElem,
63    /// Inside a DTD element declaration after content.
64    DTDElemText,
65    /// Inside a DTD attribute declaration.
66    DTDAttr,
67    /// Inside a DTD attribute declaration after content.
68    DTDAttrText,
69    /// Inside a DTD entity declaration (no content yet).
70    DTDEntity,
71    /// Inside a DTD entity declaration after content.
72    DTDEntityText,
73    /// Inside a DTD notation declaration.
74    #[allow(dead_code)]
75    DTDNotation,
76    /// Writing XML declaration.
77    XMLDecl,
78}
79
80// ═══════════════════════════════════════════════════════════════════════════════
81// XmlTextWriter struct
82// ═══════════════════════════════════════════════════════════════════════════════
83
84/// A streaming XML writer.
85///
86/// Corresponds to `xmlTextWriterPtr` in libxml2.
87///
88/// The writer accumulates output into an internal buffer and flushes to the
89/// underlying output buffer on demand. It maintains a stack of element names
90/// for proper nesting, a state machine for content-type tracking, and optional
91/// indentation.
92#[derive(Debug)]
93pub struct XmlTextWriter {
94    /// The output buffer where serialized XML is written.
95    output: *mut _xmlOutputBuffer,
96    /// Whether indentation is enabled (non-zero = enabled).
97    indent: c_int,
98    /// The string used for one level of indentation.
99    indent_string: Vec<u8>,
100    /// Quote character for attribute/entity values (upstream `qchar`).
101    qchar: u8,
102    /// Indent the next closing tag (upstream `doindent`).
103    doindent: bool,
104    /// Current nesting depth.
105    depth: c_int,
106    /// Stack of element local names (for end-element matching).
107    stack: Vec<Vec<u8>>,
108    /// Output encoding name (e.g. "UTF-8").
109    encoding: Vec<u8>,
110    /// Collected error messages.
111    errors: Vec<String>,
112    /// Current writer state.
113    state: WriterState,
114    /// Optional document reference (used when writing to a document tree).
115    doc: *mut _xmlDoc,
116    /// Whether we are in the "start tag" portion of an element (attributes can be written).
117    in_start_tag: bool,
118    /// The element name stack with full qualified names for proper end-element matching.
119    /// Stores (prefix, localname) pairs.
120    elem_stack: Vec<(Vec<u8>, Vec<u8>)>,
121    /// Whether the current DTD entity declaration is a parameter entity
122    /// (upstream XML_TEXTWRITER_DTD_PENT).
123    entity_pe: bool,
124    /// Whether an output encoder has been installed (xmlTextWriterStartDocument
125    /// with a non-NULL encoding). Once set it persists for the writer's life
126    /// and makes byte-writes report 0 bytes (upstream encoder path).
127    encoder_active: bool,
128    /// Pending namespace declarations for the current start tag (upstream
129    /// xmlTextWriterOutputNSDecl defers them until the tag closes).
130    pending_ns: Vec<(Vec<u8>, Vec<u8>)>,
131    /// Open DTD child declarations (upstream stack entries) contributing to
132    /// the indentation depth.
133    dtd_depth: c_int,
134}
135
136impl XmlTextWriter {
137    /// Create a new XML text writer.
138    ///
139    /// # SAFETY
140    ///
141    /// - `output` must be a valid pointer to a mutable `_xmlOutputBuffer` or NULL.
142    unsafe fn new(output: *mut _xmlOutputBuffer) -> *mut Self {
143        let writer = allocator::xmlMallocZero(size_of::<XmlTextWriter>() as usize) as *mut Self;
144        if writer.is_null() {
145            return ptr::null_mut();
146        }
147        unsafe {
148            (*writer).output = output;
149            (*writer).indent = 0;
150            (*writer).indent_string = b" \0".to_vec();
151            (*writer).qchar = b'"';
152            (*writer).doindent = true;
153            (*writer).depth = 0;
154            (*writer).stack = Vec::new();
155            (*writer).encoding = b"UTF-8\0".to_vec();
156            (*writer).errors = Vec::new();
157            (*writer).state = WriterState::None;
158            (*writer).doc = ptr::null_mut();
159            (*writer).in_start_tag = false;
160            (*writer).elem_stack = Vec::new();
161            (*writer).entity_pe = false;
162            (*writer).encoder_active = false;
163            (*writer).pending_ns = Vec::new();
164            (*writer).dtd_depth = 0;
165        }
166        writer
167    }
168
169    /// Write raw bytes to the output buffer.
170    ///
171    /// # SAFETY
172    ///
173    /// - `data` must point to `len` valid bytes.
174    unsafe fn write_raw(&mut self, data: *const u8, len: c_int) -> c_int {
175        if self.output.is_null() || data.is_null() || len <= 0 {
176            return -1;
177        }
178        let rc = io::output_buffer_write(self.output, len, data as *const c_char);
179        // UPSTREAM-PARITY: with an output encoder installed, xmlOutputBufferWrite
180        // reports 0 bytes for writes below the 256-byte conversion threshold.
181        if self.encoder_active {
182            0
183        } else {
184            rc
185        }
186    }
187
188    /// Write a null-terminated string to the output buffer.
189    unsafe fn write_str(&mut self, s: *const u8) -> c_int {
190        if self.output.is_null() || s.is_null() {
191            return -1;
192        }
193        let rc = io::output_buffer_write_string(self.output, s as *const c_char);
194        if self.encoder_active {
195            0
196        } else {
197            rc
198        }
199    }
200
201    /// Write a byte slice to the output buffer.
202    ///
203    /// NOTE: The slice must NOT borrow from `self` to avoid borrow checker conflicts.
204    unsafe fn write_slice(&mut self, slice: &[u8]) -> c_int {
205        if self.output.is_null() || slice.is_empty() {
206            return -1;
207        }
208        let rc = io::output_buffer_write(
209            self.output,
210            slice.len() as c_int,
211            slice.as_ptr() as *const c_char,
212        );
213        if self.encoder_active {
214            0
215        } else {
216            rc
217        }
218    }
219
220    /// Write a single byte to the output buffer.
221    unsafe fn write_byte(&mut self, b: u8) -> c_int {
222        if self.output.is_null() {
223            return -1;
224        }
225        let rc = io::output_buffer_write_char(self.output, b as c_char);
226        if self.encoder_active {
227            0
228        } else {
229            rc
230        }
231    }
232
233    /// Write indentation (if enabled).
234    ///
235    /// Uses a clone of the indent string to avoid borrow checker conflicts.
236    unsafe fn write_indent(&mut self) -> c_int {
237        if self.indent == 0 {
238            return 0;
239        }
240        // UPSTREAM-PARITY (xmlTextWriterWriteIndent): returns the number of
241        // indent strings written, not the byte count. The stored indent
242        // string is NUL-terminated; the NUL must not reach the output.
243        let indent_str = self.indent_string.clone();
244        let body = if indent_str.last() == Some(&0) {
245            &indent_str[..indent_str.len() - 1]
246        } else {
247            &indent_str[..]
248        };
249        let count = self.depth + self.dtd_depth;
250        for _ in 0..count {
251            self.write_slice(body);
252        }
253        count
254    }
255
256    /// Close any open start tag (writing `>` to transition from attribute-writing
257    /// mode to content-writing mode). Returns `(closed, bytes)` — whether a tag
258    /// was actually closed, and the byte count contributed (encoder-muted).
259    /// No newline: the NAME->TEXT transition only emits `>` (the newline after
260    /// the first child comes from the child-start paths, matching
261    /// xmlTextWriterHandleStateDependencies). Pending namespace declarations
262    /// are flushed first (upstream xmlTextWriterOutputNSDecl).
263    unsafe fn close_start_tag(&mut self) -> (bool, c_int) {
264        if self.in_start_tag {
265            self.in_start_tag = false;
266            let mut sum: c_int = self.flush_pending_ns();
267            sum += self.write_byte(b'>');
268            (true, sum)
269        } else {
270            (false, 0)
271        }
272    }
273
274    /// Write the pending namespace declarations (upstream
275    /// xmlTextWriterOutputNSDecl): ` xmlns:prefix="uri"` / ` xmlns="uri"`.
276    unsafe fn flush_pending_ns(&mut self) -> c_int {
277        let mut sum: c_int = 0;
278        let pending = core::mem::take(&mut self.pending_ns);
279        for (prefix, uri) in pending {
280            sum += self.write_byte(b' ');
281            if prefix.is_empty() {
282                sum += self.write_slice(b"xmlns=\"");
283            } else {
284                sum += self.write_slice(b"xmlns:");
285                sum += self.write_slice(&prefix);
286                sum += self.write_slice(b"=\"");
287            }
288            sum += self.write_slice(&uri);
289            sum += self.write_byte(b'"');
290        }
291        sum
292    }
293
294    /// Check if the writer is in a state where element/attribute content can be written.
295    #[allow(dead_code)]
296    const fn can_write_content(&self) -> bool {
297        matches!(
298            self.state,
299            WriterState::None
300                | WriterState::Element
301                | WriterState::Attribute
302                | WriterState::CData
303                | WriterState::Comment
304                | WriterState::PI
305                | WriterState::DTD
306                | WriterState::DTDElem
307                | WriterState::DTDAttr
308                | WriterState::DTDEntity
309                | WriterState::DTDNotation
310                | WriterState::XMLDecl
311        )
312    }
313}
314
315// ═══════════════════════════════════════════════════════════════════════════════
316// Free / destructor
317// ═══════════════════════════════════════════════════════════════════════════════
318
319/// Free an XML text writer.
320///
321/// # UPSTREAM-PARITY
322///
323/// ```c
324/// void xmlFreeTextWriter(xmlTextWriterPtr writer);
325/// ```
326///
327/// # SAFETY
328///
329/// - `writer` must be a valid pointer returned by `xmlNewTextWriter*` or NULL.
330#[no_mangle]
331pub unsafe extern "C" fn xmlFreeTextWriter(writer: *mut XmlTextWriter) {
332    if writer.is_null() {
333        return;
334    }
335    // SAFETY: writer is a valid XmlTextWriter allocated by us.
336    // Flush any pending data
337    if !(*writer).output.is_null() {
338        io::output_buffer_flush((*writer).output);
339    }
340    // Drop Rust-side allocations
341    unsafe {
342        ptr::drop_in_place(&mut (*writer).indent_string);
343        ptr::drop_in_place(&mut (*writer).stack);
344        ptr::drop_in_place(&mut (*writer).encoding);
345        ptr::drop_in_place(&mut (*writer).errors);
346        ptr::drop_in_place(&mut (*writer).elem_stack);
347    }
348    // Free the struct itself
349    unsafe { allocator::xmlFreeImpl(writer as *mut c_void) };
350}
351
352// ═══════════════════════════════════════════════════════════════════════════════
353// Writer creation
354// ═══════════════════════════════════════════════════════════════════════════════
355
356/// Create a new XML text writer from an output buffer.
357///
358/// # UPSTREAM-PARITY
359///
360/// ```c
361/// xmlTextWriterPtr xmlNewTextWriter(xmlOutputBufferPtr out);
362/// ```
363///
364/// # SAFETY
365///
366/// - `out` must be a valid pointer to an `_xmlOutputBuffer` or NULL.
367#[no_mangle]
368pub unsafe extern "C" fn xmlNewTextWriter(out: *mut _xmlOutputBuffer) -> *mut XmlTextWriter {
369    if out.is_null() {
370        return ptr::null_mut();
371    }
372    // SAFETY: out is a valid output buffer.
373    XmlTextWriter::new(out)
374}
375
376/// Create a new XML text writer for a file.
377///
378/// # UPSTREAM-PARITY
379///
380/// ```c
381/// xmlTextWriterPtr xmlNewTextWriterFilename(const char *uri, int compression);
382/// ```
383///
384/// # SAFETY
385///
386/// - `uri` must be a valid null-terminated string or NULL.
387#[no_mangle]
388pub unsafe extern "C" fn xmlNewTextWriterFilename(
389    uri: *const c_char,
390    compression: c_int,
391) -> *mut XmlTextWriter {
392    if uri.is_null() {
393        return ptr::null_mut();
394    }
395    // SAFETY: uri is a valid C string.
396    let out = io::output_buffer_create_filename(uri, ptr::null_mut(), compression);
397    if out.is_null() {
398        return ptr::null_mut();
399    }
400    XmlTextWriter::new(out)
401}
402
403/// Create a new XML text writer for a memory buffer.
404///
405/// # UPSTREAM-PARITY
406///
407/// ```c
408/// xmlTextWriterPtr xmlNewTextWriterMemory(xmlBufferPtr buf, int compression);
409/// ```
410///
411/// # SAFETY
412///
413/// - `buf` must be a valid pointer to an `_xmlBuffer` or NULL.
414#[no_mangle]
415pub unsafe extern "C" fn xmlNewTextWriterMemory(
416    buf: *mut _xmlBuffer,
417    compression: c_int,
418) -> *mut XmlTextWriter {
419    let _ = compression;
420    if buf.is_null() {
421        return ptr::null_mut();
422    }
423    // SAFETY: buf is a valid xmlBuffer.
424    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
425    if out.is_null() {
426        return ptr::null_mut();
427    }
428    XmlTextWriter::new(out)
429}
430
431/// Create a new XML text writer for a document (tree mode).
432///
433/// # UPSTREAM-PARITY
434///
435/// ```c
436/// xmlTextWriterPtr xmlNewTextWriterDoc(xmlDocPtr *doc, int compression);
437/// ```
438///
439/// # SAFETY
440///
441/// - `doc` must be a valid pointer to a (possibly NULL) xmlDocPtr.
442#[no_mangle]
443pub unsafe extern "C" fn xmlNewTextWriterDoc(
444    doc: *mut *mut _xmlDoc,
445    compression: c_int,
446) -> *mut XmlTextWriter {
447    let _ = compression;
448    if doc.is_null() {
449        return ptr::null_mut();
450    }
451    // Create a new document
452    // SAFETY: doc is a valid pointer to an xmlDocPtr.
453    let new_doc = tree::new_doc(b"1.0\0" as *const u8);
454    if new_doc.is_null() {
455        return ptr::null_mut();
456    }
457    unsafe { *doc = new_doc };
458
459    // Create a memory buffer writer
460    let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
461    if buf.is_null() {
462        tree::free_doc(new_doc);
463        return ptr::null_mut();
464    }
465
466    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
467    if out.is_null() {
468        io::buf_free(buf);
469        tree::free_doc(new_doc);
470        return ptr::null_mut();
471    }
472
473    let writer = XmlTextWriter::new(out);
474    if !writer.is_null() {
475        unsafe { (*writer).doc = new_doc };
476    }
477    writer
478}
479
480/// Create a new XML text writer for a subtree.
481///
482/// # UPSTREAM-PARITY
483///
484/// ```c
485/// xmlTextWriterPtr xmlNewTextWriterTree(xmlDocPtr doc, xmlNodePtr node, int compression);
486/// ```
487///
488/// # SAFETY
489///
490/// - `doc` must be a valid pointer to an `_xmlDoc` or NULL.
491/// - `node` must be a valid pointer to an `_xmlNode` or NULL.
492#[no_mangle]
493pub unsafe extern "C" fn xmlNewTextWriterTree(
494    doc: *mut _xmlDoc,
495    node: *mut _xmlNode,
496    compression: c_int,
497) -> *mut XmlTextWriter {
498    let _ = compression;
499    let _ = node; // node is kept for future use when we write tree content directly
500    if doc.is_null() {
501        return ptr::null_mut();
502    }
503
504    let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
505    if buf.is_null() {
506        return ptr::null_mut();
507    }
508
509    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
510    if out.is_null() {
511        io::buf_free(buf);
512        return ptr::null_mut();
513    }
514
515    let writer = XmlTextWriter::new(out);
516    if !writer.is_null() {
517        unsafe { (*writer).doc = doc };
518    }
519    writer
520}
521
522// ═══════════════════════════════════════════════════════════════════════════════
523// Document lifecycle
524// ═══════════════════════════════════════════════════════════════════════════════
525
526/// Start an XML document.
527///
528/// Writes the XML declaration `<?xml version="..." encoding="..." standalone="..."?>`.
529///
530/// # UPSTREAM-PARITY
531///
532/// ```c
533/// int xmlTextWriterStartDocument(xmlTextWriterPtr writer,
534///                                 const char *version,
535///                                 const char *encoding,
536///                                 const char *standalone);
537/// ```
538///
539/// # SAFETY
540///
541/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
542/// - `version`, `encoding`, `standalone` must be valid null-terminated strings or NULL.
543#[no_mangle]
544pub unsafe extern "C" fn xmlTextWriterStartDocument(
545    writer: *mut XmlTextWriter,
546    version: *const c_char,
547    encoding: *const c_char,
548    standalone: *const c_char,
549) -> c_int {
550    if writer.is_null() {
551        return -1;
552    }
553    // SAFETY: writer is a valid XmlTextWriter.
554    let w = unsafe { &mut *writer };
555
556    // UPSTREAM-PARITY (xmlTextWriterStartDocument): the declaration uses the
557    // writer's quote char and always ends with a newline (indent-independent).
558    let mut sum: c_int = 0;
559    sum += w.write_raw(b"<?xml version=" as *const u8, 14);
560    sum += w.write_byte(w.qchar);
561
562    let ver = if version.is_null() {
563        b"1.0\0" as *const u8
564    } else {
565        version as *const u8
566    };
567    sum += w.write_str(ver);
568    sum += w.write_byte(w.qchar);
569
570    if !encoding.is_null() {
571        sum += w.write_raw(b" encoding=" as *const u8, 10);
572        sum += w.write_byte(w.qchar);
573        sum += w.write_str(encoding as *const u8);
574        sum += w.write_byte(w.qchar);
575        // UPSTREAM-PARITY: the output encoder, once installed, persists for
576        // the writer's lifetime (a later StartDocument with encoding=NULL does
577        // NOT clear it — xmlTextWriterStartDocument only resets conv).
578        w.encoder_active = true;
579    }
580
581    if !standalone.is_null() {
582        sum += w.write_raw(b" standalone=" as *const u8, 12);
583        sum += w.write_byte(w.qchar);
584        sum += w.write_str(standalone as *const u8);
585        sum += w.write_byte(w.qchar);
586    }
587
588    sum += w.write_raw(b"?>\n" as *const u8, 3);
589
590    w.state = WriterState::XMLDecl;
591    sum
592}
593
594/// End an XML document.
595///
596/// Flushes any pending output and writes a final newline if indentation is enabled.
597///
598/// # UPSTREAM-PARITY
599///
600/// ```c
601/// int xmlTextWriterEndDocument(xmlTextWriterPtr writer);
602/// ```
603///
604/// # SAFETY
605///
606/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
607#[allow(clippy::while_immutable_condition)]
608#[no_mangle]
609pub unsafe extern "C" fn xmlTextWriterEndDocument(writer: *mut XmlTextWriter) -> c_int {
610    if writer.is_null() {
611        return -1;
612    }
613    // SAFETY: writer is a valid XmlTextWriter.
614    let w = unsafe { &mut *writer };
615
616    // Close any open elements
617    let mut sum: c_int = 0;
618    while w.depth > 0 {
619        sum += xmlTextWriterEndElement(writer);
620    }
621
622    // UPSTREAM-PARITY: the final newline is written when indentation is OFF
623    // (each indented EndElement already wrote its own newline).
624    if w.indent == 0 {
625        sum += w.write_byte(b'\n');
626    }
627
628    // Flush output
629    if !w.output.is_null() {
630        sum += io::output_buffer_flush(w.output);
631    }
632
633    w.state = WriterState::None;
634    sum
635}
636
637// ═══════════════════════════════════════════════════════════════════════════════
638// Element writing
639// ═══════════════════════════════════════════════════════════════════════════════
640
641/// Start an XML element.
642///
643/// # UPSTREAM-PARITY
644///
645/// ```c
646/// int xmlTextWriterStartElement(xmlTextWriterPtr writer, const xmlChar *name);
647/// ```
648///
649/// # SAFETY
650///
651/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
652/// - `name` must be a valid null-terminated xmlChar string or NULL.
653#[no_mangle]
654pub unsafe extern "C" fn xmlTextWriterStartElement(
655    writer: *mut XmlTextWriter,
656    name: *const xmlChar,
657) -> c_int {
658    if writer.is_null() || name.is_null() {
659        return -1;
660    }
661    // SAFETY: writer is a valid XmlTextWriter.
662    let w = unsafe { &mut *writer };
663
664    // Close any open start tag from a previous element.
665    // UPSTREAM-PARITY: closing the parent's start tag emits `>` and, when
666    // indented, a newline (xmlTextWriterStartElement NAME case).
667    let (closed, cnt) = w.close_start_tag();
668    let mut sum: c_int = cnt;
669    if closed && w.indent != 0 {
670        sum += w.write_byte(b'\n');
671    }
672
673    // Write indentation
674    sum += w.write_indent();
675
676    // Write `<name`
677    sum += w.write_byte(b'<');
678    sum += w.write_str(name);
679
680    // Push onto stack (without null terminator)
681    let name_bytes = unsafe { c_str_to_vec(name) };
682    w.elem_stack.push((b"".to_vec(), name_bytes.clone()));
683    // Strip trailing null for stack storage
684    let stack_name = if name_bytes.last() == Some(&0) {
685        name_bytes[..name_bytes.len() - 1].to_vec()
686    } else {
687        name_bytes.clone()
688    };
689    w.stack.push(stack_name);
690    w.depth += 1;
691    w.in_start_tag = true;
692    w.state = WriterState::Element;
693
694    sum
695}
696
697/// End an XML element.
698///
699/// # UPSTREAM-PARITY
700///
701/// ```c
702/// int xmlTextWriterEndElement(xmlTextWriterPtr writer);
703/// ```
704///
705/// # SAFETY
706///
707/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
708#[no_mangle]
709pub unsafe extern "C" fn xmlTextWriterEndElement(writer: *mut XmlTextWriter) -> c_int {
710    if writer.is_null() {
711        return -1;
712    }
713    // SAFETY: writer is a valid XmlTextWriter.
714    let w = unsafe { &mut *writer };
715
716    if w.depth <= 0 {
717        return -1;
718    }
719
720    // UPSTREAM-PARITY (xmlTextWriterEndElement):
721    //   NAME state (start tag still open) -> "/>", doindent=1
722    //   otherwise (content written)       -> indent if doindent, "</name>"
723    //   then, when indented, a trailing newline.
724    let mut sum: c_int = 0;
725    if w.in_start_tag {
726        sum += w.flush_pending_ns();
727        sum += w.write_raw(b"/>" as *const u8, 2);
728        w.in_start_tag = false;
729        w.doindent = true;
730        w.stack.pop();
731    } else {
732        if w.indent != 0 && w.doindent {
733            sum += w.write_indent();
734            w.doindent = true;
735        } else {
736            w.doindent = true;
737        }
738        let name = w.stack.pop().unwrap_or_default();
739        sum += w.write_raw(b"</" as *const u8, 2);
740        sum += w.write_slice(&name);
741        sum += w.write_byte(b'>');
742    }
743
744    if w.indent != 0 {
745        sum += w.write_byte(b'\n');
746    }
747
748    w.depth -= 1;
749    w.elem_stack.pop();
750    w.state = WriterState::None;
751
752    sum
753}
754
755/// Start a namespaced XML element.
756///
757/// # UPSTREAM-PARITY
758///
759/// ```c
760/// int xmlTextWriterStartElementNS(xmlTextWriterPtr writer,
761///                                  const xmlChar *prefix,
762///                                  const xmlChar *name,
763///                                  const xmlChar *namespaceURI);
764/// ```
765///
766/// # SAFETY
767///
768/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
769/// - `prefix`, `name`, `namespaceURI` must be valid null-terminated strings or NULL.
770#[no_mangle]
771pub unsafe extern "C" fn xmlTextWriterStartElementNS(
772    writer: *mut XmlTextWriter,
773    prefix: *const xmlChar,
774    name: *const xmlChar,
775    namespaceURI: *const xmlChar,
776) -> c_int {
777    if writer.is_null() || name.is_null() {
778        return -1;
779    }
780    // SAFETY: writer is a valid XmlTextWriter.
781    let w = unsafe { &mut *writer };
782
783    // UPSTREAM-PARITY: closing the parent's start tag emits `>` and, when
784    // indented, a newline.
785    let (closed, cnt) = w.close_start_tag();
786    let mut sum: c_int = cnt;
787    if closed && w.indent != 0 {
788        sum += w.write_byte(b'\n');
789    }
790    sum += w.write_indent();
791
792    sum += w.write_byte(b'<');
793
794    let prefix_bytes = if prefix.is_null() {
795        Vec::new()
796    } else {
797        unsafe { c_str_to_vec(prefix) }
798    };
799
800    let name_bytes = unsafe { c_str_to_vec(name) };
801
802    if !prefix_bytes.is_empty() {
803        // Strip trailing null before writing
804        let p = if prefix_bytes.last() == Some(&0) {
805            &prefix_bytes[..prefix_bytes.len() - 1]
806        } else {
807            &prefix_bytes
808        };
809        sum += w.write_slice(p);
810        sum += w.write_byte(b':');
811    }
812    // Strip trailing null before writing
813    let n = if name_bytes.last() == Some(&0) {
814        &name_bytes[..name_bytes.len() - 1]
815    } else {
816        &name_bytes
817    };
818    sum += w.write_slice(n);
819
820    // Defer the namespace declaration until the tag closes (upstream
821    // xmlTextWriterOutputNSDecl writes it after the attributes).
822    if !namespaceURI.is_null() {
823        let ns_uri_bytes = unsafe { c_str_to_vec(namespaceURI) };
824        let uri_body = if ns_uri_bytes.last() == Some(&0) {
825            ns_uri_bytes[..ns_uri_bytes.len() - 1].to_vec()
826        } else {
827            ns_uri_bytes
828        };
829        let prefix_body = if prefix_bytes.last() == Some(&0) {
830            prefix_bytes[..prefix_bytes.len() - 1].to_vec()
831        } else {
832            prefix_bytes.clone()
833        };
834        w.pending_ns.push((prefix_body, uri_body));
835    }
836
837    w.elem_stack.push((prefix_bytes, name_bytes.clone()));
838    // Strip trailing null for stack storage
839    let stack_name = if name_bytes.last() == Some(&0) {
840        name_bytes[..name_bytes.len() - 1].to_vec()
841    } else {
842        name_bytes
843    };
844    w.stack.push(stack_name);
845    w.depth += 1;
846    w.in_start_tag = true;
847    w.state = WriterState::Element;
848
849    sum
850}
851
852/// Write an element with inline content.
853///
854/// # UPSTREAM-PARITY
855///
856/// ```c
857/// int xmlTextWriterWriteElement(xmlTextWriterPtr writer,
858///                                const xmlChar *name,
859///                                const xmlChar *content);
860/// ```
861///
862/// # SAFETY
863///
864/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
865/// - `name`, `content` must be valid null-terminated strings or NULL.
866#[no_mangle]
867pub unsafe extern "C" fn xmlTextWriterWriteElement(
868    writer: *mut XmlTextWriter,
869    name: *const xmlChar,
870    content: *const xmlChar,
871) -> c_int {
872    if writer.is_null() || name.is_null() {
873        return -1;
874    }
875    let ret = xmlTextWriterStartElement(writer, name);
876    if ret == -1 {
877        return ret;
878    }
879    if !content.is_null() {
880        let ret2 = xmlTextWriterWriteString(writer, content);
881        if ret2 == -1 {
882            return ret2;
883        }
884    }
885    xmlTextWriterEndElement(writer)
886}
887
888/// Write a namespaced element with inline content.
889///
890/// # UPSTREAM-PARITY
891///
892/// ```c
893/// int xmlTextWriterWriteElementNS(xmlTextWriterPtr writer,
894///                                  const xmlChar *prefix,
895///                                  const xmlChar *name,
896///                                  const xmlChar *nsURI,
897///                                  const xmlChar *content);
898/// ```
899///
900/// # SAFETY
901///
902/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
903/// - `prefix`, `name`, `nsURI`, `content` must be valid null-terminated strings or NULL.
904#[no_mangle]
905pub unsafe extern "C" fn xmlTextWriterWriteElementNS(
906    writer: *mut XmlTextWriter,
907    prefix: *const xmlChar,
908    name: *const xmlChar,
909    nsURI: *const xmlChar,
910    content: *const xmlChar,
911) -> c_int {
912    if writer.is_null() || name.is_null() {
913        return -1;
914    }
915    let ret = xmlTextWriterStartElementNS(writer, prefix, name, nsURI);
916    if ret == -1 {
917        return ret;
918    }
919    if !content.is_null() {
920        let ret2 = xmlTextWriterWriteString(writer, content);
921        if ret2 == -1 {
922            return ret2;
923        }
924    }
925    xmlTextWriterEndElement(writer)
926}
927
928/// Write a full end element (always writes `</name>`, never self-closing).
929///
930/// # UPSTREAM-PARITY
931///
932/// ```c
933/// int xmlTextWriterFullEndElement(xmlTextWriterPtr writer);
934/// ```
935///
936/// # SAFETY
937///
938/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
939#[no_mangle]
940pub unsafe extern "C" fn xmlTextWriterFullEndElement(writer: *mut XmlTextWriter) -> c_int {
941    if writer.is_null() {
942        return -1;
943    }
944    // SAFETY: writer is a valid XmlTextWriter.
945    let w = unsafe { &mut *writer };
946
947    if w.depth <= 0 {
948        return -1;
949    }
950
951    // UPSTREAM-PARITY (xmlTextWriterFullEndElement): always writes `</name>`,
952    // closing the start tag with `>` first if needed.
953    let mut sum: c_int = 0;
954    if w.in_start_tag {
955        sum += w.write_byte(b'>');
956        w.in_start_tag = false;
957    }
958
959    if w.indent != 0 && w.doindent {
960        sum += w.write_indent();
961        w.doindent = true;
962    } else {
963        w.doindent = true;
964    }
965
966    // Write `</name>`
967    let name = w.stack.pop().unwrap_or_default();
968    sum += w.write_raw(b"</" as *const u8, 2);
969    sum += w.write_slice(&name);
970    sum += w.write_byte(b'>');
971
972    if w.indent != 0 {
973        sum += w.write_byte(b'\n');
974    }
975
976    w.depth -= 1;
977    w.elem_stack.pop();
978    w.state = WriterState::None;
979
980    sum
981}
982
983// ═══════════════════════════════════════════════════════════════════════════════
984// Attribute writing
985// ═══════════════════════════════════════════════════════════════════════════════
986
987/// Write an attribute.
988///
989/// # UPSTREAM-PARITY
990///
991/// ```c
992/// int xmlTextWriterWriteAttribute(xmlTextWriterPtr writer,
993///                                  const xmlChar *name,
994///                                  const xmlChar *content);
995/// ```
996///
997/// # SAFETY
998///
999/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1000/// - `name`, `content` must be valid null-terminated strings or NULL.
1001#[no_mangle]
1002pub unsafe extern "C" fn xmlTextWriterWriteAttribute(
1003    writer: *mut XmlTextWriter,
1004    name: *const xmlChar,
1005    content: *const xmlChar,
1006) -> c_int {
1007    if writer.is_null() || name.is_null() || content.is_null() {
1008        return -1;
1009    }
1010    // SAFETY: writer is a valid XmlTextWriter.
1011    let w = unsafe { &mut *writer };
1012
1013    if !w.in_start_tag {
1014        return -1;
1015    }
1016
1017    // Write ` name=` and the quote char.
1018    let mut sum: c_int = 0;
1019    sum += w.write_byte(b' ');
1020    sum += w.write_str(name);
1021    sum += w.write_raw(b"=" as *const u8, 1);
1022    sum += w.write_byte(w.qchar);
1023
1024    // Write escaped content (qchar-aware).
1025    sum += unsafe { write_attr_escaped(w, content) };
1026
1027    sum += w.write_byte(w.qchar);
1028    // UPSTREAM-PARITY: a completed attribute returns the writer to the
1029    // element start-tag state (xmlTextWriterEndAttribute -> NAME).
1030    w.state = WriterState::Element;
1031
1032    sum
1033}
1034
1035/// Write a namespaced attribute.
1036///
1037/// # UPSTREAM-PARITY
1038///
1039/// ```c
1040/// int xmlTextWriterWriteAttributeNS(xmlTextWriterPtr writer,
1041///                                    const xmlChar *prefix,
1042///                                    const xmlChar *name,
1043///                                    const xmlChar *nsURI,
1044///                                    const xmlChar *content);
1045/// ```
1046///
1047/// # SAFETY
1048///
1049/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1050/// - `prefix`, `name`, `nsURI`, `content` must be valid null-terminated strings or NULL.
1051#[no_mangle]
1052pub unsafe extern "C" fn xmlTextWriterWriteAttributeNS(
1053    writer: *mut XmlTextWriter,
1054    prefix: *const xmlChar,
1055    name: *const xmlChar,
1056    nsURI: *const xmlChar,
1057    content: *const xmlChar,
1058) -> c_int {
1059    let _ = nsURI;
1060    if writer.is_null() || name.is_null() || content.is_null() {
1061        return -1;
1062    }
1063    // SAFETY: writer is a valid XmlTextWriter.
1064    let w = unsafe { &mut *writer };
1065
1066    if !w.in_start_tag {
1067        return -1;
1068    }
1069
1070    let mut sum: c_int = 0;
1071    sum += w.write_byte(b' ');
1072
1073    if !prefix.is_null() {
1074        sum += w.write_str(prefix);
1075        sum += w.write_byte(b':');
1076    }
1077    sum += w.write_str(name);
1078
1079    sum += w.write_raw(b"=" as *const u8, 1);
1080    sum += w.write_byte(w.qchar);
1081
1082    // Write escaped content (qchar-aware).
1083    sum += unsafe { write_attr_escaped(w, content) };
1084
1085    sum += w.write_byte(w.qchar);
1086    // UPSTREAM-PARITY: a completed attribute returns the writer to the
1087    // element start-tag state.
1088    w.state = WriterState::Element;
1089
1090    sum
1091}
1092
1093/// Write a formatted attribute.
1094///
1095/// # UPSTREAM-PARITY
1096///
1097/// ```c
1098/// int xmlTextWriterWriteFormatAttribute(xmlTextWriterPtr writer,
1099///                                        const xmlChar *name,
1100///                                        ...);
1101/// ```
1102///
1103/// # SAFETY
1104///
1105/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1106/// - `name` must be a valid null-terminated string.
1107#[no_mangle]
1108///
1109/// Start an attribute (to be written incrementally).
1110///
1111/// # UPSTREAM-PARITY
1112///
1113/// ```c
1114/// int xmlTextWriterStartAttribute(xmlTextWriterPtr writer, const xmlChar *name);
1115/// ```
1116///
1117/// # SAFETY
1118///
1119/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1120/// - `name` must be a valid null-terminated string or NULL.
1121pub unsafe extern "C" fn xmlTextWriterStartAttribute(
1122    writer: *mut XmlTextWriter,
1123    name: *const xmlChar,
1124) -> c_int {
1125    if writer.is_null() || name.is_null() {
1126        return -1;
1127    }
1128    // SAFETY: writer is a valid XmlTextWriter.
1129    let w = unsafe { &mut *writer };
1130
1131    if !w.in_start_tag {
1132        return -1;
1133    }
1134
1135    let mut sum: c_int = 0;
1136    sum += w.write_byte(b' ');
1137    sum += w.write_str(name);
1138    sum += w.write_raw(b"=" as *const u8, 1);
1139    sum += w.write_byte(w.qchar);
1140    w.state = WriterState::Attribute;
1141
1142    sum
1143}
1144
1145/// Start a namespaced attribute (to be written incrementally).
1146///
1147/// # UPSTREAM-PARITY
1148///
1149/// ```c
1150/// int xmlTextWriterStartAttributeNS(xmlTextWriterPtr writer,
1151///                                    const xmlChar *prefix,
1152///                                    const xmlChar *name,
1153///                                    const xmlChar *nsURI);
1154/// ```
1155///
1156/// # SAFETY
1157///
1158/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1159/// - `prefix`, `name`, `nsURI` must be valid null-terminated strings or NULL.
1160#[no_mangle]
1161pub unsafe extern "C" fn xmlTextWriterStartAttributeNS(
1162    writer: *mut XmlTextWriter,
1163    prefix: *const xmlChar,
1164    name: *const xmlChar,
1165    nsURI: *const xmlChar,
1166) -> c_int {
1167    let _ = nsURI;
1168    if writer.is_null() || name.is_null() {
1169        return -1;
1170    }
1171    // SAFETY: writer is a valid XmlTextWriter.
1172    let w = unsafe { &mut *writer };
1173
1174    if !w.in_start_tag {
1175        return -1;
1176    }
1177
1178    let mut sum: c_int = 0;
1179    sum += w.write_byte(b' ');
1180    if !prefix.is_null() {
1181        sum += w.write_str(prefix);
1182        sum += w.write_byte(b':');
1183    }
1184    sum += w.write_str(name);
1185    sum += w.write_raw(b"=" as *const u8, 1);
1186    sum += w.write_byte(w.qchar);
1187    w.state = WriterState::Attribute;
1188
1189    sum
1190}
1191
1192/// End an attribute (closes the attribute value quote).
1193///
1194/// # UPSTREAM-PARITY
1195///
1196/// ```c
1197/// int xmlTextWriterEndAttribute(xmlTextWriterPtr writer);
1198/// ```
1199///
1200/// # SAFETY
1201///
1202/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1203#[no_mangle]
1204pub unsafe extern "C" fn xmlTextWriterEndAttribute(writer: *mut XmlTextWriter) -> c_int {
1205    if writer.is_null() {
1206        return -1;
1207    }
1208    // SAFETY: writer is a valid XmlTextWriter.
1209    let w = unsafe { &mut *writer };
1210
1211    if w.state != WriterState::Attribute {
1212        return -1;
1213    }
1214
1215    w.write_byte(w.qchar);
1216    w.state = WriterState::Element;
1217
1218    1
1219}
1220
1221// ═══════════════════════════════════════════════════════════════════════════════
1222// Content writing
1223// ═══════════════════════════════════════════════════════════════════════════════
1224
1225/// Escape text like upstream `xmlEncodeSpecialChars(NULL, content)`:
1226/// `&` `<` `>` `"` `'` are all escaped. Returns a NUL-terminated vector.
1227///
1228/// # SAFETY
1229///
1230/// - `content` must be a valid NUL-terminated string.
1231unsafe fn encode_special_chars(content: *const xmlChar) -> Vec<u8> {
1232    let mut out = Vec::new();
1233    let mut p = content;
1234    unsafe {
1235        while !p.is_null() && *p != 0 {
1236            // UPSTREAM-PARITY (xmlEncodeSpecialChars / xmlEscapeText with
1237            // XML_ESCAPE_QUOT): `&<>"` are escaped; the apostrophe is NOT.
1238            match *p {
1239                b'&' => out.extend_from_slice(b"&amp;"),
1240                b'<' => out.extend_from_slice(b"&lt;"),
1241                b'>' => out.extend_from_slice(b"&gt;"),
1242                b'"' => out.extend_from_slice(b"&quot;"),
1243                c => out.push(c),
1244            }
1245            p = p.add(1);
1246        }
1247    }
1248    out.push(0);
1249    out
1250}
1251
1252/// Serialize attribute content with the writer's quote char, mirroring
1253/// `xmlBufAttrSerializeTxtContent` (xmlsave.c): `\n`/`\r`/`\t` become
1254/// character references, `&<>` always escape, and the quote char is escaped.
1255/// Returns the bytes written.
1256///
1257/// # SAFETY
1258///
1259/// - `content` must be a valid NUL-terminated string.
1260unsafe fn write_attr_escaped(w: &mut XmlTextWriter, content: *const xmlChar) -> c_int {
1261    let mut sum: c_int = 0;
1262    let mut p = content;
1263    unsafe {
1264        while !p.is_null() && *p != 0 {
1265            let c = *p;
1266            // UPSTREAM-PARITY (xmlBufAttrSerializeTxtContent -> xmlSerializeText
1267            // with XML_ESCAPE_ATTR): `\n`/`\r`/`\t` become character
1268            // references, `&<>"` escape; the apostrophe is NEVER escaped
1269            // (the qchar only selects the outer quotes).
1270            sum += match c {
1271                b'\n' => w.write_slice(b"&#10;"),
1272                b'\r' => w.write_slice(b"&#13;"),
1273                b'\t' => w.write_slice(b"&#9;"),
1274                b'&' => w.write_slice(b"&amp;"),
1275                b'<' => w.write_slice(b"&lt;"),
1276                b'>' => w.write_slice(b"&gt;"),
1277                b'"' => w.write_slice(b"&quot;"),
1278                c => w.write_byte(c),
1279            };
1280            p = p.add(1);
1281        }
1282    }
1283    sum
1284}
1285
1286/// Write text content.
1287///
1288/// # UPSTREAM-PARITY
1289///
1290/// ```c
1291/// int xmlTextWriterWriteString(xmlTextWriterPtr writer, const xmlChar *content);
1292/// ```
1293///
1294/// NAME/TEXT states escape via xmlEncodeSpecialChars (quotes included);
1295/// ATTRIBUTE escapes via xmlBufAttrSerializeTxtContent (qchar-aware); all
1296/// other states (CDATA/comment/PI/DTD*) write raw through WriteRaw, which
1297/// performs the DTD state transitions.
1298///
1299/// # SAFETY
1300///
1301/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1302/// - `content` must be a valid null-terminated xmlChar string or NULL.
1303#[no_mangle]
1304pub unsafe extern "C" fn xmlTextWriterWriteString(
1305    writer: *mut XmlTextWriter,
1306    content: *const xmlChar,
1307) -> c_int {
1308    if writer.is_null() || content.is_null() {
1309        return -1;
1310    }
1311    // SAFETY: writer is a valid XmlTextWriter.
1312    let w = unsafe { &mut *writer };
1313
1314    match w.state {
1315        WriterState::Attribute => unsafe { write_attr_escaped(w, content) },
1316        WriterState::Element => {
1317            let esc = unsafe { encode_special_chars(content) };
1318            let (_, cnt) = w.close_start_tag();
1319            let mut sum: c_int = cnt;
1320            if !esc.is_empty() {
1321                sum += w.write_slice(&esc[..esc.len() - 1]);
1322            }
1323            w.doindent = false;
1324            sum
1325        }
1326        WriterState::None if w.depth > 0 => {
1327            // Inside an element after content: upstream TEXT state escapes.
1328            let esc = unsafe { encode_special_chars(content) };
1329            let mut sum: c_int = 0;
1330            if !esc.is_empty() {
1331                sum += w.write_slice(&esc[..esc.len() - 1]);
1332            }
1333            w.doindent = false;
1334            sum
1335        }
1336        _ => {
1337            // Raw path (CDATA/comment/PI/DTD*, and top-level with no stack
1338            // entry — upstream writes raw when no element is open): WriteRaw
1339            // performs the state transitions (DTD bracket, entity quote,
1340            // element/attr separators).
1341            let rc = unsafe { xmlTextWriterWriteRaw(writer, content) };
1342            w.doindent = false;
1343            rc
1344        }
1345    }
1346}
1347
1348/// Write raw content (no XML escaping).
1349///
1350/// # UPSTREAM-PARITY
1351///
1352/// ```c
1353/// int xmlTextWriterWriteRaw(xmlTextWriterPtr writer, const xmlChar *content);
1354/// ```
1355///
1356/// Performs the upstream state-dependent transitions before the content:
1357/// DTD -> " [" (+newline when indented), DTD_ELEM/DTD_ATTL -> " ",
1358/// DTD_ENTY/PENT -> " " + quote char, PI -> " ".
1359///
1360/// # SAFETY
1361///
1362/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1363/// - `content` must be a valid null-terminated xmlChar string or NULL.
1364#[no_mangle]
1365pub unsafe extern "C" fn xmlTextWriterWriteRaw(
1366    writer: *mut XmlTextWriter,
1367    content: *const xmlChar,
1368) -> c_int {
1369    if writer.is_null() || content.is_null() {
1370        return -1;
1371    }
1372    // SAFETY: writer is a valid XmlTextWriter.
1373    let w = unsafe { &mut *writer };
1374
1375    // UPSTREAM-PARITY (xmlTextWriterHandleStateDependencies).
1376    let mut sum: c_int = 0;
1377    match w.state {
1378        WriterState::Element => {
1379            let (_, cnt) = w.close_start_tag();
1380            sum += cnt;
1381        }
1382        WriterState::PI => {
1383            sum += w.write_byte(b' ');
1384        }
1385        WriterState::DTD => {
1386            w.state = WriterState::DTDText;
1387            if w.indent != 0 {
1388                sum += w.write_slice(b" [\n");
1389            } else {
1390                sum += w.write_slice(b" [");
1391            }
1392        }
1393        WriterState::DTDElem => {
1394            sum += w.write_byte(b' ');
1395            w.state = WriterState::DTDElemText;
1396        }
1397        WriterState::DTDAttr => {
1398            sum += w.write_byte(b' ');
1399            w.state = WriterState::DTDAttrText;
1400        }
1401        WriterState::DTDEntity => {
1402            sum += w.write_byte(b' ');
1403            sum += w.write_byte(w.qchar);
1404            w.state = WriterState::DTDEntityText;
1405        }
1406        _ => {}
1407    }
1408
1409    if w.indent != 0 {
1410        w.doindent = false;
1411    }
1412
1413    sum += w.write_str(content);
1414    sum
1415}
1416
1417/// Write raw content with explicit length (no XML escaping).
1418///
1419/// # UPSTREAM-PARITY
1420///
1421/// ```c
1422/// int xmlTextWriterWriteRawLen(xmlTextWriterPtr writer,
1423///                               const xmlChar *content,
1424///                               int len);
1425/// ```
1426///
1427/// # SAFETY
1428///
1429/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1430/// - `content` must point to `len` valid bytes or NULL.
1431#[no_mangle]
1432pub unsafe extern "C" fn xmlTextWriterWriteRawLen(
1433    writer: *mut XmlTextWriter,
1434    content: *const xmlChar,
1435    len: c_int,
1436) -> c_int {
1437    if writer.is_null() || content.is_null() || len < 0 {
1438        return -1;
1439    }
1440    // SAFETY: writer is a valid XmlTextWriter.
1441    let w = unsafe { &mut *writer };
1442
1443    // Same state transitions as WriteRaw, then the len-bounded write.
1444    let mut sum: c_int = 0;
1445    match w.state {
1446        WriterState::Element => {
1447            let (_, cnt) = w.close_start_tag();
1448            sum += cnt;
1449        }
1450        WriterState::PI => {
1451            sum += w.write_byte(b' ');
1452        }
1453        WriterState::DTD => {
1454            w.state = WriterState::DTDText;
1455            if w.indent != 0 {
1456                sum += w.write_slice(b" [\n");
1457            } else {
1458                sum += w.write_slice(b" [");
1459            }
1460        }
1461        WriterState::DTDElem => {
1462            sum += w.write_byte(b' ');
1463            w.state = WriterState::DTDElemText;
1464        }
1465        WriterState::DTDAttr => {
1466            sum += w.write_byte(b' ');
1467            w.state = WriterState::DTDAttrText;
1468        }
1469        WriterState::DTDEntity => {
1470            sum += w.write_byte(b' ');
1471            sum += w.write_byte(w.qchar);
1472            w.state = WriterState::DTDEntityText;
1473        }
1474        _ => {}
1475    }
1476
1477    if w.indent != 0 {
1478        w.doindent = false;
1479    }
1480
1481    if len > 0 {
1482        sum += w.write_raw(content, len);
1483    }
1484    sum
1485}
1486
1487/// Write a formatted string.
1488///
1489/// # UPSTREAM-PARITY
1490///
1491/// ```c
1492/// int xmlTextWriterWriteFormatString(xmlTextWriterPtr writer, const char *fmt, ...);
1493/// ```
1494///
1495/// # SAFETY
1496///
1497/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1498#[no_mangle]
1499///
1500/// Write Base64-encoded data.
1501///
1502/// # UPSTREAM-PARITY
1503///
1504/// ```c
1505/// int xmlTextWriterWriteBase64(xmlTextWriterPtr writer,
1506///                               const char *data,
1507///                               int start,
1508///                               int len);
1509/// ```
1510///
1511/// # SAFETY
1512///
1513/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1514/// - `data` must be a valid pointer to `start + len` bytes or NULL.
1515pub unsafe extern "C" fn xmlTextWriterWriteBase64(
1516    writer: *mut XmlTextWriter,
1517    data: *const c_char,
1518    start: c_int,
1519    len: c_int,
1520) -> c_int {
1521    if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1522        return -1;
1523    }
1524    // SAFETY: writer is a valid XmlTextWriter.
1525    let w = unsafe { &mut *writer };
1526
1527    w.close_start_tag();
1528
1529    // Base64 encode the data
1530    let data_slice =
1531        unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1532    let encoded = base64_encode(data_slice);
1533    w.write_slice(&encoded);
1534
1535    0
1536}
1537
1538/// Write BinHex-encoded data.
1539///
1540/// # UPSTREAM-PARITY
1541///
1542/// ```c
1543/// int xmlTextWriterWriteBinHex(xmlTextWriterPtr writer,
1544///                               const char *data,
1545///                               int start,
1546///                               int len);
1547/// ```
1548///
1549/// # SAFETY
1550///
1551/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1552/// - `data` must be a valid pointer to `start + len` bytes or NULL.
1553#[no_mangle]
1554pub unsafe extern "C" fn xmlTextWriterWriteBinHex(
1555    writer: *mut XmlTextWriter,
1556    data: *const c_char,
1557    start: c_int,
1558    len: c_int,
1559) -> c_int {
1560    if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1561        return -1;
1562    }
1563    // SAFETY: writer is a valid XmlTextWriter.
1564    let w = unsafe { &mut *writer };
1565
1566    w.close_start_tag();
1567
1568    // Hex encode the data
1569    let data_slice =
1570        unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1571    let encoded = hex_encode(data_slice);
1572    w.write_slice(&encoded);
1573
1574    0
1575}
1576
1577/// Write a CDATA section.
1578///
1579/// # UPSTREAM-PARITY
1580///
1581/// ```c
1582/// int xmlTextWriterWriteCDATA(xmlTextWriterPtr writer, const xmlChar *content);
1583/// ```
1584///
1585/// # SAFETY
1586///
1587/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1588/// - `content` must be a valid null-terminated xmlChar string or NULL.
1589#[no_mangle]
1590pub unsafe extern "C" fn xmlTextWriterWriteCDATA(
1591    writer: *mut XmlTextWriter,
1592    content: *const xmlChar,
1593) -> c_int {
1594    let mut sum: c_int = 0;
1595    let ret = unsafe { xmlTextWriterStartCDATA(writer) };
1596    if ret == -1 {
1597        return -1;
1598    }
1599    sum += ret;
1600    if !content.is_null() {
1601        let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
1602        if ret2 == -1 {
1603            return -1;
1604        }
1605        sum += ret2;
1606    }
1607    let ret3 = unsafe { xmlTextWriterEndCDATA(writer) };
1608    if ret3 == -1 {
1609        return -1;
1610    }
1611    sum + ret3
1612}
1613
1614/// Start a CDATA section.
1615///
1616/// # UPSTREAM-PARITY
1617///
1618/// ```c
1619/// int xmlTextWriterStartCDATA(xmlTextWriterPtr writer);
1620/// ```
1621///
1622/// # SAFETY
1623///
1624/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1625#[no_mangle]
1626pub unsafe extern "C" fn xmlTextWriterStartCDATA(writer: *mut XmlTextWriter) -> c_int {
1627    if writer.is_null() {
1628        return -1;
1629    }
1630    // SAFETY: writer is a valid XmlTextWriter.
1631    let w = unsafe { &mut *writer };
1632
1633    // UPSTREAM-PARITY: closing the parent's start tag emits `>` and, when
1634    // indented, a newline; no indentation precedes `<![CDATA[`.
1635    let (closed, cnt) = w.close_start_tag();
1636    let mut sum: c_int = cnt;
1637    if closed && w.indent != 0 {
1638        sum += w.write_byte(b'\n');
1639    }
1640    sum += w.write_slice(b"<![CDATA[");
1641    w.state = WriterState::CData;
1642    sum
1643}
1644
1645/// End a CDATA section.
1646///
1647/// # UPSTREAM-PARITY
1648///
1649/// ```c
1650/// int xmlTextWriterEndCDATA(xmlTextWriterPtr writer);
1651/// ```
1652///
1653/// # SAFETY
1654///
1655/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1656#[no_mangle]
1657pub unsafe extern "C" fn xmlTextWriterEndCDATA(writer: *mut XmlTextWriter) -> c_int {
1658    if writer.is_null() {
1659        return -1;
1660    }
1661    // SAFETY: writer is a valid XmlTextWriter.
1662    let w = unsafe { &mut *writer };
1663    if w.state != WriterState::CData {
1664        return -1;
1665    }
1666    let sum: c_int = w.write_slice(b"]]>");
1667    w.state = WriterState::None;
1668    sum
1669}
1670
1671/// Write a comment.
1672///
1673/// # UPSTREAM-PARITY
1674///
1675/// ```c
1676/// int xmlTextWriterWriteComment(xmlTextWriterPtr writer, const xmlChar *content);
1677/// ```
1678///
1679/// # SAFETY
1680///
1681/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1682/// - `content` must be a valid null-terminated xmlChar string or NULL.
1683#[no_mangle]
1684pub unsafe extern "C" fn xmlTextWriterWriteComment(
1685    writer: *mut XmlTextWriter,
1686    content: *const xmlChar,
1687) -> c_int {
1688    let mut sum: c_int = 0;
1689    let ret = unsafe { xmlTextWriterStartComment(writer) };
1690    if ret < 0 {
1691        return -1;
1692    }
1693    sum += ret;
1694    let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
1695    if ret2 < 0 {
1696        return -1;
1697    }
1698    sum += ret2;
1699    let ret3 = unsafe { xmlTextWriterEndComment(writer) };
1700    if ret3 < 0 {
1701        return -1;
1702    }
1703    sum + ret3
1704}
1705
1706/// Start a comment.
1707///
1708/// # UPSTREAM-PARITY
1709///
1710/// ```c
1711/// int xmlTextWriterStartComment(xmlTextWriterPtr writer);
1712/// ```
1713///
1714/// # SAFETY
1715///
1716/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1717#[no_mangle]
1718pub unsafe extern "C" fn xmlTextWriterStartComment(writer: *mut XmlTextWriter) -> c_int {
1719    if writer.is_null() {
1720        return -1;
1721    }
1722    // SAFETY: writer is a valid XmlTextWriter.
1723    let w = unsafe { &mut *writer };
1724    let (closed, cnt) = w.close_start_tag();
1725    let mut sum: c_int = cnt;
1726    if closed && w.indent != 0 {
1727        sum += w.write_byte(b'\n');
1728    }
1729    sum += w.write_indent();
1730    sum += w.write_slice(b"<!--");
1731    w.state = WriterState::Comment;
1732    sum
1733}
1734
1735/// End a comment.
1736///
1737/// # UPSTREAM-PARITY
1738///
1739/// ```c
1740/// int xmlTextWriterEndComment(xmlTextWriterPtr writer);
1741/// ```
1742///
1743/// # SAFETY
1744///
1745/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1746#[no_mangle]
1747pub unsafe extern "C" fn xmlTextWriterEndComment(writer: *mut XmlTextWriter) -> c_int {
1748    if writer.is_null() {
1749        return -1;
1750    }
1751    // SAFETY: writer is a valid XmlTextWriter.
1752    let w = unsafe { &mut *writer };
1753    if w.state != WriterState::Comment {
1754        return -1;
1755    }
1756    let mut sum: c_int = w.write_slice(b"-->");
1757    if w.indent != 0 {
1758        sum += w.write_byte(b'\n');
1759    }
1760    w.state = WriterState::None;
1761    sum
1762}
1763
1764/// Write a processing instruction.
1765///
1766/// # UPSTREAM-PARITY
1767///
1768/// ```c
1769/// int xmlTextWriterWritePI(xmlTextWriterPtr writer,
1770///                           const xmlChar *target,
1771///                           const xmlChar *content);
1772/// ```
1773///
1774/// # SAFETY
1775///
1776/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1777/// - `target`, `content` must be valid null-terminated strings or NULL.
1778#[no_mangle]
1779pub unsafe extern "C" fn xmlTextWriterWritePI(
1780    writer: *mut XmlTextWriter,
1781    target: *const xmlChar,
1782    content: *const xmlChar,
1783) -> c_int {
1784    let mut sum: c_int = 0;
1785    let ret = unsafe { xmlTextWriterStartPI(writer, target) };
1786    if ret == -1 {
1787        return -1;
1788    }
1789    sum += ret;
1790    if !content.is_null() {
1791        let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
1792        if ret2 == -1 {
1793            return -1;
1794        }
1795        sum += ret2;
1796    }
1797    let ret3 = unsafe { xmlTextWriterEndPI(writer) };
1798    if ret3 == -1 {
1799        return -1;
1800    }
1801    sum + ret3
1802}
1803
1804/// Start a processing instruction.
1805///
1806/// # UPSTREAM-PARITY
1807///
1808/// ```c
1809/// int xmlTextWriterStartPI(xmlTextWriterPtr writer, const xmlChar *target);
1810/// ```
1811///
1812/// # SAFETY
1813///
1814/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1815/// - `target` must be a valid null-terminated string or NULL.
1816#[no_mangle]
1817pub unsafe extern "C" fn xmlTextWriterStartPI(
1818    writer: *mut XmlTextWriter,
1819    target: *const xmlChar,
1820) -> c_int {
1821    if writer.is_null() || target.is_null() || unsafe { *target } == 0 {
1822        return -1;
1823    }
1824    // SAFETY: writer is a valid XmlTextWriter.
1825    let w = unsafe { &mut *writer };
1826    let (closed, cnt) = w.close_start_tag();
1827    let mut sum: c_int = cnt;
1828    if closed && w.indent != 0 {
1829        sum += w.write_byte(b'\n');
1830    }
1831    sum += w.write_slice(b"<?");
1832    sum += w.write_str(target);
1833    // UPSTREAM-PARITY: no trailing space here — the first content write
1834    // emits the separator (xmlTextWriterHandleStateDependencies PI case).
1835    w.state = WriterState::PI;
1836    sum
1837}
1838
1839/// End a processing instruction.
1840///
1841/// # UPSTREAM-PARITY
1842///
1843/// ```c
1844/// int xmlTextWriterEndPI(xmlTextWriterPtr writer);
1845/// ```
1846///
1847/// # SAFETY
1848///
1849/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1850#[no_mangle]
1851pub unsafe extern "C" fn xmlTextWriterEndPI(writer: *mut XmlTextWriter) -> c_int {
1852    if writer.is_null() {
1853        return -1;
1854    }
1855    // SAFETY: writer is a valid XmlTextWriter.
1856    let w = unsafe { &mut *writer };
1857    if w.state != WriterState::PI {
1858        return -1;
1859    }
1860    let mut sum: c_int = w.write_slice(b"?>");
1861    if w.indent != 0 {
1862        sum += w.write_byte(b'\n');
1863    }
1864    w.state = WriterState::None;
1865    sum
1866}
1867
1868// ═══════════════════════════════════════════════════════════════════════════════
1869// DTD writing
1870// ═══════════════════════════════════════════════════════════════════════════════
1871
1872/// Write a DTD declaration.
1873///
1874/// # UPSTREAM-PARITY
1875///
1876/// ```c
1877/// int xmlTextWriterWriteDTD(xmlTextWriterPtr writer,
1878///                            const xmlChar *name,
1879///                            const xmlChar *pubid,
1880///                            const xmlChar *sysid,
1881///                            const xmlChar *subset);
1882/// ```
1883///
1884/// # SAFETY
1885///
1886/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1887/// - `name`, `pubid`, `sysid`, `subset` must be valid null-terminated strings or NULL.
1888#[no_mangle]
1889pub unsafe extern "C" fn xmlTextWriterWriteDTD(
1890    writer: *mut XmlTextWriter,
1891    name: *const xmlChar,
1892    pubid: *const xmlChar,
1893    sysid: *const xmlChar,
1894    subset: *const xmlChar,
1895) -> c_int {
1896    let mut sum: c_int = 0;
1897    let ret = unsafe { xmlTextWriterStartDTD(writer, name, pubid, sysid) };
1898    if ret == -1 {
1899        return ret;
1900    }
1901    sum += ret;
1902    if !subset.is_null() {
1903        let ret2 = unsafe { xmlTextWriterWriteString(writer, subset) };
1904        if ret2 == -1 {
1905            return ret2;
1906        }
1907        sum += ret2;
1908    }
1909    let ret3 = unsafe { xmlTextWriterEndDTD(writer) };
1910    if ret3 == -1 {
1911        return ret3;
1912    }
1913    sum + ret3
1914}
1915
1916/// Write a DTD element declaration.
1917///
1918/// # UPSTREAM-PARITY
1919///
1920/// ```c
1921/// int xmlTextWriterWriteDTDElement(xmlTextWriterPtr writer,
1922///                                   const xmlChar *name,
1923///                                   const xmlChar *content);
1924/// ```
1925///
1926/// # SAFETY
1927///
1928/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1929/// - `name`, `content` must be valid null-terminated strings or NULL.
1930#[no_mangle]
1931pub unsafe extern "C" fn xmlTextWriterWriteDTDElement(
1932    writer: *mut XmlTextWriter,
1933    name: *const xmlChar,
1934    content: *const xmlChar,
1935) -> c_int {
1936    if content.is_null() {
1937        return -1;
1938    }
1939    let mut sum: c_int = 0;
1940    let ret = unsafe { xmlTextWriterStartDTDElement(writer, name) };
1941    if ret == -1 {
1942        return ret;
1943    }
1944    sum += ret;
1945    let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
1946    if ret2 == -1 {
1947        return ret2;
1948    }
1949    sum += ret2;
1950    let ret3 = unsafe { xmlTextWriterEndDTDElement(writer) };
1951    if ret3 == -1 {
1952        return ret3;
1953    }
1954    sum + ret3
1955}
1956
1957/// Write a DTD attribute declaration.
1958///
1959/// # UPSTREAM-PARITY
1960///
1961/// ```c
1962/// int xmlTextWriterWriteDTDAttribute(xmlTextWriterPtr writer,
1963///                                     const xmlChar *name,
1964///                                     const xmlChar *content);
1965/// ```
1966///
1967/// # SAFETY
1968///
1969/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1970/// - `name`, `content` must be valid null-terminated strings or NULL.
1971#[no_mangle]
1972pub unsafe extern "C" fn xmlTextWriterWriteDTDAttribute(
1973    writer: *mut XmlTextWriter,
1974    name: *const xmlChar,
1975    content: *const xmlChar,
1976) -> c_int {
1977    if content.is_null() {
1978        return -1;
1979    }
1980    // UPSTREAM-PARITY: upstream xmlTextWriterWriteDTDAttribute composes
1981    // StartDTDAttlist + WriteString + EndDTDAttlist (there is no separate
1982    // StartDTDAttribute API).
1983    let mut sum: c_int = 0;
1984    let ret = unsafe { xmlTextWriterStartDTDAttlist(writer, name) };
1985    if ret == -1 {
1986        return ret;
1987    }
1988    sum += ret;
1989    let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
1990    if ret2 == -1 {
1991        return ret2;
1992    }
1993    sum += ret2;
1994    let ret3 = unsafe { xmlTextWriterEndDTDAttlist(writer) };
1995    if ret3 == -1 {
1996        return ret3;
1997    }
1998    sum + ret3
1999}
2000
2001/// Write a DTD entity declaration.
2002///
2003/// # UPSTREAM-PARITY
2004///
2005/// ```c
2006/// int xmlTextWriterWriteDTDEntity(xmlTextWriterPtr writer,
2007///                                  const xmlChar *name,
2008///                                  const xmlChar *content);
2009/// ```
2010///
2011/// # SAFETY
2012///
2013/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2014/// - `name`, `content` must be valid null-terminated strings or NULL.
2015#[no_mangle]
2016pub unsafe extern "C" fn xmlTextWriterWriteDTDEntity(
2017    writer: *mut XmlTextWriter,
2018    pe: c_int,
2019    name: *const xmlChar,
2020    pubid: *const xmlChar,
2021    sysid: *const xmlChar,
2022    ndataid: *const xmlChar,
2023    content: *const xmlChar,
2024) -> c_int {
2025    if content.is_null() && pubid.is_null() && sysid.is_null() {
2026        return -1;
2027    }
2028    if pe != 0 && !ndataid.is_null() {
2029        return -1;
2030    }
2031    if pubid.is_null() && sysid.is_null() {
2032        return unsafe { xmlTextWriterWriteDTDInternalEntity(writer, pe, name, content) };
2033    }
2034    unsafe { xmlTextWriterWriteDTDExternalEntity(writer, pe, name, pubid, sysid, ndataid) }
2035}
2036
2037/// Write a DTD internal entity (StartDTDEntity + WriteString + EndDTDEntity).
2038///
2039/// # SAFETY
2040///
2041/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2042/// - `name`, `content` must be valid null-terminated strings or NULL.
2043#[no_mangle]
2044pub unsafe extern "C" fn xmlTextWriterWriteDTDInternalEntity(
2045    writer: *mut XmlTextWriter,
2046    pe: c_int,
2047    name: *const xmlChar,
2048    content: *const xmlChar,
2049) -> c_int {
2050    if name.is_null() || unsafe { *name } == 0 || content.is_null() {
2051        return -1;
2052    }
2053    let mut sum: c_int = 0;
2054    let ret = unsafe { xmlTextWriterStartDTDEntity(writer, pe, name) };
2055    if ret == -1 {
2056        return -1;
2057    }
2058    sum += ret;
2059    let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
2060    if ret2 == -1 {
2061        return -1;
2062    }
2063    sum += ret2;
2064    let ret3 = unsafe { xmlTextWriterEndDTDEntity(writer) };
2065    if ret3 == -1 {
2066        return -1;
2067    }
2068    sum + ret3
2069}
2070
2071/// Write a DTD external entity (StartDTDEntity + ExternalEntityContents + EndDTDEntity).
2072///
2073/// # SAFETY
2074///
2075/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2076/// - `name`, `pubid`, `sysid`, `ndataid` must be valid null-terminated
2077///   strings or NULL.
2078#[no_mangle]
2079pub unsafe extern "C" fn xmlTextWriterWriteDTDExternalEntity(
2080    writer: *mut XmlTextWriter,
2081    pe: c_int,
2082    name: *const xmlChar,
2083    pubid: *const xmlChar,
2084    sysid: *const xmlChar,
2085    ndataid: *const xmlChar,
2086) -> c_int {
2087    if pubid.is_null() && sysid.is_null() {
2088        return -1;
2089    }
2090    if pe != 0 && !ndataid.is_null() {
2091        return -1;
2092    }
2093    let mut sum: c_int = 0;
2094    let ret = unsafe { xmlTextWriterStartDTDEntity(writer, pe, name) };
2095    if ret == -1 {
2096        return -1;
2097    }
2098    sum += ret;
2099    let ret2 =
2100        unsafe { xmlTextWriterWriteDTDExternalEntityContents(writer, pubid, sysid, ndataid) };
2101    if ret2 < 0 {
2102        return -1;
2103    }
2104    sum += ret2;
2105    let ret3 = unsafe { xmlTextWriterEndDTDEntity(writer) };
2106    if ret3 == -1 {
2107        return -1;
2108    }
2109    sum + ret3
2110}
2111
2112/// Write the external-entity contents after `StartDTDEntity` (PUBLIC/SYSTEM
2113/// identifiers and NDATA).
2114///
2115/// # SAFETY
2116///
2117/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2118/// - `pubid`, `sysid`, `ndataid` must be valid null-terminated strings or NULL.
2119#[no_mangle]
2120pub unsafe extern "C" fn xmlTextWriterWriteDTDExternalEntityContents(
2121    writer: *mut XmlTextWriter,
2122    pubid: *const xmlChar,
2123    sysid: *const xmlChar,
2124    ndataid: *const xmlChar,
2125) -> c_int {
2126    if writer.is_null() {
2127        return -1;
2128    }
2129    let w = unsafe { &mut *writer };
2130    // UPSTREAM-PARITY: must be directly inside a StartDTDEntity declaration
2131    // (DTD_ENTY / DTD_PENT; content already written is rejected).
2132    if w.state != WriterState::DTDEntity {
2133        return -1;
2134    }
2135    if w.entity_pe && !ndataid.is_null() {
2136        // UPSTREAM-PARITY: notation not allowed with parameter entities.
2137        return -1;
2138    }
2139    let mut sum: c_int = 0;
2140    if !pubid.is_null() {
2141        if sysid.is_null() {
2142            return -1;
2143        }
2144        sum += w.write_slice(b" PUBLIC ");
2145        sum += w.write_byte(w.qchar);
2146        sum += w.write_str(pubid);
2147        sum += w.write_byte(w.qchar);
2148    }
2149    if !sysid.is_null() {
2150        if pubid.is_null() {
2151            sum += w.write_slice(b" SYSTEM");
2152        }
2153        sum += w.write_byte(b' ');
2154        sum += w.write_byte(w.qchar);
2155        sum += w.write_str(sysid);
2156        sum += w.write_byte(w.qchar);
2157    }
2158    if !ndataid.is_null() {
2159        sum += w.write_slice(b" NDATA ");
2160        sum += w.write_str(ndataid);
2161    }
2162    sum
2163}
2164
2165/// Write a DTD notation declaration.
2166///
2167/// # UPSTREAM-PARITY
2168///
2169/// ```c
2170/// int xmlTextWriterWriteDTDNotation(xmlTextWriterPtr writer,
2171///                                    const xmlChar *name,
2172///                                    const xmlChar *pubid,
2173///                                    const xmlChar *sysid);
2174/// ```
2175///
2176/// # SAFETY
2177///
2178/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2179/// - `name`, `pubid`, `sysid` must be valid null-terminated strings or NULL.
2180#[no_mangle]
2181pub unsafe extern "C" fn xmlTextWriterWriteDTDNotation(
2182    writer: *mut XmlTextWriter,
2183    name: *const xmlChar,
2184    pubid: *const xmlChar,
2185    sysid: *const xmlChar,
2186) -> c_int {
2187    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2188        return -1;
2189    }
2190    let w = unsafe { &mut *writer };
2191    let mut sum: c_int = 0;
2192    if w.state == WriterState::DTD {
2193        // UPSTREAM-PARITY: first DTD child writes the internal-subset bracket.
2194        sum += w.write_slice(b" [");
2195        if w.indent != 0 {
2196            sum += w.write_byte(b'\n');
2197        }
2198        w.state = WriterState::DTDText;
2199    } else if w.state != WriterState::DTDText {
2200        return -1;
2201    }
2202    sum += w.write_indent();
2203    sum += w.write_slice(b"<!NOTATION ");
2204    sum += w.write_str(name);
2205    if !pubid.is_null() {
2206        sum += w.write_slice(b" PUBLIC ");
2207        sum += w.write_byte(w.qchar);
2208        sum += w.write_str(pubid);
2209        sum += w.write_byte(w.qchar);
2210    }
2211    if !sysid.is_null() {
2212        if pubid.is_null() {
2213            sum += w.write_slice(b" SYSTEM");
2214        }
2215        sum += w.write_byte(b' ');
2216        sum += w.write_byte(w.qchar);
2217        sum += w.write_str(sysid);
2218        sum += w.write_byte(w.qchar);
2219    }
2220    sum += w.write_byte(b'>');
2221    sum
2222}
2223
2224// ═══════════════════════════════════════════════════════════════════════════════
2225// Start/End DTD declaration
2226/// Start a DTD declaration.
2227///
2228/// # UPSTREAM-PARITY
2229///
2230/// ```c
2231/// int xmlTextWriterStartDTD(xmlTextWriterPtr writer,
2232///                            const xmlChar *name,
2233///                            const xmlChar *pubid,
2234///                            const xmlChar *sysid);
2235/// ```
2236///
2237/// # SAFETY
2238///
2239/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2240/// - `name`, `pubid`, `sysid` must be valid null-terminated strings or NULL.
2241#[no_mangle]
2242pub unsafe extern "C" fn xmlTextWriterStartDTD(
2243    writer: *mut XmlTextWriter,
2244    name: *const xmlChar,
2245    pubid: *const xmlChar,
2246    sysid: *const xmlChar,
2247) -> c_int {
2248    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2249        return -1;
2250    }
2251    // SAFETY: writer is a valid XmlTextWriter.
2252    let w = unsafe { &mut *writer };
2253    if w.depth > 0 {
2254        // UPSTREAM-PARITY: DTD allowed only in the prolog (no open elements).
2255        return -1;
2256    }
2257
2258    let mut sum: c_int = 0;
2259    sum += w.write_slice(b"<!DOCTYPE ");
2260    sum += w.write_str(name);
2261
2262    if !pubid.is_null() {
2263        if sysid.is_null() {
2264            // UPSTREAM-PARITY: PUBLIC requires a system identifier.
2265            return -1;
2266        }
2267        if w.indent != 0 {
2268            sum += w.write_byte(b'\n');
2269        } else {
2270            sum += w.write_byte(b' ');
2271        }
2272        sum += w.write_slice(b"PUBLIC ");
2273        sum += w.write_byte(w.qchar);
2274        sum += w.write_str(pubid);
2275        sum += w.write_byte(w.qchar);
2276    }
2277    if !sysid.is_null() {
2278        if pubid.is_null() {
2279            if w.indent != 0 {
2280                sum += w.write_byte(b'\n');
2281            } else {
2282                sum += w.write_byte(b' ');
2283            }
2284            sum += w.write_slice(b"SYSTEM ");
2285        } else if w.indent != 0 {
2286            // UPSTREAM-PARITY: continuation line is indented 7 spaces.
2287            sum += w.write_slice(b"\n       ");
2288        } else {
2289            sum += w.write_byte(b' ');
2290        }
2291        sum += w.write_byte(w.qchar);
2292        sum += w.write_str(sysid);
2293        sum += w.write_byte(w.qchar);
2294    }
2295
2296    w.state = WriterState::DTD;
2297    sum
2298}
2299
2300/// End a DTD declaration.
2301///
2302/// # UPSTREAM-PARITY
2303///
2304/// ```c
2305/// int xmlTextWriterEndDTD(xmlTextWriterPtr writer);
2306/// ```
2307///
2308/// # SAFETY
2309///
2310/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2311#[no_mangle]
2312pub unsafe extern "C" fn xmlTextWriterEndDTD(writer: *mut XmlTextWriter) -> c_int {
2313    if writer.is_null() {
2314        return -1;
2315    }
2316    // SAFETY: writer is a valid XmlTextWriter.
2317    let w = unsafe { &mut *writer };
2318
2319    if w.state != WriterState::DTD && w.state != WriterState::DTDText {
2320        return -1;
2321    }
2322    let mut sum: c_int = 0;
2323    if w.state == WriterState::DTDText {
2324        sum += w.write_byte(b']');
2325    }
2326    sum += w.write_byte(b'>');
2327    if w.indent != 0 {
2328        sum += w.write_byte(b'\n');
2329    }
2330    w.state = WriterState::None;
2331    sum
2332}
2333
2334/// Internal: the ` [` (+ newline when indented) transition from the DTD state
2335/// used by all DTD child starts. Returns false when the state is not usable.
2336unsafe fn dtd_child_transition(w: &mut XmlTextWriter) -> bool {
2337    match w.state {
2338        WriterState::DTD => {
2339            w.write_slice(b" [");
2340            if w.indent != 0 {
2341                w.write_byte(b'\n');
2342            }
2343            w.state = WriterState::DTDText;
2344            true
2345        }
2346        WriterState::DTDText => true,
2347        _ => false,
2348    }
2349}
2350
2351/// Start a DTD element declaration.
2352///
2353/// # UPSTREAM-PARITY
2354///
2355/// ```c
2356/// int xmlTextWriterStartDTDElement(xmlTextWriterPtr writer, const xmlChar *name);
2357/// ```
2358///
2359/// # SAFETY
2360///
2361/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2362/// - `name` must be a valid null-terminated string or NULL.
2363#[no_mangle]
2364pub unsafe extern "C" fn xmlTextWriterStartDTDElement(
2365    writer: *mut XmlTextWriter,
2366    name: *const xmlChar,
2367) -> c_int {
2368    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2369        return -1;
2370    }
2371    // SAFETY: writer is a valid XmlTextWriter.
2372    let w = unsafe { &mut *writer };
2373    if !unsafe { dtd_child_transition(w) } {
2374        return -1;
2375    }
2376    w.dtd_depth += 1;
2377    let mut sum: c_int = 0;
2378    sum += w.write_indent();
2379    sum += w.write_slice(b"<!ELEMENT ");
2380    sum += w.write_str(name);
2381    w.state = WriterState::DTDElem;
2382    sum
2383}
2384
2385/// End a DTD element declaration.
2386///
2387/// # UPSTREAM-PARITY
2388///
2389/// ```c
2390/// int xmlTextWriterEndDTDElement(xmlTextWriterPtr writer);
2391/// ```
2392///
2393/// # SAFETY
2394///
2395/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2396#[no_mangle]
2397pub unsafe extern "C" fn xmlTextWriterEndDTDElement(writer: *mut XmlTextWriter) -> c_int {
2398    if writer.is_null() {
2399        return -1;
2400    }
2401    // SAFETY: writer is a valid XmlTextWriter.
2402    let w = unsafe { &mut *writer };
2403    if w.state != WriterState::DTDElem && w.state != WriterState::DTDElemText {
2404        return -1;
2405    }
2406    let mut sum: c_int = w.write_byte(b'>');
2407    if w.indent != 0 {
2408        sum += w.write_byte(b'\n');
2409    }
2410    w.state = WriterState::DTDText;
2411    w.dtd_depth -= 1;
2412    sum
2413}
2414
2415/// Start a DTD attribute declaration.
2416///
2417/// # UPSTREAM-PARITY
2418///
2419/// ```c
2420/// int xmlTextWriterStartDTDAttribute(xmlTextWriterPtr writer, const xmlChar *name);
2421/// ```
2422///
2423/// # SAFETY
2424///
2425/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2426/// - `name` must be a valid null-terminated string or NULL.
2427#[no_mangle]
2428pub unsafe extern "C" fn xmlTextWriterStartDTDAttribute(
2429    writer: *mut XmlTextWriter,
2430    name: *const xmlChar,
2431) -> c_int {
2432    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2433        return -1;
2434    }
2435    // SAFETY: writer is a valid XmlTextWriter.
2436    let w = unsafe { &mut *writer };
2437    if !unsafe { dtd_child_transition(w) } {
2438        return -1;
2439    }
2440    w.dtd_depth += 1;
2441    let mut sum: c_int = 0;
2442    sum += w.write_indent();
2443    sum += w.write_slice(b"<!ATTLIST ");
2444    sum += w.write_str(name);
2445    w.state = WriterState::DTDAttr;
2446    sum
2447}
2448
2449/// End a DTD attribute declaration.
2450///
2451/// # UPSTREAM-PARITY
2452///
2453/// ```c
2454/// int xmlTextWriterEndDTDAttribute(xmlTextWriterPtr writer);
2455/// ```
2456///
2457/// # SAFETY
2458///
2459/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2460#[no_mangle]
2461pub unsafe extern "C" fn xmlTextWriterEndDTDAttribute(writer: *mut XmlTextWriter) -> c_int {
2462    if writer.is_null() {
2463        return -1;
2464    }
2465    // SAFETY: writer is a valid XmlTextWriter.
2466    let w = unsafe { &mut *writer };
2467    if w.state != WriterState::DTDAttr && w.state != WriterState::DTDAttrText {
2468        return -1;
2469    }
2470    let mut sum: c_int = w.write_byte(b'>');
2471    if w.indent != 0 {
2472        sum += w.write_byte(b'\n');
2473    }
2474    w.state = WriterState::DTDText;
2475    w.dtd_depth -= 1;
2476    sum
2477}
2478
2479/// Start a DTD entity declaration.
2480///
2481/// # UPSTREAM-PARITY
2482///
2483/// ```c
2484/// int xmlTextWriterStartDTDEntity(xmlTextWriterPtr writer, const xmlChar *name);
2485/// ```
2486///
2487/// # SAFETY
2488///
2489/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2490/// - `name` must be a valid null-terminated string or NULL.
2491#[no_mangle]
2492pub unsafe extern "C" fn xmlTextWriterStartDTDEntity(
2493    writer: *mut XmlTextWriter,
2494    pe: c_int,
2495    name: *const xmlChar,
2496) -> c_int {
2497    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2498        return -1;
2499    }
2500    // SAFETY: writer is a valid XmlTextWriter.
2501    let w = unsafe { &mut *writer };
2502    if !unsafe { dtd_child_transition(w) } {
2503        return -1;
2504    }
2505    w.dtd_depth += 1;
2506    let mut sum: c_int = 0;
2507    sum += w.write_indent();
2508    sum += w.write_slice(b"<!ENTITY ");
2509    if pe != 0 {
2510        sum += w.write_slice(b"% ");
2511    }
2512    sum += w.write_str(name);
2513    w.state = WriterState::DTDEntity;
2514    w.entity_pe = pe != 0;
2515    sum
2516}
2517
2518/// End a DTD entity declaration.
2519///
2520/// # UPSTREAM-PARITY
2521///
2522/// ```c
2523/// int xmlTextWriterEndDTDEntity(xmlTextWriterPtr writer);
2524/// ```
2525///
2526/// # SAFETY
2527///
2528/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2529#[no_mangle]
2530pub unsafe extern "C" fn xmlTextWriterEndDTDEntity(writer: *mut XmlTextWriter) -> c_int {
2531    if writer.is_null() {
2532        return -1;
2533    }
2534    // SAFETY: writer is a valid XmlTextWriter.
2535    let w = unsafe { &mut *writer };
2536    let mut sum: c_int = 0;
2537    if w.state == WriterState::DTDEntityText {
2538        sum += w.write_byte(w.qchar);
2539    } else if w.state != WriterState::DTDEntity {
2540        return -1;
2541    }
2542    sum += w.write_byte(b'>');
2543    if w.indent != 0 {
2544        sum += w.write_byte(b'\n');
2545    }
2546    w.state = WriterState::DTDText;
2547    w.entity_pe = false;
2548    w.dtd_depth -= 1;
2549    sum
2550}
2551
2552/// Start a DTD attribute-list declaration (`<!ATTLIST name`).
2553///
2554/// # SAFETY
2555///
2556/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2557/// - `name` must be a valid null-terminated string or NULL.
2558#[no_mangle]
2559pub unsafe extern "C" fn xmlTextWriterStartDTDAttlist(
2560    writer: *mut XmlTextWriter,
2561    name: *const xmlChar,
2562) -> c_int {
2563    if writer.is_null() || name.is_null() || unsafe { *name } == 0 {
2564        return -1;
2565    }
2566    // SAFETY: writer is a valid XmlTextWriter.
2567    let w = unsafe { &mut *writer };
2568    if !unsafe { dtd_child_transition(w) } {
2569        return -1;
2570    }
2571    w.dtd_depth += 1;
2572    let mut sum: c_int = 0;
2573    sum += w.write_indent();
2574    sum += w.write_slice(b"<!ATTLIST ");
2575    sum += w.write_str(name);
2576    w.state = WriterState::DTDAttr;
2577    sum
2578}
2579
2580/// End a DTD attribute-list declaration.
2581///
2582/// # SAFETY
2583///
2584/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2585#[no_mangle]
2586pub unsafe extern "C" fn xmlTextWriterEndDTDAttlist(writer: *mut XmlTextWriter) -> c_int {
2587    if writer.is_null() {
2588        return -1;
2589    }
2590    // SAFETY: writer is a valid XmlTextWriter.
2591    let w = unsafe { &mut *writer };
2592    if w.state != WriterState::DTDAttr && w.state != WriterState::DTDAttrText {
2593        return -1;
2594    }
2595    let mut sum: c_int = w.write_byte(b'>');
2596    if w.indent != 0 {
2597        sum += w.write_byte(b'\n');
2598    }
2599    w.state = WriterState::DTDText;
2600    w.dtd_depth -= 1;
2601    sum
2602}
2603
2604/// Write a DTD attribute-list declaration
2605/// (StartDTDAttlist + WriteString + EndDTDAttlist).
2606///
2607/// # SAFETY
2608///
2609/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2610/// - `name`, `content` must be valid null-terminated strings or NULL.
2611#[no_mangle]
2612pub unsafe extern "C" fn xmlTextWriterWriteDTDAttlist(
2613    writer: *mut XmlTextWriter,
2614    name: *const xmlChar,
2615    content: *const xmlChar,
2616) -> c_int {
2617    if content.is_null() {
2618        return -1;
2619    }
2620    let mut sum: c_int = 0;
2621    let ret = unsafe { xmlTextWriterStartDTDAttlist(writer, name) };
2622    if ret == -1 {
2623        return -1;
2624    }
2625    sum += ret;
2626    let ret2 = unsafe { xmlTextWriterWriteString(writer, content) };
2627    if ret2 == -1 {
2628        return -1;
2629    }
2630    sum += ret2;
2631    let ret3 = unsafe { xmlTextWriterEndDTDAttlist(writer) };
2632    if ret3 == -1 {
2633        return -1;
2634    }
2635    sum + ret3
2636}
2637
2638// ═══════════════════════════════════════════════════════════════════════════════
2639// Output management
2640// ═══════════════════════════════════════════════════════════════════════════════
2641
2642/// Flush the writer's output buffer.
2643///
2644/// # UPSTREAM-PARITY
2645///
2646/// ```c
2647/// int xmlTextWriterFlush(xmlTextWriterPtr writer);
2648/// ```
2649///
2650/// # SAFETY
2651///
2652/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2653#[no_mangle]
2654pub unsafe extern "C" fn xmlTextWriterFlush(writer: *mut XmlTextWriter) -> c_int {
2655    if writer.is_null() {
2656        return -1;
2657    }
2658    // SAFETY: writer is a valid XmlTextWriter.
2659    let w = unsafe { &mut *writer };
2660
2661    if w.output.is_null() {
2662        return -1;
2663    }
2664
2665    // Close any open start tag
2666    w.close_start_tag();
2667
2668    io::output_buffer_flush(w.output)
2669}
2670
2671/// Set indentation on/off.
2672///
2673/// # UPSTREAM-PARITY
2674///
2675/// ```c
2676/// int xmlTextWriterSetIndent(xmlTextWriterPtr writer, int indent);
2677/// ```
2678///
2679/// # SAFETY
2680///
2681/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2682#[no_mangle]
2683pub unsafe extern "C" fn xmlTextWriterSetIndent(
2684    writer: *mut XmlTextWriter,
2685    indent: c_int,
2686) -> c_int {
2687    if writer.is_null() {
2688        return -1;
2689    }
2690    // SAFETY: writer is a valid XmlTextWriter.
2691    unsafe { (*writer).indent = indent };
2692    0
2693}
2694
2695/// Set the indentation string.
2696///
2697/// # UPSTREAM-PARITY
2698///
2699/// ```c
2700/// int xmlTextWriterSetIndentString(xmlTextWriterPtr writer, const xmlChar *str);
2701/// ```
2702///
2703/// # SAFETY
2704///
2705/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2706/// - `str` must be a valid null-terminated xmlChar string or NULL.
2707#[no_mangle]
2708pub unsafe extern "C" fn xmlTextWriterSetIndentString(
2709    writer: *mut XmlTextWriter,
2710    str: *const xmlChar,
2711) -> c_int {
2712    if writer.is_null() || str.is_null() {
2713        return -1;
2714    }
2715    // SAFETY: writer is a valid XmlTextWriter.
2716    let w = unsafe { &mut *writer };
2717    w.indent_string = unsafe { c_str_to_vec(str) };
2718    0
2719}
2720
2721/// Set the quote character used for attribute and entity values.
2722///
2723/// # UPSTREAM-PARITY
2724///
2725/// ```c
2726/// int xmlTextWriterSetQuoteChar(xmlTextWriterPtr writer, xmlChar quotechar);
2727/// ```
2728///
2729/// Only `'` and `'\"'` are accepted; anything else returns -1.
2730///
2731/// # SAFETY
2732///
2733/// - `writer` must be valid pointers (or NULL
2734///   where the upstream C contract allows), obtained from the
2735///   matching constructor/owner and not yet freed; the callee may
2736///   take or keep ownership exactly as the C API specifies.
2737///
2738/// The caller must not race this call with concurrent mutation of the
2739/// same objects from other threads (per-object state is not internally
2740/// synchronized). Violating any of the above is undefined behavior.
2741///
2742/// Exercised by the C-API differential courts
2743/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2744/// courts; those pass byte-for-byte against the upstream oracle.
2745#[no_mangle]
2746pub unsafe extern "C" fn xmlTextWriterSetQuoteChar(
2747    writer: *mut XmlTextWriter,
2748    quotechar: xmlChar,
2749) -> c_int {
2750    if writer.is_null() || (quotechar != b'\'' && quotechar != b'"') {
2751        return -1;
2752    }
2753    // SAFETY: writer is a valid XmlTextWriter.
2754    unsafe { (*writer).qchar = quotechar };
2755    0
2756}
2757
2758/// Close the writer's output buffer. The writer itself is NOT freed (upstream
2759/// contract: xmlFreeTextWriter does that). Returns XML_ERR_OK (0) on success,
2760/// XML_ERR_ARGUMENT (9) for a NULL writer or NULL output buffer.
2761///
2762/// # UPSTREAM-PARITY
2763///
2764/// ```c
2765/// int xmlTextWriterClose(xmlTextWriterPtr writer);
2766/// ```
2767///
2768/// # SAFETY
2769///
2770/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2771#[no_mangle]
2772pub unsafe extern "C" fn xmlTextWriterClose(writer: *mut XmlTextWriter) -> c_int {
2773    if writer.is_null() {
2774        return crate::abi::types::XML_ERR_ARGUMENT as c_int;
2775    }
2776    let w = unsafe { &mut *writer };
2777    if w.output.is_null() {
2778        return crate::abi::types::XML_ERR_ARGUMENT as c_int;
2779    }
2780    let result = io::output_buffer_close(w.output);
2781    w.output = ptr::null_mut();
2782    if result >= 0 {
2783        crate::abi::types::XML_ERR_OK as c_int
2784    } else {
2785        -result
2786    }
2787}
2788
2789// ═══════════════════════════════════════════════════════════════════════════════
2790// Format / VFormat family
2791// ═══════════════════════════════════════════════════════════════════════════════
2792
2793/// The System V AMD64 `__va_list_tag` (24 bytes): gp_offset, fp_offset,
2794/// overflow_arg_area, reg_save_area. A C `va_list` parameter decays to a
2795/// pointer to this structure, which is exactly what the VFormat exports and
2796/// the Format shims exchange.
2797#[repr(C)]
2798#[derive(Clone, Copy, Debug)]
2799pub struct VaListTag {
2800    gp_offset: c_uint,
2801    fp_offset: c_uint,
2802    overflow_arg_area: *mut c_void,
2803    reg_save_area: *mut c_void,
2804}
2805
2806// The platform `vsnprintf` (system libc — not an oracle dependency).
2807unsafe extern "C" {
2808    fn vsnprintf(s: *mut c_char, n: usize, format: *const c_char, ap: *mut VaListTag) -> c_int;
2809}
2810
2811/// Format a printf-style string with the given va_list into a fresh buffer,
2812/// mirroring upstream `xmlTextWriterVSprintf` (BUFSIZ start, doubling growth,
2813/// fresh va_copy per attempt).
2814///
2815/// Returns Err(()) on failure (unrepresentable output or absurd size).
2816///
2817/// # SAFETY
2818///
2819/// - `format` must be a valid printf format string.
2820/// - `args` must point to a valid va_list.
2821unsafe fn vformat_buf(format: *const c_char, args: *mut VaListTag) -> Result<Vec<u8>, ()> {
2822    let mut size: usize = 8192;
2823    loop {
2824        let mut buf = vec![0u8; size];
2825        // Fresh va_copy per attempt: vsnprintf consumes the va_list.
2826        // SAFETY: args points to a valid va_list; the bitwise copy is va_copy.
2827        let mut copy = unsafe { core::ptr::read(args) };
2828        let n = unsafe { vsnprintf(buf.as_mut_ptr() as *mut c_char, size, format, &mut copy) };
2829        if n >= 0 && (n as usize) < size {
2830            buf.truncate(n as usize);
2831            return Ok(buf);
2832        }
2833        if size >= (1 << 26) {
2834            return Err(());
2835        }
2836        size *= 2;
2837    }
2838}
2839
2840// The VFormat functions have heterogeneous fixed-arg lists, so each is written
2841// explicitly rather than through a macro (mirroring the upstream C).
2842/// `xmlTextWriterWriteVFormatRaw` — C ABI export.
2843///
2844/// # SAFETY
2845///
2846/// - `writer`, `argptr` must be valid pointers (or NULL
2847///   where the upstream C contract allows), obtained from the
2848///   matching constructor/owner and not yet freed; the callee may
2849///   take or keep ownership exactly as the C API specifies.
2850///
2851/// - `format` must point to valid NUL-terminated
2852///   strings (or NULL where the C contract allows) for the lifetime
2853///   of the call.
2854///
2855/// The caller must not race this call with concurrent mutation of the
2856/// same objects from other threads (per-object state is not internally
2857/// synchronized). Violating any of the above is undefined behavior.
2858///
2859/// Exercised by the C-API differential courts
2860/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2861/// courts; those pass byte-for-byte against the upstream oracle.
2862#[no_mangle]
2863pub unsafe extern "C" fn xmlTextWriterWriteVFormatRaw(
2864    writer: *mut XmlTextWriter,
2865    format: *const c_char,
2866    argptr: *mut VaListTag,
2867) -> c_int {
2868    if writer.is_null() {
2869        return -1;
2870    }
2871    let buf = match unsafe { vformat_buf(format, argptr) } {
2872        Ok(b) => b,
2873        Err(()) => return -1,
2874    };
2875    unsafe { xmlTextWriterWriteRaw(writer, buf.as_ptr() as *const xmlChar) }
2876}
2877/// `xmlTextWriterWriteVFormatString` — C ABI export.
2878///
2879/// # SAFETY
2880///
2881/// - `writer`, `argptr` must be valid pointers (or NULL
2882///   where the upstream C contract allows), obtained from the
2883///   matching constructor/owner and not yet freed; the callee may
2884///   take or keep ownership exactly as the C API specifies.
2885///
2886/// - `format` must point to valid NUL-terminated
2887///   strings (or NULL where the C contract allows) for the lifetime
2888///   of the call.
2889///
2890/// The caller must not race this call with concurrent mutation of the
2891/// same objects from other threads (per-object state is not internally
2892/// synchronized). Violating any of the above is undefined behavior.
2893///
2894/// Exercised by the C-API differential courts
2895/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2896/// courts; those pass byte-for-byte against the upstream oracle.
2897#[no_mangle]
2898pub unsafe extern "C" fn xmlTextWriterWriteVFormatString(
2899    writer: *mut XmlTextWriter,
2900    format: *const c_char,
2901    argptr: *mut VaListTag,
2902) -> c_int {
2903    if writer.is_null() || format.is_null() {
2904        return -1;
2905    }
2906    let buf = match unsafe { vformat_buf(format, argptr) } {
2907        Ok(b) => b,
2908        Err(()) => return -1,
2909    };
2910    unsafe { xmlTextWriterWriteString(writer, buf.as_ptr() as *const xmlChar) }
2911}
2912/// `xmlTextWriterWriteVFormatComment` — C ABI export.
2913///
2914/// # SAFETY
2915///
2916/// - `writer`, `argptr` must be valid pointers (or NULL
2917///   where the upstream C contract allows), obtained from the
2918///   matching constructor/owner and not yet freed; the callee may
2919///   take or keep ownership exactly as the C API specifies.
2920///
2921/// - `format` must point to valid NUL-terminated
2922///   strings (or NULL where the C contract allows) for the lifetime
2923///   of the call.
2924///
2925/// The caller must not race this call with concurrent mutation of the
2926/// same objects from other threads (per-object state is not internally
2927/// synchronized). Violating any of the above is undefined behavior.
2928///
2929/// Exercised by the C-API differential courts
2930/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2931/// courts; those pass byte-for-byte against the upstream oracle.
2932#[no_mangle]
2933pub unsafe extern "C" fn xmlTextWriterWriteVFormatComment(
2934    writer: *mut XmlTextWriter,
2935    format: *const c_char,
2936    argptr: *mut VaListTag,
2937) -> c_int {
2938    if writer.is_null() {
2939        return -1;
2940    }
2941    let buf = match unsafe { vformat_buf(format, argptr) } {
2942        Ok(b) => b,
2943        Err(()) => return -1,
2944    };
2945    unsafe { xmlTextWriterWriteComment(writer, buf.as_ptr() as *const xmlChar) }
2946}
2947/// `xmlTextWriterWriteVFormatCDATA` — C ABI export.
2948///
2949/// # SAFETY
2950///
2951/// - `writer`, `argptr` must be valid pointers (or NULL
2952///   where the upstream C contract allows), obtained from the
2953///   matching constructor/owner and not yet freed; the callee may
2954///   take or keep ownership exactly as the C API specifies.
2955///
2956/// - `format` must point to valid NUL-terminated
2957///   strings (or NULL where the C contract allows) for the lifetime
2958///   of the call.
2959///
2960/// The caller must not race this call with concurrent mutation of the
2961/// same objects from other threads (per-object state is not internally
2962/// synchronized). Violating any of the above is undefined behavior.
2963///
2964/// Exercised by the C-API differential courts
2965/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2966/// courts; those pass byte-for-byte against the upstream oracle.
2967#[no_mangle]
2968pub unsafe extern "C" fn xmlTextWriterWriteVFormatCDATA(
2969    writer: *mut XmlTextWriter,
2970    format: *const c_char,
2971    argptr: *mut VaListTag,
2972) -> c_int {
2973    if writer.is_null() {
2974        return -1;
2975    }
2976    let buf = match unsafe { vformat_buf(format, argptr) } {
2977        Ok(b) => b,
2978        Err(()) => return -1,
2979    };
2980    unsafe { xmlTextWriterWriteCDATA(writer, buf.as_ptr() as *const xmlChar) }
2981}
2982/// `xmlTextWriterWriteVFormatPI` — C ABI export.
2983///
2984/// # SAFETY
2985///
2986/// - `writer`, `argptr` must be valid pointers (or NULL
2987///   where the upstream C contract allows), obtained from the
2988///   matching constructor/owner and not yet freed; the callee may
2989///   take or keep ownership exactly as the C API specifies.
2990///
2991/// - `target`, `format` must point to valid NUL-terminated
2992///   strings (or NULL where the C contract allows) for the lifetime
2993///   of the call.
2994///
2995/// The caller must not race this call with concurrent mutation of the
2996/// same objects from other threads (per-object state is not internally
2997/// synchronized). Violating any of the above is undefined behavior.
2998///
2999/// Exercised by the C-API differential courts
3000/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3001/// courts; those pass byte-for-byte against the upstream oracle.
3002#[no_mangle]
3003pub unsafe extern "C" fn xmlTextWriterWriteVFormatPI(
3004    writer: *mut XmlTextWriter,
3005    target: *const xmlChar,
3006    format: *const c_char,
3007    argptr: *mut VaListTag,
3008) -> c_int {
3009    if writer.is_null() {
3010        return -1;
3011    }
3012    let buf = match unsafe { vformat_buf(format, argptr) } {
3013        Ok(b) => b,
3014        Err(()) => return -1,
3015    };
3016    unsafe { xmlTextWriterWritePI(writer, target, buf.as_ptr() as *const xmlChar) }
3017}
3018/// `xmlTextWriterWriteVFormatElement` — C ABI export.
3019///
3020/// # SAFETY
3021///
3022/// - `writer`, `argptr` must be valid pointers (or NULL
3023///   where the upstream C contract allows), obtained from the
3024///   matching constructor/owner and not yet freed; the callee may
3025///   take or keep ownership exactly as the C API specifies.
3026///
3027/// - `name`, `format` must point to valid NUL-terminated
3028///   strings (or NULL where the C contract allows) for the lifetime
3029///   of the call.
3030///
3031/// The caller must not race this call with concurrent mutation of the
3032/// same objects from other threads (per-object state is not internally
3033/// synchronized). Violating any of the above is undefined behavior.
3034///
3035/// Exercised by the C-API differential courts
3036/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3037/// courts; those pass byte-for-byte against the upstream oracle.
3038#[no_mangle]
3039pub unsafe extern "C" fn xmlTextWriterWriteVFormatElement(
3040    writer: *mut XmlTextWriter,
3041    name: *const xmlChar,
3042    format: *const c_char,
3043    argptr: *mut VaListTag,
3044) -> c_int {
3045    if writer.is_null() {
3046        return -1;
3047    }
3048    let buf = match unsafe { vformat_buf(format, argptr) } {
3049        Ok(b) => b,
3050        Err(()) => return -1,
3051    };
3052    unsafe { xmlTextWriterWriteElement(writer, name, buf.as_ptr() as *const xmlChar) }
3053}
3054/// `xmlTextWriterWriteVFormatElementNS` — C ABI export.
3055///
3056/// # SAFETY
3057///
3058/// - `writer`, `argptr` must be valid pointers (or NULL
3059///   where the upstream C contract allows), obtained from the
3060///   matching constructor/owner and not yet freed; the callee may
3061///   take or keep ownership exactly as the C API specifies.
3062///
3063/// - `prefix`, `name`, `namespaceURI`, `format` must point to valid NUL-terminated
3064///   strings (or NULL where the C contract allows) for the lifetime
3065///   of the call.
3066///
3067/// The caller must not race this call with concurrent mutation of the
3068/// same objects from other threads (per-object state is not internally
3069/// synchronized). Violating any of the above is undefined behavior.
3070///
3071/// Exercised by the C-API differential courts
3072/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3073/// courts; those pass byte-for-byte against the upstream oracle.
3074#[no_mangle]
3075pub unsafe extern "C" fn xmlTextWriterWriteVFormatElementNS(
3076    writer: *mut XmlTextWriter,
3077    prefix: *const xmlChar,
3078    name: *const xmlChar,
3079    namespaceURI: *const xmlChar,
3080    format: *const c_char,
3081    argptr: *mut VaListTag,
3082) -> c_int {
3083    if writer.is_null() {
3084        return -1;
3085    }
3086    let buf = match unsafe { vformat_buf(format, argptr) } {
3087        Ok(b) => b,
3088        Err(()) => return -1,
3089    };
3090    unsafe {
3091        xmlTextWriterWriteElementNS(
3092            writer,
3093            prefix,
3094            name,
3095            namespaceURI,
3096            buf.as_ptr() as *const xmlChar,
3097        )
3098    }
3099}
3100/// `xmlTextWriterWriteVFormatAttribute` — C ABI export.
3101///
3102/// # SAFETY
3103///
3104/// - `writer`, `argptr` must be valid pointers (or NULL
3105///   where the upstream C contract allows), obtained from the
3106///   matching constructor/owner and not yet freed; the callee may
3107///   take or keep ownership exactly as the C API specifies.
3108///
3109/// - `name`, `format` must point to valid NUL-terminated
3110///   strings (or NULL where the C contract allows) for the lifetime
3111///   of the call.
3112///
3113/// The caller must not race this call with concurrent mutation of the
3114/// same objects from other threads (per-object state is not internally
3115/// synchronized). Violating any of the above is undefined behavior.
3116///
3117/// Exercised by the C-API differential courts
3118/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3119/// courts; those pass byte-for-byte against the upstream oracle.
3120#[no_mangle]
3121pub unsafe extern "C" fn xmlTextWriterWriteVFormatAttribute(
3122    writer: *mut XmlTextWriter,
3123    name: *const xmlChar,
3124    format: *const c_char,
3125    argptr: *mut VaListTag,
3126) -> c_int {
3127    if writer.is_null() {
3128        return -1;
3129    }
3130    let buf = match unsafe { vformat_buf(format, argptr) } {
3131        Ok(b) => b,
3132        Err(()) => return -1,
3133    };
3134    unsafe { xmlTextWriterWriteAttribute(writer, name, buf.as_ptr() as *const xmlChar) }
3135}
3136/// `xmlTextWriterWriteVFormatAttributeNS` — C ABI export.
3137///
3138/// # SAFETY
3139///
3140/// - `writer`, `argptr` must be valid pointers (or NULL
3141///   where the upstream C contract allows), obtained from the
3142///   matching constructor/owner and not yet freed; the callee may
3143///   take or keep ownership exactly as the C API specifies.
3144///
3145/// - `prefix`, `name`, `namespaceURI`, `format` must point to valid NUL-terminated
3146///   strings (or NULL where the C contract allows) for the lifetime
3147///   of the call.
3148///
3149/// The caller must not race this call with concurrent mutation of the
3150/// same objects from other threads (per-object state is not internally
3151/// synchronized). Violating any of the above is undefined behavior.
3152///
3153/// Exercised by the C-API differential courts
3154/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3155/// courts; those pass byte-for-byte against the upstream oracle.
3156#[no_mangle]
3157pub unsafe extern "C" fn xmlTextWriterWriteVFormatAttributeNS(
3158    writer: *mut XmlTextWriter,
3159    prefix: *const xmlChar,
3160    name: *const xmlChar,
3161    namespaceURI: *const xmlChar,
3162    format: *const c_char,
3163    argptr: *mut VaListTag,
3164) -> c_int {
3165    if writer.is_null() {
3166        return -1;
3167    }
3168    let buf = match unsafe { vformat_buf(format, argptr) } {
3169        Ok(b) => b,
3170        Err(()) => return -1,
3171    };
3172    unsafe {
3173        xmlTextWriterWriteAttributeNS(
3174            writer,
3175            prefix,
3176            name,
3177            namespaceURI,
3178            buf.as_ptr() as *const xmlChar,
3179        )
3180    }
3181}
3182/// `xmlTextWriterWriteVFormatDTD` — C ABI export.
3183///
3184/// # SAFETY
3185///
3186/// - `writer`, `argptr` must be valid pointers (or NULL
3187///   where the upstream C contract allows), obtained from the
3188///   matching constructor/owner and not yet freed; the callee may
3189///   take or keep ownership exactly as the C API specifies.
3190///
3191/// - `name`, `pubid`, `sysid`, `format` must point to valid NUL-terminated
3192///   strings (or NULL where the C contract allows) for the lifetime
3193///   of the call.
3194///
3195/// The caller must not race this call with concurrent mutation of the
3196/// same objects from other threads (per-object state is not internally
3197/// synchronized). Violating any of the above is undefined behavior.
3198///
3199/// Exercised by the C-API differential courts
3200/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3201/// courts; those pass byte-for-byte against the upstream oracle.
3202#[no_mangle]
3203pub unsafe extern "C" fn xmlTextWriterWriteVFormatDTD(
3204    writer: *mut XmlTextWriter,
3205    name: *const xmlChar,
3206    pubid: *const xmlChar,
3207    sysid: *const xmlChar,
3208    format: *const c_char,
3209    argptr: *mut VaListTag,
3210) -> c_int {
3211    if writer.is_null() {
3212        return -1;
3213    }
3214    let buf = match unsafe { vformat_buf(format, argptr) } {
3215        Ok(b) => b,
3216        Err(()) => return -1,
3217    };
3218    unsafe { xmlTextWriterWriteDTD(writer, name, pubid, sysid, buf.as_ptr() as *const xmlChar) }
3219}
3220/// `xmlTextWriterWriteVFormatDTDElement` — C ABI export.
3221///
3222/// # SAFETY
3223///
3224/// - `writer`, `argptr` must be valid pointers (or NULL
3225///   where the upstream C contract allows), obtained from the
3226///   matching constructor/owner and not yet freed; the callee may
3227///   take or keep ownership exactly as the C API specifies.
3228///
3229/// - `name`, `format` must point to valid NUL-terminated
3230///   strings (or NULL where the C contract allows) for the lifetime
3231///   of the call.
3232///
3233/// The caller must not race this call with concurrent mutation of the
3234/// same objects from other threads (per-object state is not internally
3235/// synchronized). Violating any of the above is undefined behavior.
3236///
3237/// Exercised by the C-API differential courts
3238/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3239/// courts; those pass byte-for-byte against the upstream oracle.
3240#[no_mangle]
3241pub unsafe extern "C" fn xmlTextWriterWriteVFormatDTDElement(
3242    writer: *mut XmlTextWriter,
3243    name: *const xmlChar,
3244    format: *const c_char,
3245    argptr: *mut VaListTag,
3246) -> c_int {
3247    if writer.is_null() {
3248        return -1;
3249    }
3250    let buf = match unsafe { vformat_buf(format, argptr) } {
3251        Ok(b) => b,
3252        Err(()) => return -1,
3253    };
3254    unsafe { xmlTextWriterWriteDTDElement(writer, name, buf.as_ptr() as *const xmlChar) }
3255}
3256/// `xmlTextWriterWriteVFormatDTDAttlist` — C ABI export.
3257///
3258/// # SAFETY
3259///
3260/// - `writer`, `argptr` must be valid pointers (or NULL
3261///   where the upstream C contract allows), obtained from the
3262///   matching constructor/owner and not yet freed; the callee may
3263///   take or keep ownership exactly as the C API specifies.
3264///
3265/// - `name`, `format` must point to valid NUL-terminated
3266///   strings (or NULL where the C contract allows) for the lifetime
3267///   of the call.
3268///
3269/// The caller must not race this call with concurrent mutation of the
3270/// same objects from other threads (per-object state is not internally
3271/// synchronized). Violating any of the above is undefined behavior.
3272///
3273/// Exercised by the C-API differential courts
3274/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3275/// courts; those pass byte-for-byte against the upstream oracle.
3276#[no_mangle]
3277pub unsafe extern "C" fn xmlTextWriterWriteVFormatDTDAttlist(
3278    writer: *mut XmlTextWriter,
3279    name: *const xmlChar,
3280    format: *const c_char,
3281    argptr: *mut VaListTag,
3282) -> c_int {
3283    if writer.is_null() {
3284        return -1;
3285    }
3286    let buf = match unsafe { vformat_buf(format, argptr) } {
3287        Ok(b) => b,
3288        Err(()) => return -1,
3289    };
3290    unsafe { xmlTextWriterWriteDTDAttlist(writer, name, buf.as_ptr() as *const xmlChar) }
3291}
3292/// `xmlTextWriterWriteVFormatDTDInternalEntity` — C ABI export.
3293///
3294/// # SAFETY
3295///
3296/// - `writer`, `argptr` must be valid pointers (or NULL
3297///   where the upstream C contract allows), obtained from the
3298///   matching constructor/owner and not yet freed; the callee may
3299///   take or keep ownership exactly as the C API specifies.
3300///
3301/// - `name`, `format` must point to valid NUL-terminated
3302///   strings (or NULL where the C contract allows) for the lifetime
3303///   of the call.
3304///
3305/// The caller must not race this call with concurrent mutation of the
3306/// same objects from other threads (per-object state is not internally
3307/// synchronized). Violating any of the above is undefined behavior.
3308///
3309/// Exercised by the C-API differential courts
3310/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3311/// courts; those pass byte-for-byte against the upstream oracle.
3312#[no_mangle]
3313pub unsafe extern "C" fn xmlTextWriterWriteVFormatDTDInternalEntity(
3314    writer: *mut XmlTextWriter,
3315    pe: c_int,
3316    name: *const xmlChar,
3317    format: *const c_char,
3318    argptr: *mut VaListTag,
3319) -> c_int {
3320    if writer.is_null() {
3321        return -1;
3322    }
3323    let buf = match unsafe { vformat_buf(format, argptr) } {
3324        Ok(b) => b,
3325        Err(()) => return -1,
3326    };
3327    unsafe { xmlTextWriterWriteDTDInternalEntity(writer, pe, name, buf.as_ptr() as *const xmlChar) }
3328}
3329
3330/// Assembly shims for the variadic `xmlTextWriterWriteFormat*` exports.
3331///
3332/// Stable Rust cannot define variadic `extern "C"` functions (c_variadic is
3333/// unstable), so each Format export is a #[no_mangle] function whose body is a
3334/// single `noreturn` inline-asm block: it captures the SysV x86-64 register
3335/// save area exactly like `va_start`, builds a `va_list`, forwards it to the
3336/// VFormat implementation, restores the stack and returns directly.
3337/// `#![no_mangle]` puts these exports into rustc's cdylib export list (a
3338/// version script localizes every other global).
3339///
3340/// Layout: reg_save_area = rsp+0 (6 GP + 8 SSE slots, 176 bytes); the va_list
3341/// struct lives at rsp+176 (gp_offset, fp_offset, overflow_arg_area,
3342/// reg_save_area); overflow varargs are above the return address.
3343///
3344/// NOTE on the frame: LLVM emits an 8-byte alignment `push` before the block
3345/// (verified for rustc 1.98.0 at opt-level 0); the block therefore uses a
3346/// 240-byte frame (≡ 0 mod 16, keeping the `call` 16-aligned), points the
3347/// overflow area at rsp+256 (= entry_rsp + 8) and pops the alignment push
3348/// before `ret`. The overflow-argument pointer is only dereferenced when more
3349/// than 6 general-purpose varargs are passed; WRITER-001 exercises that path.
3350/// This is native code with no dependency on any XML library.
3351#[cfg(target_arch = "x86_64")]
3352mod format_shims {
3353    use super::*;
3354
3355    /// `gp` is the gp_offset for the fixed-argument count (8 bytes each);
3356    /// `aptr` is the register receiving the va_list pointer for the VFormat
3357    /// call (rdx=2 fixed, rcx=3, r8=4, r9=5). The parameter list is types
3358    /// only — the values are read directly from registers inside the asm.
3359    macro_rules! vfmt_shim {
3360        ($name:ident, $vname:ident, $gp:literal, $aptr:tt, ($($pty:ty),*)) => {
3361            // No declared parameters: the C caller's fixed arguments arrive in
3362            // the ABI registers and are read directly inside the asm; with no
3363            // parameters and a noreturn body LLVM emits only an 8-byte
3364            // alignment push, which the block pops before `ret`.
3365            #[no_mangle]
3366            pub unsafe extern "C" fn $name() -> c_int {
3367                unsafe {
3368                    core::arch::asm!(
3369                        "sub rsp, 240",
3370                        "mov [rsp+0], rdi",
3371                        "mov [rsp+8], rsi",
3372                        "mov [rsp+16], rdx",
3373                        "mov [rsp+24], rcx",
3374                        "mov [rsp+32], r8",
3375                        "mov [rsp+40], r9",
3376                        "movaps [rsp+48], xmm0",
3377                        "movaps [rsp+64], xmm1",
3378                        "movaps [rsp+80], xmm2",
3379                        "movaps [rsp+96], xmm3",
3380                        "movaps [rsp+112], xmm4",
3381                        "movaps [rsp+128], xmm5",
3382                        "movaps [rsp+144], xmm6",
3383                        "movaps [rsp+160], xmm7",
3384                        concat!("mov dword ptr [rsp+176], ", $gp),
3385                        "mov dword ptr [rsp+180], 48",
3386                        "lea rax, [rsp+256]",
3387                        "mov [rsp+184], rax",
3388                        "lea rax, [rsp]",
3389                        "mov [rsp+192], rax",
3390                        concat!("lea ", stringify!($aptr), ", [rsp+176]"),
3391                        concat!("call ", stringify!($vname)),
3392                        "add rsp, 240",
3393                        "add rsp, 8",
3394                        "ret",
3395                        options(noreturn),
3396                    );
3397                }
3398            }
3399        };
3400    }
3401
3402    vfmt_shim!(
3403        xmlTextWriterWriteFormatRaw,
3404        xmlTextWriterWriteVFormatRaw,
3405        16,
3406        rdx,
3407        (*mut XmlTextWriter, *const c_char)
3408    );
3409    vfmt_shim!(
3410        xmlTextWriterWriteFormatString,
3411        xmlTextWriterWriteVFormatString,
3412        16,
3413        rdx,
3414        (*mut XmlTextWriter, *const c_char)
3415    );
3416    vfmt_shim!(
3417        xmlTextWriterWriteFormatComment,
3418        xmlTextWriterWriteVFormatComment,
3419        16,
3420        rdx,
3421        (*mut XmlTextWriter, *const c_char)
3422    );
3423    vfmt_shim!(
3424        xmlTextWriterWriteFormatCDATA,
3425        xmlTextWriterWriteVFormatCDATA,
3426        16,
3427        rdx,
3428        (*mut XmlTextWriter, *const c_char)
3429    );
3430    vfmt_shim!(
3431        xmlTextWriterWriteFormatPI,
3432        xmlTextWriterWriteVFormatPI,
3433        24,
3434        rcx,
3435        (*mut XmlTextWriter, *const xmlChar, *const c_char)
3436    );
3437    vfmt_shim!(
3438        xmlTextWriterWriteFormatElement,
3439        xmlTextWriterWriteVFormatElement,
3440        24,
3441        rcx,
3442        (*mut XmlTextWriter, *const xmlChar, *const c_char)
3443    );
3444    vfmt_shim!(
3445        xmlTextWriterWriteFormatAttribute,
3446        xmlTextWriterWriteVFormatAttribute,
3447        24,
3448        rcx,
3449        (*mut XmlTextWriter, *const xmlChar, *const c_char)
3450    );
3451    vfmt_shim!(
3452        xmlTextWriterWriteFormatDTDElement,
3453        xmlTextWriterWriteVFormatDTDElement,
3454        24,
3455        rcx,
3456        (*mut XmlTextWriter, *const xmlChar, *const c_char)
3457    );
3458    vfmt_shim!(
3459        xmlTextWriterWriteFormatDTDAttlist,
3460        xmlTextWriterWriteVFormatDTDAttlist,
3461        24,
3462        rcx,
3463        (*mut XmlTextWriter, *const xmlChar, *const c_char)
3464    );
3465    vfmt_shim!(
3466        xmlTextWriterWriteFormatDTDInternalEntity,
3467        xmlTextWriterWriteVFormatDTDInternalEntity,
3468        32,
3469        r8,
3470        (*mut XmlTextWriter, c_int, *const xmlChar, *const c_char)
3471    );
3472    vfmt_shim!(
3473        xmlTextWriterWriteFormatDTD,
3474        xmlTextWriterWriteVFormatDTD,
3475        40,
3476        r9,
3477        (
3478            *mut XmlTextWriter,
3479            *const xmlChar,
3480            *const xmlChar,
3481            *const xmlChar,
3482            *const c_char
3483        )
3484    );
3485    vfmt_shim!(
3486        xmlTextWriterWriteFormatElementNS,
3487        xmlTextWriterWriteVFormatElementNS,
3488        40,
3489        r9,
3490        (
3491            *mut XmlTextWriter,
3492            *const xmlChar,
3493            *const xmlChar,
3494            *const xmlChar,
3495            *const c_char
3496        )
3497    );
3498    vfmt_shim!(
3499        xmlTextWriterWriteFormatAttributeNS,
3500        xmlTextWriterWriteVFormatAttributeNS,
3501        40,
3502        r9,
3503        (
3504            *mut XmlTextWriter,
3505            *const xmlChar,
3506            *const xmlChar,
3507            *const xmlChar,
3508            *const c_char
3509        )
3510    );
3511}
3512
3513// Non-x86-64 fallback: honest stubs (the variadic ABI cannot be forwarded on
3514// stable Rust); the platform surface is documented as not yet executable there.
3515#[cfg(not(target_arch = "x86_64"))]
3516mod format_fallback {
3517    use super::*;
3518    macro_rules! fmt_stub {
3519        ($($name:ident),*) => {$(
3520            #[no_mangle]
3521            pub unsafe extern "C" fn $name(_writer: *mut XmlTextWriter, _format: *const c_char) -> c_int {
3522                -1
3523            }
3524        )*};
3525    }
3526    fmt_stub!(
3527        xmlTextWriterWriteFormatRaw,
3528        xmlTextWriterWriteFormatString,
3529        xmlTextWriterWriteFormatComment,
3530        xmlTextWriterWriteFormatCDATA,
3531        xmlTextWriterWriteFormatPI,
3532        xmlTextWriterWriteFormatElement,
3533        xmlTextWriterWriteFormatElementNS,
3534        xmlTextWriterWriteFormatAttribute,
3535        xmlTextWriterWriteFormatAttributeNS,
3536        xmlTextWriterWriteFormatDTD,
3537        xmlTextWriterWriteFormatDTDElement,
3538        xmlTextWriterWriteFormatDTDAttlist,
3539        xmlTextWriterWriteFormatDTDInternalEntity
3540    );
3541}
3542
3543// ═══════════════════════════════════════════════════════════════════════════════
3544// Internal helpers
3545// ═══════════════════════════════════════════════════════════════════════════════
3546
3547/// Convert a null-terminated C string to a Vec<u8> (including the null terminator).
3548///
3549/// # SAFETY
3550///
3551/// - `s` must be a valid pointer to a null-terminated string.
3552unsafe fn c_str_to_vec(s: *const u8) -> Vec<u8> {
3553    if s.is_null() {
3554        return Vec::new();
3555    }
3556    let len = tree::xml_strlen(s);
3557    let mut v = Vec::with_capacity(len as usize + 1);
3558    unsafe {
3559        for i in 0..len as isize {
3560            v.push(*s.offset(i));
3561        }
3562        v.push(0);
3563    }
3564    v
3565}
3566
3567/// Base64 encode a byte slice.
3568fn base64_encode(data: &[u8]) -> Vec<u8> {
3569    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3570    let mut result = Vec::with_capacity(data.len().div_ceil(3) * 4);
3571    for chunk in data.chunks(3) {
3572        let b0 = chunk[0];
3573        let b1 = chunk.get(1).copied().unwrap_or(0);
3574        let b2 = chunk.get(2).copied().unwrap_or(0);
3575
3576        result.push(CHARS[((b0 >> 2) & 0x3F) as usize]);
3577        result.push(CHARS[(((b0 << 4) | (b1 >> 4)) & 0x3F) as usize]);
3578        result.push(if chunk.len() > 1 {
3579            CHARS[(((b1 << 2) | (b2 >> 6)) & 0x3F) as usize]
3580        } else {
3581            b'='
3582        });
3583        result.push(if chunk.len() > 2 {
3584            CHARS[(b2 & 0x3F) as usize]
3585        } else {
3586            b'='
3587        });
3588    }
3589    result
3590}
3591
3592/// Hex encode a byte slice (lowercase).
3593fn hex_encode(data: &[u8]) -> Vec<u8> {
3594    const CHARS: &[u8] = b"0123456789abcdef";
3595    let mut result = Vec::with_capacity(data.len() * 2);
3596    for &b in data {
3597        result.push(CHARS[((b >> 4) & 0x0F) as usize]);
3598        result.push(CHARS[(b & 0x0F) as usize]);
3599    }
3600    result
3601}
3602
3603// ═══════════════════════════════════════════════════════════════════════════════
3604// Tests
3605// ═══════════════════════════════════════════════════════════════════════════════
3606
3607#[cfg(test)]
3608mod tests {
3609    use super::*;
3610    use core::ptr;
3611
3612    /// Helper: create a memory buffer writer for testing.
3613    unsafe fn create_test_writer() -> (*mut XmlTextWriter, *mut _xmlBuffer) {
3614        let buf = io::buf_create(256);
3615        assert!(!buf.is_null(), "buf_create failed");
3616        let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
3617        assert!(!out.is_null(), "output_buffer_create_buffer failed");
3618        let writer = xmlNewTextWriter(out);
3619        assert!(!writer.is_null(), "xmlNewTextWriter failed");
3620        (writer, buf)
3621    }
3622
3623    /// Helper: get the buffer content as a string.
3624    unsafe fn buf_to_string(buf: *mut _xmlBuffer) -> String {
3625        let content = io::buf_content(buf);
3626        let len = io::buf_length(buf);
3627        if content.is_null() || len <= 0 {
3628            return String::new();
3629        }
3630        let slice = unsafe { core::slice::from_raw_parts(content, len as usize) };
3631        String::from_utf8_lossy(slice).to_string()
3632    }
3633
3634    /// Helper: flush writer and return buffer content.
3635    unsafe fn flush_and_get(writer: *mut XmlTextWriter, buf: *mut _xmlBuffer) -> String {
3636        xmlTextWriterFlush(writer);
3637        buf_to_string(buf)
3638    }
3639
3640    // ═══════════════════════════════════════════════════════════════════════════
3641    // Test: Write a simple document
3642    // ═══════════════════════════════════════════════════════════════════════════
3643
3644    #[test]
3645    fn test_write_simple_document() {
3646        unsafe {
3647            let (writer, buf) = create_test_writer();
3648
3649            let r = xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3650            assert_eq!(r, 0, "StartDocument failed");
3651
3652            let r = xmlTextWriterStartElement(writer, b"root\0" as *const u8);
3653            assert_eq!(r, 0, "StartElement(root) failed");
3654
3655            let r = xmlTextWriterWriteString(writer, b"Hello, World!\0" as *const u8);
3656            assert_eq!(r, 0, "WriteString failed");
3657
3658            let r = xmlTextWriterEndElement(writer);
3659            assert_eq!(r, 0, "EndElement failed");
3660
3661            let r = xmlTextWriterEndDocument(writer);
3662            assert!(r > 0, "EndDocument failed (rc={})", r);
3663
3664            let result = flush_and_get(writer, buf);
3665            assert!(
3666                result.contains("<?xml version=\"1.0\"?>"),
3667                "Missing XML declaration. Got: {}",
3668                result
3669            );
3670            assert!(
3671                result.contains("<root>"),
3672                "Missing <root> start tag. Got: {}",
3673                result
3674            );
3675            assert!(
3676                result.contains("Hello, World!"),
3677                "Missing content. Got: {}",
3678                result
3679            );
3680            assert!(
3681                result.contains("</root>"),
3682                "Missing </root> end tag. Got: {}",
3683                result
3684            );
3685
3686            xmlFreeTextWriter(writer);
3687            io::buf_free(buf);
3688        }
3689    }
3690
3691    // ═══════════════════════════════════════════════════════════════════════════
3692    // Test: Write elements with attributes
3693    // ═══════════════════════════════════════════════════════════════════════════
3694
3695    #[test]
3696    fn test_write_element_with_attributes() {
3697        unsafe {
3698            let (writer, buf) = create_test_writer();
3699
3700            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3701            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
3702            xmlTextWriterWriteAttribute(writer, b"id\0" as *const u8, b"123\0" as *const u8);
3703            xmlTextWriterWriteAttribute(
3704                writer,
3705                b"name\0" as *const u8,
3706                b"test & demo\0" as *const u8,
3707            );
3708            xmlTextWriterEndElement(writer);
3709            xmlTextWriterEndDocument(writer);
3710
3711            let result = flush_and_get(writer, buf);
3712            assert!(
3713                result.contains("id=\"123\""),
3714                "Missing id attribute. Got: {}",
3715                result
3716            );
3717            assert!(
3718                result.contains("name=\"test &amp; demo\""),
3719                "Missing or improperly escaped name attribute. Got: {}",
3720                result
3721            );
3722            assert!(
3723                result.contains("<root"),
3724                "Missing root element. Got: {}",
3725                result
3726            );
3727
3728            xmlFreeTextWriter(writer);
3729            io::buf_free(buf);
3730        }
3731    }
3732
3733    // ═══════════════════════════════════════════════════════════════════════════
3734    // Test: Write with namespaces
3735    // ═══════════════════════════════════════════════════════════════════════════
3736
3737    #[test]
3738    fn test_write_with_namespaces() {
3739        unsafe {
3740            let (writer, buf) = create_test_writer();
3741
3742            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3743            xmlTextWriterStartElementNS(
3744                writer,
3745                b"ns\0" as *const u8,
3746                b"root\0" as *const u8,
3747                b"http://example.com/ns\0" as *const u8,
3748            );
3749            xmlTextWriterWriteAttributeNS(
3750                writer,
3751                ptr::null(),
3752                b"attr\0" as *const u8,
3753                ptr::null(),
3754                b"value\0" as *const u8,
3755            );
3756            xmlTextWriterEndElement(writer);
3757            xmlTextWriterEndDocument(writer);
3758
3759            let result = flush_and_get(writer, buf);
3760            assert!(
3761                result.contains("ns:root"),
3762                "Missing namespace prefix. Got: {}",
3763                result
3764            );
3765            assert!(
3766                result.contains("xmlns:ns=\"http://example.com/ns\""),
3767                "Missing xmlns declaration. Got: {}",
3768                result
3769            );
3770
3771            xmlFreeTextWriter(writer);
3772            io::buf_free(buf);
3773        }
3774    }
3775
3776    // ═══════════════════════════════════════════════════════════════════════════
3777    // Test: Write text, CDATA, comments, PIs
3778    // ═══════════════════════════════════════════════════════════════════════════
3779
3780    #[test]
3781    fn test_write_text_cdata_comment_pi() {
3782        unsafe {
3783            let (writer, buf) = create_test_writer();
3784
3785            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3786
3787            xmlTextWriterStartElement(writer, b"doc\0" as *const u8);
3788            xmlTextWriterWriteString(writer, b"text content\0" as *const u8);
3789            xmlTextWriterEndElement(writer);
3790
3791            xmlTextWriterWriteComment(writer, b"a comment\0" as *const u8);
3792
3793            xmlTextWriterWritePI(writer, b"target\0" as *const u8, b"data\0" as *const u8);
3794
3795            xmlTextWriterStartElement(writer, b"cdata\0" as *const u8);
3796            xmlTextWriterWriteCDATA(writer, b"<greeting>Hello</greeting>\0" as *const u8);
3797            xmlTextWriterEndElement(writer);
3798
3799            xmlTextWriterEndDocument(writer);
3800
3801            let result = flush_and_get(writer, buf);
3802            assert!(
3803                result.contains("text content"),
3804                "Missing text content. Got: {}",
3805                result
3806            );
3807            assert!(
3808                result.contains("<!--a comment-->"),
3809                "Missing comment. Got: {}",
3810                result
3811            );
3812            assert!(
3813                result.contains("<?target data?>"),
3814                "Missing PI. Got: {}",
3815                result
3816            );
3817            assert!(
3818                result.contains("<![CDATA["),
3819                "Missing CDATA start. Got: {}",
3820                result
3821            );
3822            assert!(
3823                result.contains("<greeting>Hello</greeting>"),
3824                "Missing CDATA content. Got: {}",
3825                result
3826            );
3827
3828            xmlFreeTextWriter(writer);
3829            io::buf_free(buf);
3830        }
3831    }
3832
3833    // ═══════════════════════════════════════════════════════════════════════════
3834    // Test: DTD writing
3835    // ═══════════════════════════════════════════════════════════════════════════
3836
3837    #[test]
3838    fn test_write_dtd() {
3839        unsafe {
3840            let (writer, buf) = create_test_writer();
3841
3842            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3843
3844            xmlTextWriterWriteDTD(
3845                writer,
3846                b"html\0" as *const u8,
3847                ptr::null(),
3848                b"http://www.w3.org/TR/html4/strict.dtd\0" as *const u8,
3849                ptr::null(),
3850            );
3851
3852            xmlTextWriterStartElement(writer, b"html\0" as *const u8);
3853            xmlTextWriterEndElement(writer);
3854            xmlTextWriterEndDocument(writer);
3855
3856            let result = flush_and_get(writer, buf);
3857            assert!(
3858                result.contains("<!DOCTYPE html SYSTEM"),
3859                "Missing DTD. Got: {}",
3860                result
3861            );
3862
3863            xmlFreeTextWriter(writer);
3864            io::buf_free(buf);
3865        }
3866    }
3867
3868    // ═══════════════════════════════════════════════════════════════════════════
3869    // Test: DTD with internal subset declarations
3870    // ═══════════════════════════════════════════════════════════════════════════
3871
3872    #[test]
3873    fn test_write_dtd_with_subset() {
3874        unsafe {
3875            let (writer, buf) = create_test_writer();
3876
3877            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3878
3879            xmlTextWriterStartDTD(writer, b"root\0" as *const u8, ptr::null(), ptr::null());
3880            xmlTextWriterWriteDTDElement(
3881                writer,
3882                b"child\0" as *const u8,
3883                b"(#PCDATA)\0" as *const u8,
3884            );
3885            xmlTextWriterWriteDTDAttribute(
3886                writer,
3887                b"child\0" as *const u8,
3888                b"id CDATA #IMPLIED\0" as *const u8,
3889            );
3890            xmlTextWriterWriteDTDEntity(
3891                writer,
3892                0, // pe
3893                b"copy\0" as *const u8,
3894                ptr::null(), // pubid
3895                ptr::null(), // sysid
3896                ptr::null(), // ndataid
3897                b"Copyright Me\0" as *const u8,
3898            );
3899            xmlTextWriterWriteDTDNotation(
3900                writer,
3901                b"note\0" as *const u8,
3902                b"PublicID\0" as *const u8,
3903                ptr::null(),
3904            );
3905            xmlTextWriterEndDTD(writer);
3906
3907            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
3908            xmlTextWriterEndElement(writer);
3909            xmlTextWriterEndDocument(writer);
3910
3911            let result = flush_and_get(writer, buf);
3912            assert!(
3913                result.contains("<!DOCTYPE root"),
3914                "Missing DTD start. Got: {}",
3915                result
3916            );
3917            assert!(
3918                result.contains("<!ELEMENT child (#PCDATA)>"),
3919                "Missing DTD element. Got: {}",
3920                result
3921            );
3922            assert!(
3923                result.contains("<!ATTLIST child id CDATA #IMPLIED>"),
3924                "Missing DTD attribute. Got: {}",
3925                result
3926            );
3927            assert!(
3928                result.contains("<!ENTITY copy \"Copyright Me\">"),
3929                "Missing DTD entity. Got: {}",
3930                result
3931            );
3932            assert!(
3933                result.contains("<!NOTATION note PUBLIC \"PublicID\">"),
3934                "Missing DTD notation. Got: {}",
3935                result
3936            );
3937
3938            xmlFreeTextWriter(writer);
3939            io::buf_free(buf);
3940        }
3941    }
3942
3943    // ═══════════════════════════════════════════════════════════════════════════
3944    // Test: Indentation control
3945    // ═══════════════════════════════════════════════════════════════════════════
3946
3947    #[test]
3948    fn test_indentation_control() {
3949        unsafe {
3950            let (writer, buf) = create_test_writer();
3951
3952            // Enable indentation with tabs
3953            xmlTextWriterSetIndent(writer, 1);
3954            xmlTextWriterSetIndentString(writer, b"\t\0" as *const u8);
3955
3956            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3957            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
3958            xmlTextWriterStartElement(writer, b"child\0" as *const u8);
3959            xmlTextWriterWriteString(writer, b"content\0" as *const u8);
3960            xmlTextWriterEndElement(writer);
3961            xmlTextWriterEndElement(writer);
3962            xmlTextWriterEndDocument(writer);
3963
3964            let result = flush_and_get(writer, buf);
3965
3966            // Check that we have indentation
3967            assert!(
3968                result.contains('\t'),
3969                "Expected tab indentation. Got: {}",
3970                result
3971            );
3972            // Check the XML declaration and elements are present
3973            assert!(result.contains("<root>"), "Missing root. Got: {}", result);
3974            assert!(result.contains("<child>"), "Missing child. Got: {}", result);
3975
3976            xmlFreeTextWriter(writer);
3977            io::buf_free(buf);
3978        }
3979    }
3980
3981    // ═══════════════════════════════════════════════════════════════════════════
3982    // Test: Memory output
3983    // ═══════════════════════════════════════════════════════════════════════════
3984
3985    #[test]
3986    fn test_memory_output() {
3987        unsafe {
3988            let buf = io::buf_create(256);
3989            assert!(!buf.is_null(), "buf_create failed");
3990
3991            let writer = xmlNewTextWriterMemory(buf, 0);
3992            assert!(!writer.is_null(), "xmlNewTextWriterMemory failed");
3993
3994            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
3995            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
3996            xmlTextWriterWriteString(writer, b"memory test\0" as *const u8);
3997            xmlTextWriterEndElement(writer);
3998            xmlTextWriterEndDocument(writer);
3999
4000            xmlTextWriterFlush(writer);
4001            let result = buf_to_string(buf);
4002            assert!(
4003                result.contains("memory test"),
4004                "Missing content in memory output. Got: {}",
4005                result
4006            );
4007
4008            xmlFreeTextWriter(writer);
4009            io::buf_free(buf);
4010        }
4011    }
4012
4013    // ═══════════════════════════════════════════════════════════════════════════
4014    // Test: Flush and close
4015    // ═══════════════════════════════════════════════════════════════════════════
4016
4017    #[test]
4018    fn test_flush_and_close() {
4019        unsafe {
4020            let (writer, buf) = create_test_writer();
4021
4022            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4023            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
4024            xmlTextWriterWriteString(writer, b"flush me\0" as *const u8);
4025
4026            // Flush mid-document
4027            let r = xmlTextWriterFlush(writer);
4028            assert!(r >= 0, "Flush should return non-negative, got {}", r);
4029
4030            xmlTextWriterEndElement(writer);
4031            xmlTextWriterEndDocument(writer);
4032
4033            xmlFreeTextWriter(writer);
4034            io::buf_free(buf);
4035        }
4036    }
4037
4038    // ═══════════════════════════════════════════════════════════════════════════
4039    // Test: Edge cases — null writer, null parameters
4040    // ═══════════════════════════════════════════════════════════════════════════
4041
4042    #[test]
4043    fn test_null_handling() {
4044        unsafe {
4045            // All functions should gracefully handle NULL writer
4046            assert_eq!(
4047                xmlTextWriterStartDocument(ptr::null_mut(), ptr::null(), ptr::null(), ptr::null()),
4048                -1
4049            );
4050            assert_eq!(xmlTextWriterEndDocument(ptr::null_mut()), -1);
4051            assert_eq!(
4052                xmlTextWriterStartElement(ptr::null_mut(), b"x\0" as *const u8),
4053                -1
4054            );
4055            assert_eq!(xmlTextWriterEndElement(ptr::null_mut()), -1);
4056            assert_eq!(
4057                xmlTextWriterWriteString(ptr::null_mut(), b"x\0" as *const u8),
4058                -1
4059            );
4060            assert_eq!(
4061                xmlTextWriterWriteRaw(ptr::null_mut(), b"x\0" as *const u8),
4062                -1
4063            );
4064            assert_eq!(
4065                xmlTextWriterWriteCDATA(ptr::null_mut(), b"x\0" as *const u8),
4066                -1
4067            );
4068            assert_eq!(
4069                xmlTextWriterWriteComment(ptr::null_mut(), b"x\0" as *const u8),
4070                -1
4071            );
4072            assert_eq!(
4073                xmlTextWriterWritePI(ptr::null_mut(), b"x\0" as *const u8, ptr::null()),
4074                -1
4075            );
4076            assert_eq!(xmlTextWriterFlush(ptr::null_mut()), -1);
4077            assert_eq!(xmlTextWriterSetIndent(ptr::null_mut(), 1), -1);
4078            assert_eq!(
4079                xmlTextWriterSetIndentString(ptr::null_mut(), b"  \0" as *const u8),
4080                -1
4081            );
4082            assert_eq!(
4083                xmlTextWriterWriteAttribute(
4084                    ptr::null_mut(),
4085                    b"n\0" as *const u8,
4086                    b"v\0" as *const u8
4087                ),
4088                -1
4089            );
4090
4091            // Null writer should not crash free
4092            xmlFreeTextWriter(ptr::null_mut());
4093        }
4094    }
4095
4096    // ═══════════════════════════════════════════════════════════════════════════
4097    // Test: Nested elements
4098    // ═══════════════════════════════════════════════════════════════════════════
4099
4100    #[test]
4101    fn test_nested_elements() {
4102        unsafe {
4103            let (writer, buf) = create_test_writer();
4104
4105            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4106            xmlTextWriterStartElement(writer, b"a\0" as *const u8);
4107            xmlTextWriterStartElement(writer, b"b\0" as *const u8);
4108            xmlTextWriterStartElement(writer, b"c\0" as *const u8);
4109            xmlTextWriterWriteString(writer, b"deep\0" as *const u8);
4110            xmlTextWriterEndElement(writer);
4111            xmlTextWriterEndElement(writer);
4112            xmlTextWriterEndElement(writer);
4113            xmlTextWriterEndDocument(writer);
4114
4115            let result = flush_and_get(writer, buf);
4116            assert!(result.contains("<a>"), "Missing <a>. Got: {}", result);
4117            assert!(result.contains("<b>"), "Missing <b>. Got: {}", result);
4118            assert!(result.contains("<c>"), "Missing <c>. Got: {}", result);
4119            assert!(result.contains("</a>"), "Missing </a>. Got: {}", result);
4120            assert!(result.contains("</b>"), "Missing </b>. Got: {}", result);
4121            assert!(result.contains("</c>"), "Missing </c>. Got: {}", result);
4122
4123            xmlFreeTextWriter(writer);
4124            io::buf_free(buf);
4125        }
4126    }
4127
4128    // ═══════════════════════════════════════════════════════════════════════════
4129    // Test: Self-closing element (no content)
4130    // ═══════════════════════════════════════════════════════════════════════════
4131
4132    #[test]
4133    fn test_self_closing_element() {
4134        unsafe {
4135            let (writer, buf) = create_test_writer();
4136
4137            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4138            xmlTextWriterStartElement(writer, b"empty\0" as *const u8);
4139            xmlTextWriterEndElement(writer);
4140            xmlTextWriterEndDocument(writer);
4141
4142            let result = flush_and_get(writer, buf);
4143            assert!(
4144                result.contains("<empty/>"),
4145                "Expected self-closing <empty/>. Got: {}",
4146                result
4147            );
4148
4149            xmlFreeTextWriter(writer);
4150            io::buf_free(buf);
4151        }
4152    }
4153
4154    // ═══════════════════════════════════════════════════════════════════════════
4155    // Test: Full end element (not self-closing)
4156    // ═══════════════════════════════════════════════════════════════════════════
4157
4158    #[test]
4159    fn test_full_end_element() {
4160        unsafe {
4161            let (writer, buf) = create_test_writer();
4162
4163            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4164            xmlTextWriterStartElement(writer, b"container\0" as *const u8);
4165            xmlTextWriterFullEndElement(writer);
4166            xmlTextWriterEndDocument(writer);
4167
4168            let result = flush_and_get(writer, buf);
4169            assert!(
4170                result.contains("<container>"),
4171                "Missing <container>. Got: {}",
4172                result
4173            );
4174            assert!(
4175                result.contains("</container>"),
4176                "Missing </container>. Got: {}",
4177                result
4178            );
4179
4180            xmlFreeTextWriter(writer);
4181            io::buf_free(buf);
4182        }
4183    }
4184
4185    // ═══════════════════════════════════════════════════════════════════════════
4186    // Test: WriteElement (element with inline content)
4187    // ═══════════════════════════════════════════════════════════════════════════
4188
4189    #[test]
4190    fn test_write_element_inline() {
4191        unsafe {
4192            let (writer, buf) = create_test_writer();
4193
4194            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4195            xmlTextWriterWriteElement(writer, b"greeting\0" as *const u8, b"Hello\0" as *const u8);
4196            xmlTextWriterEndDocument(writer);
4197
4198            let result = flush_and_get(writer, buf);
4199            assert!(
4200                result.contains("<greeting>Hello</greeting>"),
4201                "Expected <greeting>Hello</greeting>. Got: {}",
4202                result
4203            );
4204
4205            xmlFreeTextWriter(writer);
4206            io::buf_free(buf);
4207        }
4208    }
4209
4210    // ═══════════════════════════════════════════════════════════════════════════
4211    // Test: XML escaping in text content
4212    // ═══════════════════════════════════════════════════════════════════════════
4213
4214    #[test]
4215    fn test_text_escaping() {
4216        unsafe {
4217            let (writer, buf) = create_test_writer();
4218
4219            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4220            xmlTextWriterStartElement(writer, b"esc\0" as *const u8);
4221            xmlTextWriterWriteString(writer, b"a < b & b > a\0" as *const u8);
4222            xmlTextWriterEndElement(writer);
4223            xmlTextWriterEndDocument(writer);
4224
4225            let result = flush_and_get(writer, buf);
4226            assert!(
4227                result.contains("a &lt; b &amp; b &gt; a"),
4228                "Expected escaped content. Got: {}",
4229                result
4230            );
4231
4232            xmlFreeTextWriter(writer);
4233            io::buf_free(buf);
4234        }
4235    }
4236
4237    // ═══════════════════════════════════════════════════════════════════════════
4238    // Test: Raw content (no escaping)
4239    // ═══════════════════════════════════════════════════════════════════════════
4240
4241    #[test]
4242    fn test_raw_content() {
4243        unsafe {
4244            let (writer, buf) = create_test_writer();
4245
4246            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4247            xmlTextWriterStartElement(writer, b"raw\0" as *const u8);
4248            xmlTextWriterWriteRaw(writer, b"<unencoded>&special;</unencoded>\0" as *const u8);
4249            xmlTextWriterEndElement(writer);
4250            xmlTextWriterEndDocument(writer);
4251
4252            let result = flush_and_get(writer, buf);
4253            assert!(
4254                result.contains("<unencoded>&special;</unencoded>"),
4255                "Expected raw unencoded content. Got: {}",
4256                result
4257            );
4258
4259            xmlFreeTextWriter(writer);
4260            io::buf_free(buf);
4261        }
4262    }
4263
4264    // ═══════════════════════════════════════════════════════════════════════════
4265    // Test: Base64 writing
4266    // ═══════════════════════════════════════════════════════════════════════════
4267
4268    #[test]
4269    fn test_base64_write() {
4270        unsafe {
4271            let (writer, buf) = create_test_writer();
4272
4273            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4274            xmlTextWriterStartElement(writer, b"data\0" as *const u8);
4275            let test_data = b"Hello, World!";
4276            xmlTextWriterWriteBase64(
4277                writer,
4278                test_data.as_ptr() as *const c_char,
4279                0,
4280                test_data.len() as c_int,
4281            );
4282            xmlTextWriterEndElement(writer);
4283            xmlTextWriterEndDocument(writer);
4284
4285            let result = flush_and_get(writer, buf);
4286            assert!(
4287                result.contains("SGVsbG8sIFdvcmxkIQ"),
4288                "Expected Base64-encoded content. Got: {}",
4289                result
4290            );
4291
4292            xmlFreeTextWriter(writer);
4293            io::buf_free(buf);
4294        }
4295    }
4296
4297    // ═══════════════════════════════════════════════════════════════════════════
4298    // Test: Incremental CDATA/comment/PI
4299    // ═══════════════════════════════════════════════════════════════════════════
4300
4301    #[test]
4302    fn test_incremental_cdata_comment_pi() {
4303        unsafe {
4304            let (writer, buf) = create_test_writer();
4305
4306            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
4307
4308            // Incremental CDATA
4309            xmlTextWriterStartElement(writer, b"inc\0" as *const u8);
4310            xmlTextWriterStartCDATA(writer);
4311            xmlTextWriterWriteString(writer, b"cdata content\0" as *const u8);
4312            xmlTextWriterEndCDATA(writer);
4313            xmlTextWriterEndElement(writer);
4314
4315            // Incremental comment
4316            xmlTextWriterStartComment(writer);
4317            xmlTextWriterWriteString(writer, b"comment text\0" as *const u8);
4318            xmlTextWriterEndComment(writer);
4319
4320            // Incremental PI
4321            xmlTextWriterStartPI(writer, b"xml-stylesheet\0" as *const u8);
4322            xmlTextWriterWriteString(
4323                writer,
4324                b"type=\"text/xsl\" href=\"style.xsl\"\0" as *const u8,
4325            );
4326            xmlTextWriterEndPI(writer);
4327
4328            xmlTextWriterEndDocument(writer);
4329
4330            let result = flush_and_get(writer, buf);
4331            assert!(
4332                result.contains("<![CDATA["),
4333                "Missing CDATA. Got: {}",
4334                result
4335            );
4336            assert!(
4337                result.contains("<!--comment text-->"),
4338                "Missing comment. Got: {}",
4339                result
4340            );
4341            assert!(
4342                result.contains("<?xml-stylesheet"),
4343                "Missing PI. Got: {}",
4344                result
4345            );
4346
4347            xmlFreeTextWriter(writer);
4348            io::buf_free(buf);
4349        }
4350    }
4351
4352    // ═══════════════════════════════════════════════════════════════════════════
4353    // Test: xmlNewTextWriterFilename returns NULL for NULL uri
4354    // ═══════════════════════════════════════════════════════════════════════════
4355
4356    #[test]
4357    fn test_new_writer_filename_null() {
4358        unsafe {
4359            let writer = xmlNewTextWriterFilename(ptr::null(), 0);
4360            assert!(writer.is_null(), "Expected NULL for null URI");
4361        }
4362    }
4363
4364    // ═══════════════════════════════════════════════════════════════════════════
4365    // Test: xmlNewTextWriter returns NULL for NULL output
4366    // ═══════════════════════════════════════════════════════════════════════════
4367
4368    #[test]
4369    fn test_new_writer_null_output() {
4370        unsafe {
4371            let writer = xmlNewTextWriter(ptr::null_mut());
4372            assert!(writer.is_null(), "Expected NULL for null output");
4373        }
4374    }
4375}