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