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