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