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