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};
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.
58    DTD,
59    /// Inside a DTD element declaration.
60    DTDElem,
61    /// Inside a DTD attribute declaration.
62    DTDAttr,
63    /// Inside a DTD entity declaration.
64    DTDEntity,
65    /// Inside a DTD notation declaration.
66    DTDNotation,
67    /// Writing XML declaration.
68    XMLDecl,
69}
70
71// ═══════════════════════════════════════════════════════════════════════════════
72// XmlTextWriter struct
73// ═══════════════════════════════════════════════════════════════════════════════
74
75/// A streaming XML writer.
76///
77/// Corresponds to `xmlTextWriterPtr` in libxml2.
78///
79/// The writer accumulates output into an internal buffer and flushes to the
80/// underlying output buffer on demand. It maintains a stack of element names
81/// for proper nesting, a state machine for content-type tracking, and optional
82/// indentation.
83pub struct XmlTextWriter {
84    /// The output buffer where serialized XML is written.
85    output: *mut _xmlOutputBuffer,
86    /// Whether indentation is enabled (non-zero = enabled).
87    indent: c_int,
88    /// The string used for one level of indentation.
89    indent_string: Vec<u8>,
90    /// Current nesting depth.
91    depth: c_int,
92    /// Stack of element local names (for end-element matching).
93    stack: Vec<Vec<u8>>,
94    /// Output encoding name (e.g. "UTF-8").
95    encoding: Vec<u8>,
96    /// Collected error messages.
97    errors: Vec<String>,
98    /// Current writer state.
99    state: WriterState,
100    /// Optional document reference (used when writing to a document tree).
101    doc: *mut _xmlDoc,
102    /// Whether we are in the "start tag" portion of an element (attributes can be written).
103    in_start_tag: bool,
104    /// The element name stack with full qualified names for proper end-element matching.
105    /// Stores (prefix, localname) pairs.
106    elem_stack: Vec<(Vec<u8>, Vec<u8>)>,
107}
108
109impl XmlTextWriter {
110    /// Create a new XML text writer.
111    ///
112    /// # SAFETY
113    ///
114    /// - `output` must be a valid pointer to a mutable `_xmlOutputBuffer` or NULL.
115    unsafe fn new(output: *mut _xmlOutputBuffer) -> *mut Self {
116        let writer = allocator::xmlMallocZero(size_of::<XmlTextWriter>() as usize) as *mut Self;
117        if writer.is_null() {
118            return ptr::null_mut();
119        }
120        unsafe {
121            (*writer).output = output;
122            (*writer).indent = 0;
123            (*writer).indent_string = b"  \0".to_vec();
124            (*writer).depth = 0;
125            (*writer).stack = Vec::new();
126            (*writer).encoding = b"UTF-8\0".to_vec();
127            (*writer).errors = Vec::new();
128            (*writer).state = WriterState::None;
129            (*writer).doc = ptr::null_mut();
130            (*writer).in_start_tag = false;
131            (*writer).elem_stack = Vec::new();
132        }
133        writer
134    }
135
136    /// Write raw bytes to the output buffer.
137    ///
138    /// # SAFETY
139    ///
140    /// - `data` must point to `len` valid bytes.
141    unsafe fn write_raw(&mut self, data: *const u8, len: c_int) {
142        if self.output.is_null() || data.is_null() || len <= 0 {
143            return;
144        }
145        io::output_buffer_write(self.output, len, data as *const c_char);
146    }
147
148    /// Write a null-terminated string to the output buffer.
149    unsafe fn write_str(&mut self, s: *const u8) {
150        if self.output.is_null() || s.is_null() {
151            return;
152        }
153        io::output_buffer_write_string(self.output, s as *const c_char);
154    }
155
156    /// Write a byte slice to the output buffer.
157    ///
158    /// NOTE: The slice must NOT borrow from `self` to avoid borrow checker conflicts.
159    unsafe fn write_slice(&mut self, slice: &[u8]) {
160        if self.output.is_null() || slice.is_empty() {
161            return;
162        }
163        io::output_buffer_write(
164            self.output,
165            slice.len() as c_int,
166            slice.as_ptr() as *const c_char,
167        );
168    }
169
170    /// Write a single byte to the output buffer.
171    unsafe fn write_byte(&mut self, b: u8) {
172        if self.output.is_null() {
173            return;
174        }
175        io::output_buffer_write_char(self.output, b as c_char);
176    }
177
178    /// Write indentation (if enabled).
179    ///
180    /// Uses a clone of the indent string to avoid borrow checker conflicts.
181    unsafe fn write_indent(&mut self) {
182        if self.indent == 0 {
183            return;
184        }
185        self.write_byte(b'\n');
186        let indent_str = self.indent_string.clone();
187        for _ in 0..self.depth {
188            self.write_slice(&indent_str);
189        }
190    }
191
192    /// Close any open start tag (writing `>` to transition from attribute-writing
193    /// mode to content-writing mode).
194    unsafe fn close_start_tag(&mut self) {
195        if self.in_start_tag {
196            self.write_byte(b'>');
197            self.in_start_tag = false;
198        }
199    }
200
201    /// Check if the writer is in a state where element/attribute content can be written.
202    fn can_write_content(&self) -> bool {
203        matches!(
204            self.state,
205            WriterState::None
206                | WriterState::Element
207                | WriterState::Attribute
208                | WriterState::CData
209                | WriterState::Comment
210                | WriterState::PI
211                | WriterState::DTD
212                | WriterState::DTDElem
213                | WriterState::DTDAttr
214                | WriterState::DTDEntity
215                | WriterState::DTDNotation
216                | WriterState::XMLDecl
217        )
218    }
219}
220
221// ═══════════════════════════════════════════════════════════════════════════════
222// Free / destructor
223// ═══════════════════════════════════════════════════════════════════════════════
224
225/// Free an XML text writer.
226///
227/// # UPSTREAM-PARITY
228///
229/// ```c
230/// void xmlFreeTextWriter(xmlTextWriterPtr writer);
231/// ```
232///
233/// # SAFETY
234///
235/// - `writer` must be a valid pointer returned by `xmlNewTextWriter*` or NULL.
236#[no_mangle]
237pub unsafe extern "C" fn xmlFreeTextWriter(writer: *mut XmlTextWriter) {
238    if writer.is_null() {
239        return;
240    }
241    // SAFETY: writer is a valid XmlTextWriter allocated by us.
242    // Flush any pending data
243    if !(*writer).output.is_null() {
244        io::output_buffer_flush((*writer).output);
245    }
246    // Drop Rust-side allocations
247    unsafe {
248        ptr::drop_in_place(&mut (*writer).indent_string);
249        ptr::drop_in_place(&mut (*writer).stack);
250        ptr::drop_in_place(&mut (*writer).encoding);
251        ptr::drop_in_place(&mut (*writer).errors);
252        ptr::drop_in_place(&mut (*writer).elem_stack);
253    }
254    // Free the struct itself
255    unsafe { allocator::xmlFree(writer as *mut c_void) };
256}
257
258// ═══════════════════════════════════════════════════════════════════════════════
259// Writer creation
260// ═══════════════════════════════════════════════════════════════════════════════
261
262/// Create a new XML text writer from an output buffer.
263///
264/// # UPSTREAM-PARITY
265///
266/// ```c
267/// xmlTextWriterPtr xmlNewTextWriter(xmlOutputBufferPtr out);
268/// ```
269///
270/// # SAFETY
271///
272/// - `out` must be a valid pointer to an `_xmlOutputBuffer` or NULL.
273#[no_mangle]
274pub unsafe extern "C" fn xmlNewTextWriter(out: *mut _xmlOutputBuffer) -> *mut XmlTextWriter {
275    if out.is_null() {
276        return ptr::null_mut();
277    }
278    // SAFETY: out is a valid output buffer.
279    XmlTextWriter::new(out)
280}
281
282/// Create a new XML text writer for a file.
283///
284/// # UPSTREAM-PARITY
285///
286/// ```c
287/// xmlTextWriterPtr xmlNewTextWriterFilename(const char *uri, int compression);
288/// ```
289///
290/// # SAFETY
291///
292/// - `uri` must be a valid null-terminated string or NULL.
293#[no_mangle]
294pub unsafe extern "C" fn xmlNewTextWriterFilename(
295    uri: *const c_char,
296    compression: c_int,
297) -> *mut XmlTextWriter {
298    if uri.is_null() {
299        return ptr::null_mut();
300    }
301    // SAFETY: uri is a valid C string.
302    let out = io::output_buffer_create_filename(uri, ptr::null_mut(), compression);
303    if out.is_null() {
304        return ptr::null_mut();
305    }
306    XmlTextWriter::new(out)
307}
308
309/// Create a new XML text writer for a memory buffer.
310///
311/// # UPSTREAM-PARITY
312///
313/// ```c
314/// xmlTextWriterPtr xmlNewTextWriterMemory(xmlBufferPtr buf, int compression);
315/// ```
316///
317/// # SAFETY
318///
319/// - `buf` must be a valid pointer to an `_xmlBuffer` or NULL.
320#[no_mangle]
321pub unsafe extern "C" fn xmlNewTextWriterMemory(
322    buf: *mut _xmlBuffer,
323    compression: c_int,
324) -> *mut XmlTextWriter {
325    let _ = compression;
326    if buf.is_null() {
327        return ptr::null_mut();
328    }
329    // SAFETY: buf is a valid xmlBuffer.
330    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
331    if out.is_null() {
332        return ptr::null_mut();
333    }
334    XmlTextWriter::new(out)
335}
336
337/// Create a new XML text writer for a document (tree mode).
338///
339/// # UPSTREAM-PARITY
340///
341/// ```c
342/// xmlTextWriterPtr xmlNewTextWriterDoc(xmlDocPtr *doc, int compression);
343/// ```
344///
345/// # SAFETY
346///
347/// - `doc` must be a valid pointer to a (possibly NULL) xmlDocPtr.
348#[no_mangle]
349pub unsafe extern "C" fn xmlNewTextWriterDoc(
350    doc: *mut *mut _xmlDoc,
351    compression: c_int,
352) -> *mut XmlTextWriter {
353    let _ = compression;
354    if doc.is_null() {
355        return ptr::null_mut();
356    }
357    // Create a new document
358    // SAFETY: doc is a valid pointer to an xmlDocPtr.
359    let new_doc = tree::new_doc(b"1.0\0" as *const u8);
360    if new_doc.is_null() {
361        return ptr::null_mut();
362    }
363    unsafe { *doc = new_doc };
364
365    // Create a memory buffer writer
366    let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
367    if buf.is_null() {
368        tree::free_doc(new_doc);
369        return ptr::null_mut();
370    }
371
372    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
373    if out.is_null() {
374        io::buf_free(buf);
375        tree::free_doc(new_doc);
376        return ptr::null_mut();
377    }
378
379    let writer = XmlTextWriter::new(out);
380    if !writer.is_null() {
381        unsafe { (*writer).doc = new_doc };
382    }
383    writer
384}
385
386/// Create a new XML text writer for a subtree.
387///
388/// # UPSTREAM-PARITY
389///
390/// ```c
391/// xmlTextWriterPtr xmlNewTextWriterTree(xmlDocPtr doc, xmlNodePtr node, int compression);
392/// ```
393///
394/// # SAFETY
395///
396/// - `doc` must be a valid pointer to an `_xmlDoc` or NULL.
397/// - `node` must be a valid pointer to an `_xmlNode` or NULL.
398#[no_mangle]
399pub unsafe extern "C" fn xmlNewTextWriterTree(
400    doc: *mut _xmlDoc,
401    node: *mut _xmlNode,
402    compression: c_int,
403) -> *mut XmlTextWriter {
404    let _ = compression;
405    let _ = node; // node is kept for future use when we write tree content directly
406    if doc.is_null() {
407        return ptr::null_mut();
408    }
409
410    let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
411    if buf.is_null() {
412        return ptr::null_mut();
413    }
414
415    let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
416    if out.is_null() {
417        io::buf_free(buf);
418        return ptr::null_mut();
419    }
420
421    let writer = XmlTextWriter::new(out);
422    if !writer.is_null() {
423        unsafe { (*writer).doc = doc };
424    }
425    writer
426}
427
428// ═══════════════════════════════════════════════════════════════════════════════
429// Document lifecycle
430// ═══════════════════════════════════════════════════════════════════════════════
431
432/// Start an XML document.
433///
434/// Writes the XML declaration `<?xml version="..." encoding="..." standalone="..."?>`.
435///
436/// # UPSTREAM-PARITY
437///
438/// ```c
439/// int xmlTextWriterStartDocument(xmlTextWriterPtr writer,
440///                                 const char *version,
441///                                 const char *encoding,
442///                                 const char *standalone);
443/// ```
444///
445/// # SAFETY
446///
447/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
448/// - `version`, `encoding`, `standalone` must be valid null-terminated strings or NULL.
449#[no_mangle]
450pub unsafe extern "C" fn xmlTextWriterStartDocument(
451    writer: *mut XmlTextWriter,
452    version: *const c_char,
453    encoding: *const c_char,
454    standalone: *const c_char,
455) -> c_int {
456    if writer.is_null() {
457        return -1;
458    }
459    // SAFETY: writer is a valid XmlTextWriter.
460    let w = unsafe { &mut *writer };
461
462    w.write_raw(b"<?xml version=\"" as *const u8, 15);
463
464    let ver = if version.is_null() {
465        b"1.0\0" as *const u8
466    } else {
467        version as *const u8
468    };
469    w.write_str(ver);
470
471    w.write_raw(b"\"" as *const u8, 1);
472
473    let enc = if encoding.is_null() {
474        ptr::null()
475    } else {
476        encoding as *const u8
477    };
478    if !enc.is_null() {
479        w.write_raw(b" encoding=\"" as *const u8, 11);
480        w.write_str(enc);
481        w.write_byte(b'"');
482    }
483
484    if !standalone.is_null() {
485        let sa = standalone as *const u8;
486        w.write_raw(b" standalone=\"" as *const u8, 13);
487        w.write_str(sa);
488        w.write_byte(b'"');
489    }
490
491    w.write_raw(b"?>" as *const u8, 2);
492
493    if w.indent != 0 {
494        w.write_byte(b'\n');
495    }
496
497    w.state = WriterState::XMLDecl;
498    0
499}
500
501/// End an XML document.
502///
503/// Flushes any pending output and writes a final newline if indentation is enabled.
504///
505/// # UPSTREAM-PARITY
506///
507/// ```c
508/// int xmlTextWriterEndDocument(xmlTextWriterPtr writer);
509/// ```
510///
511/// # SAFETY
512///
513/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
514#[no_mangle]
515pub unsafe extern "C" fn xmlTextWriterEndDocument(writer: *mut XmlTextWriter) -> c_int {
516    if writer.is_null() {
517        return -1;
518    }
519    // SAFETY: writer is a valid XmlTextWriter.
520    let w = unsafe { &mut *writer };
521
522    // Close any open elements
523    while w.depth > 0 {
524        xmlTextWriterEndElement(writer);
525    }
526
527    // Final newline if indentation is enabled
528    if w.indent != 0 {
529        w.write_byte(b'\n');
530    }
531
532    // Flush output
533    if !w.output.is_null() {
534        io::output_buffer_flush(w.output);
535    }
536
537    w.state = WriterState::None;
538    0
539}
540
541// ═══════════════════════════════════════════════════════════════════════════════
542// Element writing
543// ═══════════════════════════════════════════════════════════════════════════════
544
545/// Start an XML element.
546///
547/// # UPSTREAM-PARITY
548///
549/// ```c
550/// int xmlTextWriterStartElement(xmlTextWriterPtr writer, const xmlChar *name);
551/// ```
552///
553/// # SAFETY
554///
555/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
556/// - `name` must be a valid null-terminated xmlChar string or NULL.
557#[no_mangle]
558pub unsafe extern "C" fn xmlTextWriterStartElement(
559    writer: *mut XmlTextWriter,
560    name: *const xmlChar,
561) -> c_int {
562    if writer.is_null() || name.is_null() {
563        return -1;
564    }
565    // SAFETY: writer is a valid XmlTextWriter.
566    let w = unsafe { &mut *writer };
567
568    // Close any open start tag from a previous element
569    w.close_start_tag();
570
571    // Write indentation
572    w.write_indent();
573
574    // Write `<name`
575    w.write_byte(b'<');
576    w.write_str(name);
577
578    // Push onto stack (without null terminator)
579    let name_bytes = unsafe { c_str_to_vec(name) };
580    w.elem_stack.push((b"".to_vec(), name_bytes.clone()));
581    // Strip trailing null for stack storage
582    let stack_name = if name_bytes.last() == Some(&0) {
583        name_bytes[..name_bytes.len() - 1].to_vec()
584    } else {
585        name_bytes.clone()
586    };
587    w.stack.push(stack_name);
588    w.depth += 1;
589    w.in_start_tag = true;
590    w.state = WriterState::Element;
591
592    0
593}
594
595/// End an XML element.
596///
597/// # UPSTREAM-PARITY
598///
599/// ```c
600/// int xmlTextWriterEndElement(xmlTextWriterPtr writer);
601/// ```
602///
603/// # SAFETY
604///
605/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
606#[no_mangle]
607pub unsafe extern "C" fn xmlTextWriterEndElement(writer: *mut XmlTextWriter) -> c_int {
608    if writer.is_null() {
609        return -1;
610    }
611    // SAFETY: writer is a valid XmlTextWriter.
612    let w = unsafe { &mut *writer };
613
614    if w.depth <= 0 {
615        return -1;
616    }
617
618    // If we're still in the start tag (no content written), write self-closing tag
619    if w.in_start_tag {
620        // Remove the trailing `>` or just rewrite as `/>`
621        // Since we wrote `<name` and then haven't closed, we just write `/>` and we're done
622        w.write_raw(b"/>" as *const u8, 2);
623        w.in_start_tag = false;
624    } else {
625        // Write indentation before end tag for non-inline content
626        if w.indent != 0 {
627            w.write_byte(b'\n');
628            let indent_str = w.indent_string.clone();
629            for _ in 0..(w.depth - 1) {
630                w.write_slice(&indent_str);
631            }
632        }
633
634        // Write `</name>`
635        let name = w.stack.pop().unwrap_or_default();
636        w.write_raw(b"</" as *const u8, 2);
637        w.write_slice(&name);
638        w.write_byte(b'>');
639    }
640
641    w.depth -= 1;
642    w.elem_stack.pop();
643    w.state = WriterState::None;
644
645    0
646}
647
648/// Start a namespaced XML element.
649///
650/// # UPSTREAM-PARITY
651///
652/// ```c
653/// int xmlTextWriterStartElementNS(xmlTextWriterPtr writer,
654///                                  const xmlChar *prefix,
655///                                  const xmlChar *name,
656///                                  const xmlChar *namespaceURI);
657/// ```
658///
659/// # SAFETY
660///
661/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
662/// - `prefix`, `name`, `namespaceURI` must be valid null-terminated strings or NULL.
663#[no_mangle]
664pub unsafe extern "C" fn xmlTextWriterStartElementNS(
665    writer: *mut XmlTextWriter,
666    prefix: *const xmlChar,
667    name: *const xmlChar,
668    namespaceURI: *const xmlChar,
669) -> c_int {
670    if writer.is_null() || name.is_null() {
671        return -1;
672    }
673    // SAFETY: writer is a valid XmlTextWriter.
674    let w = unsafe { &mut *writer };
675
676    w.close_start_tag();
677    w.write_indent();
678
679    w.write_byte(b'<');
680
681    let prefix_bytes = if prefix.is_null() {
682        Vec::new()
683    } else {
684        unsafe { c_str_to_vec(prefix) }
685    };
686
687    let name_bytes = unsafe { c_str_to_vec(name) };
688
689    if !prefix_bytes.is_empty() {
690        // Strip trailing null before writing
691        let p = if prefix_bytes.last() == Some(&0) {
692            &prefix_bytes[..prefix_bytes.len() - 1]
693        } else {
694            &prefix_bytes
695        };
696        w.write_slice(p);
697        w.write_byte(b':');
698    }
699    // Strip trailing null before writing
700    let n = if name_bytes.last() == Some(&0) {
701        &name_bytes[..name_bytes.len() - 1]
702    } else {
703        &name_bytes
704    };
705    w.write_slice(n);
706
707    // Write namespace declaration
708    if !namespaceURI.is_null() {
709        let ns_uri_bytes = unsafe { c_str_to_vec(namespaceURI) };
710        if !prefix_bytes.is_empty() {
711            w.write_raw(b" xmlns:" as *const u8, 7);
712            let p = if prefix_bytes.last() == Some(&0) {
713                &prefix_bytes[..prefix_bytes.len() - 1]
714            } else {
715                &prefix_bytes
716            };
717            w.write_slice(p);
718        } else {
719            w.write_raw(b" xmlns" as *const u8, 6);
720        }
721        w.write_raw(b"=\"" as *const u8, 2);
722        let ns = if ns_uri_bytes.last() == Some(&0) {
723            &ns_uri_bytes[..ns_uri_bytes.len() - 1]
724        } else {
725            &ns_uri_bytes
726        };
727        w.write_slice(ns);
728        w.write_byte(b'"');
729    }
730
731    w.elem_stack.push((prefix_bytes, name_bytes.clone()));
732    // Strip trailing null for stack storage
733    let stack_name = if name_bytes.last() == Some(&0) {
734        name_bytes[..name_bytes.len() - 1].to_vec()
735    } else {
736        name_bytes
737    };
738    w.stack.push(stack_name);
739    w.depth += 1;
740    w.in_start_tag = true;
741    w.state = WriterState::Element;
742
743    0
744}
745
746/// Write an element with inline content.
747///
748/// # UPSTREAM-PARITY
749///
750/// ```c
751/// int xmlTextWriterWriteElement(xmlTextWriterPtr writer,
752///                                const xmlChar *name,
753///                                const xmlChar *content);
754/// ```
755///
756/// # SAFETY
757///
758/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
759/// - `name`, `content` must be valid null-terminated strings or NULL.
760#[no_mangle]
761pub unsafe extern "C" fn xmlTextWriterWriteElement(
762    writer: *mut XmlTextWriter,
763    name: *const xmlChar,
764    content: *const xmlChar,
765) -> c_int {
766    if writer.is_null() || name.is_null() {
767        return -1;
768    }
769    let ret = xmlTextWriterStartElement(writer, name);
770    if ret != 0 {
771        return ret;
772    }
773    if !content.is_null() {
774        let ret2 = xmlTextWriterWriteString(writer, content);
775        if ret2 != 0 {
776            return ret2;
777        }
778    }
779    xmlTextWriterEndElement(writer)
780}
781
782/// Write a namespaced element with inline content.
783///
784/// # UPSTREAM-PARITY
785///
786/// ```c
787/// int xmlTextWriterWriteElementNS(xmlTextWriterPtr writer,
788///                                  const xmlChar *prefix,
789///                                  const xmlChar *name,
790///                                  const xmlChar *nsURI,
791///                                  const xmlChar *content);
792/// ```
793///
794/// # SAFETY
795///
796/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
797/// - `prefix`, `name`, `nsURI`, `content` must be valid null-terminated strings or NULL.
798#[no_mangle]
799pub unsafe extern "C" fn xmlTextWriterWriteElementNS(
800    writer: *mut XmlTextWriter,
801    prefix: *const xmlChar,
802    name: *const xmlChar,
803    nsURI: *const xmlChar,
804    content: *const xmlChar,
805) -> c_int {
806    if writer.is_null() || name.is_null() {
807        return -1;
808    }
809    let ret = xmlTextWriterStartElementNS(writer, prefix, name, nsURI);
810    if ret != 0 {
811        return ret;
812    }
813    if !content.is_null() {
814        let ret2 = xmlTextWriterWriteString(writer, content);
815        if ret2 != 0 {
816            return ret2;
817        }
818    }
819    xmlTextWriterEndElement(writer)
820}
821
822/// Write a full end element (always writes `</name>`, never self-closing).
823///
824/// # UPSTREAM-PARITY
825///
826/// ```c
827/// int xmlTextWriterFullEndElement(xmlTextWriterPtr writer);
828/// ```
829///
830/// # SAFETY
831///
832/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
833#[no_mangle]
834pub unsafe extern "C" fn xmlTextWriterFullEndElement(writer: *mut XmlTextWriter) -> c_int {
835    if writer.is_null() {
836        return -1;
837    }
838    // SAFETY: writer is a valid XmlTextWriter.
839    let w = unsafe { &mut *writer };
840
841    if w.depth <= 0 {
842        return -1;
843    }
844
845    // Close any open start tag first (if we're still in the start tag, close it with `>`)
846    if w.in_start_tag {
847        w.write_byte(b'>');
848        w.in_start_tag = false;
849    }
850
851    // Write indentation
852    if w.indent != 0 {
853        w.write_byte(b'\n');
854        let indent_str = w.indent_string.clone();
855        for _ in 0..(w.depth - 1) {
856            w.write_slice(&indent_str);
857        }
858    }
859
860    // Write `</name>`
861    let name = w.stack.pop().unwrap_or_default();
862    w.write_raw(b"</" as *const u8, 2);
863    w.write_slice(&name);
864    w.write_byte(b'>');
865
866    w.depth -= 1;
867    w.elem_stack.pop();
868    w.state = WriterState::None;
869
870    0
871}
872
873// ═══════════════════════════════════════════════════════════════════════════════
874// Attribute writing
875// ═══════════════════════════════════════════════════════════════════════════════
876
877/// Write an attribute.
878///
879/// # UPSTREAM-PARITY
880///
881/// ```c
882/// int xmlTextWriterWriteAttribute(xmlTextWriterPtr writer,
883///                                  const xmlChar *name,
884///                                  const xmlChar *content);
885/// ```
886///
887/// # SAFETY
888///
889/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
890/// - `name`, `content` must be valid null-terminated strings or NULL.
891#[no_mangle]
892pub unsafe extern "C" fn xmlTextWriterWriteAttribute(
893    writer: *mut XmlTextWriter,
894    name: *const xmlChar,
895    content: *const xmlChar,
896) -> c_int {
897    if writer.is_null() || name.is_null() || content.is_null() {
898        return -1;
899    }
900    // SAFETY: writer is a valid XmlTextWriter.
901    let w = unsafe { &mut *writer };
902
903    if !w.in_start_tag {
904        return -1;
905    }
906
907    // Write ` name="`
908    w.write_byte(b' ');
909    w.write_str(name);
910    w.write_raw(b"=\"" as *const u8, 2);
911
912    // Write escaped content
913    tree::serialize_attr_value(
914        unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) },
915        content,
916    );
917
918    w.write_byte(b'"');
919    w.state = WriterState::Attribute;
920
921    0
922}
923
924/// Write a namespaced attribute.
925///
926/// # UPSTREAM-PARITY
927///
928/// ```c
929/// int xmlTextWriterWriteAttributeNS(xmlTextWriterPtr writer,
930///                                    const xmlChar *prefix,
931///                                    const xmlChar *name,
932///                                    const xmlChar *nsURI,
933///                                    const xmlChar *content);
934/// ```
935///
936/// # SAFETY
937///
938/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
939/// - `prefix`, `name`, `nsURI`, `content` must be valid null-terminated strings or NULL.
940#[no_mangle]
941pub unsafe extern "C" fn xmlTextWriterWriteAttributeNS(
942    writer: *mut XmlTextWriter,
943    prefix: *const xmlChar,
944    name: *const xmlChar,
945    nsURI: *const xmlChar,
946    content: *const xmlChar,
947) -> c_int {
948    let _ = nsURI;
949    if writer.is_null() || name.is_null() || content.is_null() {
950        return -1;
951    }
952    // SAFETY: writer is a valid XmlTextWriter.
953    let w = unsafe { &mut *writer };
954
955    if !w.in_start_tag {
956        return -1;
957    }
958
959    w.write_byte(b' ');
960
961    if !prefix.is_null() {
962        w.write_str(prefix);
963        w.write_byte(b':');
964    }
965    w.write_str(name);
966
967    w.write_raw(b"=\"" as *const u8, 2);
968
969    // Write escaped content
970    let buf = unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) };
971    tree::serialize_attr_value(buf, content);
972
973    w.write_byte(b'"');
974    w.state = WriterState::Attribute;
975
976    0
977}
978
979/// Write a formatted attribute.
980///
981/// # UPSTREAM-PARITY
982///
983/// ```c
984/// int xmlTextWriterWriteFormatAttribute(xmlTextWriterPtr writer,
985///                                        const xmlChar *name,
986///                                        ...);
987/// ```
988///
989/// # SAFETY
990///
991/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
992/// - `name` must be a valid null-terminated string.
993#[no_mangle]
994
995/// Start an attribute (to be written incrementally).
996///
997/// # UPSTREAM-PARITY
998///
999/// ```c
1000/// int xmlTextWriterStartAttribute(xmlTextWriterPtr writer, const xmlChar *name);
1001/// ```
1002///
1003/// # SAFETY
1004///
1005/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1006/// - `name` must be a valid null-terminated string or NULL.
1007#[no_mangle]
1008pub unsafe extern "C" fn xmlTextWriterStartAttribute(
1009    writer: *mut XmlTextWriter,
1010    name: *const xmlChar,
1011) -> c_int {
1012    if writer.is_null() || name.is_null() {
1013        return -1;
1014    }
1015    // SAFETY: writer is a valid XmlTextWriter.
1016    let w = unsafe { &mut *writer };
1017
1018    if !w.in_start_tag {
1019        return -1;
1020    }
1021
1022    w.write_byte(b' ');
1023    w.write_str(name);
1024    w.write_raw(b"=\"" as *const u8, 2);
1025    w.state = WriterState::Attribute;
1026
1027    0
1028}
1029
1030/// Start a namespaced attribute (to be written incrementally).
1031///
1032/// # UPSTREAM-PARITY
1033///
1034/// ```c
1035/// int xmlTextWriterStartAttributeNS(xmlTextWriterPtr writer,
1036///                                    const xmlChar *prefix,
1037///                                    const xmlChar *name,
1038///                                    const xmlChar *nsURI);
1039/// ```
1040///
1041/// # SAFETY
1042///
1043/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1044/// - `prefix`, `name`, `nsURI` must be valid null-terminated strings or NULL.
1045#[no_mangle]
1046pub unsafe extern "C" fn xmlTextWriterStartAttributeNS(
1047    writer: *mut XmlTextWriter,
1048    prefix: *const xmlChar,
1049    name: *const xmlChar,
1050    nsURI: *const xmlChar,
1051) -> c_int {
1052    let _ = nsURI;
1053    if writer.is_null() || name.is_null() {
1054        return -1;
1055    }
1056    // SAFETY: writer is a valid XmlTextWriter.
1057    let w = unsafe { &mut *writer };
1058
1059    if !w.in_start_tag {
1060        return -1;
1061    }
1062
1063    w.write_byte(b' ');
1064    if !prefix.is_null() {
1065        w.write_str(prefix);
1066        w.write_byte(b':');
1067    }
1068    w.write_str(name);
1069    w.write_raw(b"=\"" as *const u8, 2);
1070    w.state = WriterState::Attribute;
1071
1072    0
1073}
1074
1075/// End an attribute (closes the attribute value quote).
1076///
1077/// # UPSTREAM-PARITY
1078///
1079/// ```c
1080/// int xmlTextWriterEndAttribute(xmlTextWriterPtr writer);
1081/// ```
1082///
1083/// # SAFETY
1084///
1085/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1086#[no_mangle]
1087pub unsafe extern "C" fn xmlTextWriterEndAttribute(writer: *mut XmlTextWriter) -> c_int {
1088    if writer.is_null() {
1089        return -1;
1090    }
1091    // SAFETY: writer is a valid XmlTextWriter.
1092    let w = unsafe { &mut *writer };
1093
1094    if w.state != WriterState::Attribute {
1095        return -1;
1096    }
1097
1098    w.write_byte(b'"');
1099    w.state = WriterState::Element;
1100
1101    0
1102}
1103
1104// ═══════════════════════════════════════════════════════════════════════════════
1105// Content writing
1106// ═══════════════════════════════════════════════════════════════════════════════
1107
1108/// Write text content (with XML escaping).
1109///
1110/// # UPSTREAM-PARITY
1111///
1112/// ```c
1113/// int xmlTextWriterWriteString(xmlTextWriterPtr writer, const xmlChar *content);
1114/// ```
1115///
1116/// # SAFETY
1117///
1118/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1119/// - `content` must be a valid null-terminated xmlChar string or NULL.
1120#[no_mangle]
1121pub unsafe extern "C" fn xmlTextWriterWriteString(
1122    writer: *mut XmlTextWriter,
1123    content: *const xmlChar,
1124) -> c_int {
1125    if writer.is_null() || content.is_null() {
1126        return -1;
1127    }
1128    // SAFETY: writer is a valid XmlTextWriter.
1129    let w = unsafe { &mut *writer };
1130
1131    w.close_start_tag();
1132
1133    let buf = if w.output.is_null() {
1134        return -1;
1135    } else {
1136        unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) }
1137    };
1138
1139    let len = tree::xml_strlen(content);
1140    tree::serialize_text(buf, content, len);
1141
1142    0
1143}
1144
1145/// Write raw content (no XML escaping).
1146///
1147/// # UPSTREAM-PARITY
1148///
1149/// ```c
1150/// int xmlTextWriterWriteRaw(xmlTextWriterPtr writer, const xmlChar *content);
1151/// ```
1152///
1153/// # SAFETY
1154///
1155/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1156/// - `content` must be a valid null-terminated xmlChar string or NULL.
1157#[no_mangle]
1158pub unsafe extern "C" fn xmlTextWriterWriteRaw(
1159    writer: *mut XmlTextWriter,
1160    content: *const xmlChar,
1161) -> c_int {
1162    if writer.is_null() || content.is_null() {
1163        return -1;
1164    }
1165    // SAFETY: writer is a valid XmlTextWriter.
1166    let w = unsafe { &mut *writer };
1167
1168    w.close_start_tag();
1169    w.write_str(content);
1170
1171    0
1172}
1173
1174/// Write raw content with explicit length (no XML escaping).
1175///
1176/// # UPSTREAM-PARITY
1177///
1178/// ```c
1179/// int xmlTextWriterWriteRawLen(xmlTextWriterPtr writer,
1180///                               const xmlChar *content,
1181///                               int len);
1182/// ```
1183///
1184/// # SAFETY
1185///
1186/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1187/// - `content` must point to `len` valid bytes or NULL.
1188#[no_mangle]
1189pub unsafe extern "C" fn xmlTextWriterWriteRawLen(
1190    writer: *mut XmlTextWriter,
1191    content: *const xmlChar,
1192    len: c_int,
1193) -> c_int {
1194    if writer.is_null() || content.is_null() || len <= 0 {
1195        return -1;
1196    }
1197    // SAFETY: writer is a valid XmlTextWriter.
1198    let w = unsafe { &mut *writer };
1199
1200    w.close_start_tag();
1201    w.write_raw(content, len);
1202
1203    0
1204}
1205
1206/// Write a formatted string.
1207///
1208/// # UPSTREAM-PARITY
1209///
1210/// ```c
1211/// int xmlTextWriterWriteFormatString(xmlTextWriterPtr writer, const char *fmt, ...);
1212/// ```
1213///
1214/// # SAFETY
1215///
1216/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1217#[no_mangle]
1218
1219/// Write Base64-encoded data.
1220///
1221/// # UPSTREAM-PARITY
1222///
1223/// ```c
1224/// int xmlTextWriterWriteBase64(xmlTextWriterPtr writer,
1225///                               const char *data,
1226///                               int start,
1227///                               int len);
1228/// ```
1229///
1230/// # SAFETY
1231///
1232/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1233/// - `data` must be a valid pointer to `start + len` bytes or NULL.
1234#[no_mangle]
1235pub unsafe extern "C" fn xmlTextWriterWriteBase64(
1236    writer: *mut XmlTextWriter,
1237    data: *const c_char,
1238    start: c_int,
1239    len: c_int,
1240) -> c_int {
1241    if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1242        return -1;
1243    }
1244    // SAFETY: writer is a valid XmlTextWriter.
1245    let w = unsafe { &mut *writer };
1246
1247    w.close_start_tag();
1248
1249    // Base64 encode the data
1250    let data_slice =
1251        unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1252    let encoded = base64_encode(data_slice);
1253    w.write_slice(&encoded);
1254
1255    0
1256}
1257
1258/// Write BinHex-encoded data.
1259///
1260/// # UPSTREAM-PARITY
1261///
1262/// ```c
1263/// int xmlTextWriterWriteBinHex(xmlTextWriterPtr writer,
1264///                               const char *data,
1265///                               int start,
1266///                               int len);
1267/// ```
1268///
1269/// # SAFETY
1270///
1271/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1272/// - `data` must be a valid pointer to `start + len` bytes or NULL.
1273#[no_mangle]
1274pub unsafe extern "C" fn xmlTextWriterWriteBinHex(
1275    writer: *mut XmlTextWriter,
1276    data: *const c_char,
1277    start: c_int,
1278    len: c_int,
1279) -> c_int {
1280    if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1281        return -1;
1282    }
1283    // SAFETY: writer is a valid XmlTextWriter.
1284    let w = unsafe { &mut *writer };
1285
1286    w.close_start_tag();
1287
1288    // Hex encode the data
1289    let data_slice =
1290        unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1291    let encoded = hex_encode(data_slice);
1292    w.write_slice(&encoded);
1293
1294    0
1295}
1296
1297/// Write a CDATA section.
1298///
1299/// # UPSTREAM-PARITY
1300///
1301/// ```c
1302/// int xmlTextWriterWriteCDATA(xmlTextWriterPtr writer, const xmlChar *content);
1303/// ```
1304///
1305/// # SAFETY
1306///
1307/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1308/// - `content` must be a valid null-terminated xmlChar string or NULL.
1309#[no_mangle]
1310pub unsafe extern "C" fn xmlTextWriterWriteCDATA(
1311    writer: *mut XmlTextWriter,
1312    content: *const xmlChar,
1313) -> c_int {
1314    if writer.is_null() {
1315        return -1;
1316    }
1317    // SAFETY: writer is a valid XmlTextWriter.
1318    let w = unsafe { &mut *writer };
1319
1320    w.close_start_tag();
1321
1322    w.write_raw(b"<![CDATA[" as *const u8, 9);
1323    if !content.is_null() {
1324        w.write_str(content);
1325    }
1326    w.write_raw(b"]]>" as *const u8, 3);
1327
1328    0
1329}
1330
1331/// Start a CDATA section.
1332///
1333/// # UPSTREAM-PARITY
1334///
1335/// ```c
1336/// int xmlTextWriterStartCDATA(xmlTextWriterPtr writer);
1337/// ```
1338///
1339/// # SAFETY
1340///
1341/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1342#[no_mangle]
1343pub unsafe extern "C" fn xmlTextWriterStartCDATA(writer: *mut XmlTextWriter) -> c_int {
1344    if writer.is_null() {
1345        return -1;
1346    }
1347    // SAFETY: writer is a valid XmlTextWriter.
1348    let w = unsafe { &mut *writer };
1349
1350    w.close_start_tag();
1351    w.write_raw(b"<![CDATA[" as *const u8, 9);
1352    w.state = WriterState::CData;
1353
1354    0
1355}
1356
1357/// End a CDATA section.
1358///
1359/// # UPSTREAM-PARITY
1360///
1361/// ```c
1362/// int xmlTextWriterEndCDATA(xmlTextWriterPtr writer);
1363/// ```
1364///
1365/// # SAFETY
1366///
1367/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1368#[no_mangle]
1369pub unsafe extern "C" fn xmlTextWriterEndCDATA(writer: *mut XmlTextWriter) -> c_int {
1370    if writer.is_null() {
1371        return -1;
1372    }
1373    // SAFETY: writer is a valid XmlTextWriter.
1374    let w = unsafe { &mut *writer };
1375
1376    if w.state != WriterState::CData {
1377        return -1;
1378    }
1379
1380    w.write_raw(b"]]>" as *const u8, 3);
1381    w.state = WriterState::None;
1382
1383    0
1384}
1385
1386/// Write a comment.
1387///
1388/// # UPSTREAM-PARITY
1389///
1390/// ```c
1391/// int xmlTextWriterWriteComment(xmlTextWriterPtr writer, const xmlChar *content);
1392/// ```
1393///
1394/// # SAFETY
1395///
1396/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1397/// - `content` must be a valid null-terminated xmlChar string or NULL.
1398#[no_mangle]
1399pub unsafe extern "C" fn xmlTextWriterWriteComment(
1400    writer: *mut XmlTextWriter,
1401    content: *const xmlChar,
1402) -> c_int {
1403    if writer.is_null() {
1404        return -1;
1405    }
1406    // SAFETY: writer is a valid XmlTextWriter.
1407    let w = unsafe { &mut *writer };
1408
1409    w.close_start_tag();
1410    w.write_indent();
1411    w.write_raw(b"<!--" as *const u8, 4);
1412    if !content.is_null() {
1413        w.write_str(content);
1414    }
1415    w.write_raw(b"-->" as *const u8, 3);
1416
1417    0
1418}
1419
1420/// Start a comment.
1421///
1422/// # UPSTREAM-PARITY
1423///
1424/// ```c
1425/// int xmlTextWriterStartComment(xmlTextWriterPtr writer);
1426/// ```
1427///
1428/// # SAFETY
1429///
1430/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1431#[no_mangle]
1432pub unsafe extern "C" fn xmlTextWriterStartComment(writer: *mut XmlTextWriter) -> c_int {
1433    if writer.is_null() {
1434        return -1;
1435    }
1436    // SAFETY: writer is a valid XmlTextWriter.
1437    let w = unsafe { &mut *writer };
1438
1439    w.close_start_tag();
1440    w.write_indent();
1441    w.write_raw(b"<!--" as *const u8, 4);
1442    w.state = WriterState::Comment;
1443
1444    0
1445}
1446
1447/// End a comment.
1448///
1449/// # UPSTREAM-PARITY
1450///
1451/// ```c
1452/// int xmlTextWriterEndComment(xmlTextWriterPtr writer);
1453/// ```
1454///
1455/// # SAFETY
1456///
1457/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1458#[no_mangle]
1459pub unsafe extern "C" fn xmlTextWriterEndComment(writer: *mut XmlTextWriter) -> c_int {
1460    if writer.is_null() {
1461        return -1;
1462    }
1463    // SAFETY: writer is a valid XmlTextWriter.
1464    let w = unsafe { &mut *writer };
1465
1466    if w.state != WriterState::Comment {
1467        return -1;
1468    }
1469
1470    w.write_raw(b"-->" as *const u8, 3);
1471    w.state = WriterState::None;
1472
1473    0
1474}
1475
1476/// Write a processing instruction.
1477///
1478/// # UPSTREAM-PARITY
1479///
1480/// ```c
1481/// int xmlTextWriterWritePI(xmlTextWriterPtr writer,
1482///                           const xmlChar *target,
1483///                           const xmlChar *content);
1484/// ```
1485///
1486/// # SAFETY
1487///
1488/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1489/// - `target`, `content` must be valid null-terminated strings or NULL.
1490#[no_mangle]
1491pub unsafe extern "C" fn xmlTextWriterWritePI(
1492    writer: *mut XmlTextWriter,
1493    target: *const xmlChar,
1494    content: *const xmlChar,
1495) -> c_int {
1496    if writer.is_null() || target.is_null() {
1497        return -1;
1498    }
1499    // SAFETY: writer is a valid XmlTextWriter.
1500    let w = unsafe { &mut *writer };
1501
1502    w.close_start_tag();
1503    w.write_indent();
1504    w.write_raw(b"<?" as *const u8, 2);
1505    w.write_str(target);
1506    if !content.is_null() && unsafe { *content != 0 } {
1507        w.write_byte(b' ');
1508        w.write_str(content);
1509    }
1510    w.write_raw(b"?>" as *const u8, 2);
1511
1512    0
1513}
1514
1515/// Start a processing instruction.
1516///
1517/// # UPSTREAM-PARITY
1518///
1519/// ```c
1520/// int xmlTextWriterStartPI(xmlTextWriterPtr writer, const xmlChar *target);
1521/// ```
1522///
1523/// # SAFETY
1524///
1525/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1526/// - `target` must be a valid null-terminated string or NULL.
1527#[no_mangle]
1528pub unsafe extern "C" fn xmlTextWriterStartPI(
1529    writer: *mut XmlTextWriter,
1530    target: *const xmlChar,
1531) -> c_int {
1532    if writer.is_null() || target.is_null() {
1533        return -1;
1534    }
1535    // SAFETY: writer is a valid XmlTextWriter.
1536    let w = unsafe { &mut *writer };
1537
1538    w.close_start_tag();
1539    w.write_indent();
1540    w.write_raw(b"<?" as *const u8, 2);
1541    w.write_str(target);
1542    w.write_byte(b' ');
1543    w.state = WriterState::PI;
1544
1545    0
1546}
1547
1548/// End a processing instruction.
1549///
1550/// # UPSTREAM-PARITY
1551///
1552/// ```c
1553/// int xmlTextWriterEndPI(xmlTextWriterPtr writer);
1554/// ```
1555///
1556/// # SAFETY
1557///
1558/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1559#[no_mangle]
1560pub unsafe extern "C" fn xmlTextWriterEndPI(writer: *mut XmlTextWriter) -> c_int {
1561    if writer.is_null() {
1562        return -1;
1563    }
1564    // SAFETY: writer is a valid XmlTextWriter.
1565    let w = unsafe { &mut *writer };
1566
1567    if w.state != WriterState::PI {
1568        return -1;
1569    }
1570
1571    w.write_raw(b"?>" as *const u8, 2);
1572    w.state = WriterState::None;
1573
1574    0
1575}
1576
1577// ═══════════════════════════════════════════════════════════════════════════════
1578// DTD writing
1579// ═══════════════════════════════════════════════════════════════════════════════
1580
1581/// Write a DTD declaration.
1582///
1583/// # UPSTREAM-PARITY
1584///
1585/// ```c
1586/// int xmlTextWriterWriteDTD(xmlTextWriterPtr writer,
1587///                            const xmlChar *name,
1588///                            const xmlChar *pubid,
1589///                            const xmlChar *sysid,
1590///                            const xmlChar *subset);
1591/// ```
1592///
1593/// # SAFETY
1594///
1595/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1596/// - `name`, `pubid`, `sysid`, `subset` must be valid null-terminated strings or NULL.
1597#[no_mangle]
1598pub unsafe extern "C" fn xmlTextWriterWriteDTD(
1599    writer: *mut XmlTextWriter,
1600    name: *const xmlChar,
1601    pubid: *const xmlChar,
1602    sysid: *const xmlChar,
1603    subset: *const xmlChar,
1604) -> c_int {
1605    if writer.is_null() || name.is_null() {
1606        return -1;
1607    }
1608    // SAFETY: writer is a valid XmlTextWriter.
1609    let w = unsafe { &mut *writer };
1610
1611    w.close_start_tag();
1612    w.write_raw(b"<!DOCTYPE " as *const u8, 10);
1613    w.write_str(name);
1614
1615    if !pubid.is_null() {
1616        w.write_raw(b" PUBLIC \"" as *const u8, 9);
1617        w.write_str(pubid);
1618        w.write_byte(b'"');
1619        if !sysid.is_null() {
1620            w.write_byte(b' ');
1621            w.write_byte(b'"');
1622            w.write_str(sysid);
1623            w.write_byte(b'"');
1624        }
1625    } else if !sysid.is_null() {
1626        w.write_raw(b" SYSTEM \"" as *const u8, 9);
1627        w.write_str(sysid);
1628        w.write_byte(b'"');
1629    }
1630
1631    if !subset.is_null() {
1632        w.write_raw(b" [" as *const u8, 2);
1633        w.write_str(subset);
1634        w.write_byte(b']');
1635    }
1636
1637    w.write_byte(b'>');
1638
1639    if w.indent != 0 {
1640        w.write_byte(b'\n');
1641    }
1642
1643    0
1644}
1645
1646/// Write a DTD element declaration.
1647///
1648/// # UPSTREAM-PARITY
1649///
1650/// ```c
1651/// int xmlTextWriterWriteDTDElement(xmlTextWriterPtr writer,
1652///                                   const xmlChar *name,
1653///                                   const xmlChar *content);
1654/// ```
1655///
1656/// # SAFETY
1657///
1658/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1659/// - `name`, `content` must be valid null-terminated strings or NULL.
1660#[no_mangle]
1661pub unsafe extern "C" fn xmlTextWriterWriteDTDElement(
1662    writer: *mut XmlTextWriter,
1663    name: *const xmlChar,
1664    content: *const xmlChar,
1665) -> c_int {
1666    if writer.is_null() || name.is_null() || content.is_null() {
1667        return -1;
1668    }
1669    // SAFETY: writer is a valid XmlTextWriter.
1670    let w = unsafe { &mut *writer };
1671
1672    w.close_start_tag();
1673    w.write_indent();
1674    w.write_raw(b"<!ELEMENT " as *const u8, 10);
1675    w.write_str(name);
1676    w.write_byte(b' ');
1677    w.write_str(content);
1678    w.write_raw(b">" as *const u8, 1);
1679
1680    0
1681}
1682
1683/// Write a DTD attribute declaration.
1684///
1685/// # UPSTREAM-PARITY
1686///
1687/// ```c
1688/// int xmlTextWriterWriteDTDAttribute(xmlTextWriterPtr writer,
1689///                                     const xmlChar *name,
1690///                                     const xmlChar *content);
1691/// ```
1692///
1693/// # SAFETY
1694///
1695/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1696/// - `name`, `content` must be valid null-terminated strings or NULL.
1697#[no_mangle]
1698pub unsafe extern "C" fn xmlTextWriterWriteDTDAttribute(
1699    writer: *mut XmlTextWriter,
1700    name: *const xmlChar,
1701    content: *const xmlChar,
1702) -> c_int {
1703    if writer.is_null() || name.is_null() || content.is_null() {
1704        return -1;
1705    }
1706    // SAFETY: writer is a valid XmlTextWriter.
1707    let w = unsafe { &mut *writer };
1708
1709    w.close_start_tag();
1710    w.write_indent();
1711    w.write_raw(b"<!ATTLIST " as *const u8, 10);
1712    w.write_str(name);
1713    w.write_byte(b' ');
1714    w.write_str(content);
1715    w.write_raw(b">" as *const u8, 1);
1716
1717    0
1718}
1719
1720/// Write a DTD entity declaration.
1721///
1722/// # UPSTREAM-PARITY
1723///
1724/// ```c
1725/// int xmlTextWriterWriteDTDEntity(xmlTextWriterPtr writer,
1726///                                  const xmlChar *name,
1727///                                  const xmlChar *content);
1728/// ```
1729///
1730/// # SAFETY
1731///
1732/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1733/// - `name`, `content` must be valid null-terminated strings or NULL.
1734#[no_mangle]
1735pub unsafe extern "C" fn xmlTextWriterWriteDTDEntity(
1736    writer: *mut XmlTextWriter,
1737    name: *const xmlChar,
1738    content: *const xmlChar,
1739) -> c_int {
1740    if writer.is_null() || name.is_null() || content.is_null() {
1741        return -1;
1742    }
1743    // SAFETY: writer is a valid XmlTextWriter.
1744    let w = unsafe { &mut *writer };
1745
1746    w.close_start_tag();
1747    w.write_indent();
1748    w.write_raw(b"<!ENTITY " as *const u8, 9);
1749    w.write_str(name);
1750    w.write_raw(b" \"" as *const u8, 2);
1751    w.write_str(content);
1752    w.write_raw(b"\">" as *const u8, 2);
1753
1754    0
1755}
1756
1757/// Write a DTD notation declaration.
1758///
1759/// # UPSTREAM-PARITY
1760///
1761/// ```c
1762/// int xmlTextWriterWriteDTDNotation(xmlTextWriterPtr writer,
1763///                                    const xmlChar *name,
1764///                                    const xmlChar *pubid,
1765///                                    const xmlChar *sysid);
1766/// ```
1767///
1768/// # SAFETY
1769///
1770/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1771/// - `name`, `pubid`, `sysid` must be valid null-terminated strings or NULL.
1772#[no_mangle]
1773pub unsafe extern "C" fn xmlTextWriterWriteDTDNotation(
1774    writer: *mut XmlTextWriter,
1775    name: *const xmlChar,
1776    pubid: *const xmlChar,
1777    sysid: *const xmlChar,
1778) -> c_int {
1779    if writer.is_null() || name.is_null() {
1780        return -1;
1781    }
1782    // SAFETY: writer is a valid XmlTextWriter.
1783    let w = unsafe { &mut *writer };
1784
1785    w.close_start_tag();
1786    w.write_indent();
1787    w.write_raw(b"<!NOTATION " as *const u8, 11);
1788    w.write_str(name);
1789
1790    if !pubid.is_null() {
1791        w.write_raw(b" PUBLIC \"" as *const u8, 9);
1792        w.write_str(pubid);
1793        w.write_byte(b'"');
1794        if !sysid.is_null() {
1795            w.write_byte(b' ');
1796            w.write_byte(b'"');
1797            w.write_str(sysid);
1798            w.write_byte(b'"');
1799        }
1800    } else if !sysid.is_null() {
1801        w.write_raw(b" SYSTEM \"" as *const u8, 9);
1802        w.write_str(sysid);
1803        w.write_byte(b'"');
1804    }
1805
1806    w.write_raw(b">" as *const u8, 1);
1807
1808    0
1809}
1810
1811// ═══════════════════════════════════════════════════════════════════════════════
1812// Start/End DTD declaration
1813/// Start a DTD declaration.
1814///
1815/// # UPSTREAM-PARITY
1816///
1817/// ```c
1818/// int xmlTextWriterStartDTD(xmlTextWriterPtr writer,
1819///                            const xmlChar *name,
1820///                            const xmlChar *pubid,
1821///                            const xmlChar *sysid);
1822/// ```
1823///
1824/// # SAFETY
1825///
1826/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1827/// - `name`, `pubid`, `sysid` must be valid null-terminated strings or NULL.
1828#[no_mangle]
1829pub unsafe extern "C" fn xmlTextWriterStartDTD(
1830    writer: *mut XmlTextWriter,
1831    name: *const xmlChar,
1832    pubid: *const xmlChar,
1833    sysid: *const xmlChar,
1834) -> c_int {
1835    if writer.is_null() || name.is_null() {
1836        return -1;
1837    }
1838    // SAFETY: writer is a valid XmlTextWriter.
1839    let w = unsafe { &mut *writer };
1840
1841    w.close_start_tag();
1842    w.write_raw(b"<!DOCTYPE " as *const u8, 10);
1843    w.write_str(name);
1844
1845    if !pubid.is_null() {
1846        w.write_raw(b" PUBLIC \"" as *const u8, 9);
1847        w.write_str(pubid);
1848        w.write_byte(b'"');
1849        if !sysid.is_null() {
1850            w.write_byte(b' ');
1851            w.write_byte(b'"');
1852            w.write_str(sysid);
1853            w.write_byte(b'"');
1854        }
1855    } else if !sysid.is_null() {
1856        w.write_raw(b" SYSTEM \"" as *const u8, 9);
1857        w.write_str(sysid);
1858        w.write_byte(b'"');
1859    }
1860
1861    w.write_raw(b" [" as *const u8, 2);
1862    w.state = WriterState::DTD;
1863
1864    if w.indent != 0 {
1865        w.write_byte(b'\n');
1866    }
1867
1868    0
1869}
1870
1871/// End a DTD declaration.
1872///
1873/// # UPSTREAM-PARITY
1874///
1875/// ```c
1876/// int xmlTextWriterEndDTD(xmlTextWriterPtr writer);
1877/// ```
1878///
1879/// # SAFETY
1880///
1881/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1882#[no_mangle]
1883pub unsafe extern "C" fn xmlTextWriterEndDTD(writer: *mut XmlTextWriter) -> c_int {
1884    if writer.is_null() {
1885        return -1;
1886    }
1887    // SAFETY: writer is a valid XmlTextWriter.
1888    let w = unsafe { &mut *writer };
1889
1890    if w.state != WriterState::DTD {
1891        return -1;
1892    }
1893
1894    if w.indent != 0 {
1895        w.write_byte(b'\n');
1896    }
1897    w.write_raw(b"]>" as *const u8, 2);
1898
1899    if w.indent != 0 {
1900        w.write_byte(b'\n');
1901    }
1902
1903    w.state = WriterState::None;
1904
1905    0
1906}
1907
1908/// Start a DTD element declaration.
1909///
1910/// # UPSTREAM-PARITY
1911///
1912/// ```c
1913/// int xmlTextWriterStartDTDElement(xmlTextWriterPtr writer, const xmlChar *name);
1914/// ```
1915///
1916/// # SAFETY
1917///
1918/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1919/// - `name` must be a valid null-terminated string or NULL.
1920#[no_mangle]
1921pub unsafe extern "C" fn xmlTextWriterStartDTDElement(
1922    writer: *mut XmlTextWriter,
1923    name: *const xmlChar,
1924) -> c_int {
1925    if writer.is_null() || name.is_null() {
1926        return -1;
1927    }
1928    // SAFETY: writer is a valid XmlTextWriter.
1929    let w = unsafe { &mut *writer };
1930
1931    w.close_start_tag();
1932    w.write_indent();
1933    w.write_raw(b"<!ELEMENT " as *const u8, 10);
1934    w.write_str(name);
1935    w.write_byte(b' ');
1936    w.state = WriterState::DTDElem;
1937
1938    0
1939}
1940
1941/// End a DTD element declaration.
1942///
1943/// # UPSTREAM-PARITY
1944///
1945/// ```c
1946/// int xmlTextWriterEndDTDElement(xmlTextWriterPtr writer);
1947/// ```
1948///
1949/// # SAFETY
1950///
1951/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1952#[no_mangle]
1953pub unsafe extern "C" fn xmlTextWriterEndDTDElement(writer: *mut XmlTextWriter) -> c_int {
1954    if writer.is_null() {
1955        return -1;
1956    }
1957    // SAFETY: writer is a valid XmlTextWriter.
1958    let w = unsafe { &mut *writer };
1959
1960    if w.state != WriterState::DTDElem {
1961        return -1;
1962    }
1963
1964    w.write_raw(b">" as *const u8, 1);
1965    w.state = WriterState::DTD;
1966
1967    0
1968}
1969
1970/// Start a DTD attribute declaration.
1971///
1972/// # UPSTREAM-PARITY
1973///
1974/// ```c
1975/// int xmlTextWriterStartDTDAttribute(xmlTextWriterPtr writer, const xmlChar *name);
1976/// ```
1977///
1978/// # SAFETY
1979///
1980/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
1981/// - `name` must be a valid null-terminated string or NULL.
1982#[no_mangle]
1983pub unsafe extern "C" fn xmlTextWriterStartDTDAttribute(
1984    writer: *mut XmlTextWriter,
1985    name: *const xmlChar,
1986) -> c_int {
1987    if writer.is_null() || name.is_null() {
1988        return -1;
1989    }
1990    // SAFETY: writer is a valid XmlTextWriter.
1991    let w = unsafe { &mut *writer };
1992
1993    w.close_start_tag();
1994    w.write_indent();
1995    w.write_raw(b"<!ATTLIST " as *const u8, 10);
1996    w.write_str(name);
1997    w.write_byte(b' ');
1998    w.state = WriterState::DTDAttr;
1999
2000    0
2001}
2002
2003/// End a DTD attribute declaration.
2004///
2005/// # UPSTREAM-PARITY
2006///
2007/// ```c
2008/// int xmlTextWriterEndDTDAttribute(xmlTextWriterPtr writer);
2009/// ```
2010///
2011/// # SAFETY
2012///
2013/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2014#[no_mangle]
2015pub unsafe extern "C" fn xmlTextWriterEndDTDAttribute(writer: *mut XmlTextWriter) -> c_int {
2016    if writer.is_null() {
2017        return -1;
2018    }
2019    // SAFETY: writer is a valid XmlTextWriter.
2020    let w = unsafe { &mut *writer };
2021
2022    if w.state != WriterState::DTDAttr {
2023        return -1;
2024    }
2025
2026    w.write_raw(b">" as *const u8, 1);
2027    w.state = WriterState::DTD;
2028
2029    0
2030}
2031
2032/// Start a DTD entity declaration.
2033///
2034/// # UPSTREAM-PARITY
2035///
2036/// ```c
2037/// int xmlTextWriterStartDTDEntity(xmlTextWriterPtr writer, const xmlChar *name);
2038/// ```
2039///
2040/// # SAFETY
2041///
2042/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2043/// - `name` must be a valid null-terminated string or NULL.
2044#[no_mangle]
2045pub unsafe extern "C" fn xmlTextWriterStartDTDEntity(
2046    writer: *mut XmlTextWriter,
2047    name: *const xmlChar,
2048) -> c_int {
2049    if writer.is_null() || name.is_null() {
2050        return -1;
2051    }
2052    // SAFETY: writer is a valid XmlTextWriter.
2053    let w = unsafe { &mut *writer };
2054
2055    w.close_start_tag();
2056    w.write_indent();
2057    w.write_raw(b"<!ENTITY " as *const u8, 9);
2058    w.write_str(name);
2059    w.write_raw(b" \"" as *const u8, 2);
2060    w.state = WriterState::DTDEntity;
2061
2062    0
2063}
2064
2065/// End a DTD entity declaration.
2066///
2067/// # UPSTREAM-PARITY
2068///
2069/// ```c
2070/// int xmlTextWriterEndDTDEntity(xmlTextWriterPtr writer);
2071/// ```
2072///
2073/// # SAFETY
2074///
2075/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2076#[no_mangle]
2077pub unsafe extern "C" fn xmlTextWriterEndDTDEntity(writer: *mut XmlTextWriter) -> c_int {
2078    if writer.is_null() {
2079        return -1;
2080    }
2081    // SAFETY: writer is a valid XmlTextWriter.
2082    let w = unsafe { &mut *writer };
2083
2084    if w.state != WriterState::DTDEntity {
2085        return -1;
2086    }
2087
2088    w.write_raw(b"\">" as *const u8, 2);
2089    w.state = WriterState::DTD;
2090
2091    0
2092}
2093
2094// ═══════════════════════════════════════════════════════════════════════════════
2095// Output management
2096// ═══════════════════════════════════════════════════════════════════════════════
2097
2098/// Flush the writer's output buffer.
2099///
2100/// # UPSTREAM-PARITY
2101///
2102/// ```c
2103/// int xmlTextWriterFlush(xmlTextWriterPtr writer);
2104/// ```
2105///
2106/// # SAFETY
2107///
2108/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2109#[no_mangle]
2110pub unsafe extern "C" fn xmlTextWriterFlush(writer: *mut XmlTextWriter) -> c_int {
2111    if writer.is_null() {
2112        return -1;
2113    }
2114    // SAFETY: writer is a valid XmlTextWriter.
2115    let w = unsafe { &mut *writer };
2116
2117    if w.output.is_null() {
2118        return -1;
2119    }
2120
2121    // Close any open start tag
2122    w.close_start_tag();
2123
2124    io::output_buffer_flush(w.output)
2125}
2126
2127/// Set indentation on/off.
2128///
2129/// # UPSTREAM-PARITY
2130///
2131/// ```c
2132/// int xmlTextWriterSetIndent(xmlTextWriterPtr writer, int indent);
2133/// ```
2134///
2135/// # SAFETY
2136///
2137/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2138#[no_mangle]
2139pub unsafe extern "C" fn xmlTextWriterSetIndent(
2140    writer: *mut XmlTextWriter,
2141    indent: c_int,
2142) -> c_int {
2143    if writer.is_null() {
2144        return -1;
2145    }
2146    // SAFETY: writer is a valid XmlTextWriter.
2147    unsafe { (*writer).indent = indent };
2148    0
2149}
2150
2151/// Set the indentation string.
2152///
2153/// # UPSTREAM-PARITY
2154///
2155/// ```c
2156/// int xmlTextWriterSetIndentString(xmlTextWriterPtr writer, const xmlChar *str);
2157/// ```
2158///
2159/// # SAFETY
2160///
2161/// - `writer` must be a valid pointer to an `XmlTextWriter` or NULL.
2162/// - `str` must be a valid null-terminated xmlChar string or NULL.
2163#[no_mangle]
2164pub unsafe extern "C" fn xmlTextWriterSetIndentString(
2165    writer: *mut XmlTextWriter,
2166    str: *const xmlChar,
2167) -> c_int {
2168    if writer.is_null() || str.is_null() {
2169        return -1;
2170    }
2171    // SAFETY: writer is a valid XmlTextWriter.
2172    let w = unsafe { &mut *writer };
2173    w.indent_string = unsafe { c_str_to_vec(str) };
2174    0
2175}
2176
2177// ═══════════════════════════════════════════════════════════════════════════════
2178// Internal helpers
2179// ═══════════════════════════════════════════════════════════════════════════════
2180
2181/// Convert a null-terminated C string to a Vec<u8> (including the null terminator).
2182///
2183/// # SAFETY
2184///
2185/// - `s` must be a valid pointer to a null-terminated string.
2186unsafe fn c_str_to_vec(s: *const u8) -> Vec<u8> {
2187    if s.is_null() {
2188        return Vec::new();
2189    }
2190    let len = tree::xml_strlen(s);
2191    let mut v = Vec::with_capacity(len as usize + 1);
2192    unsafe {
2193        for i in 0..len as isize {
2194            v.push(*s.offset(i));
2195        }
2196        v.push(0);
2197    }
2198    v
2199}
2200
2201/// Base64 encode a byte slice.
2202fn base64_encode(data: &[u8]) -> Vec<u8> {
2203    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2204    let mut result = Vec::with_capacity((data.len() + 2) / 3 * 4);
2205    for chunk in data.chunks(3) {
2206        let b0 = chunk[0];
2207        let b1 = chunk.get(1).copied().unwrap_or(0);
2208        let b2 = chunk.get(2).copied().unwrap_or(0);
2209
2210        result.push(CHARS[((b0 >> 2) & 0x3F) as usize]);
2211        result.push(CHARS[(((b0 << 4) | (b1 >> 4)) & 0x3F) as usize]);
2212        result.push(if chunk.len() > 1 {
2213            CHARS[(((b1 << 2) | (b2 >> 6)) & 0x3F) as usize]
2214        } else {
2215            b'='
2216        });
2217        result.push(if chunk.len() > 2 {
2218            CHARS[(b2 & 0x3F) as usize]
2219        } else {
2220            b'='
2221        });
2222    }
2223    result
2224}
2225
2226/// Hex encode a byte slice (lowercase).
2227fn hex_encode(data: &[u8]) -> Vec<u8> {
2228    const CHARS: &[u8] = b"0123456789abcdef";
2229    let mut result = Vec::with_capacity(data.len() * 2);
2230    for &b in data {
2231        result.push(CHARS[((b >> 4) & 0x0F) as usize]);
2232        result.push(CHARS[(b & 0x0F) as usize]);
2233    }
2234    result
2235}
2236
2237// ═══════════════════════════════════════════════════════════════════════════════
2238// Tests
2239// ═══════════════════════════════════════════════════════════════════════════════
2240
2241#[cfg(test)]
2242mod tests {
2243    use super::*;
2244    use core::ptr;
2245
2246    /// Helper: create a memory buffer writer for testing.
2247    unsafe fn create_test_writer() -> (*mut XmlTextWriter, *mut _xmlBuffer) {
2248        let buf = io::buf_create(256);
2249        assert!(!buf.is_null(), "buf_create failed");
2250        let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
2251        assert!(!out.is_null(), "output_buffer_create_buffer failed");
2252        let writer = xmlNewTextWriter(out);
2253        assert!(!writer.is_null(), "xmlNewTextWriter failed");
2254        (writer, buf)
2255    }
2256
2257    /// Helper: get the buffer content as a string.
2258    unsafe fn buf_to_string(buf: *mut _xmlBuffer) -> String {
2259        let content = io::buf_content(buf);
2260        let len = io::buf_length(buf);
2261        if content.is_null() || len <= 0 {
2262            return String::new();
2263        }
2264        let slice = unsafe { core::slice::from_raw_parts(content, len as usize) };
2265        String::from_utf8_lossy(slice).to_string()
2266    }
2267
2268    /// Helper: flush writer and return buffer content.
2269    unsafe fn flush_and_get(writer: *mut XmlTextWriter, buf: *mut _xmlBuffer) -> String {
2270        xmlTextWriterFlush(writer);
2271        buf_to_string(buf)
2272    }
2273
2274    // ═══════════════════════════════════════════════════════════════════════════
2275    // Test: Write a simple document
2276    // ═══════════════════════════════════════════════════════════════════════════
2277
2278    #[test]
2279    fn test_write_simple_document() {
2280        unsafe {
2281            let (writer, buf) = create_test_writer();
2282
2283            let r = xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2284            assert_eq!(r, 0, "StartDocument failed");
2285
2286            let r = xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2287            assert_eq!(r, 0, "StartElement(root) failed");
2288
2289            let r = xmlTextWriterWriteString(writer, b"Hello, World!\0" as *const u8);
2290            assert_eq!(r, 0, "WriteString failed");
2291
2292            let r = xmlTextWriterEndElement(writer);
2293            assert_eq!(r, 0, "EndElement failed");
2294
2295            let r = xmlTextWriterEndDocument(writer);
2296            assert_eq!(r, 0, "EndDocument failed");
2297
2298            let result = flush_and_get(writer, buf);
2299            assert!(
2300                result.contains("<?xml version=\"1.0\"?>"),
2301                "Missing XML declaration. Got: {}",
2302                result
2303            );
2304            assert!(
2305                result.contains("<root>"),
2306                "Missing <root> start tag. Got: {}",
2307                result
2308            );
2309            assert!(
2310                result.contains("Hello, World!"),
2311                "Missing content. Got: {}",
2312                result
2313            );
2314            assert!(
2315                result.contains("</root>"),
2316                "Missing </root> end tag. Got: {}",
2317                result
2318            );
2319
2320            xmlFreeTextWriter(writer);
2321            io::buf_free(buf);
2322        }
2323    }
2324
2325    // ═══════════════════════════════════════════════════════════════════════════
2326    // Test: Write elements with attributes
2327    // ═══════════════════════════════════════════════════════════════════════════
2328
2329    #[test]
2330    fn test_write_element_with_attributes() {
2331        unsafe {
2332            let (writer, buf) = create_test_writer();
2333
2334            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2335            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2336            xmlTextWriterWriteAttribute(writer, b"id\0" as *const u8, b"123\0" as *const u8);
2337            xmlTextWriterWriteAttribute(
2338                writer,
2339                b"name\0" as *const u8,
2340                b"test & demo\0" as *const u8,
2341            );
2342            xmlTextWriterEndElement(writer);
2343            xmlTextWriterEndDocument(writer);
2344
2345            let result = flush_and_get(writer, buf);
2346            assert!(
2347                result.contains("id=\"123\""),
2348                "Missing id attribute. Got: {}",
2349                result
2350            );
2351            assert!(
2352                result.contains("name=\"test &amp; demo\""),
2353                "Missing or improperly escaped name attribute. Got: {}",
2354                result
2355            );
2356            assert!(
2357                result.contains("<root"),
2358                "Missing root element. Got: {}",
2359                result
2360            );
2361
2362            xmlFreeTextWriter(writer);
2363            io::buf_free(buf);
2364        }
2365    }
2366
2367    // ═══════════════════════════════════════════════════════════════════════════
2368    // Test: Write with namespaces
2369    // ═══════════════════════════════════════════════════════════════════════════
2370
2371    #[test]
2372    fn test_write_with_namespaces() {
2373        unsafe {
2374            let (writer, buf) = create_test_writer();
2375
2376            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2377            xmlTextWriterStartElementNS(
2378                writer,
2379                b"ns\0" as *const u8,
2380                b"root\0" as *const u8,
2381                b"http://example.com/ns\0" as *const u8,
2382            );
2383            xmlTextWriterWriteAttributeNS(
2384                writer,
2385                ptr::null(),
2386                b"attr\0" as *const u8,
2387                ptr::null(),
2388                b"value\0" as *const u8,
2389            );
2390            xmlTextWriterEndElement(writer);
2391            xmlTextWriterEndDocument(writer);
2392
2393            let result = flush_and_get(writer, buf);
2394            assert!(
2395                result.contains("ns:root"),
2396                "Missing namespace prefix. Got: {}",
2397                result
2398            );
2399            assert!(
2400                result.contains("xmlns:ns=\"http://example.com/ns\""),
2401                "Missing xmlns declaration. Got: {}",
2402                result
2403            );
2404
2405            xmlFreeTextWriter(writer);
2406            io::buf_free(buf);
2407        }
2408    }
2409
2410    // ═══════════════════════════════════════════════════════════════════════════
2411    // Test: Write text, CDATA, comments, PIs
2412    // ═══════════════════════════════════════════════════════════════════════════
2413
2414    #[test]
2415    fn test_write_text_cdata_comment_pi() {
2416        unsafe {
2417            let (writer, buf) = create_test_writer();
2418
2419            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2420
2421            xmlTextWriterStartElement(writer, b"doc\0" as *const u8);
2422            xmlTextWriterWriteString(writer, b"text content\0" as *const u8);
2423            xmlTextWriterEndElement(writer);
2424
2425            xmlTextWriterWriteComment(writer, b"a comment\0" as *const u8);
2426
2427            xmlTextWriterWritePI(writer, b"target\0" as *const u8, b"data\0" as *const u8);
2428
2429            xmlTextWriterStartElement(writer, b"cdata\0" as *const u8);
2430            xmlTextWriterWriteCDATA(writer, b"<greeting>Hello</greeting>\0" as *const u8);
2431            xmlTextWriterEndElement(writer);
2432
2433            xmlTextWriterEndDocument(writer);
2434
2435            let result = flush_and_get(writer, buf);
2436            assert!(
2437                result.contains("text content"),
2438                "Missing text content. Got: {}",
2439                result
2440            );
2441            assert!(
2442                result.contains("<!--a comment-->"),
2443                "Missing comment. Got: {}",
2444                result
2445            );
2446            assert!(
2447                result.contains("<?target data?>"),
2448                "Missing PI. Got: {}",
2449                result
2450            );
2451            assert!(
2452                result.contains("<![CDATA["),
2453                "Missing CDATA start. Got: {}",
2454                result
2455            );
2456            assert!(
2457                result.contains("<greeting>Hello</greeting>"),
2458                "Missing CDATA content. Got: {}",
2459                result
2460            );
2461
2462            xmlFreeTextWriter(writer);
2463            io::buf_free(buf);
2464        }
2465    }
2466
2467    // ═══════════════════════════════════════════════════════════════════════════
2468    // Test: DTD writing
2469    // ═══════════════════════════════════════════════════════════════════════════
2470
2471    #[test]
2472    fn test_write_dtd() {
2473        unsafe {
2474            let (writer, buf) = create_test_writer();
2475
2476            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2477
2478            xmlTextWriterWriteDTD(
2479                writer,
2480                b"html\0" as *const u8,
2481                ptr::null(),
2482                b"http://www.w3.org/TR/html4/strict.dtd\0" as *const u8,
2483                ptr::null(),
2484            );
2485
2486            xmlTextWriterStartElement(writer, b"html\0" as *const u8);
2487            xmlTextWriterEndElement(writer);
2488            xmlTextWriterEndDocument(writer);
2489
2490            let result = flush_and_get(writer, buf);
2491            assert!(
2492                result.contains("<!DOCTYPE html SYSTEM"),
2493                "Missing DTD. Got: {}",
2494                result
2495            );
2496
2497            xmlFreeTextWriter(writer);
2498            io::buf_free(buf);
2499        }
2500    }
2501
2502    // ═══════════════════════════════════════════════════════════════════════════
2503    // Test: DTD with internal subset declarations
2504    // ═══════════════════════════════════════════════════════════════════════════
2505
2506    #[test]
2507    fn test_write_dtd_with_subset() {
2508        unsafe {
2509            let (writer, buf) = create_test_writer();
2510
2511            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2512
2513            xmlTextWriterStartDTD(writer, b"root\0" as *const u8, ptr::null(), ptr::null());
2514            xmlTextWriterWriteDTDElement(
2515                writer,
2516                b"child\0" as *const u8,
2517                b"(#PCDATA)\0" as *const u8,
2518            );
2519            xmlTextWriterWriteDTDAttribute(
2520                writer,
2521                b"child\0" as *const u8,
2522                b"id CDATA #IMPLIED\0" as *const u8,
2523            );
2524            xmlTextWriterWriteDTDEntity(
2525                writer,
2526                b"copy\0" as *const u8,
2527                b"Copyright Me\0" as *const u8,
2528            );
2529            xmlTextWriterWriteDTDNotation(
2530                writer,
2531                b"note\0" as *const u8,
2532                b"PublicID\0" as *const u8,
2533                ptr::null(),
2534            );
2535            xmlTextWriterEndDTD(writer);
2536
2537            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2538            xmlTextWriterEndElement(writer);
2539            xmlTextWriterEndDocument(writer);
2540
2541            let result = flush_and_get(writer, buf);
2542            assert!(
2543                result.contains("<!DOCTYPE root"),
2544                "Missing DTD start. Got: {}",
2545                result
2546            );
2547            assert!(
2548                result.contains("<!ELEMENT child (#PCDATA)>"),
2549                "Missing DTD element. Got: {}",
2550                result
2551            );
2552            assert!(
2553                result.contains("<!ATTLIST child id CDATA #IMPLIED>"),
2554                "Missing DTD attribute. Got: {}",
2555                result
2556            );
2557            assert!(
2558                result.contains("<!ENTITY copy \"Copyright Me\">"),
2559                "Missing DTD entity. Got: {}",
2560                result
2561            );
2562            assert!(
2563                result.contains("<!NOTATION note PUBLIC \"PublicID\">"),
2564                "Missing DTD notation. Got: {}",
2565                result
2566            );
2567
2568            xmlFreeTextWriter(writer);
2569            io::buf_free(buf);
2570        }
2571    }
2572
2573    // ═══════════════════════════════════════════════════════════════════════════
2574    // Test: Indentation control
2575    // ═══════════════════════════════════════════════════════════════════════════
2576
2577    #[test]
2578    fn test_indentation_control() {
2579        unsafe {
2580            let (writer, buf) = create_test_writer();
2581
2582            // Enable indentation with tabs
2583            xmlTextWriterSetIndent(writer, 1);
2584            xmlTextWriterSetIndentString(writer, b"\t\0" as *const u8);
2585
2586            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2587            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2588            xmlTextWriterStartElement(writer, b"child\0" as *const u8);
2589            xmlTextWriterWriteString(writer, b"content\0" as *const u8);
2590            xmlTextWriterEndElement(writer);
2591            xmlTextWriterEndElement(writer);
2592            xmlTextWriterEndDocument(writer);
2593
2594            let result = flush_and_get(writer, buf);
2595
2596            // Check that we have indentation
2597            assert!(
2598                result.contains('\t'),
2599                "Expected tab indentation. Got: {}",
2600                result
2601            );
2602            // Check the XML declaration and elements are present
2603            assert!(result.contains("<root>"), "Missing root. Got: {}", result);
2604            assert!(result.contains("<child>"), "Missing child. Got: {}", result);
2605
2606            xmlFreeTextWriter(writer);
2607            io::buf_free(buf);
2608        }
2609    }
2610
2611    // ═══════════════════════════════════════════════════════════════════════════
2612    // Test: Memory output
2613    // ═══════════════════════════════════════════════════════════════════════════
2614
2615    #[test]
2616    fn test_memory_output() {
2617        unsafe {
2618            let buf = io::buf_create(256);
2619            assert!(!buf.is_null(), "buf_create failed");
2620
2621            let writer = xmlNewTextWriterMemory(buf, 0);
2622            assert!(!writer.is_null(), "xmlNewTextWriterMemory failed");
2623
2624            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2625            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2626            xmlTextWriterWriteString(writer, b"memory test\0" as *const u8);
2627            xmlTextWriterEndElement(writer);
2628            xmlTextWriterEndDocument(writer);
2629
2630            xmlTextWriterFlush(writer);
2631            let result = buf_to_string(buf);
2632            assert!(
2633                result.contains("memory test"),
2634                "Missing content in memory output. Got: {}",
2635                result
2636            );
2637
2638            xmlFreeTextWriter(writer);
2639            io::buf_free(buf);
2640        }
2641    }
2642
2643    // ═══════════════════════════════════════════════════════════════════════════
2644    // Test: Flush and close
2645    // ═══════════════════════════════════════════════════════════════════════════
2646
2647    #[test]
2648    fn test_flush_and_close() {
2649        unsafe {
2650            let (writer, buf) = create_test_writer();
2651
2652            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2653            xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2654            xmlTextWriterWriteString(writer, b"flush me\0" as *const u8);
2655
2656            // Flush mid-document
2657            let r = xmlTextWriterFlush(writer);
2658            assert!(r >= 0, "Flush should return non-negative, got {}", r);
2659
2660            xmlTextWriterEndElement(writer);
2661            xmlTextWriterEndDocument(writer);
2662
2663            xmlFreeTextWriter(writer);
2664            io::buf_free(buf);
2665        }
2666    }
2667
2668    // ═══════════════════════════════════════════════════════════════════════════
2669    // Test: Edge cases — null writer, null parameters
2670    // ═══════════════════════════════════════════════════════════════════════════
2671
2672    #[test]
2673    fn test_null_handling() {
2674        unsafe {
2675            // All functions should gracefully handle NULL writer
2676            assert_eq!(
2677                xmlTextWriterStartDocument(ptr::null_mut(), ptr::null(), ptr::null(), ptr::null()),
2678                -1
2679            );
2680            assert_eq!(xmlTextWriterEndDocument(ptr::null_mut()), -1);
2681            assert_eq!(
2682                xmlTextWriterStartElement(ptr::null_mut(), b"x\0" as *const u8),
2683                -1
2684            );
2685            assert_eq!(xmlTextWriterEndElement(ptr::null_mut()), -1);
2686            assert_eq!(
2687                xmlTextWriterWriteString(ptr::null_mut(), b"x\0" as *const u8),
2688                -1
2689            );
2690            assert_eq!(
2691                xmlTextWriterWriteRaw(ptr::null_mut(), b"x\0" as *const u8),
2692                -1
2693            );
2694            assert_eq!(
2695                xmlTextWriterWriteCDATA(ptr::null_mut(), b"x\0" as *const u8),
2696                -1
2697            );
2698            assert_eq!(
2699                xmlTextWriterWriteComment(ptr::null_mut(), b"x\0" as *const u8),
2700                -1
2701            );
2702            assert_eq!(
2703                xmlTextWriterWritePI(ptr::null_mut(), b"x\0" as *const u8, ptr::null()),
2704                -1
2705            );
2706            assert_eq!(xmlTextWriterFlush(ptr::null_mut()), -1);
2707            assert_eq!(xmlTextWriterSetIndent(ptr::null_mut(), 1), -1);
2708            assert_eq!(
2709                xmlTextWriterSetIndentString(ptr::null_mut(), b"  \0" as *const u8),
2710                -1
2711            );
2712            assert_eq!(
2713                xmlTextWriterWriteAttribute(
2714                    ptr::null_mut(),
2715                    b"n\0" as *const u8,
2716                    b"v\0" as *const u8
2717                ),
2718                -1
2719            );
2720
2721            // Null writer should not crash free
2722            xmlFreeTextWriter(ptr::null_mut());
2723        }
2724    }
2725
2726    // ═══════════════════════════════════════════════════════════════════════════
2727    // Test: Nested elements
2728    // ═══════════════════════════════════════════════════════════════════════════
2729
2730    #[test]
2731    fn test_nested_elements() {
2732        unsafe {
2733            let (writer, buf) = create_test_writer();
2734
2735            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2736            xmlTextWriterStartElement(writer, b"a\0" as *const u8);
2737            xmlTextWriterStartElement(writer, b"b\0" as *const u8);
2738            xmlTextWriterStartElement(writer, b"c\0" as *const u8);
2739            xmlTextWriterWriteString(writer, b"deep\0" as *const u8);
2740            xmlTextWriterEndElement(writer);
2741            xmlTextWriterEndElement(writer);
2742            xmlTextWriterEndElement(writer);
2743            xmlTextWriterEndDocument(writer);
2744
2745            let result = flush_and_get(writer, buf);
2746            assert!(result.contains("<a>"), "Missing <a>. Got: {}", result);
2747            assert!(result.contains("<b>"), "Missing <b>. Got: {}", result);
2748            assert!(result.contains("<c>"), "Missing <c>. Got: {}", result);
2749            assert!(result.contains("</a>"), "Missing </a>. Got: {}", result);
2750            assert!(result.contains("</b>"), "Missing </b>. Got: {}", result);
2751            assert!(result.contains("</c>"), "Missing </c>. Got: {}", result);
2752
2753            xmlFreeTextWriter(writer);
2754            io::buf_free(buf);
2755        }
2756    }
2757
2758    // ═══════════════════════════════════════════════════════════════════════════
2759    // Test: Self-closing element (no content)
2760    // ═══════════════════════════════════════════════════════════════════════════
2761
2762    #[test]
2763    fn test_self_closing_element() {
2764        unsafe {
2765            let (writer, buf) = create_test_writer();
2766
2767            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2768            xmlTextWriterStartElement(writer, b"empty\0" as *const u8);
2769            xmlTextWriterEndElement(writer);
2770            xmlTextWriterEndDocument(writer);
2771
2772            let result = flush_and_get(writer, buf);
2773            assert!(
2774                result.contains("<empty/>"),
2775                "Expected self-closing <empty/>. Got: {}",
2776                result
2777            );
2778
2779            xmlFreeTextWriter(writer);
2780            io::buf_free(buf);
2781        }
2782    }
2783
2784    // ═══════════════════════════════════════════════════════════════════════════
2785    // Test: Full end element (not self-closing)
2786    // ═══════════════════════════════════════════════════════════════════════════
2787
2788    #[test]
2789    fn test_full_end_element() {
2790        unsafe {
2791            let (writer, buf) = create_test_writer();
2792
2793            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2794            xmlTextWriterStartElement(writer, b"container\0" as *const u8);
2795            xmlTextWriterFullEndElement(writer);
2796            xmlTextWriterEndDocument(writer);
2797
2798            let result = flush_and_get(writer, buf);
2799            assert!(
2800                result.contains("<container>"),
2801                "Missing <container>. Got: {}",
2802                result
2803            );
2804            assert!(
2805                result.contains("</container>"),
2806                "Missing </container>. Got: {}",
2807                result
2808            );
2809
2810            xmlFreeTextWriter(writer);
2811            io::buf_free(buf);
2812        }
2813    }
2814
2815    // ═══════════════════════════════════════════════════════════════════════════
2816    // Test: WriteElement (element with inline content)
2817    // ═══════════════════════════════════════════════════════════════════════════
2818
2819    #[test]
2820    fn test_write_element_inline() {
2821        unsafe {
2822            let (writer, buf) = create_test_writer();
2823
2824            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2825            xmlTextWriterWriteElement(writer, b"greeting\0" as *const u8, b"Hello\0" as *const u8);
2826            xmlTextWriterEndDocument(writer);
2827
2828            let result = flush_and_get(writer, buf);
2829            assert!(
2830                result.contains("<greeting>Hello</greeting>"),
2831                "Expected <greeting>Hello</greeting>. Got: {}",
2832                result
2833            );
2834
2835            xmlFreeTextWriter(writer);
2836            io::buf_free(buf);
2837        }
2838    }
2839
2840    // ═══════════════════════════════════════════════════════════════════════════
2841    // Test: XML escaping in text content
2842    // ═══════════════════════════════════════════════════════════════════════════
2843
2844    #[test]
2845    fn test_text_escaping() {
2846        unsafe {
2847            let (writer, buf) = create_test_writer();
2848
2849            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2850            xmlTextWriterStartElement(writer, b"esc\0" as *const u8);
2851            xmlTextWriterWriteString(writer, b"a < b & b > a\0" as *const u8);
2852            xmlTextWriterEndElement(writer);
2853            xmlTextWriterEndDocument(writer);
2854
2855            let result = flush_and_get(writer, buf);
2856            assert!(
2857                result.contains("a &lt; b &amp; b &gt; a"),
2858                "Expected escaped content. Got: {}",
2859                result
2860            );
2861
2862            xmlFreeTextWriter(writer);
2863            io::buf_free(buf);
2864        }
2865    }
2866
2867    // ═══════════════════════════════════════════════════════════════════════════
2868    // Test: Raw content (no escaping)
2869    // ═══════════════════════════════════════════════════════════════════════════
2870
2871    #[test]
2872    fn test_raw_content() {
2873        unsafe {
2874            let (writer, buf) = create_test_writer();
2875
2876            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2877            xmlTextWriterStartElement(writer, b"raw\0" as *const u8);
2878            xmlTextWriterWriteRaw(writer, b"<unencoded>&special;</unencoded>\0" as *const u8);
2879            xmlTextWriterEndElement(writer);
2880            xmlTextWriterEndDocument(writer);
2881
2882            let result = flush_and_get(writer, buf);
2883            assert!(
2884                result.contains("<unencoded>&special;</unencoded>"),
2885                "Expected raw unencoded content. Got: {}",
2886                result
2887            );
2888
2889            xmlFreeTextWriter(writer);
2890            io::buf_free(buf);
2891        }
2892    }
2893
2894    // ═══════════════════════════════════════════════════════════════════════════
2895    // Test: Base64 writing
2896    // ═══════════════════════════════════════════════════════════════════════════
2897
2898    #[test]
2899    fn test_base64_write() {
2900        unsafe {
2901            let (writer, buf) = create_test_writer();
2902
2903            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2904            xmlTextWriterStartElement(writer, b"data\0" as *const u8);
2905            let test_data = b"Hello, World!";
2906            xmlTextWriterWriteBase64(
2907                writer,
2908                test_data.as_ptr() as *const c_char,
2909                0,
2910                test_data.len() as c_int,
2911            );
2912            xmlTextWriterEndElement(writer);
2913            xmlTextWriterEndDocument(writer);
2914
2915            let result = flush_and_get(writer, buf);
2916            assert!(
2917                result.contains("SGVsbG8sIFdvcmxkIQ"),
2918                "Expected Base64-encoded content. Got: {}",
2919                result
2920            );
2921
2922            xmlFreeTextWriter(writer);
2923            io::buf_free(buf);
2924        }
2925    }
2926
2927    // ═══════════════════════════════════════════════════════════════════════════
2928    // Test: Incremental CDATA/comment/PI
2929    // ═══════════════════════════════════════════════════════════════════════════
2930
2931    #[test]
2932    fn test_incremental_cdata_comment_pi() {
2933        unsafe {
2934            let (writer, buf) = create_test_writer();
2935
2936            xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2937
2938            // Incremental CDATA
2939            xmlTextWriterStartElement(writer, b"inc\0" as *const u8);
2940            xmlTextWriterStartCDATA(writer);
2941            xmlTextWriterWriteString(writer, b"cdata content\0" as *const u8);
2942            xmlTextWriterEndCDATA(writer);
2943            xmlTextWriterEndElement(writer);
2944
2945            // Incremental comment
2946            xmlTextWriterStartComment(writer);
2947            xmlTextWriterWriteString(writer, b"comment text\0" as *const u8);
2948            xmlTextWriterEndComment(writer);
2949
2950            // Incremental PI
2951            xmlTextWriterStartPI(writer, b"xml-stylesheet\0" as *const u8);
2952            xmlTextWriterWriteString(
2953                writer,
2954                b"type=\"text/xsl\" href=\"style.xsl\"\0" as *const u8,
2955            );
2956            xmlTextWriterEndPI(writer);
2957
2958            xmlTextWriterEndDocument(writer);
2959
2960            let result = flush_and_get(writer, buf);
2961            assert!(
2962                result.contains("<![CDATA["),
2963                "Missing CDATA. Got: {}",
2964                result
2965            );
2966            assert!(
2967                result.contains("<!--comment text-->"),
2968                "Missing comment. Got: {}",
2969                result
2970            );
2971            assert!(
2972                result.contains("<?xml-stylesheet"),
2973                "Missing PI. Got: {}",
2974                result
2975            );
2976
2977            xmlFreeTextWriter(writer);
2978            io::buf_free(buf);
2979        }
2980    }
2981
2982    // ═══════════════════════════════════════════════════════════════════════════
2983    // Test: xmlNewTextWriterFilename returns NULL for NULL uri
2984    // ═══════════════════════════════════════════════════════════════════════════
2985
2986    #[test]
2987    fn test_new_writer_filename_null() {
2988        unsafe {
2989            let writer = xmlNewTextWriterFilename(ptr::null(), 0);
2990            assert!(writer.is_null(), "Expected NULL for null URI");
2991        }
2992    }
2993
2994    // ═══════════════════════════════════════════════════════════════════════════
2995    // Test: xmlNewTextWriter returns NULL for NULL output
2996    // ═══════════════════════════════════════════════════════════════════════════
2997
2998    #[test]
2999    fn test_new_writer_null_output() {
3000        unsafe {
3001            let writer = xmlNewTextWriter(ptr::null_mut());
3002            assert!(writer.is_null(), "Expected NULL for null output");
3003        }
3004    }
3005}