Skip to main content

libxml_rs/abi/
exports_xml2.rs

1//! C ABI exports for libxml2.so.2 — no_mangle extern "C" functions (§1, §16).
2//!
3//! This module contains all `#[no_mangle] pub extern "C"` function definitions
4//! that form the public ABI of libxml2.so.2. Every function here corresponds to
5//! a function in the upstream libxml2 headers.
6//!
7//! # Phase 1 status
8//!
9//! Complete — all major ABI entry points are implemented. Functions that require
10//! modules not yet implemented (tree, parser, etc.) call into those modules,
11//! which will be filled in as Phase 1 continues.
12//!
13//! # Organization
14//!
15//! Exports are grouped by subsystem in the order they appear in upstream headers:
16//!
17//! 1. Initialization / Cleanup
18//! 2. Version
19//! 3. Memory / Allocator
20//! 4. Error handling
21//! 5. String utilities
22//! 6. Tree (document, node, attribute, namespace, DTD, entity)
23//! 7. Parser (SAX, DOM, push, reader)
24//! 8. I/O
25//! 9. Dictionary
26//! 10. Hash table
27//! 11. List
28//! 12. Buffer
29//! 13. Encoding
30//! 14. XPath
31//! 15. XInclude
32//! 16. Catalog
33//! 17. HTML
34//! 18. Debug/misc
35//!
36//! # Upstream contract
37//!
38//! The parity target is the complete libxml2.so.2 export surface of the oracle
39//! DSO (libxml2 2.15.3): `globals.c`, `parser.c`, `threads.c`, `encoding.c`,
40//! `xmlmemory.c` and `xmlstring.c` entry points with upstream header
41//! signatures. Residuals R-000116, R-000117, R-000132, R-000133, R-000135,
42//! R-000136, R-000138, R-000157, R-000161, R-000162, R-000163, R-000164 and
43//! R-000165 all touch this module.
44//!
45//! # Conceptual behavior
46//!
47//! This module implements the core libxml2 ABI: init/cleanup, version, memory,
48//! error handling, string utilities, tree/document construction, parser entry
49//! points, I/O, dict/hash/list, buffer, encoding, XPath and catalog — grouped
50//! by subsystem in upstream header order. Functions needing the engine call
51//! into `src/xml/*`; the rest are faithful ports.
52//!
53//! # Ownership & safety invariants
54//!
55//! Ownership follows OWNERSHIP_ATLAS: docs/nodes/strings returned to C are
56//! caller-owned (freed with the documented xmlFree-family); `xmlReadMemory`/
57//! `xmlCtxtRead*` propagate the URL into the owned input filename (R-000161);
58//! the error channel routes through the exported handler slots
59//! (R-000161/R-000163). The deprecated init/cleanup no-ops are deliberate:
60//! upstream bodies are empty (R-000138).
61//!
62//! # Historical quirks & epochs
63//!
64//! QUIRK-0001: 2.9.0 default parser limits (commit `52d8ade7`); E-002/E-005:
65//! parse-error diagnostics and exit codes across 2.9.10-2.13.0; R-000138: the
66//! deprecated no-op entry points dispositioned as the oracles own behavior;
67//! R-000161: the five wrong default values fixed (e.g. `xmlParserVersion` =
68//! `21503-GITv2.15.3`); R-000162: allocator entry points exported as DATA;
69//! R-000157: iconv-only encodings report XML_ERR_UNSUPPORTED_ENCODING (no
70//! iconv backend — OPEN residual, UNRESOLVED since 11.1-Z.1: the executed
71//! Iconv+ICU-enabled oracle serves those encodings, so closing requires an
72//! iconv/ICU backend rather than a waiver).
73//!
74//! # Deliberate oddities
75//!
76//! The no-op init/cleanup exports (R-000138), the `xmlGenericError`/
77//! `xsltGenericError` variadic asm stderr printers (R-000161), and the
78//! XML_ERR_UNSUPPORTED_ENCODING divergence for iconv/ICU-only encodings
79//! (R-000157, OPEN/UNRESOLVED) are the deliberate oddities of this module.
80//!
81//! # Proving courts
82//!
83//! ABI-DATA, ALLOCATOR, GLOBAL-STATE, PARSER and THREADING court families;
84//! the data-ABI probes (CALLBACK-001, ERROR-001, TREE-001, ENCODING-001, ...)
85//! require byte-identical output vs the oracle DSO; DSO-LOADER and
86//! HEADER-COMPILE close the surface.
87//!
88//! # Tempting simplifications that would break parity
89//!
90//! A tempting simplification is to delete the deprecated no-op exports as dead
91//! code — R-000138 records they ARE the upstream observable behavior and
92//! downstream linking depends on them. Another shortcut, defaulting
93//! `xmlGenericError` to NULL instead of the variadic stderr printer, is the
94//! pre-R-000161 state that broke counting handlers (err-count 1 vs oracle 6).
95//! Both must not be simplified.
96
97#![allow(non_snake_case)]
98#![allow(unused_variables)]
99#![allow(clippy::missing_safety_doc)]
100#![allow(clippy::not_unsafe_ptr_arg_deref)]
101
102// SAFETY-SCOPE: EXPORT-XML2-MECHANICAL-001
103// (11.1-Z.3 proof scope, classified-generated) — this module is the
104// mechanical extern-"C" export surface: every `unsafe` block in it is
105// the documented indirection/registry-access pattern whose validity
106// rests on the upstream C contract, and the exported signatures are
107// machine-measured by the ABI-FUNCTION-SIGNATURE and DSO-LOADER
108// courts and the C-API differential probes. The safety contract of
109// each export is stated in its own doc comment; this scope covers the
110// mechanical wrappers' unsafe blocks.
111
112use core::ffi::c_void;
113use core::ptr;
114use once_cell::sync::Lazy;
115use parking_lot::Mutex;
116use std::collections::HashMap;
117use std::ffi::{CStr, CString};
118use std::mem::size_of;
119use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong};
120
121use crate::xml::xpath::ast::CompiledExpr;
122use crate::xml::xpath::context::{BoxedXPathFunction, XPathContext};
123use crate::xml::xpath::types::{NodeSet, XPathValue};
124
125use crate::abi::allocator::*;
126use crate::abi::callbacks::*;
127use crate::abi::structs::*;
128use crate::abi::types::*;
129
130// ═══════════════════════════════════════════════════════════════════════════════
131// 1. Initialization / Cleanup
132// ═══════════════════════════════════════════════════════════════════════════════
133
134/// Initialize the parser library.
135///
136/// Must be called before any other libxml2 functions.
137/// Safe to call multiple times (reference-counted in modern libxml2).
138///
139/// # UPSTREAM-PARITY
140///
141/// ```c
142/// void xmlInitParser(void);
143/// ```
144#[no_mangle]
145pub unsafe extern "C" fn xmlInitParser() {
146    crate::internal::globals::init_parser();
147}
148
149/// Initialize the global variables module (upstream globals.h).
150///
151/// # UPSTREAM-PARITY
152///
153/// ```c
154/// void xmlInitGlobals(void);
155/// ```
156///
157/// Upstream `xmlInitGlobals` (globals.c) is called once to initialize the
158/// global variable defaults. The candidate's globals are initialized
159/// statically/on first use, so this is a no-op that exists for ABI
160/// compatibility.
161#[no_mangle]
162pub const unsafe extern "C" fn xmlInitGlobals() {
163    // Globals are statically initialized in the candidate.
164}
165
166/// Upstream `xmlInitializeGlobalState` (globals.c) — initializes a
167/// `xmlGlobalState` struct; the candidate keeps no global-state struct, so
168/// this is a no-op for ABI compatibility.
169///
170/// # UPSTREAM-PARITY
171///
172/// ```c
173/// void xmlInitializeGlobalState(xmlGlobalStatePtr gs);
174/// ```
175#[no_mangle]
176pub const unsafe extern "C" fn xmlInitializeGlobalState(_gs: *mut c_void) {
177    // No-op: the candidate's globals are statically initialized.
178    // R-000138: upstream globals.c body is empty (lazy init); the no-op
179    // IS the oracle behavior, so this must never become a real initializer.
180}
181
182/// Upstream `xmlInitializeDict` (dict.c) — ensures the dictionary
183/// subsystem is initialized; no-op in the candidate (lazy init).
184///
185/// # UPSTREAM-PARITY
186///
187/// ```c
188/// int xmlInitializeDict(void);
189/// ```
190#[no_mangle]
191pub const extern "C" fn xmlInitializeDict() -> c_int {
192    // R-000138: upstream dict.c xmlInitializeDict is an empty body after lazy
193    // init; returning 0 is the oracle observable behavior.
194    0
195}
196
197/// Upstream `xmlInitializePredefinedEntities` (entities.c) — the
198/// predefined entities (& < > " ') are built lazily by
199/// the candidate; no-op.
200///
201/// # UPSTREAM-PARITY
202///
203/// ```c
204/// void xmlInitializePredefinedEntities(void);
205/// ```
206#[no_mangle]
207pub const extern "C" fn xmlInitializePredefinedEntities() {
208    // No-op: predefined entities are resolved on demand.
209}
210
211/// Upstream `xmlCleanupPredefinedEntities` (entities.c) — no-op in the
212/// candidate (no global entity table to release).
213///
214/// # UPSTREAM-PARITY
215///
216/// ```c
217/// void xmlCleanupPredefinedEntities(void);
218/// ```
219#[no_mangle]
220pub const extern "C" fn xmlCleanupPredefinedEntities() {
221    // No-op.
222}
223
224/// Upstream `xmlDefaultSAXHandlerInit` (SAX2.c) — fills the
225/// `xmlDefaultSAXHandler` global. The candidate's default handler is built
226/// on demand; this initializes the exported default-handler global when it
227/// is added (currently tracked in R-000135). No-op for now.
228///
229/// # UPSTREAM-PARITY
230///
231/// ```c
232/// void xmlDefaultSAXHandlerInit(void);
233/// ```
234#[no_mangle]
235pub const extern "C" fn xmlDefaultSAXHandlerInit() {
236    // The candidate builds default handlers on demand; the exported
237    // xmlDefaultSAXHandler global is part of the R-000135 data closure.
238}
239
240/// The current default SAX version (2), stored as an atomic so callers can
241/// query it without taking a lock.
242static SAX2_DEFAULT_VERSION: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(2);
243/// Set the default SAX version (upstream SAX2.c `xmlSAXDefaultVersion`):
244/// returns the previous default; -1 when the version is not 1 or 2.
245///
246/// # UPSTREAM-PARITY
247///
248/// ```c
249/// int xmlSAXDefaultVersion(int version);
250/// ```
251#[no_mangle]
252pub extern "C" fn xmlSAXDefaultVersion(version: c_int) -> c_int {
253    use core::sync::atomic::Ordering;
254    let ret = SAX2_DEFAULT_VERSION.load(Ordering::Relaxed);
255    if version != 1 && version != 2 {
256        return -1;
257    }
258    SAX2_DEFAULT_VERSION.store(version, Ordering::Relaxed);
259    ret
260}
261
262/// Initialize a SAX handler for a given SAX version (upstream SAX2.c
263/// `xmlSAXVersion`): fills the handler with the default callbacks and sets
264/// `initialized` (XML_SAX2_MAGIC for version 2, 1 for version 1).
265///
266/// # UPSTREAM-PARITY
267///
268/// ```c
269/// int xmlSAXVersion(xmlSAXHandler *hdlr, int version);
270/// ```
271#[no_mangle]
272pub unsafe extern "C" fn xmlSAXVersion(
273    hdlr: *mut crate::abi::structs::_xmlSAXHandler,
274    version: c_int,
275) -> c_int {
276    if hdlr.is_null() {
277        return -1;
278    }
279    if version != 1 && version != 2 {
280        return -1;
281    }
282    // SAFETY: hdlr is non-NULL and writable.
283    unsafe {
284        crate::xml::sax::dispatch::xmlSAX2InitDefaultSAXHandler(hdlr);
285        let h = &mut *hdlr;
286        if version == 2 {
287            h.initialized = crate::abi::constants::XML_SAX2_MAGIC as c_uint;
288        } else {
289            h.initialized = 1;
290        }
291    }
292    0
293}
294
295/// Upstream `xmlHasFeature` (parser.c): returns 1 when the library was
296/// compiled with the requested feature. The candidate enables the full
297/// feature set (see include/libxml/xmlversion.h), so every known feature
298/// reports 1; unknown features report 0.
299///
300/// # UPSTREAM-PARITY
301///
302/// ```c
303/// int xmlHasFeature(xmlFeature feature);
304/// ```
305#[no_mangle]
306pub extern "C" fn xmlHasFeature(feature: c_int) -> c_int {
307    // xmlFeature enum values (upstream xmlversion.h): XML_WITH_* run 1..24
308    // (XML_WITH_THREAD=1 ... XML_WITH_MODULES=24).
309    if (1..=24).contains(&feature) {
310        1
311    } else {
312        0
313    }
314}
315
316/// Clean up the global variables module (upstream globals.h).
317///
318/// # UPSTREAM-PARITY
319///
320/// ```c
321/// void xmlCleanupGlobals(void);
322/// ```
323///
324/// Upstream `xmlCleanupGlobals` frees the global defaults. The candidate
325/// keeps globals alive for the process lifetime (repeated init/cleanup is
326/// reference-counted); no-op for ABI compatibility.
327#[no_mangle]
328pub const unsafe extern "C" fn xmlCleanupGlobals() {
329    // The candidate's globals are process-lifetime statics.
330}
331
332/// Clean up the parser library.
333///
334/// Should be called when the library is no longer needed.
335///
336/// # UPSTREAM-PARITY
337///
338/// ```c
339/// void xmlCleanupParser(void);
340/// ```
341#[no_mangle]
342pub unsafe extern "C" fn xmlCleanupParser() {
343    crate::internal::globals::cleanup_parser();
344}
345
346/// Create a simple mutex (upstream threads.h).
347///
348/// # UPSTREAM-PARITY
349///
350/// ```c
351/// xmlMutexPtr xmlNewMutex(void);
352/// ```
353#[no_mangle]
354pub extern "C" fn xmlNewMutex() -> *mut c_void {
355    crate::xml::threads::new_mutex()
356}
357
358/// Free a simple mutex (upstream threads.h).
359///
360/// # UPSTREAM-PARITY
361///
362/// ```c
363/// void xmlFreeMutex(xmlMutexPtr tok);
364/// ```
365#[no_mangle]
366pub unsafe extern "C" fn xmlFreeMutex(tok: *mut c_void) {
367    crate::xml::threads::free_mutex(tok);
368}
369
370/// Lock a simple mutex (upstream threads.h).
371///
372/// # UPSTREAM-PARITY
373///
374/// ```c
375/// void xmlMutexLock(xmlMutexPtr tok);
376/// ```
377#[no_mangle]
378pub unsafe extern "C" fn xmlMutexLock(tok: *mut c_void) {
379    crate::xml::threads::mutex_lock(tok);
380}
381
382/// Unlock a simple mutex (upstream threads.h).
383///
384/// # UPSTREAM-PARITY
385///
386/// ```c
387/// void xmlMutexUnlock(xmlMutexPtr tok);
388/// ```
389#[no_mangle]
390pub unsafe extern "C" fn xmlMutexUnlock(tok: *mut c_void) {
391    crate::xml::threads::mutex_unlock(tok);
392}
393
394/// Create a recursive mutex (upstream threads.h).
395///
396/// # UPSTREAM-PARITY
397///
398/// ```c
399/// xmlRMutexPtr xmlNewRMutex(void);
400/// ```
401#[no_mangle]
402pub extern "C" fn xmlNewRMutex() -> *mut c_void {
403    crate::xml::threads::new_rmutex()
404}
405
406/// Free a recursive mutex (upstream threads.h).
407///
408/// # UPSTREAM-PARITY
409///
410/// ```c
411/// void xmlFreeRMutex(xmlRMutexPtr tok);
412/// ```
413#[no_mangle]
414pub unsafe extern "C" fn xmlFreeRMutex(tok: *mut c_void) {
415    crate::xml::threads::free_rmutex(tok);
416}
417
418/// Lock a recursive mutex (upstream threads.h).
419///
420/// # UPSTREAM-PARITY
421///
422/// ```c
423/// void xmlRMutexLock(xmlRMutexPtr tok);
424/// ```
425#[no_mangle]
426pub unsafe extern "C" fn xmlRMutexLock(tok: *mut c_void) {
427    crate::xml::threads::rmutex_lock(tok);
428}
429
430/// Unlock a recursive mutex (upstream threads.h).
431///
432/// # UPSTREAM-PARITY
433///
434/// ```c
435/// void xmlRMutexUnlock(xmlRMutexPtr tok);
436/// ```
437#[no_mangle]
438pub unsafe extern "C" fn xmlRMutexUnlock(tok: *mut c_void) {
439    crate::xml::threads::rmutex_unlock(tok);
440}
441
442/// Check the thread-local storage (upstream threads.h `xmlCheckThreadLocalStorage`):
443/// returns 0 when TLS is functional, -1 otherwise. The candidate uses Rust
444/// thread-locals which are always functional.
445///
446/// # UPSTREAM-PARITY
447///
448/// ```c
449/// int xmlCheckThreadLocalStorage(void);
450/// ```
451#[no_mangle]
452pub const extern "C" fn xmlCheckThreadLocalStorage() -> c_int {
453    // R-000138: upstream threads.c body is empty (TLS always works); 0 is the
454    // oracle observable behavior, not a stub.
455    0
456}
457
458/// Initialize threading support.
459///
460/// # UPSTREAM-PARITY
461///
462/// ```c
463/// void xmlInitThreads(void);
464/// ```
465///
466/// Upstream threads.h declares `void xmlInitThreads(void)` (11.1-Z.3
467/// signature court: the pre-Z.3 candidate returned `int`).
468#[no_mangle]
469pub unsafe extern "C" fn xmlInitThreads() {
470    let _ = unsafe { crate::internal::globals::init_threads() };
471}
472
473/// Clean up threading support.
474///
475/// # UPSTREAM-PARITY
476///
477/// ```c
478/// void xmlCleanupThreads(void);
479/// ```
480#[no_mangle]
481pub unsafe extern "C" fn xmlCleanupThreads() {
482    crate::xml::threads::cleanup_threads();
483}
484
485/// Check whether the library has been initialized.
486///
487/// # UPSTREAM-PARITY
488///
489/// ```c
490/// int xmlIsInitialized(void);
491/// ```
492#[no_mangle]
493pub extern "C" fn xmlIsInitialized() -> c_int {
494    if crate::abi::versioning::is_initialized() {
495        1
496    } else {
497        0
498    }
499}
500
501/// Initialize a set of threads (libxml2 compat).
502///
503/// # UPSTREAM-PARITY
504///
505/// ```c
506/// int xmlInitThreads(void);
507/// ```
508/// This is an alias.
509#[no_mangle]
510pub const unsafe extern "C" fn xmlLockLibrary() {
511    crate::xml::threads::lock_library();
512}
513
514/// Unlock the library (libxml2 compat).
515///
516/// # UPSTREAM-PARITY
517///
518/// ```c
519/// void xmlUnlockLibrary(void);
520/// ```
521#[no_mangle]
522pub const unsafe extern "C" fn xmlUnlockLibrary() {
523    crate::xml::threads::unlock_library();
524}
525
526// ═══════════════════════════════════════════════════════════════════════════════
527// 4. Error Handling
528// ═══════════════════════════════════════════════════════════════════════════════
529
530/// Set the generic error handler.
531///
532/// # UPSTREAM-PARITY
533///
534/// ```c
535/// void xmlSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc handler);
536/// ```
537///
538/// # SAFETY
539///
540/// - `handler` must be a valid function pointer or NULL (to reset to default).
541/// - If non-NULL, the handler may be called at any time with `ctx`.
542#[no_mangle]
543pub unsafe extern "C" fn xmlSetGenericErrorFunc(
544    ctx: *mut c_void,
545    handler: Option<xmlGenericErrorFunc>,
546) {
547    // SAFETY: Delegates to xml::errors with same safety contract.
548    unsafe { crate::xml::errors::set_generic_error_func(ctx, handler) };
549}
550
551/// Set the structured error handler.
552///
553/// # UPSTREAM-PARITY
554///
555/// ```c
556/// void xmlSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc handler);
557/// ```
558///
559/// # SAFETY
560///
561/// - `handler` must be a valid function pointer or NULL.
562#[no_mangle]
563pub unsafe extern "C" fn xmlSetStructuredErrorFunc(
564    ctx: *mut c_void,
565    handler: Option<xmlStructuredErrorFunc>,
566) {
567    // SAFETY: Delegates to xml::errors with same safety contract.
568    unsafe { crate::xml::errors::set_structured_error_func(ctx, handler) };
569}
570
571/// Get the last error for the current thread.
572///
573/// # UPSTREAM-PARITY
574///
575/// ```c
576/// xmlErrorPtr xmlGetLastError(void);
577/// ```
578///
579/// Returns a pointer to the last error, or NULL if no error occurred.
580/// The returned pointer is valid until the next libxml2 call in this thread.
581#[no_mangle]
582pub extern "C" fn xmlGetLastError() -> *mut _xmlError {
583    crate::xml::errors::get_last_error()
584}
585
586/// Get a copy of the last error for the current thread.
587///
588/// # UPSTREAM-PARITY
589///
590/// ```c
591/// xmlErrorPtr xmlCopyError(xmlErrorPtr from, xmlErrorPtr to);
592/// ```
593///
594/// Copies `from` into `to`. Returns 0 on success, -1 on error.
595///
596/// # SAFETY
597///
598/// - `from` and `to` must be valid pointers to `_xmlError` structs, or NULL.
599#[no_mangle]
600pub const unsafe extern "C" fn xmlCopyError(from: *const _xmlError, to: *mut _xmlError) -> c_int {
601    // SAFETY: Delegates to xml::errors with same safety contract.
602    unsafe { crate::xml::errors::copy_error(from, to) }
603}
604
605/// Reset an error structure.
606///
607/// # UPSTREAM-PARITY
608///
609/// ```c
610/// void xmlResetError(xmlErrorPtr err);
611/// ```
612///
613/// # SAFETY
614///
615/// - `err` must be a valid pointer to `_xmlError`, or NULL.
616#[no_mangle]
617pub const unsafe extern "C" fn xmlResetError(err: *mut _xmlError) {
618    // SAFETY: Delegates to xml::errors with same safety contract.
619    unsafe { crate::xml::errors::reset_error(err) };
620}
621
622/// Raise a structured error.
623///
624/// This is called internally when an error occurs. It updates the last error
625/// and invokes the structured error handler if one is set.
626///
627/// # SAFETY
628///
629/// - `ctxt` may be NULL (context of the error).
630/// - `domain`, `code`, `level`: valid error codes.
631/// - `msg` must be a valid C string or NULL.
632/// - `file` must be a valid C string or NULL.
633/// - `str1`, `str2`, `str3`: error-related strings (may be NULL).
634#[no_mangle]
635pub unsafe extern "C" fn xmlRaiseError(
636    ctxt: *mut c_void,
637    ctxt2: *mut c_void,
638    ctxt3: *mut c_void,
639    ctxt4: *mut c_void,
640    ctxt5: *mut c_void,
641    domain: c_int,
642    code: c_int,
643    level: c_int,
644    file: *const c_char,
645    line: c_int,
646    str1: *const c_char,
647    str2: *const c_char,
648    str3: *const c_char,
649    int1: c_int,
650    int2: c_int,
651    msg: *const c_char,
652) {
653    // SAFETY: Delegates to xml::errors with same safety contract.
654    unsafe {
655        crate::xml::errors::raise_error(
656            ctxt, ctxt2, ctxt3, ctxt4, ctxt5, domain, code, level, file, line, str1, str2, str3,
657            int1, int2, msg,
658        );
659    }
660}
661
662/// Remove any error from the last error stack.
663///
664/// # UPSTREAM-PARITY
665///
666/// ```c
667/// void xmlResetLastError(void);
668/// ```
669#[no_mangle]
670pub extern "C" fn xmlResetLastError() {
671    crate::xml::errors::reset_last_error();
672}
673
674// ═══════════════════════════════════════════════════════════════════════════════
675// 5. String Utilities
676// ═══════════════════════════════════════════════════════════════════════════════
677
678/// Duplicate a string using xmlChar.
679///
680/// # UPSTREAM-PARITY
681///
682/// ```c
683/// xmlChar *xmlStrdup(const xmlChar *cur);
684/// ```
685///
686/// # SAFETY
687///
688/// - `cur` must be a valid null-terminated xmlChar string or NULL.
689#[no_mangle]
690pub unsafe extern "C" fn xmlStrdup(cur: *const xmlChar) -> *mut xmlChar {
691    if cur.is_null() {
692        return ptr::null_mut();
693    }
694    let len = unsafe { xmlStrlen(cur) };
695    let size = len + 1;
696    let new_ptr = unsafe { xmlMallocImpl(size as usize) };
697    if new_ptr.is_null() {
698        return ptr::null_mut();
699    }
700    unsafe {
701        ptr::copy_nonoverlapping(cur, new_ptr as *mut u8, size as usize);
702    }
703    new_ptr as *mut xmlChar
704}
705
706/// Duplicate a substring.
707///
708/// # UPSTREAM-PARITY
709///
710/// ```c
711/// xmlChar *xmlStrndup(const xmlChar *cur, int len);
712/// ```
713///
714/// # SAFETY
715///
716/// - `cur` must be a valid pointer or NULL.
717#[no_mangle]
718pub unsafe extern "C" fn xmlStrndup(cur: *const xmlChar, len: c_int) -> *mut xmlChar {
719    if cur.is_null() || len <= 0 {
720        return ptr::null_mut();
721    }
722    let size = len as usize + 1;
723    let new_ptr = unsafe { xmlMallocImpl(size) };
724    if new_ptr.is_null() {
725        return ptr::null_mut();
726    }
727    unsafe {
728        ptr::copy_nonoverlapping(cur, new_ptr as *mut u8, len as usize);
729        *(new_ptr.add(len as usize) as *mut u8) = 0;
730    }
731    new_ptr as *mut xmlChar
732}
733
734/// Get the length of an xmlChar string.
735///
736/// # UPSTREAM-PARITY
737///
738/// ```c
739/// int xmlStrlen(const xmlChar *str);
740/// ```
741///
742/// # SAFETY
743///
744/// - `str` must be a valid null-terminated string or NULL (returns 0).
745#[no_mangle]
746pub unsafe extern "C" fn xmlStrlen(str: *const xmlChar) -> c_int {
747    if str.is_null() {
748        return 0;
749    }
750    unsafe { libc::strlen(str as *const c_char) as c_int }
751}
752
753/// Find the first occurrence of a character in a string (upstream tree.c
754/// `xmlStrchr`): returns a pointer to the first occurrence or NULL.
755///
756/// # UPSTREAM-PARITY
757///
758/// ```c
759/// const xmlChar *xmlStrchr(const xmlChar *str, xmlChar val);
760/// ```
761///
762/// # SAFETY
763///
764/// - `str` must be a valid null-terminated string or NULL.
765#[no_mangle]
766pub const unsafe extern "C" fn xmlStrchr(str: *const xmlChar, val: xmlChar) -> *const xmlChar {
767    if str.is_null() {
768        return ptr::null();
769    }
770    unsafe {
771        let mut cur = str;
772        while *cur != 0 {
773            if *cur == val {
774                return cur;
775            }
776            cur = cur.add(1);
777        }
778        ptr::null()
779    }
780}
781
782/// Compare two xmlChar strings.
783///
784/// # UPSTREAM-PARITY
785///
786/// ```c
787/// int xmlStrcmp(const xmlChar *str1, const xmlChar *str2);
788/// ```
789///
790/// Returns 0 if equal, <0 if str1 < str2, >0 if str1 > str2.
791/// NULL-safe: NULL sorts before any non-NULL string.
792#[no_mangle]
793pub unsafe extern "C" fn xmlStrcmp(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
794    if str1.is_null() && str2.is_null() {
795        return 0;
796    }
797    if str1.is_null() {
798        return -1;
799    }
800    if str2.is_null() {
801        return 1;
802    }
803    unsafe { libc::strcmp(str1 as *const c_char, str2 as *const c_char) as c_int }
804}
805
806/// Compare two xmlChar strings up to a given length.
807///
808/// # UPSTREAM-PARITY
809///
810/// ```c
811/// int xmlStrncmp(const xmlChar *str1, const xmlChar *str2, int len);
812/// ```
813#[no_mangle]
814pub unsafe extern "C" fn xmlStrncmp(
815    str1: *const xmlChar,
816    str2: *const xmlChar,
817    len: c_int,
818) -> c_int {
819    if len <= 0 {
820        return 0;
821    }
822    if str1.is_null() && str2.is_null() {
823        return 0;
824    }
825    if str1.is_null() {
826        return -1;
827    }
828    if str2.is_null() {
829        return 1;
830    }
831    unsafe { libc::strncmp(str1 as *const c_char, str2 as *const c_char, len as usize) as c_int }
832}
833
834/// Case-insensitive comparison of two xmlChar strings.
835///
836/// # UPSTREAM-PARITY
837///
838/// ```c
839/// int xmlStrcasecmp(const xmlChar *str1, const xmlChar *str2);
840/// ```
841#[no_mangle]
842pub unsafe extern "C" fn xmlStrcasecmp(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
843    if str1.is_null() && str2.is_null() {
844        return 0;
845    }
846    if str1.is_null() {
847        return -1;
848    }
849    if str2.is_null() {
850        return 1;
851    }
852    unsafe { libc::strcasecmp(str1 as *const c_char, str2 as *const c_char) as c_int }
853}
854
855/// Case-insensitive comparison with length limit.
856///
857/// # UPSTREAM-PARITY
858///
859/// ```c
860/// int xmlStrncasecmp(const xmlChar *str1, const xmlChar *str2, int len);
861/// ```
862#[no_mangle]
863pub unsafe extern "C" fn xmlStrncasecmp(
864    str1: *const xmlChar,
865    str2: *const xmlChar,
866    len: c_int,
867) -> c_int {
868    if len <= 0 {
869        return 0;
870    }
871    if str1.is_null() && str2.is_null() {
872        return 0;
873    }
874    if str1.is_null() {
875        return -1;
876    }
877    if str2.is_null() {
878        return 1;
879    }
880    unsafe {
881        libc::strncasecmp(str1 as *const c_char, str2 as *const c_char, len as usize) as c_int
882    }
883}
884
885/// Check if two xmlChar strings are equal.
886///
887/// # UPSTREAM-PARITY
888///
889/// ```c
890/// int xmlStrEqual(const xmlChar *str1, const xmlChar *str2);
891/// ```
892///
893/// Returns 1 if equal, 0 if not. NULL-safe.
894#[no_mangle]
895pub unsafe extern "C" fn xmlStrEqual(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
896    if str1.is_null() && str2.is_null() {
897        return 1;
898    }
899    if str1.is_null() || str2.is_null() {
900        return 0;
901    }
902    unsafe { (libc::strcmp(str1 as *const c_char, str2 as *const c_char) == 0) as c_int }
903}
904
905/// Build a QName (upstream tree.h).
906///
907/// # UPSTREAM-PARITY
908///
909/// ```c
910/// xmlChar *xmlBuildQName(const xmlChar *ncname, const xmlChar *prefix,
911///                        xmlChar *memory, int len);
912/// ```
913#[no_mangle]
914pub unsafe extern "C" fn xmlBuildQName(
915    ncname: *const xmlChar,
916    prefix: *const xmlChar,
917    memory: *mut xmlChar,
918    len: c_int,
919) -> *mut xmlChar {
920    crate::xml::string::build_qname(ncname, prefix, memory, len)
921}
922
923/// Split a QName into prefix + local part (upstream tree.h).
924///
925/// # UPSTREAM-PARITY
926///
927/// ```c
928/// xmlChar *xmlSplitQName2(const xmlChar *name, xmlChar **prefix);
929/// ```
930#[no_mangle]
931pub unsafe extern "C" fn xmlSplitQName2(
932    name: *const xmlChar,
933    prefix: *mut *mut xmlChar,
934) -> *mut xmlChar {
935    crate::xml::string::split_qname2(name, prefix)
936}
937
938/// Return a pointer to the local part of a QName, filling `*len` with the
939/// prefix length (upstream tree.h — R-000176, the candidate previously
940/// returned the prefix length as an int).
941///
942/// # UPSTREAM-PARITY
943///
944/// ```c
945/// const xmlChar *xmlSplitQName3(const xmlChar *name, int *len);
946/// ```
947#[no_mangle]
948pub unsafe extern "C" fn xmlSplitQName3(name: *const xmlChar, len: *mut c_int) -> *mut xmlChar {
949    crate::xml::string::split_qname3(name, len)
950}
951
952/// Split a QName into prefix + local part (upstream parserInternals.h
953/// `xmlSplitQName(ctxt, name, prefix)` — the `ctxt` first argument is part
954/// of the ABI; R-000176, the candidate previously exported the 2-argument
955/// tree.h `xmlSplitQName2` form under this name).
956///
957/// # UPSTREAM-PARITY
958///
959/// ```c
960/// xmlChar *xmlSplitQName(xmlParserCtxt *ctxt, const xmlChar *name,
961///                        xmlChar **prefix);
962/// ```
963///
964/// `ctxt` is accepted for ABI parity; upstream uses it only for the name-
965/// length limit and error reporting (candidate: parser-side error model).
966#[no_mangle]
967pub unsafe extern "C" fn xmlSplitQName(
968    _ctxt: *mut _xmlParserCtxt,
969    name: *const xmlChar,
970    prefix: *mut *mut xmlChar,
971) -> *mut xmlChar {
972    crate::xml::string::split_qname2(name, prefix)
973}
974
975/// Count UTF-8 characters (upstream xmlstring.h).
976///
977/// # UPSTREAM-PARITY
978///
979/// ```c
980/// int xmlUTF8Strlen(const xmlChar *utf);
981/// ```
982#[no_mangle]
983pub const unsafe extern "C" fn xmlUTF8Strlen(utf: *const xmlChar) -> c_int {
984    crate::xml::string::utf8_strlen(utf)
985}
986
987/// Size in bytes of a UTF-8 sequence (upstream xmlstring.h).
988///
989/// # UPSTREAM-PARITY
990///
991/// ```c
992/// int xmlUTF8Size(const xmlChar *utf);
993/// ```
994#[no_mangle]
995pub const unsafe extern "C" fn xmlUTF8Size(utf: *const xmlChar) -> c_int {
996    crate::xml::string::utf8_size(utf)
997}
998
999/// Check UTF-8 validity (upstream xmlstring.h).
1000///
1001/// # UPSTREAM-PARITY
1002///
1003/// ```c
1004/// int xmlCheckUTF8(const unsigned char *utf);
1005/// ```
1006#[no_mangle]
1007pub const unsafe extern "C" fn xmlCheckUTF8(utf: *const xmlChar) -> c_int {
1008    crate::xml::string::check_utf8(utf)
1009}
1010
1011/// Check if an xmlChar string equals a qualified name.
1012///
1013/// # UPSTREAM-PARITY
1014///
1015/// ```c
1016/// int xmlStrQEqual(const xmlChar *pref, const xmlChar *name, const xmlChar *str);
1017/// ```
1018///
1019/// Returns 1 if `pref:name` equals `str`, 0 otherwise.
1020/// `pref` may be NULL (compares only name).
1021#[no_mangle]
1022pub unsafe extern "C" fn xmlStrQEqual(
1023    pref: *const xmlChar,
1024    name: *const xmlChar,
1025    str: *const xmlChar,
1026) -> c_int {
1027    if name.is_null() || str.is_null() {
1028        return 0;
1029    }
1030    if pref.is_null() {
1031        return unsafe { xmlStrEqual(name, str) };
1032    }
1033    // Compare "pref:name" with str
1034    let pref_len = unsafe { xmlStrlen(pref) };
1035    let name_len = unsafe { xmlStrlen(name) };
1036    let total_len = pref_len + 1 + name_len;
1037    let str_len = unsafe { xmlStrlen(str) };
1038    if total_len != str_len {
1039        return 0;
1040    }
1041    // Compare prefix part
1042    if unsafe {
1043        libc::strncmp(
1044            pref as *const c_char,
1045            str as *const c_char,
1046            pref_len as usize,
1047        )
1048    } != 0
1049    {
1050        return 0;
1051    }
1052    // Check colon
1053    if unsafe { *str.add(pref_len as usize) } != b':' as xmlChar {
1054        return 0;
1055    }
1056    // Compare name part
1057    (unsafe {
1058        libc::strncmp(
1059            name as *const c_char,
1060            str.add((pref_len + 1) as usize) as *const c_char,
1061            name_len as usize,
1062        ) == 0
1063    }) as c_int
1064}
1065
1066/// Concatenate two strings.
1067///
1068/// # UPSTREAM-PARITY
1069///
1070/// ```c
1071/// xmlChar *xmlStrcat(xmlChar *cur, const xmlChar *add);
1072/// ```
1073///
1074/// # SAFETY
1075///
1076/// - `cur` must be a valid xmlMalloc'd string or NULL.
1077/// - `add` must be a valid string or NULL.
1078/// - If `cur` is NULL, behaves like xmlStrdup(add).
1079#[no_mangle]
1080pub unsafe extern "C" fn xmlStrcat(cur: *mut xmlChar, add: *const xmlChar) -> *mut xmlChar {
1081    if add.is_null() {
1082        return cur;
1083    }
1084    if cur.is_null() {
1085        return unsafe { xmlStrdup(add) };
1086    }
1087    let cur_len = unsafe { xmlStrlen(cur) } as usize;
1088    let add_len = unsafe { xmlStrlen(add) } as usize;
1089    let new_size = cur_len + add_len + 1;
1090    let new_ptr = unsafe { xmlReallocImpl(cur as *mut c_void, new_size) };
1091    if new_ptr.is_null() {
1092        // UPSTREAM-PARITY (xmlstring.c xmlStrcat -> xmlStrncat): on realloc
1093        // failure upstream frees `cur` and returns NULL — the caller must
1094        // not touch `cur` afterwards (HOSTILE-ALLOCATOR H4).
1095        unsafe { xmlFreeImpl(cur as *mut c_void) };
1096        return ptr::null_mut();
1097    }
1098    unsafe {
1099        ptr::copy_nonoverlapping(add, (new_ptr as *mut u8).add(cur_len), add_len);
1100        *((new_ptr as *mut u8).add(cur_len + add_len)) = 0;
1101    }
1102    new_ptr as *mut xmlChar
1103}
1104
1105/// Concatenate up to `len` characters.
1106///
1107/// # UPSTREAM-PARITY
1108///
1109/// ```c
1110/// xmlChar *xmlStrncat(xmlChar *cur, const xmlChar *add, int len);
1111/// ```
1112///
1113/// # SAFETY
1114///
1115/// Same as xmlStrcat, but only copies up to `len` characters from `add`.
1116#[no_mangle]
1117pub unsafe extern "C" fn xmlStrncat(
1118    cur: *mut xmlChar,
1119    add: *const xmlChar,
1120    len: c_int,
1121) -> *mut xmlChar {
1122    if add.is_null() || len <= 0 {
1123        return cur;
1124    }
1125    let len = len as usize;
1126    if cur.is_null() {
1127        return unsafe { xmlStrndup(add, len as c_int) };
1128    }
1129    let cur_len = unsafe { xmlStrlen(cur) } as usize;
1130    let new_size = cur_len + len + 1;
1131    let new_ptr = unsafe { xmlReallocImpl(cur as *mut c_void, new_size) };
1132    if new_ptr.is_null() {
1133        // UPSTREAM-PARITY (xmlstring.c xmlStrncat): on realloc failure
1134        // upstream frees `cur` and returns NULL — the caller must not touch
1135        // `cur` afterwards (HOSTILE-ALLOCATOR H4).
1136        unsafe { xmlFreeImpl(cur as *mut c_void) };
1137        return ptr::null_mut();
1138    }
1139    unsafe {
1140        ptr::copy_nonoverlapping(add, (new_ptr as *mut u8).add(cur_len), len);
1141        *((new_ptr as *mut u8).add(cur_len + len)) = 0;
1142    }
1143    new_ptr as *mut xmlChar
1144}
1145
1146/// Create a new string by concatenating up to `len` characters.
1147///
1148/// # UPSTREAM-PARITY
1149///
1150/// ```c
1151/// xmlChar *xmlStrncatNew(const xmlChar *str1, const xmlChar *str2, int len);
1152/// ```
1153#[no_mangle]
1154pub unsafe extern "C" fn xmlStrncatNew(
1155    str1: *const xmlChar,
1156    str2: *const xmlChar,
1157    len: c_int,
1158) -> *mut xmlChar {
1159    let mut result: *mut xmlChar = ptr::null_mut();
1160    if !str1.is_null() {
1161        result = unsafe { xmlStrdup(str1) };
1162    }
1163    if !str2.is_null() && len > 0 {
1164        result = unsafe { xmlStrncat(result, str2, len) };
1165    }
1166    result
1167}
1168
1169/// Copy a string.
1170///
1171/// # UPSTREAM-PARITY
1172///
1173/// ```c
1174/// xmlChar *xmlStrcpy(xmlChar *dst, const xmlChar *src);
1175/// ```
1176///
1177/// # SAFETY
1178///
1179/// - `dst` must be a valid xmlMalloc'd buffer large enough to hold `src`.
1180/// - `src` must be a valid string.
1181#[no_mangle]
1182pub unsafe extern "C" fn xmlStrcpy(dst: *mut xmlChar, src: *const xmlChar) -> *mut xmlChar {
1183    if dst.is_null() || src.is_null() {
1184        return dst;
1185    }
1186    let len = unsafe { xmlStrlen(src) } as usize + 1;
1187    unsafe {
1188        ptr::copy_nonoverlapping(src, dst, len);
1189    }
1190    dst
1191}
1192
1193/// Copy up to `len` characters.
1194///
1195/// # UPSTREAM-PARITY
1196///
1197/// ```c
1198/// xmlChar *xmlStrncpy(xmlChar *dst, const xmlChar *src, int len);
1199/// ```
1200#[no_mangle]
1201pub unsafe extern "C" fn xmlStrncpy(
1202    dst: *mut xmlChar,
1203    src: *const xmlChar,
1204    len: c_int,
1205) -> *mut xmlChar {
1206    if dst.is_null() || src.is_null() || len <= 0 {
1207        return dst;
1208    }
1209    let len = len as usize;
1210    let src_len = unsafe { xmlStrlen(src) } as usize;
1211    let copy_len = if src_len < len { src_len } else { len - 1 };
1212    unsafe {
1213        ptr::copy_nonoverlapping(src, dst, copy_len);
1214        *dst.add(copy_len) = 0;
1215    }
1216    dst
1217}
1218
1219/// Extract a substring.
1220///
1221/// # UPSTREAM-PARITY
1222///
1223/// ```c
1224/// xmlChar *xmlStrsub(const xmlChar *str, int start, int len);
1225/// ```
1226///
1227/// Returns a newly allocated substring, or NULL on error.
1228#[no_mangle]
1229pub unsafe extern "C" fn xmlStrsub(str: *const xmlChar, start: c_int, len: c_int) -> *mut xmlChar {
1230    if str.is_null() || start < 0 || len < 0 {
1231        return ptr::null_mut();
1232    }
1233    let str_len = unsafe { xmlStrlen(str) };
1234    if start >= str_len {
1235        return unsafe { xmlStrdup(b"\0" as *const u8 as *const xmlChar) };
1236    }
1237    let actual_len = if start + len > str_len {
1238        str_len - start
1239    } else {
1240        len
1241    };
1242    unsafe { xmlStrndup(str.add(start as usize), actual_len) }
1243}
1244
1245// ═══════════════════════════════════════════════════════════════════════════════
1246// 6. Tree — Document, Node, Attribute, Namespace, DTD, Entity
1247// ═══════════════════════════════════════════════════════════════════════════════
1248
1249/// Create a new document.
1250///
1251/// # UPSTREAM-PARITY
1252///
1253/// ```c
1254/// xmlDocPtr xmlNewDoc(const xmlChar *version);
1255/// ```
1256///
1257/// # SAFETY
1258///
1259/// - `version` must be a valid string or NULL (defaults to "1.0").
1260/// - Returns a newly allocated document. Caller must free with `xmlFreeDoc`.
1261#[no_mangle]
1262pub unsafe extern "C" fn xmlNewDoc(version: *const xmlChar) -> *mut _xmlDoc {
1263    crate::xml::tree::new_doc(version)
1264}
1265
1266/// Free a document.
1267///
1268/// # UPSTREAM-PARITY
1269///
1270/// ```c
1271/// void xmlFreeDoc(xmlDocPtr doc);
1272/// ```
1273///
1274/// # SAFETY
1275///
1276/// - `doc` must be a valid document pointer or NULL.
1277#[no_mangle]
1278pub unsafe extern "C" fn xmlFreeDoc(doc: *mut _xmlDoc) {
1279    crate::xml::tree::free_doc(doc);
1280}
1281
1282/// Get the compression mode of a document (upstream tree.h).
1283///
1284/// # UPSTREAM-PARITY
1285///
1286/// ```c
1287/// int xmlGetDocCompressMode(const xmlDoc *doc);
1288/// ```
1289///
1290/// Returns the compression level (0-9) or -1 if `doc` is NULL.
1291#[no_mangle]
1292pub unsafe extern "C" fn xmlGetDocCompressMode(doc: *mut _xmlDoc) -> c_int {
1293    crate::xml::tree::xmlGetDocCompressMode(doc)
1294}
1295
1296/// Set the compression mode of a document (upstream tree.h).
1297///
1298/// # UPSTREAM-PARITY
1299///
1300/// ```c
1301/// void xmlSetDocCompressMode(xmlDocPtr doc, int mode);
1302/// ```
1303#[no_mangle]
1304pub unsafe extern "C" fn xmlSetDocCompressMode(doc: *mut _xmlDoc, mode: c_int) {
1305    crate::xml::tree::xmlSetDocCompressMode(doc, mode);
1306}
1307
1308/// Create a new node.
1309///
1310/// # UPSTREAM-PARITY
1311///
1312/// ```c
1313/// xmlNodePtr xmlNewNode(xmlNsPtr ns, const xmlChar *name);
1314/// ```
1315///
1316/// # SAFETY
1317///
1318/// - `ns` may be NULL.
1319/// - `name` must be a valid string.
1320/// - Returns a newly allocated node. Caller must free with `xmlFreeNode`.
1321#[no_mangle]
1322pub unsafe extern "C" fn xmlNewNode(ns: *mut _xmlNs, name: *const xmlChar) -> *mut _xmlNode {
1323    crate::xml::tree::new_node(ns, name)
1324}
1325
1326/// Free a node.
1327///
1328/// # UPSTREAM-PARITY
1329///
1330/// ```c
1331/// void xmlFreeNode(xmlNodePtr node);
1332/// ```
1333///
1334/// # SAFETY
1335///
1336/// - `node` must be a valid node pointer or NULL.
1337/// - The node must NOT be part of a document tree (must be unlinked first).
1338#[no_mangle]
1339pub unsafe extern "C" fn xmlFreeNode(node: *mut _xmlNode) {
1340    crate::xml::tree::free_node(node);
1341}
1342
1343/// Free a linked list of nodes (upstream tree.h).
1344///
1345/// # UPSTREAM-PARITY
1346///
1347/// ```c
1348/// void xmlFreeNodeList(xmlNodePtr node);
1349/// ```
1350///
1351/// Frees the node and all its siblings (following `next` pointers),
1352/// recursively freeing children. Matches upstream `xmlFreeNodeList`
1353/// (tree.c): a NULL argument is a no-op.
1354///
1355/// # SAFETY
1356///
1357/// - `node` must be a valid node pointer or NULL.
1358/// - The list must NOT be part of a document tree (must be unlinked first).
1359#[no_mangle]
1360pub unsafe extern "C" fn xmlFreeNodeList(node: *mut _xmlNode) {
1361    crate::xml::tree::free_node_list(node);
1362}
1363
1364/// Unlink a node from its tree.
1365///
1366/// # UPSTREAM-PARITY
1367///
1368/// ```c
1369/// void xmlUnlinkNode(xmlNodePtr node);
1370/// ```
1371///
1372/// # SAFETY
1373///
1374/// - `node` must be a valid node pointer or NULL.
1375#[no_mangle]
1376pub unsafe extern "C" fn xmlUnlinkNode(node: *mut _xmlNode) {
1377    crate::xml::tree::unlink_node(node);
1378}
1379
1380/// Initialize a SAX handler with the default SAX2 callbacks (upstream SAX2.h).
1381///
1382/// # UPSTREAM-PARITY
1383///
1384/// ```c
1385/// void xmlSAX2InitDefaultSAXHandler(xmlSAXHandler *hdlr, int warning);
1386/// ```
1387///
1388/// The `warning` parameter controls whether the warning callback is set in
1389/// upstream; the candidate always sets it (the parser dispatches warnings
1390/// identically) — a documented safe divergence tracked in the parity ledger.
1391///
1392/// # SAFETY
1393///
1394/// - `handler` must be a valid writable `_xmlSAXHandler` or NULL.
1395#[no_mangle]
1396pub unsafe extern "C" fn xmlSAX2InitDefaultSAXHandler(
1397    handler: *mut crate::abi::structs::_xmlSAXHandler,
1398    _warning: c_int,
1399) {
1400    crate::xml::sax::dispatch::xmlSAX2InitDefaultSAXHandler(handler);
1401}
1402
1403/// Initialize a SAX handler with the default HTML callbacks (upstream SAX2.h).
1404///
1405/// # UPSTREAM-PARITY
1406///
1407/// ```c
1408/// void xmlSAX2InitHtmlDefaultSAXHandler(xmlSAXHandler *hdlr);
1409/// ```
1410///
1411/// Upstream (SAX2.c `xmlSAX2InitHtmlDefaultSAXHandler`) fills the handler
1412/// with the SAX2 defaults minus the DTD-declaration callbacks
1413/// (resolveEntity/getParameterEntity/entityDecl/attributeDecl/elementDecl/
1414/// notationDecl/unparsedEntityDecl/reference/externalSubset are NULL) and
1415/// sets `initialized = 1` (not XML_SAX2_MAGIC). The candidate mirrors that
1416/// exactly.
1417///
1418/// # SAFETY
1419///
1420/// - `handler` must be a valid writable `_xmlSAXHandler` or NULL.
1421#[no_mangle]
1422pub unsafe extern "C" fn xmlSAX2InitHtmlDefaultSAXHandler(
1423    handler: *mut crate::abi::structs::_xmlSAXHandler,
1424) {
1425    if handler.is_null() {
1426        return;
1427    }
1428    // SAFETY: handler is non-NULL and writable.
1429    unsafe {
1430        // The DTD-ish callbacks are not part of the HTML default set.
1431        if (*handler).initialized != 0 {
1432            return;
1433        }
1434        crate::xml::sax::dispatch::xmlSAX2InitDefaultSAXHandler(handler);
1435        let h = &mut *handler;
1436        h.resolveEntity = None;
1437        h.getParameterEntity = None;
1438        h.entityDecl = None;
1439        h.attributeDecl = None;
1440        h.elementDecl = None;
1441        h.notationDecl = None;
1442        h.unparsedEntityDecl = None;
1443        h.reference = None;
1444        h.externalSubset = None;
1445        h.initialized = 1;
1446    }
1447}
1448
1449/// Add a child node.
1450///
1451/// # UPSTREAM-PARITY
1452///
1453/// ```c
1454/// xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur);
1455/// ```
1456///
1457/// # SAFETY
1458///
1459/// - `parent` must be a valid node.
1460/// - `cur` must be a valid node (ownership transfers to parent).
1461/// - Returns pointer to the added child (borrowed).
1462#[no_mangle]
1463pub unsafe extern "C" fn xmlAddChild(parent: *mut _xmlNode, cur: *mut _xmlNode) -> *mut _xmlNode {
1464    crate::xml::tree::add_child(parent, cur)
1465}
1466
1467/// Add a sibling node.
1468///
1469/// # UPSTREAM-PARITY
1470///
1471/// ```c
1472/// xmlNodePtr xmlAddSibling(xmlNodePtr cur, xmlNodePtr sibling);
1473/// ```
1474///
1475/// # SAFETY
1476///
1477/// Same as xmlAddChild, but adds after `cur` instead of as a child.
1478#[no_mangle]
1479pub unsafe extern "C" fn xmlAddSibling(
1480    cur: *mut _xmlNode,
1481    sibling: *mut _xmlNode,
1482) -> *mut _xmlNode {
1483    crate::xml::tree::add_sibling(cur, sibling)
1484}
1485
1486/// Create a new child element.
1487///
1488/// # UPSTREAM-PARITY
1489///
1490/// ```c
1491/// xmlNodePtr xmlNewChild(xmlNodePtr parent, xmlNsPtr ns,
1492///                        const xmlChar *name, const xmlChar *content);
1493/// ```
1494///
1495/// Creates a new element node, adds it as a child of `parent`, and
1496/// sets its content if `content` is non-NULL.
1497///
1498/// # SAFETY
1499///
1500/// - `parent` must be a valid node (may be NULL).
1501/// - `ns` may be NULL.
1502/// - `name` must be a valid string.
1503/// - Returns a newly allocated node (owned by parent).
1504#[no_mangle]
1505pub unsafe extern "C" fn xmlNewChild(
1506    parent: *mut _xmlNode,
1507    ns: *mut _xmlNs,
1508    name: *const xmlChar,
1509    content: *const xmlChar,
1510) -> *mut _xmlNode {
1511    // UPSTREAM-PARITY (tree.c 2.15 xmlNewChild -> xmlNewDocNode): a non-NULL
1512    // content becomes a text child of the new element
1513    // (xmlNewDocText + xmlAddChild).
1514    if parent.is_null() || name.is_null() {
1515        return ptr::null_mut();
1516    }
1517    let node = crate::xml::tree::new_child(parent, ns, name);
1518    if node.is_null() {
1519        return ptr::null_mut();
1520    }
1521    if !content.is_null() {
1522        let text = crate::xml::tree::new_text(content);
1523        if !text.is_null() {
1524            crate::xml::tree::add_child(node, text);
1525        }
1526    }
1527    node
1528}
1529
1530/// Set the root element of a document.
1531///
1532/// # UPSTREAM-PARITY
1533///
1534/// ```c
1535/// xmlNodePtr xmlDocSetRootElement(xmlDocPtr doc, xmlNodePtr root);
1536/// ```
1537///
1538/// Returns the old root element (if any), which the caller must free.
1539///
1540/// # SAFETY
1541///
1542/// - `doc` must be a valid document.
1543/// - `root` must be a valid node (ownership transfers to doc).
1544#[no_mangle]
1545pub unsafe extern "C" fn xmlDocSetRootElement(
1546    doc: *mut _xmlDoc,
1547    root: *mut _xmlNode,
1548) -> *mut _xmlNode {
1549    crate::xml::tree::doc_set_root_element(doc, root)
1550}
1551
1552/// Get the root element of a document.
1553///
1554/// # UPSTREAM-PARITY
1555///
1556/// ```c
1557/// xmlNodePtr xmlDocGetRootElement(const xmlDoc *doc);
1558/// ```
1559///
1560/// Returns a borrowed pointer (do not free).
1561#[no_mangle]
1562pub extern "C" fn xmlDocGetRootElement(doc: *const _xmlDoc) -> *mut _xmlNode {
1563    crate::xml::tree::doc_get_root_element(doc as *mut _xmlDoc)
1564}
1565
1566/// Copy a node.
1567///
1568/// # UPSTREAM-PARITY
1569///
1570/// ```c
1571/// xmlNodePtr xmlCopyNode(const xmlNodePtr node, int extended);
1572/// ```
1573///
1574/// If `extended` is 1, copies recursively (deep copy).
1575/// If `extended` is 0, copies only the node itself (shallow copy).
1576///
1577/// Returns a newly allocated copy. Caller must free with `xmlFreeNode`.
1578#[no_mangle]
1579pub unsafe extern "C" fn xmlCopyNode(node: *const _xmlNode, extended: c_int) -> *mut _xmlNode {
1580    crate::xml::tree::copy_node(node, extended)
1581}
1582
1583/// Copy a document.
1584///
1585/// # UPSTREAM-PARITY
1586///
1587/// ```c
1588/// xmlDocPtr xmlCopyDoc(const xmlDocPtr doc, int recursive);
1589/// ```
1590///
1591/// Returns a newly allocated copy. Caller must free with `xmlFreeDoc`.
1592#[no_mangle]
1593pub unsafe extern "C" fn xmlCopyDoc(doc: *const _xmlDoc, recursive: c_int) -> *mut _xmlDoc {
1594    crate::xml::tree::copy_doc(doc, recursive)
1595}
1596
1597/// Create a text node.
1598///
1599/// # UPSTREAM-PARITY
1600///
1601/// ```c
1602/// xmlNodePtr xmlNewText(const xmlChar *content);
1603/// ```
1604///
1605/// Creates a new text node with the given content.
1606/// If `content` is NULL, creates an empty text node.
1607#[no_mangle]
1608pub unsafe extern "C" fn xmlNewText(content: *const xmlChar) -> *mut _xmlNode {
1609    crate::xml::tree::new_text(content)
1610}
1611
1612/// Create a new comment node.
1613///
1614/// # UPSTREAM-PARITY
1615///
1616/// ```c
1617/// xmlNodePtr xmlNewComment(const xmlChar *content);
1618/// ```
1619#[no_mangle]
1620pub unsafe extern "C" fn xmlNewComment(content: *const xmlChar) -> *mut _xmlNode {
1621    crate::xml::tree::new_comment(content)
1622}
1623
1624/// Create a new PI node.
1625///
1626/// # UPSTREAM-PARITY
1627///
1628/// ```c
1629/// xmlNodePtr xmlNewPI(const xmlChar *name, const xmlChar *content);
1630/// ```
1631#[no_mangle]
1632pub unsafe extern "C" fn xmlNewPI(name: *const xmlChar, content: *const xmlChar) -> *mut _xmlNode {
1633    crate::xml::tree::new_pi(name, content)
1634}
1635
1636/// Create a new CDATA node.
1637///
1638/// # UPSTREAM-PARITY
1639///
1640/// ```c
1641/// xmlNodePtr xmlNewCDataBlock(xmlDocPtr doc, const xmlChar *content, int len);
1642/// ```
1643#[no_mangle]
1644pub unsafe extern "C" fn xmlNewCDataBlock(
1645    doc: *mut _xmlDoc,
1646    content: *const xmlChar,
1647    len: c_int,
1648) -> *mut _xmlNode {
1649    crate::xml::tree::new_cdata_block(doc, content, len)
1650}
1651
1652/// Create a new namespace definition.
1653///
1654/// # UPSTREAM-PARITY
1655///
1656/// ```c
1657/// xmlNsPtr xmlNewNs(xmlNodePtr node, const xmlChar *href, const xmlChar *prefix);
1658/// ```
1659///
1660/// # SAFETY
1661///
1662/// - `node` may be NULL.
1663/// - `href` and `prefix` are copied.
1664/// - Returns a borrowed pointer (namespace is owned by the node).
1665#[no_mangle]
1666pub unsafe extern "C" fn xmlNewNs(
1667    node: *mut _xmlNode,
1668    href: *const xmlChar,
1669    prefix: *const xmlChar,
1670) -> *mut _xmlNs {
1671    crate::xml::tree::new_ns(node, href, prefix)
1672}
1673
1674/// Set the namespace of a node.
1675///
1676/// # UPSTREAM-PARITY
1677///
1678/// ```c
1679/// void xmlSetNs(xmlNodePtr node, xmlNsPtr ns);
1680/// ```
1681#[no_mangle]
1682pub unsafe extern "C" fn xmlSetNs(node: *mut _xmlNode, ns: *mut _xmlNs) {
1683    crate::xml::tree::set_ns(node, ns);
1684}
1685
1686/// Get the namespace of a node.
1687///
1688/// # UPSTREAM-PARITY
1689///
1690/// ```c
1691/// xmlNsPtr xmlGetNsList(xmlDocPtr doc, const xmlNode *node);
1692/// ```
1693#[no_mangle]
1694pub unsafe extern "C" fn xmlGetNsList(
1695    doc: *mut _xmlDoc,
1696    node: *const _xmlNode,
1697) -> *mut *mut _xmlNs {
1698    crate::xml::tree::get_ns_list(doc, node as *mut _xmlNode)
1699}
1700
1701/// Search for a namespace by href.
1702///
1703/// # UPSTREAM-PARITY
1704///
1705/// ```c
1706/// xmlNsPtr xmlSearchNs(xmlDocPtr doc, xmlNodePtr node, const xmlChar *nameSpace);
1707/// ```
1708#[no_mangle]
1709pub unsafe extern "C" fn xmlSearchNs(
1710    doc: *mut _xmlDoc,
1711    node: *mut _xmlNode,
1712    nameSpace: *const xmlChar,
1713) -> *mut _xmlNs {
1714    crate::xml::tree::search_ns(doc, node, nameSpace)
1715}
1716
1717/// Search for a namespace by href, using the full in-scope chain.
1718///
1719/// # UPSTREAM-PARITY
1720///
1721/// ```c
1722/// xmlNsPtr xmlSearchNsByHref(xmlDocPtr doc, xmlNodePtr node, const xmlChar *href);
1723/// ```
1724#[no_mangle]
1725pub unsafe extern "C" fn xmlSearchNsByHref(
1726    doc: *mut _xmlDoc,
1727    node: *mut _xmlNode,
1728    href: *const xmlChar,
1729) -> *mut _xmlNs {
1730    crate::xml::tree::search_ns_by_href(doc, node, href)
1731}
1732
1733/// Set a property (attribute) on a node.
1734///
1735/// # UPSTREAM-PARITY
1736///
1737/// ```c
1738/// xmlAttrPtr xmlSetProp(xmlNodePtr node, const xmlChar *name, const xmlChar *value);
1739/// ```
1740///
1741/// If the attribute already exists, its value is updated.
1742/// Returns a borrowed pointer to the attribute.
1743///
1744/// # SAFETY
1745///
1746/// - `node` must be a valid element node.
1747/// - `name` must be a valid string.
1748/// - `value` may be NULL.
1749#[no_mangle]
1750pub unsafe extern "C" fn xmlSetProp(
1751    node: *mut _xmlNode,
1752    name: *const xmlChar,
1753    value: *const xmlChar,
1754) -> *mut _xmlAttr {
1755    crate::xml::tree::set_prop(node, name, value)
1756}
1757
1758/// Get a property value by name.
1759///
1760/// # UPSTREAM-PARITY
1761///
1762/// ```c
1763/// xmlChar *xmlGetProp(const xmlNode *node, const xmlChar *name);
1764/// ```
1765///
1766/// Returns a newly allocated string. Caller must free with `xmlFree`.
1767#[no_mangle]
1768pub unsafe extern "C" fn xmlGetProp(node: *const _xmlNode, name: *const xmlChar) -> *mut xmlChar {
1769    crate::xml::tree::get_prop(node as *mut _xmlNode, name)
1770}
1771
1772/// Get a namespaced property value.
1773///
1774/// # UPSTREAM-PARITY
1775///
1776/// ```c
1777/// xmlChar *xmlGetNsProp(const xmlNode *node, const xmlChar *name, const xmlChar *nameSpace);
1778/// ```
1779#[no_mangle]
1780pub unsafe extern "C" fn xmlGetNsProp(
1781    node: *const _xmlNode,
1782    name: *const xmlChar,
1783    nameSpace: *const xmlChar,
1784) -> *mut xmlChar {
1785    crate::xml::tree::get_ns_prop(node as *mut _xmlNode, name, nameSpace)
1786}
1787
1788/// Set a namespaced property.
1789///
1790/// # UPSTREAM-PARITY
1791///
1792/// ```c
1793/// xmlAttrPtr xmlSetNsProp(xmlNodePtr node, xmlNsPtr ns,
1794///                         const xmlChar *name, const xmlChar *value);
1795/// ```
1796#[no_mangle]
1797pub unsafe extern "C" fn xmlSetNsProp(
1798    node: *mut _xmlNode,
1799    ns: *mut _xmlNs,
1800    name: *const xmlChar,
1801    value: *const xmlChar,
1802) -> *mut _xmlAttr {
1803    crate::xml::tree::set_ns_prop(node, ns, name, value)
1804}
1805
1806/// Remove a property by name.
1807///
1808/// # UPSTREAM-PARITY
1809///
1810/// ```c
1811/// int xmlRemoveProp(xmlAttrPtr attr);
1812/// ```
1813///
1814/// Returns 0 on success, -1 on error.
1815#[no_mangle]
1816pub unsafe extern "C" fn xmlRemoveProp(attr: *mut _xmlAttr) -> c_int {
1817    crate::xml::tree::remove_prop(attr)
1818}
1819
1820/// Check whether a node has a property (upstream tree.h).
1821///
1822/// # UPSTREAM-PARITY
1823///
1824/// ```c
1825/// xmlAttrPtr xmlHasProp(const xmlNode *node, const xmlChar *name);
1826/// ```
1827///
1828/// Returns the attribute pointer or NULL.
1829#[no_mangle]
1830pub unsafe extern "C" fn xmlHasProp(node: *const _xmlNode, name: *const xmlChar) -> *mut _xmlAttr {
1831    crate::xml::tree::has_prop(node as *mut _xmlNode, name)
1832}
1833
1834/// Check whether a node has a namespaced property (upstream tree.h).
1835///
1836/// # UPSTREAM-PARITY
1837///
1838/// ```c
1839/// xmlAttrPtr xmlHasNsProp(const xmlNode *node, const xmlChar *name,
1840///                         const xmlChar *nameSpace);
1841/// ```
1842#[no_mangle]
1843pub unsafe extern "C" fn xmlHasNsProp(
1844    node: *const _xmlNode,
1845    name: *const xmlChar,
1846    nameSpace: *const xmlChar,
1847) -> *mut _xmlAttr {
1848    crate::xml::tree::has_ns_prop(node as *mut _xmlNode, name, nameSpace)
1849}
1850
1851/// Remove a property by name (upstream tree.h).
1852///
1853/// # UPSTREAM-PARITY
1854///
1855/// ```c
1856/// int xmlUnsetProp(xmlNodePtr node, const xmlChar *name);
1857/// ```
1858///
1859/// Returns 0 on success, -1 if not found.
1860#[no_mangle]
1861pub unsafe extern "C" fn xmlUnsetProp(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1862    crate::xml::tree::unset_prop(node, name)
1863}
1864
1865/// Remove a namespaced property by name (upstream tree.h).
1866///
1867/// # UPSTREAM-PARITY
1868///
1869/// ```c
1870/// int xmlUnsetNsProp(xmlNodePtr node, const xmlChar *name,
1871///                    const xmlChar *nameSpace);
1872/// ```
1873#[no_mangle]
1874pub unsafe extern "C" fn xmlUnsetNsProp(
1875    node: *mut _xmlNode,
1876    name: *const xmlChar,
1877    nameSpace: *const xmlChar,
1878) -> c_int {
1879    crate::xml::tree::unset_ns_prop(node, name, nameSpace)
1880}
1881
1882/// Return the first child element (upstream tree.h).
1883///
1884/// # UPSTREAM-PARITY
1885///
1886/// ```c
1887/// xmlNodePtr xmlFirstElementChild(xmlNodePtr parent);
1888/// ```
1889#[no_mangle]
1890pub unsafe extern "C" fn xmlFirstElementChild(parent: *mut _xmlNode) -> *mut _xmlNode {
1891    crate::xml::tree::first_element_child(parent)
1892}
1893
1894/// Return the last child element (upstream tree.h).
1895#[no_mangle]
1896pub unsafe extern "C" fn xmlLastElementChild(parent: *mut _xmlNode) -> *mut _xmlNode {
1897    crate::xml::tree::last_element_child(parent)
1898}
1899
1900/// Return the next element sibling (upstream tree.h).
1901#[no_mangle]
1902pub unsafe extern "C" fn xmlNextElementSibling(node: *mut _xmlNode) -> *mut _xmlNode {
1903    crate::xml::tree::next_element_sibling(node)
1904}
1905
1906/// Return the previous element sibling (upstream tree.h).
1907#[no_mangle]
1908pub unsafe extern "C" fn xmlPreviousElementSibling(node: *mut _xmlNode) -> *mut _xmlNode {
1909    crate::xml::tree::previous_element_sibling(node)
1910}
1911
1912/// Count the child elements (upstream tree.h).
1913///
1914/// # UPSTREAM-PARITY
1915///
1916/// ```c
1917/// unsigned long xmlChildElementCount(xmlNodePtr parent);
1918/// ```
1919#[no_mangle]
1920pub unsafe extern "C" fn xmlChildElementCount(parent: *mut _xmlNode) -> c_ulong {
1921    crate::xml::tree::child_element_count(parent)
1922}
1923
1924/// Concatenate text to a node (upstream tree.h).
1925///
1926/// # UPSTREAM-PARITY
1927///
1928/// ```c
1929/// int xmlTextConcat(xmlNodePtr node, const xmlChar *content, int len);
1930/// ```
1931#[no_mangle]
1932pub unsafe extern "C" fn xmlTextConcat(
1933    node: *mut _xmlNode,
1934    content: *const xmlChar,
1935    len: c_int,
1936) -> c_int {
1937    crate::xml::tree::text_concat(node, content, len)
1938}
1939
1940/// Merge two text nodes (upstream tree.h).
1941///
1942/// # UPSTREAM-PARITY
1943///
1944/// ```c
1945/// xmlNodePtr xmlTextMerge(xmlNodePtr first, xmlNodePtr second);
1946/// ```
1947#[no_mangle]
1948pub unsafe extern "C" fn xmlTextMerge(
1949    first: *mut _xmlNode,
1950    second: *mut _xmlNode,
1951) -> *mut _xmlNode {
1952    crate::xml::tree::text_merge(first, second)
1953}
1954
1955/// Get a DTD from a document, creating one if needed.
1956///
1957/// # UPSTREAM-PARITY
1958///
1959/// ```c
1960/// xmlDtdPtr xmlGetIntSubset(const xmlDoc *doc);
1961/// ```
1962#[no_mangle]
1963pub const extern "C" fn xmlGetIntSubset(doc: *const _xmlDoc) -> *mut _xmlDtd {
1964    crate::xml::tree::get_int_subset(doc)
1965}
1966
1967/// Create a new DTD.
1968///
1969/// # UPSTREAM-PARITY
1970///
1971/// ```c
1972/// xmlDtdPtr xmlNewDtd(xmlDocPtr doc, const xmlChar *name,
1973///                     const xmlChar *ExternalID, const xmlChar *SystemID);
1974/// ```
1975#[no_mangle]
1976pub unsafe extern "C" fn xmlNewDtd(
1977    doc: *mut _xmlDoc,
1978    name: *const xmlChar,
1979    ExternalID: *const xmlChar,
1980    SystemID: *const xmlChar,
1981) -> *mut _xmlDtd {
1982    crate::xml::tree::new_dtd(doc, name, ExternalID, SystemID)
1983}
1984
1985/// Create a new entity.
1986///
1987/// # UPSTREAM-PARITY
1988///
1989/// ```c
1990/// xmlEntityPtr xmlNewEntity(xmlDocPtr doc, const xmlChar *name, int type,
1991///                           const xmlChar *ExternalID, const xmlChar *SystemID,
1992///                           const xmlChar *content);
1993/// ```
1994#[no_mangle]
1995pub unsafe extern "C" fn xmlNewEntity(
1996    doc: *mut _xmlDoc,
1997    name: *const xmlChar,
1998    type_: c_int,
1999    ExternalID: *const xmlChar,
2000    SystemID: *const xmlChar,
2001    content: *const xmlChar,
2002) -> *mut _xmlEntity {
2003    crate::xml::tree::new_entity(doc, name, type_, ExternalID, SystemID, content)
2004}
2005
2006/// Get an entity by name.
2007///
2008/// # UPSTREAM-PARITY
2009///
2010/// ```c
2011/// xmlEntityPtr xmlGetDocEntity(const xmlDoc *doc, const xmlChar *name);
2012/// ```
2013#[no_mangle]
2014pub unsafe extern "C" fn xmlGetDocEntity(
2015    doc: *const _xmlDoc,
2016    name: *const xmlChar,
2017) -> *mut _xmlEntity {
2018    crate::xml::tree::get_doc_entity(doc, name)
2019}
2020
2021/// Get a parameter entity by name.
2022///
2023/// # UPSTREAM-PARITY
2024///
2025/// ```c
2026/// xmlEntityPtr xmlGetParameterEntity(const xmlDoc *doc, const xmlChar *name);
2027/// ```
2028#[no_mangle]
2029pub unsafe extern "C" fn xmlGetParameterEntity(
2030    doc: *const _xmlDoc,
2031    name: *const xmlChar,
2032) -> *mut _xmlEntity {
2033    crate::xml::tree::get_parameter_entity(doc, name)
2034}
2035
2036// ── DTD Declaration Exports ────────────────────────────────────────────
2037
2038/// Create an internal subset (DTD).
2039///
2040/// # UPSTREAM-PARITY
2041///
2042/// ```c
2043/// xmlDtdPtr xmlCreateIntSubset(xmlDocPtr doc, const xmlChar *name,
2044///                              const xmlChar *ExternalID, const xmlChar *SystemID);
2045/// ```
2046#[no_mangle]
2047pub unsafe extern "C" fn xmlCreateIntSubset(
2048    doc: *mut _xmlDoc,
2049    name: *const xmlChar,
2050    ExternalID: *const xmlChar,
2051    SystemID: *const xmlChar,
2052) -> *mut _xmlDtd {
2053    crate::xml::dtd::create_int_subset(doc, name, ExternalID, SystemID)
2054}
2055
2056/// Free a DTD.
2057///
2058/// # UPSTREAM-PARITY
2059///
2060/// ```c
2061/// void xmlFreeDtd(xmlDtdPtr dtd);
2062/// ```
2063#[no_mangle]
2064pub unsafe extern "C" fn xmlFreeDtd(dtd: *mut _xmlDtd) {
2065    crate::xml::dtd::free_dtd(dtd);
2066}
2067
2068/// Add a notation declaration (upstream valid.h 2.15: the `ctxt` first
2069/// argument is part of the ABI; R-000176).
2070///
2071/// # UPSTREAM-PARITY
2072///
2073/// ```c
2074/// xmlNotationPtr xmlAddNotationDecl(xmlValidCtxt *ctxt, xmlDtd *dtd,
2075///                                   const xmlChar *name,
2076///                                   const xmlChar *publicId,
2077///                                   const xmlChar *systemId);
2078/// ```
2079///
2080/// `ctxt` is accepted for ABI parity; upstream uses it only for validation
2081/// error reporting, which the candidate performs in `xml/validation` (the
2082/// parser-side error model), so the argument is currently unused.
2083#[no_mangle]
2084pub unsafe extern "C" fn xmlAddNotationDecl(
2085    _ctxt: *mut _xmlValidCtxt,
2086    dtd: *mut _xmlDtd,
2087    name: *const xmlChar,
2088    PublicID: *const xmlChar,
2089    SystemID: *const xmlChar,
2090) -> *mut _xmlNotation {
2091    crate::xml::dtd::add_notation_decl(dtd, name, PublicID, SystemID)
2092}
2093
2094/// Look up a notation declaration.
2095///
2096/// # UPSTREAM-PARITY
2097///
2098/// ```c
2099/// xmlNotationPtr xmlGetNotationDecl(xmlDtdPtr dtd, const xmlChar *name);
2100/// ```
2101#[no_mangle]
2102pub unsafe extern "C" fn xmlGetNotationDecl(
2103    dtd: *mut _xmlDtd,
2104    name: *const xmlChar,
2105) -> *mut _xmlNotation {
2106    crate::xml::dtd::get_notation_decl(dtd, name)
2107}
2108
2109/// Copy a notation declaration.
2110///
2111/// # UPSTREAM-PARITY
2112///
2113/// ```c
2114/// xmlNotationPtr xmlCopyNotation(xmlNotationPtr notation);
2115/// ```
2116#[no_mangle]
2117pub unsafe extern "C" fn xmlCopyNotation(notation: *mut _xmlNotation) -> *mut _xmlNotation {
2118    crate::xml::dtd::copy_notation(notation)
2119}
2120
2121/// Free a notation declaration.
2122///
2123/// # UPSTREAM-PARITY
2124///
2125/// ```c
2126/// void xmlFreeNotation(xmlNotationPtr notation);
2127/// ```
2128#[no_mangle]
2129pub unsafe extern "C" fn xmlFreeNotation(notation: *mut _xmlNotation) {
2130    crate::xml::dtd::free_notation(notation);
2131}
2132
2133/// Add an element declaration (upstream valid.h 2.15: `ctxt` first arg;
2134/// R-000176).
2135///
2136/// # UPSTREAM-PARITY
2137///
2138/// ```c
2139/// xmlElementPtr xmlAddElementDecl(xmlValidCtxt *ctxt, xmlDtd *dtd,
2140///                                 const xmlChar *name, xmlElementTypeVal type,
2141///                                 xmlElementContent *content);
2142/// ```
2143///
2144/// `ctxt` is accepted for ABI parity; upstream uses it only for validation
2145/// error reporting (candidate: parser-side error model in `xml/validation`).
2146#[no_mangle]
2147pub unsafe extern "C" fn xmlAddElementDecl(
2148    _ctxt: *mut _xmlValidCtxt,
2149    dtd: *mut _xmlDtd,
2150    name: *const xmlChar,
2151    type_: c_int,
2152    content: *mut _xmlElementContent,
2153) -> *mut _xmlElement {
2154    crate::xml::dtd::add_element_decl(dtd, name, type_, content)
2155}
2156
2157/// Look up an element declaration.
2158///
2159/// # UPSTREAM-PARITY
2160///
2161/// ```c
2162/// xmlElementPtr xmlGetElementDecl(xmlDtdPtr dtd, const xmlChar *name);
2163/// ```
2164#[no_mangle]
2165pub unsafe extern "C" fn xmlGetElementDecl(
2166    dtd: *mut _xmlDtd,
2167    name: *const xmlChar,
2168) -> *mut _xmlElement {
2169    crate::xml::dtd::get_element_decl(dtd, name)
2170}
2171
2172/// Copy an element declaration.
2173///
2174/// # UPSTREAM-PARITY
2175///
2176/// ```c
2177/// xmlElementPtr xmlCopyElement(xmlElementPtr elem);
2178/// ```
2179#[no_mangle]
2180pub unsafe extern "C" fn xmlCopyElement(elem: *mut _xmlElement) -> *mut _xmlElement {
2181    crate::xml::dtd::copy_element(elem)
2182}
2183
2184/// Free an element declaration.
2185///
2186/// # UPSTREAM-PARITY
2187///
2188/// ```c
2189/// void xmlFreeElement(xmlElementPtr elem);
2190/// ```
2191#[no_mangle]
2192pub unsafe extern "C" fn xmlFreeElement(elem: *mut _xmlElement) {
2193    crate::xml::dtd::free_element(elem);
2194}
2195
2196/// Add an attribute declaration (upstream valid.h 2.15: 9-arg contract with
2197/// `ctxt` and the namespace key; R-000176).
2198///
2199/// # UPSTREAM-PARITY
2200///
2201/// ```c
2202/// xmlAttributePtr xmlAddAttributeDecl(xmlValidCtxt *ctxt, xmlDtd *dtd,
2203///                                     const xmlChar *elem, const xmlChar *name,
2204///                                     const xmlChar *ns, xmlAttributeType type,
2205///                                     xmlAttributeDefault def,
2206///                                     const xmlChar *defaultValue,
2207///                                     xmlEnumeration *tree);
2208/// ```
2209///
2210/// `ctxt` is accepted for ABI parity; upstream uses it only for validation
2211/// error reporting (candidate: parser-side error model in `xml/validation`).
2212/// The `ns` namespace is threaded into the DTD attribute table as the
2213/// middle hash key and into `attr->prefix`, exactly as upstream valid.c.
2214#[no_mangle]
2215pub unsafe extern "C" fn xmlAddAttributeDecl(
2216    _ctxt: *mut _xmlValidCtxt,
2217    dtd: *mut _xmlDtd,
2218    elem: *mut _xmlElement,
2219    name: *const xmlChar,
2220    ns: *const xmlChar,
2221    type_: c_int,
2222    def: c_int,
2223    defaultValue: *const xmlChar,
2224    tree: *mut _xmlEnumeration,
2225) -> *mut _xmlAttribute {
2226    crate::xml::dtd::add_attribute_decl(dtd, elem, name, ns, type_, def, defaultValue, tree)
2227}
2228
2229/// Look up an attribute declaration.
2230///
2231/// # UPSTREAM-PARITY
2232///
2233/// ```c
2234/// xmlAttributePtr xmlGetAttributeDecl(xmlDtdPtr dtd, xmlElementPtr elem,
2235///                                     const xmlChar *name, int namePrefix);
2236/// ```
2237#[no_mangle]
2238pub unsafe extern "C" fn xmlGetAttributeDecl(
2239    dtd: *mut _xmlDtd,
2240    elem: *mut _xmlElement,
2241    name: *const xmlChar,
2242    namePrefix: c_int,
2243) -> *mut _xmlAttribute {
2244    crate::xml::dtd::get_attribute_decl(dtd, elem, name, namePrefix)
2245}
2246
2247/// Copy an attribute declaration.
2248///
2249/// # UPSTREAM-PARITY
2250///
2251/// ```c
2252/// xmlAttributePtr xmlCopyAttribute(xmlAttributePtr attr);
2253/// ```
2254#[no_mangle]
2255pub unsafe extern "C" fn xmlCopyAttribute(attr: *mut _xmlAttribute) -> *mut _xmlAttribute {
2256    crate::xml::dtd::copy_attribute_decl(attr)
2257}
2258
2259/// Free an attribute declaration.
2260///
2261/// # UPSTREAM-PARITY
2262///
2263/// ```c
2264/// void xmlFreeAttribute(xmlAttributePtr attr);
2265/// ```
2266#[no_mangle]
2267pub unsafe extern "C" fn xmlFreeAttribute(attr: *mut _xmlAttribute) {
2268    crate::xml::dtd::free_attribute(attr);
2269}
2270
2271/// Create a new element content model.
2272///
2273/// # UPSTREAM-PARITY
2274///
2275/// ```c
2276/// xmlElementContentPtr xmlNewElementContent(const xmlChar *name, int type);
2277/// ```
2278#[no_mangle]
2279pub unsafe extern "C" fn xmlNewElementContent(
2280    name: *const xmlChar,
2281    type_: c_int,
2282) -> *mut _xmlElementContent {
2283    crate::xml::dtd::create_content_model(name, type_)
2284}
2285
2286/// Copy an element content model.
2287///
2288/// # UPSTREAM-PARITY
2289///
2290/// ```c
2291/// xmlElementContentPtr xmlCopyElementContent(xmlElementContentPtr content);
2292/// ```
2293#[no_mangle]
2294pub unsafe extern "C" fn xmlCopyElementContent(
2295    content: *mut _xmlElementContent,
2296) -> *mut _xmlElementContent {
2297    crate::xml::dtd::copy_content_model(content)
2298}
2299
2300/// Free an element content model.
2301///
2302/// # UPSTREAM-PARITY
2303///
2304/// ```c
2305/// void xmlFreeElementContent(xmlElementContentPtr cur);
2306/// ```
2307#[no_mangle]
2308pub unsafe extern "C" fn xmlFreeElementContent(cur: *mut _xmlElementContent) {
2309    crate::xml::dtd::free_content_model(cur);
2310}
2311
2312// ── Entity Exports ─────────────────────────────────────────────────────
2313
2314/// Add an entity declaration to a document's DTD (upstream entities.h 2.15:
2315/// entities.c `xmlAddEntity` — a full int/error-code contract, R-000176).
2316///
2317/// # UPSTREAM-PARITY
2318///
2319/// ```c
2320/// int xmlAddEntity(xmlDoc *doc, int extSubset, const xmlChar *name, int type,
2321///                  const xmlChar *publicId, const xmlChar *systemId,
2322///                  const xmlChar *content, xmlEntity **out);
2323/// ```
2324///
2325/// Returns 0 on success (with the new entity in `*out`), `XML_ERR_ARGUMENT`
2326/// for NULL doc/name or an unknown type, `XML_DTD_NO_DTD` when the selected
2327/// subset does not exist, `XML_ERR_REDECL_PREDEF_ENTITY` for an invalid
2328/// predefined-entity redeclaration, `XML_ERR_NO_MEMORY` on allocation
2329/// failure and `XML_WAR_ENTITY_REDEFINED` when the name already exists.
2330/// `*out` is set to NULL on entry and on every failure.
2331#[no_mangle]
2332pub unsafe extern "C" fn xmlAddEntity(
2333    doc: *mut _xmlDoc,
2334    extSubset: c_int,
2335    name: *const xmlChar,
2336    type_: c_int,
2337    publicId: *const xmlChar,
2338    systemId: *const xmlChar,
2339    content: *const xmlChar,
2340    out: *mut *mut _xmlEntity,
2341) -> c_int {
2342    crate::xml::entities::add_entity_doc(
2343        doc, extSubset, name, type_, publicId, systemId, content, out,
2344    )
2345}
2346
2347/// Add an entity declaration to a document's internal subset (upstream
2348/// entities.c `xmlAddDocEntity`): if the document has no internal subset
2349/// one is created.
2350///
2351/// # UPSTREAM-PARITY
2352///
2353/// ```c
2354/// xmlEntityPtr xmlAddDocEntity(xmlDocPtr doc, const xmlChar *name, int type,
2355///                              const xmlChar *ExternalID, const xmlChar *SystemID,
2356///                              const xmlChar *content);
2357/// ```
2358#[no_mangle]
2359pub unsafe extern "C" fn xmlAddDocEntity(
2360    doc: *mut _xmlDoc,
2361    name: *const xmlChar,
2362    type_: c_int,
2363    ExternalID: *const xmlChar,
2364    SystemID: *const xmlChar,
2365    content: *const xmlChar,
2366) -> *mut _xmlEntity {
2367    crate::xml::tree::add_doc_entity(doc, name, type_, ExternalID, SystemID, content)
2368}
2369
2370/// Add an entity declaration to a document's external subset (upstream
2371/// entities.c `xmlAddDtdEntity`).
2372///
2373/// # UPSTREAM-PARITY
2374///
2375/// ```c
2376/// xmlEntityPtr xmlAddDtdEntity(xmlDocPtr doc, const xmlChar *name, int type,
2377///                              const xmlChar *ExternalID, const xmlChar *SystemID,
2378///                              const xmlChar *content);
2379/// ```
2380#[no_mangle]
2381pub unsafe extern "C" fn xmlAddDtdEntity(
2382    doc: *mut _xmlDoc,
2383    name: *const xmlChar,
2384    type_: c_int,
2385    ExternalID: *const xmlChar,
2386    SystemID: *const xmlChar,
2387    content: *const xmlChar,
2388) -> *mut _xmlEntity {
2389    crate::xml::tree::add_dtd_entity(doc, name, type_, ExternalID, SystemID, content)
2390}
2391
2392/// Get an entity declaration from a DTD (upstream entities.c
2393/// `xmlGetDtdEntity`): searches the internal then external subset.
2394///
2395/// # UPSTREAM-PARITY
2396///
2397/// ```c
2398/// xmlEntityPtr xmlGetDtdEntity(xmlDocPtr doc, const xmlChar *name);
2399/// ```
2400#[no_mangle]
2401pub unsafe extern "C" fn xmlGetDtdEntity(
2402    doc: *mut _xmlDoc,
2403    name: *const xmlChar,
2404) -> *mut _xmlEntity {
2405    crate::xml::tree::get_dtd_entity(doc, name)
2406}
2407
2408/// Get an entity by name.
2409///
2410/// # UPSTREAM-PARITY
2411///
2412/// ```c
2413/// xmlEntityPtr xmlGetEntity(xmlDocPtr doc, const xmlChar *name);
2414/// ```
2415#[no_mangle]
2416pub unsafe extern "C" fn xmlGetEntity(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlEntity {
2417    crate::xml::entities::get_entity(doc, name)
2418}
2419
2420/// Copy an entity.
2421///
2422/// # UPSTREAM-PARITY
2423///
2424/// ```c
2425/// xmlEntityPtr xmlCopyEntity(xmlEntityPtr entity);
2426/// ```
2427#[no_mangle]
2428pub unsafe extern "C" fn xmlCopyEntity(entity: *mut _xmlEntity) -> *mut _xmlEntity {
2429    crate::xml::entities::copy_entity(entity)
2430}
2431
2432/// Free an entity.
2433///
2434/// # UPSTREAM-PARITY
2435///
2436/// ```c
2437/// void xmlFreeEntity(xmlEntityPtr entity);
2438/// ```
2439#[no_mangle]
2440pub unsafe extern "C" fn xmlFreeEntity(entity: *mut _xmlEntity) {
2441    crate::xml::entities::free_entity(entity);
2442}
2443
2444/// Encode entities for reentrant output.
2445///
2446/// # UPSTREAM-PARITY
2447///
2448/// ```c
2449/// xmlChar *xmlEncodeEntitiesReentrant(xmlDocPtr doc, const xmlChar *input);
2450/// ```
2451#[no_mangle]
2452pub unsafe extern "C" fn xmlEncodeEntitiesReentrant(
2453    doc: *mut _xmlDoc,
2454    input: *const xmlChar,
2455) -> *mut xmlChar {
2456    crate::xml::entities::encode_entities_reentrant(doc, input)
2457}
2458
2459/// Encode special characters in a string (upstream entities.c
2460/// `xmlEncodeSpecialChars`): escapes `<`, `>`, `&`, `"` and `\r`.
2461///
2462/// # UPSTREAM-PARITY
2463///
2464/// ```c
2465/// xmlChar *xmlEncodeSpecialChars(const xmlDoc *doc, const xmlChar *input);
2466/// ```
2467///
2468/// Returns a newly allocated string (free with `xmlFree`) or NULL.
2469#[no_mangle]
2470pub unsafe extern "C" fn xmlEncodeSpecialChars(
2471    _doc: *const _xmlDoc,
2472    input: *const xmlChar,
2473) -> *mut xmlChar {
2474    if input.is_null() {
2475        return ptr::null_mut();
2476    }
2477    unsafe {
2478        let len = crate::xml::string::xml_strlen(input);
2479        // Worst case: every byte becomes a 6-byte entity (&#13; is 5; &quot;
2480        // is 6).
2481        let cap = len * 6 + 1;
2482        let out = crate::abi::allocator::xmlMallocImpl(cap) as *mut xmlChar;
2483        if out.is_null() {
2484            return ptr::null_mut();
2485        }
2486        let mut o = 0usize;
2487        let mut i = 0usize;
2488        while i < len {
2489            let c = *input.add(i);
2490            let rep: &[u8] = match c {
2491                b'<' => b"&lt;",
2492                b'>' => b"&gt;",
2493                b'&' => b"&amp;",
2494                b'"' => b"&quot;",
2495                b'\r' => b"&#13;",
2496                _ => {
2497                    *out.add(o) = c;
2498                    o += 1;
2499                    i += 1;
2500                    continue;
2501                }
2502            };
2503            core::ptr::copy_nonoverlapping(rep.as_ptr(), out.add(o), rep.len());
2504            o += rep.len();
2505            i += 1;
2506        }
2507        *out.add(o) = 0;
2508        out
2509    }
2510}
2511
2512/// Deprecated entity encoder (upstream 2.15 `xmlEncodeEntities`): the
2513/// symbol still exists for ABI compatibility but emits a one-time
2514/// deprecation warning and returns NULL (verified against the oracle DSO
2515/// disassembly — the 2.15 implementation returns NULL after warning).
2516///
2517/// # UPSTREAM-PARITY
2518///
2519/// ```c
2520/// xmlChar *xmlEncodeEntities(xmlDocPtr doc, const xmlChar *input);
2521/// ```
2522#[no_mangle]
2523pub unsafe extern "C" fn xmlEncodeEntities(
2524    _doc: *mut _xmlDoc,
2525    _input: *const xmlChar,
2526) -> *mut xmlChar {
2527    use core::sync::atomic::{AtomicBool, Ordering};
2528    static WARNED: AtomicBool = AtomicBool::new(false);
2529    if !WARNED.swap(true, Ordering::Relaxed) {
2530        // Match the oracle: one-time "deprecated" diagnostic on stderr.
2531        let msg = b"xmlEncodeEntities is deprecated, use xmlEncodeSpecialChars or xmlEncodeEntitiesReentrant\n";
2532        unsafe {
2533            libc::fwrite(
2534                msg.as_ptr() as *const c_void,
2535                1,
2536                msg.len(),
2537                libc::fdopen(2, b"w\0" as *const u8 as *const c_char),
2538            );
2539        }
2540    }
2541    ptr::null_mut()
2542}
2543
2544/// Get the line number of a node.
2545///
2546/// # UPSTREAM-PARITY
2547///
2548/// ```c
2549/// long xmlGetLineNo(const xmlNode *node);
2550/// ```
2551#[no_mangle]
2552pub extern "C" fn xmlGetLineNo(node: *const _xmlNode) -> c_long {
2553    crate::xml::tree::get_line_no(node)
2554}
2555
2556// ═══════════════════════════════════════════════════════════════════════════════
2557// Serialization — xmlNodeDump, xmlDocDump, xmlSaveFile, etc.
2558// ═══════════════════════════════════════════════════════════════════════════════
2559
2560/// Dump a node to a buffer.
2561///
2562/// # UPSTREAM-PARITY
2563///
2564/// ```c
2565/// int xmlNodeDump(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur, int level, int format);
2566/// ```
2567#[no_mangle]
2568pub unsafe extern "C" fn xmlNodeDump(
2569    buf: *mut _xmlBuffer,
2570    doc: *mut _xmlDoc,
2571    cur: *mut _xmlNode,
2572    level: c_int,
2573    format: c_int,
2574) -> c_int {
2575    if buf.is_null() || cur.is_null() {
2576        return -1;
2577    }
2578    crate::xml::tree::xmlNodeDump(buf, doc, cur, level, format)
2579}
2580
2581/// Dump a document to a file pointer.
2582///
2583/// # UPSTREAM-PARITY
2584///
2585/// ```c
2586/// int xmlDocDump(FILE *f, xmlDocPtr doc);
2587/// ```
2588#[no_mangle]
2589pub unsafe extern "C" fn xmlDocDump(fp: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2590    if fp.is_null() || doc.is_null() {
2591        return -1;
2592    }
2593    crate::xml::tree::xmlDocDump(fp, doc)
2594}
2595
2596/// Dump a document to memory with format.
2597///
2598/// # UPSTREAM-PARITY
2599///
2600/// ```c
2601/// void xmlDocDumpFormatMemory(xmlDocPtr doc, xmlChar **mem, int *size, int format);
2602/// ```
2603#[no_mangle]
2604pub unsafe extern "C" fn xmlDocDumpFormatMemory(
2605    doc: *mut _xmlDoc,
2606    mem: *mut *mut xmlChar,
2607    size: *mut c_int,
2608    format: c_int,
2609) {
2610    if doc.is_null() || mem.is_null() || size.is_null() {
2611        return;
2612    }
2613    crate::xml::tree::xmlDocDumpFormatMemory(doc, mem, size, format)
2614}
2615
2616/// Dump a document to memory (unformatted).
2617///
2618/// # UPSTREAM-PARITY
2619///
2620/// ```c
2621/// void xmlDocDumpMemory(xmlDocPtr doc, xmlChar **mem, int *size);
2622/// ```
2623#[no_mangle]
2624pub unsafe extern "C" fn xmlDocDumpMemory(
2625    doc: *mut _xmlDoc,
2626    mem: *mut *mut xmlChar,
2627    size: *mut c_int,
2628) {
2629    if doc.is_null() || mem.is_null() || size.is_null() {
2630        return;
2631    }
2632    crate::xml::tree::xmlDocDumpMemory(doc, mem, size)
2633}
2634
2635/// Save a document to a file.
2636///
2637/// # UPSTREAM-PARITY
2638///
2639/// ```c
2640/// int xmlSaveFile(const char *filename, xmlDocPtr cur);
2641/// ```
2642#[no_mangle]
2643pub unsafe extern "C" fn xmlSaveFile(filename: *const c_char, cur: *mut _xmlDoc) -> c_int {
2644    if filename.is_null() || cur.is_null() {
2645        return -1;
2646    }
2647    crate::xml::tree::xmlSaveFile(filename, cur)
2648}
2649
2650/// Save a document to a file with encoding.
2651///
2652/// # UPSTREAM-PARITY
2653///
2654/// ```c
2655/// int xmlSaveFileEnc(const char *filename, xmlDocPtr cur, const char *encoding);
2656/// ```
2657#[no_mangle]
2658pub unsafe extern "C" fn xmlSaveFileEnc(
2659    filename: *const c_char,
2660    cur: *mut _xmlDoc,
2661    encoding: *const c_char,
2662) -> c_int {
2663    if filename.is_null() || cur.is_null() {
2664        return -1;
2665    }
2666    crate::xml::tree::xmlSaveFileEnc(filename, cur, encoding)
2667}
2668
2669/// Save a document to a file with format.
2670///
2671/// # UPSTREAM-PARITY
2672///
2673/// ```c
2674/// int xmlSaveFormatFile(const char *filename, xmlDocPtr cur, int format);
2675/// ```
2676#[no_mangle]
2677pub unsafe extern "C" fn xmlSaveFormatFile(
2678    filename: *const c_char,
2679    cur: *mut _xmlDoc,
2680    format: c_int,
2681) -> c_int {
2682    if filename.is_null() || cur.is_null() {
2683        return -1;
2684    }
2685    crate::xml::tree::xmlSaveFormatFile(filename, cur, format)
2686}
2687
2688/// Save a document to a file with encoding and format.
2689///
2690/// # UPSTREAM-PARITY
2691///
2692/// ```c
2693/// int xmlSaveFormatFileEnc(const char *filename, xmlDocPtr cur, const char *encoding, int format);
2694/// ```
2695#[no_mangle]
2696pub unsafe extern "C" fn xmlSaveFormatFileEnc(
2697    filename: *const c_char,
2698    cur: *mut _xmlDoc,
2699    encoding: *const c_char,
2700    format: c_int,
2701) -> c_int {
2702    if filename.is_null() || cur.is_null() {
2703        return -1;
2704    }
2705    crate::xml::tree::xmlSaveFormatFileEnc(filename, cur, encoding, format)
2706}
2707
2708// ═══════════════════════════════════════════════════════════════════════════════
2709// 7. Parser — SAX, DOM, Push, Reader
2710// ═══════════════════════════════════════════════════════════════════════════════
2711
2712/// Read an XML document from a string.
2713///
2714/// # UPSTREAM-PARITY
2715///
2716/// ```c
2717/// xmlDocPtr xmlReadDoc(const xmlChar *cur, const char *URL,
2718///                      const char *encoding, int options);
2719/// ```
2720///
2721/// Returns a parsed document. Caller must free with `xmlFreeDoc`.
2722#[no_mangle]
2723pub unsafe extern "C" fn xmlReadDoc(
2724    cur: *const xmlChar,
2725    URL: *const c_char,
2726    encoding: *const c_char,
2727    options: c_int,
2728) -> *mut _xmlDoc {
2729    // SAFETY: cur must be a valid null-terminated xmlChar string if non-null.
2730    if cur.is_null() {
2731        return ptr::null_mut();
2732    }
2733    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2734    if ctxt.is_null() {
2735        return ptr::null_mut();
2736    }
2737    let len = crate::xml::string::xml_strlen(cur);
2738    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
2739    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2740    (*ctxt).options = options;
2741    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
2742        let doc = (*ctxt).myDoc;
2743        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2744        return doc;
2745    }
2746    let doc = (*ctxt).myDoc;
2747    if !doc.is_null() {
2748        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
2749    }
2750    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2751    doc
2752}
2753
2754/// Read an XML document from a file.
2755///
2756/// # UPSTREAM-PARITY
2757///
2758/// ```c
2759/// xmlDocPtr xmlReadFile(const char *URL, const char *encoding, int options);
2760/// ```
2761#[no_mangle]
2762pub unsafe extern "C" fn xmlReadFile(
2763    URL: *const c_char,
2764    encoding: *const c_char,
2765    options: c_int,
2766) -> *mut _xmlDoc {
2767    // SAFETY: URL must be a valid C string or NULL.
2768    if URL.is_null() {
2769        return ptr::null_mut();
2770    }
2771    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2772    if ctxt.is_null() {
2773        return ptr::null_mut();
2774    }
2775    let input = match crate::xml::parser::helpers::input_from_file(URL) {
2776        Ok(input) => input,
2777        Err(_) => {
2778            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2779            return ptr::null_mut();
2780        }
2781    };
2782    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2783    (*ctxt).options = options;
2784    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
2785        let doc = (*ctxt).myDoc;
2786        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2787        // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
2788        // partially built document is discarded and NULL is returned; with
2789        // XML_PARSE_RECOVER the partial tree is kept.
2790        if options & 1 << 0 != 0 {
2791            return doc;
2792        }
2793        if !doc.is_null() {
2794            crate::xml::tree::free_doc(doc);
2795        }
2796        return ptr::null_mut();
2797    }
2798    let doc = (*ctxt).myDoc;
2799    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2800    doc
2801}
2802
2803/// Recover-parse a document from a string (upstream parser.h): same as
2804/// `xmlReadDoc` with XML_PARSE_RECOVER forced.
2805///
2806/// # UPSTREAM-PARITY
2807///
2808/// ```c
2809/// xmlDocPtr xmlRecoverDoc(const xmlChar *cur);
2810/// ```
2811#[no_mangle]
2812pub unsafe extern "C" fn xmlRecoverDoc(cur: *const xmlChar) -> *mut _xmlDoc {
2813    unsafe { xmlReadDoc(cur, ptr::null(), ptr::null(), 1 << 0) }
2814}
2815
2816/// Recover-parse a document from a file (upstream parser.h).
2817///
2818/// # UPSTREAM-PARITY
2819///
2820/// ```c
2821/// xmlDocPtr xmlRecoverFile(const char *filename);
2822/// ```
2823#[no_mangle]
2824pub unsafe extern "C" fn xmlRecoverFile(filename: *const c_char) -> *mut _xmlDoc {
2825    unsafe { xmlReadFile(filename, ptr::null(), 1 << 0) }
2826}
2827
2828/// Recover-parse a document from memory (upstream parser.h).
2829///
2830/// # UPSTREAM-PARITY
2831///
2832/// ```c
2833/// xmlDocPtr xmlRecoverMemory(const char *buffer, int size);
2834/// ```
2835#[no_mangle]
2836pub unsafe extern "C" fn xmlRecoverMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
2837    unsafe { xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 1 << 0) }
2838}
2839
2840/// Read an XML document from a file (upstream parser.h).
2841///
2842/// # UPSTREAM-PARITY
2843///
2844/// ```c
2845/// xmlDocPtr xmlReadMemory(const char *buffer, int size,
2846///                         const char *URL, const char *encoding, int options);
2847/// ```
2848#[no_mangle]
2849pub unsafe extern "C" fn xmlReadMemory(
2850    buffer: *const c_char,
2851    size: c_int,
2852    URL: *const c_char,
2853    encoding: *const c_char,
2854    options: c_int,
2855) -> *mut _xmlDoc {
2856    // SAFETY: buffer must be a valid pointer with at least `size` readable
2857    // bytes. An empty input (size 0) is still parsed — upstream reports
2858    // "Document is empty".
2859    if buffer.is_null() || size < 0 {
2860        return ptr::null_mut();
2861    }
2862    // UPSTREAM-PARITY + HOSTILE-ABI hardening (parser.c 2.15 xmlReadMemory):
2863    // upstream only rejects `size < 0` and then streams from the caller's
2864    // buffer, so a size at or beyond INT_MAX turns into an unsized wild read
2865    // (the oracle's own outcome for xmlReadMemory("<a/>", INT_MAX, ...) is a
2866    // NULL document — or a crash of the oracle itself depending on the heap
2867    // layout). The candidate rejects such sizes up front instead of copying
2868    // ~2 GiB from the caller's buffer; the observable result matches the
2869    // oracle's deterministic probe outcome (HOSTILE-ABI D1).
2870    if size == c_int::MAX {
2871        return ptr::null_mut();
2872    }
2873    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2874    if ctxt.is_null() {
2875        return ptr::null_mut();
2876    }
2877    // UPSTREAM-PARITY: the URL becomes the input's filename (feeds the
2878    // `file:line:` error prefix and doc->URL).
2879    let input = crate::xml::parser::helpers::input_from_memory_named(buffer, size, URL);
2880    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2881    // UPSTREAM-PARITY (xmlReadMemory -> xmlCtxtReadMemory): the parse
2882    // options are mirrored into the context members (dictNames, keepBlanks,
2883    // recovery, ...) before parsing starts.
2884    crate::abi::exports_parser::apply_options(ctxt, options);
2885    let parsed = crate::xml::parser::helpers::parse_document(ctxt);
2886    let doc = (*ctxt).myDoc;
2887    // UPSTREAM-PARITY: the URL is attached to the document on success AND on
2888    // the recovery path (the partial tree keeps the document identity).
2889    if !doc.is_null() && !URL.is_null() && (*doc).URL.is_null() {
2890        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
2891    }
2892    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2893    if parsed != 0 {
2894        // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
2895        // partially built document is discarded and NULL is returned; with
2896        // XML_PARSE_RECOVER the partial tree is kept.
2897        if options & 1 << 0 != 0 {
2898            return doc;
2899        }
2900        if !doc.is_null() {
2901            crate::xml::tree::free_doc(doc);
2902        }
2903        return ptr::null_mut();
2904    }
2905    doc
2906}
2907
2908/// Load a list of catalogs (upstream `xmlLoadCatalogs`).
2909///
2910/// # SAFETY
2911///
2912/// - `catalogs` must be a valid NUL-terminated string or NULL.
2913#[no_mangle]
2914pub unsafe extern "C" fn xmlLoadCatalogs(catalogs: *const c_char) {
2915    if !catalogs.is_null() {
2916        crate::xml::catalog::load_catalog(catalogs);
2917    }
2918}
2919
2920/// Load a single catalog (upstream `xmlLoadCatalog`).
2921///
2922/// # SAFETY
2923///
2924/// - `catalogs` must be a valid NUL-terminated string or NULL.
2925#[no_mangle]
2926pub unsafe extern "C" fn xmlLoadCatalog(catalogs: *const c_char) -> c_int {
2927    // UPSTREAM-PARITY (catalog.c xmlLoadCatalog): returns 0 on success,
2928    // 1 on error (unlike xmlCatalogLoad which returns the catalog handle).
2929    let handle = crate::xml::catalog::load_catalog(catalogs);
2930    if handle.is_null() {
2931        1
2932    } else {
2933        0
2934    }
2935}
2936
2937/// Read an XML document from a file descriptor.
2938///
2939/// # UPSTREAM-PARITY
2940///
2941/// ```c
2942/// xmlDocPtr xmlReadFd(int fd, const char *URL, const char *encoding, int options);
2943/// ```
2944#[no_mangle]
2945pub unsafe extern "C" fn xmlReadFd(
2946    fd: c_int,
2947    URL: *const c_char,
2948    encoding: *const c_char,
2949    options: c_int,
2950) -> *mut _xmlDoc {
2951    // SAFETY: fd must be a valid open file descriptor.
2952    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2953    if ctxt.is_null() {
2954        return ptr::null_mut();
2955    }
2956    // Read all data from the fd
2957    let mut buf = Vec::new();
2958    let mut tmp = [0u8; 4096];
2959    loop {
2960        let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
2961        if n <= 0 {
2962            break;
2963        }
2964        buf.extend_from_slice(&tmp[..n as usize]);
2965    }
2966    let input = crate::xml::parser::helpers::input_from_memory(
2967        buf.as_ptr() as *const c_char,
2968        buf.len() as c_int,
2969    );
2970    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2971    (*ctxt).options = options;
2972    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
2973        let doc = (*ctxt).myDoc;
2974        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2975        return doc;
2976    }
2977    let doc = (*ctxt).myDoc;
2978    if !doc.is_null() && !URL.is_null() {
2979        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
2980    }
2981    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2982    doc
2983}
2984
2985/// Read an XML document from I/O callbacks.
2986///
2987/// # UPSTREAM-PARITY
2988///
2989/// ```c
2990/// xmlDocPtr xmlReadIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
2991///                     void *ioctx, const char *URL, const char *encoding, int options);
2992/// ```
2993#[no_mangle]
2994pub unsafe extern "C" fn xmlReadIO(
2995    ioread: Option<xmlInputReadCallback>,
2996    ioclose: Option<xmlInputCloseCallback>,
2997    ioctx: *mut c_void,
2998    URL: *const c_char,
2999    encoding: *const c_char,
3000    options: c_int,
3001) -> *mut _xmlDoc {
3002    // SAFETY: callbacks must be valid function pointers if non-NULL.
3003    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3004    if ctxt.is_null() {
3005        return ptr::null_mut();
3006    }
3007    let input = crate::xml::parser::helpers::input_from_io(ioread, ioclose, ioctx);
3008    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3009    (*ctxt).options = options;
3010    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
3011        let doc = (*ctxt).myDoc;
3012        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3013        return doc;
3014    }
3015    let doc = (*ctxt).myDoc;
3016    if !doc.is_null() && !URL.is_null() {
3017        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
3018    }
3019    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3020    doc
3021}
3022
3023/// Parse an XML document (SAX1).
3024///
3025/// # UPSTREAM-PARITY
3026///
3027/// ```c
3028/// xmlDocPtr xmlSAXParseDoc(xmlSAXHandlerPtr sax, const xmlChar *cur, int recovery);
3029/// ```
3030#[no_mangle]
3031pub unsafe extern "C" fn xmlSAXParseDoc(
3032    sax: *mut _xmlSAXHandler,
3033    cur: *const xmlChar,
3034    recovery: c_int,
3035) -> *mut _xmlDoc {
3036    // SAFETY: cur must be a valid null-terminated xmlChar string.
3037    if cur.is_null() {
3038        return ptr::null_mut();
3039    }
3040    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3041    if ctxt.is_null() {
3042        return ptr::null_mut();
3043    }
3044    if !sax.is_null() {
3045        (*ctxt).sax = sax;
3046        (*ctxt).userData = (*ctxt).sax as *mut c_void;
3047    }
3048    if recovery != 0 {
3049        (*ctxt).recovery = 1;
3050        (*ctxt).options |= 1; // XML_PARSE_RECOVER
3051    }
3052    let len = crate::xml::string::xml_strlen(cur);
3053    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
3054    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3055    crate::xml::parser::helpers::parse_document(ctxt);
3056    let doc = (*ctxt).myDoc;
3057    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3058    doc
3059}
3060
3061/// Parse an XML document (SAX1) with user data (upstream parser.h
3062/// `xmlSAXParseDocWithData`): `user_data` is passed to the SAX callbacks.
3063///
3064/// # UPSTREAM-PARITY
3065///
3066/// ```c
3067/// xmlDocPtr xmlSAXParseDocWithData(xmlSAXHandlerPtr sax, const xmlChar *cur,
3068///                                  int recovery, void *data);
3069/// ```
3070#[no_mangle]
3071pub unsafe extern "C" fn xmlSAXParseDocWithData(
3072    sax: *mut _xmlSAXHandler,
3073    cur: *const xmlChar,
3074    recovery: c_int,
3075    data: *mut c_void,
3076) -> *mut _xmlDoc {
3077    // SAFETY: cur must be a valid null-terminated xmlChar string.
3078    if cur.is_null() {
3079        return ptr::null_mut();
3080    }
3081    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3082    if ctxt.is_null() {
3083        return ptr::null_mut();
3084    }
3085    if !sax.is_null() {
3086        (*ctxt).sax = sax;
3087    }
3088    (*ctxt).userData = data;
3089    if recovery != 0 {
3090        (*ctxt).recovery = 1;
3091        (*ctxt).options |= 1; // XML_PARSE_RECOVER
3092    }
3093    let len = crate::xml::string::xml_strlen(cur);
3094    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
3095    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3096    crate::xml::parser::helpers::parse_document(ctxt);
3097    let doc = (*ctxt).myDoc;
3098    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3099    doc
3100}
3101
3102/// Parse an XML file (SAX1) with user data (upstream parser.h
3103/// `xmlSAXParseFileWithData`).
3104///
3105/// # UPSTREAM-PARITY
3106///
3107/// ```c
3108/// xmlDocPtr xmlSAXParseFileWithData(xmlSAXHandlerPtr sax, const char *filename,
3109///                                   int recovery, void *data);
3110/// ```
3111#[no_mangle]
3112pub unsafe extern "C" fn xmlSAXParseFileWithData(
3113    sax: *mut _xmlSAXHandler,
3114    filename: *const c_char,
3115    recovery: c_int,
3116    data: *mut c_void,
3117) -> *mut _xmlDoc {
3118    // SAFETY: filename must be a valid C string or NULL.
3119    if filename.is_null() {
3120        return ptr::null_mut();
3121    }
3122    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3123    if ctxt.is_null() {
3124        return ptr::null_mut();
3125    }
3126    if !sax.is_null() {
3127        (*ctxt).sax = sax;
3128    }
3129    (*ctxt).userData = data;
3130    if recovery != 0 {
3131        (*ctxt).recovery = 1;
3132        (*ctxt).options |= 1; // XML_PARSE_RECOVER
3133    }
3134    let input = match crate::xml::parser::helpers::input_from_file(filename) {
3135        Ok(input) => input,
3136        Err(_) => {
3137            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3138            return ptr::null_mut();
3139        }
3140    };
3141    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3142    crate::xml::parser::helpers::parse_document(ctxt);
3143    let doc = (*ctxt).myDoc;
3144    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3145    doc
3146}
3147
3148/// Parse an XML document (SAX1) with user data from memory (upstream
3149/// parser.h `xmlSAXParseMemoryWithData`).
3150///
3151/// # UPSTREAM-PARITY
3152///
3153/// ```c
3154/// xmlDocPtr xmlSAXParseMemoryWithData(xmlSAXHandlerPtr sax, const char *buffer,
3155///                                     int size, int recovery, void *data);
3156/// ```
3157#[no_mangle]
3158pub unsafe extern "C" fn xmlSAXParseMemoryWithData(
3159    sax: *mut _xmlSAXHandler,
3160    buffer: *const c_char,
3161    size: c_int,
3162    recovery: c_int,
3163    data: *mut c_void,
3164) -> *mut _xmlDoc {
3165    // SAFETY: buffer must be a valid pointer with `size` readable bytes.
3166    if buffer.is_null() || size <= 0 {
3167        return ptr::null_mut();
3168    }
3169    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3170    if ctxt.is_null() {
3171        return ptr::null_mut();
3172    }
3173    if !sax.is_null() {
3174        (*ctxt).sax = sax;
3175    }
3176    (*ctxt).userData = data;
3177    if recovery != 0 {
3178        (*ctxt).recovery = 1;
3179        (*ctxt).options |= 1; // XML_PARSE_RECOVER
3180    }
3181    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
3182    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3183    crate::xml::parser::helpers::parse_document(ctxt);
3184    let doc = (*ctxt).myDoc;
3185    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3186    doc
3187}
3188
3189/// Parse an XML file (SAX1).
3190///
3191/// # UPSTREAM-PARITY
3192///
3193/// ```c
3194/// xmlDocPtr xmlSAXParseFile(xmlSAXHandlerPtr sax, const char *filename, int recovery);
3195/// ```
3196#[no_mangle]
3197pub unsafe extern "C" fn xmlSAXParseFile(
3198    sax: *mut _xmlSAXHandler,
3199    filename: *const c_char,
3200    recovery: c_int,
3201) -> *mut _xmlDoc {
3202    // SAFETY: filename must be a valid C string.
3203    if filename.is_null() {
3204        return ptr::null_mut();
3205    }
3206    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3207    if ctxt.is_null() {
3208        return ptr::null_mut();
3209    }
3210    if !sax.is_null() {
3211        (*ctxt).sax = sax;
3212        (*ctxt).userData = (*ctxt).sax as *mut c_void;
3213    }
3214    if recovery != 0 {
3215        (*ctxt).recovery = 1;
3216        (*ctxt).options |= 1;
3217    }
3218    let input = match crate::xml::parser::helpers::input_from_file(filename) {
3219        Ok(input) => input,
3220        Err(_) => {
3221            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3222            return ptr::null_mut();
3223        }
3224    };
3225    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3226    crate::xml::parser::helpers::parse_document(ctxt);
3227    let doc = (*ctxt).myDoc;
3228    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3229    doc
3230}
3231
3232/// Parse an XML document from memory (SAX1).
3233///
3234/// # UPSTREAM-PARITY
3235///
3236/// ```c
3237/// xmlDocPtr xmlSAXParseMemory(xmlSAXHandlerPtr sax,
3238///                             const char *buffer, int size, int recovery);
3239/// ```
3240#[no_mangle]
3241pub unsafe extern "C" fn xmlSAXParseMemory(
3242    sax: *mut _xmlSAXHandler,
3243    buffer: *const c_char,
3244    size: c_int,
3245    recovery: c_int,
3246) -> *mut _xmlDoc {
3247    // SAFETY: buffer must be valid with at least `size` bytes.
3248    if buffer.is_null() || size <= 0 {
3249        return ptr::null_mut();
3250    }
3251    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3252    if ctxt.is_null() {
3253        return ptr::null_mut();
3254    }
3255    if !sax.is_null() {
3256        (*ctxt).sax = sax;
3257        (*ctxt).userData = (*ctxt).sax as *mut c_void;
3258    }
3259    if recovery != 0 {
3260        (*ctxt).recovery = 1;
3261        (*ctxt).options |= 1;
3262    }
3263    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
3264    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3265    crate::xml::parser::helpers::parse_document(ctxt);
3266    let doc = (*ctxt).myDoc;
3267    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3268    doc
3269}
3270
3271/// SAX user parse file.
3272///
3273/// # UPSTREAM-PARITY
3274///
3275/// ```c
3276/// int xmlSAXUserParseFile(xmlSAXHandlerPtr sax, void *user_data,
3277///                         const char *filename);
3278/// ```
3279#[no_mangle]
3280pub unsafe extern "C" fn xmlSAXUserParseFile(
3281    sax: *mut _xmlSAXHandler,
3282    user_data: *mut c_void,
3283    filename: *const c_char,
3284) -> c_int {
3285    // SAFETY: filename must be a valid C string. sax and user_data may be NULL.
3286    if filename.is_null() {
3287        return -1;
3288    }
3289    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3290    if ctxt.is_null() {
3291        return -1;
3292    }
3293    if !sax.is_null() {
3294        // UPSTREAM-PARITY (parser.c xmlSAXUserParseFile): same copy-into-
3295        //-own-storage contract as xmlSAXUserParseMemory (HOSTILE-CALLBACKS
3296        // C10 class).
3297        if unsafe { (*sax).initialized } == XML_SAX2_MAGIC as c_uint {
3298            unsafe {
3299                ptr::copy_nonoverlapping(sax as *const _xmlSAXHandler, (*ctxt).sax, 1);
3300            }
3301        } else {
3302            unsafe {
3303                ptr::copy_nonoverlapping(
3304                    sax as *const u8,
3305                    (*ctxt).sax as *mut u8,
3306                    size_of::<crate::abi::structs::_xmlSAXHandlerV1>(),
3307                );
3308            }
3309        }
3310    }
3311    (*ctxt).userData = if !user_data.is_null() {
3312        user_data
3313    } else {
3314        ctxt as *mut c_void
3315    };
3316    let input = match crate::xml::parser::helpers::input_from_file(filename) {
3317        Ok(input) => input,
3318        Err(_) => {
3319            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3320            return -1;
3321        }
3322    };
3323    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3324    let ret = crate::xml::parser::helpers::parse_document(ctxt);
3325    // UPSTREAM-PARITY (parser.c xmlSAXUserParseFile): 0 when well-formed,
3326    // otherwise the recorded errNo (or -1).
3327    let ret = if ret == 0 {
3328        0
3329    } else {
3330        let err = unsafe { (*ctxt).errNo };
3331        if err != crate::abi::types::XML_ERR_OK {
3332            err
3333        } else {
3334            -1
3335        }
3336    };
3337    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3338    ret
3339}
3340
3341/// SAX user parse memory.
3342///
3343/// # UPSTREAM-PARITY
3344///
3345/// ```c
3346/// int xmlSAXUserParseMemory(xmlSAXHandlerPtr sax, void *user_data,
3347///                           const char *buffer, int size);
3348/// ```
3349#[no_mangle]
3350pub unsafe extern "C" fn xmlSAXUserParseMemory(
3351    sax: *mut _xmlSAXHandler,
3352    user_data: *mut c_void,
3353    buffer: *const c_char,
3354    size: c_int,
3355) -> c_int {
3356    // SAFETY: buffer must be valid with at least `size` bytes.
3357    if buffer.is_null() || size <= 0 {
3358        return -1;
3359    }
3360    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3361    if ctxt.is_null() {
3362        return -1;
3363    }
3364    if !sax.is_null() {
3365        // UPSTREAM-PARITY (parser.c xmlSAXUserParseMemory): the caller's
3366        // handler is COPIED into the context's own SAX struct — never
3367        // borrowed — so xmlFreeParserCtxt frees only the copy. SAX2-magic
3368        // handlers are copied in full; legacy handlers expose only the V1
3369        // prefix (HOSTILE-CALLBACKS C10: borrowing the caller's struct made
3370        // xmlFreeParserCtxt free a stack object).
3371        if unsafe { (*sax).initialized } == XML_SAX2_MAGIC as c_uint {
3372            unsafe {
3373                ptr::copy_nonoverlapping(sax as *const _xmlSAXHandler, (*ctxt).sax, 1);
3374            }
3375        } else {
3376            unsafe {
3377                ptr::copy_nonoverlapping(
3378                    sax as *const u8,
3379                    (*ctxt).sax as *mut u8,
3380                    size_of::<crate::abi::structs::_xmlSAXHandlerV1>(),
3381                );
3382            }
3383        }
3384    }
3385    (*ctxt).userData = if !user_data.is_null() {
3386        user_data
3387    } else {
3388        ctxt as *mut c_void
3389    };
3390    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
3391    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3392    let ret = crate::xml::parser::helpers::parse_document(ctxt);
3393    // UPSTREAM-PARITY (parser.c xmlSAXUserParseMemory): the return value is
3394    // 0 when well-formed, otherwise the recorded errNo (or -1).
3395    let ret = if ret == 0 {
3396        0
3397    } else {
3398        let err = unsafe { (*ctxt).errNo };
3399        if err != crate::abi::types::XML_ERR_OK {
3400            err
3401        } else {
3402            -1
3403        }
3404    };
3405    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3406    ret
3407}
3408
3409/// Parse an XML document from a string (DOM).
3410///
3411/// # UPSTREAM-PARITY
3412///
3413/// ```c
3414/// xmlDocPtr xmlParseDoc(const xmlChar *cur);
3415/// ```
3416#[no_mangle]
3417pub unsafe extern "C" fn xmlParseDoc(cur: *const xmlChar) -> *mut _xmlDoc {
3418    // SAFETY: cur must be a valid null-terminated xmlChar string.
3419    if cur.is_null() {
3420        return ptr::null_mut();
3421    }
3422    xmlReadDoc(cur, ptr::null(), ptr::null(), 0)
3423}
3424
3425/// Parse an XML file (DOM).
3426///
3427/// # UPSTREAM-PARITY
3428///
3429/// ```c
3430/// xmlDocPtr xmlParseFile(const char *filename);
3431/// ```
3432#[no_mangle]
3433pub unsafe extern "C" fn xmlParseFile(filename: *const c_char) -> *mut _xmlDoc {
3434    // SAFETY: filename must be a valid C string.
3435    if filename.is_null() {
3436        return ptr::null_mut();
3437    }
3438    xmlReadFile(filename, ptr::null(), 0)
3439}
3440
3441/// Parse an XML document from memory (DOM).
3442///
3443/// # UPSTREAM-PARITY
3444///
3445/// ```c
3446/// xmlDocPtr xmlParseMemory(const char *buffer, int size);
3447/// ```
3448#[no_mangle]
3449pub unsafe extern "C" fn xmlParseMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
3450    // SAFETY: buffer must be valid with at least `size` bytes.
3451    if buffer.is_null() || size <= 0 {
3452        return ptr::null_mut();
3453    }
3454    xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 0)
3455}
3456
3457/// Create a file parser context.
3458///
3459/// # UPSTREAM-PARITY
3460///
3461/// ```c
3462/// xmlParserCtxtPtr xmlCreateFileParserCtxt(const char *filename);
3463/// ```
3464#[no_mangle]
3465pub unsafe extern "C" fn xmlCreateFileParserCtxt(filename: *const c_char) -> *mut _xmlParserCtxt {
3466    // SAFETY: filename must be a valid C string.
3467    if filename.is_null() {
3468        return ptr::null_mut();
3469    }
3470    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3471    if ctxt.is_null() {
3472        return ptr::null_mut();
3473    }
3474    let input = match crate::xml::parser::helpers::input_from_file(filename) {
3475        Ok(input) => input,
3476        Err(_) => {
3477            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3478            return ptr::null_mut();
3479        }
3480    };
3481    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3482    ctxt
3483}
3484
3485/// Create a document parser context.
3486///
3487/// # UPSTREAM-PARITY
3488///
3489/// ```c
3490/// xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar *cur);
3491/// ```
3492#[no_mangle]
3493pub unsafe extern "C" fn xmlCreateDocParserCtxt(cur: *const xmlChar) -> *mut _xmlParserCtxt {
3494    // SAFETY: cur must be a valid null-terminated xmlChar string.
3495    if cur.is_null() {
3496        return ptr::null_mut();
3497    }
3498    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3499    if ctxt.is_null() {
3500        return ptr::null_mut();
3501    }
3502    let len = crate::xml::string::xml_strlen(cur);
3503    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
3504    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3505    ctxt
3506}
3507
3508/// Parse a document using an existing parser context.
3509///
3510/// # UPSTREAM-PARITY
3511///
3512/// ```c
3513/// int xmlParseDocument(xmlParserCtxtPtr ctxt);
3514/// ```
3515#[no_mangle]
3516pub unsafe extern "C" fn xmlParseDocument(ctxt: *mut _xmlParserCtxt) -> c_int {
3517    // SAFETY: ctxt must be a valid parser context.
3518    if ctxt.is_null() {
3519        return -1;
3520    }
3521    crate::xml::parser::helpers::parse_document(ctxt)
3522}
3523
3524/// Free a parser context.
3525///
3526/// # UPSTREAM-PARITY
3527///
3528/// ```c
3529/// void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt);
3530/// ```
3531#[no_mangle]
3532pub unsafe extern "C" fn xmlFreeParserCtxt(ctxt: *mut _xmlParserCtxt) {
3533    if ctxt.is_null() {
3534        return;
3535    }
3536    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3537}
3538
3539/// Set parser options.
3540///
3541/// # UPSTREAM-PARITY
3542///
3543/// ```c
3544/// int xmlCtxtUseOptions(xmlParserCtxtPtr ctxt, int options);
3545/// ```
3546#[no_mangle]
3547pub unsafe extern "C" fn xmlCtxtUseOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
3548    if ctxt.is_null() {
3549        return -1;
3550    }
3551    // Phase 1: STUB
3552    unsafe {
3553        (*ctxt).options = options;
3554    }
3555    0
3556}
3557
3558/// Parse a well-balanced chunk (for push parsing).
3559///
3560/// # UPSTREAM-PARITY
3561///
3562/// ```c
3563/// xmlParserErrors xmlParseChunk(xmlParserCtxtPtr ctxt,
3564///                               const char *chunk, int size, int terminate);
3565/// ```
3566#[no_mangle]
3567pub unsafe extern "C" fn xmlParseChunk(
3568    ctxt: *mut _xmlParserCtxt,
3569    chunk: *const c_char,
3570    size: c_int,
3571    terminate: c_int,
3572) -> c_int {
3573    // SAFETY: ctxt must be a valid parser context.
3574    // chunk may be NULL if terminate is set (finalize without data).
3575    //
3576    // UPSTREAM-PARITY (parser.c 2.15 xmlParseChunk): NULL context, negative
3577    // sizes and NULL chunk with positive size all return XML_ERR_ARGUMENT
3578    // (115), never -1 — HOSTILE-ABI B10/B13/D2.
3579    if ctxt.is_null() || size < 0 || (chunk.is_null() && size > 0) {
3580        return XML_ERR_ARGUMENT;
3581    }
3582    crate::xml::parser::helpers::parse_chunk(ctxt, chunk, size, terminate)
3583}
3584
3585/// Create a memory parser input buffer.
3586///
3587/// # UPSTREAM-PARITY
3588///
3589/// ```c
3590/// xmlParserInputBufferPtr xmlParserInputBufferCreateMem(const char *buffer, int size, int enc);
3591/// ```
3592#[no_mangle]
3593pub unsafe extern "C" fn xmlParserInputBufferCreateMem(
3594    buffer: *const c_char,
3595    size: c_int,
3596    enc: c_int,
3597) -> *mut _xmlParserInputBuffer {
3598    // SAFETY: buffer must be valid with at least `size` bytes.
3599    if buffer.is_null() || size <= 0 {
3600        return ptr::null_mut();
3601    }
3602    crate::xml::parser::helpers::alloc_parser_input_buffer()
3603}
3604
3605/// Create a file parser input buffer.
3606///
3607/// # UPSTREAM-PARITY
3608///
3609/// ```c
3610/// xmlParserInputBufferPtr xmlParserInputBufferCreateFilename(const char *URI, int enc);
3611/// ```
3612#[no_mangle]
3613pub unsafe extern "C" fn xmlParserInputBufferCreateFilename(
3614    URI: *const c_char,
3615    enc: c_int,
3616) -> *mut _xmlParserInputBuffer {
3617    // SAFETY: URI must be a valid C string or NULL.
3618    if URI.is_null() {
3619        return ptr::null_mut();
3620    }
3621    crate::xml::parser::helpers::alloc_parser_input_buffer()
3622}
3623
3624/// Create an I/O parser input buffer.
3625///
3626/// # UPSTREAM-PARITY
3627///
3628/// ```c
3629/// xmlParserInputBufferPtr xmlParserInputBufferCreateIO(
3630///     xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
3631///     void *ioctx, int enc);
3632/// ```
3633#[no_mangle]
3634pub unsafe extern "C" fn xmlParserInputBufferCreateIO(
3635    ioread: Option<xmlInputReadCallback>,
3636    ioclose: Option<xmlInputCloseCallback>,
3637    ioctx: *mut c_void,
3638    enc: c_int,
3639) -> *mut _xmlParserInputBuffer {
3640    // SAFETY: ioread must be a valid callback if Some. ioctx may be NULL.
3641    let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
3642    if !buf.is_null() {
3643        (*buf).readcallback = ioread;
3644        (*buf).closecallback = ioclose;
3645        (*buf).context = ioctx;
3646    }
3647    buf
3648}
3649
3650/// Free a parser input buffer.
3651///
3652/// # UPSTREAM-PARITY
3653///
3654/// ```c
3655/// void xmlFreeParserInputBuffer(xmlParserInputBufferPtr buf);
3656/// ```
3657#[no_mangle]
3658pub unsafe extern "C" fn xmlFreeParserInputBuffer(buf: *mut _xmlParserInputBuffer) {
3659    if buf.is_null() {
3660        return;
3661    }
3662    crate::xml::parser::helpers::free_parser_input_buffer(buf);
3663}
3664
3665/// Create a new parser input.
3666///
3667/// # UPSTREAM-PARITY
3668///
3669/// ```c
3670/// xmlParserInputPtr xmlNewInputFromFile(xmlParserCtxtPtr ctxt, const char *filename);
3671/// ```
3672#[no_mangle]
3673pub unsafe extern "C" fn xmlNewInputFromFile(
3674    ctxt: *mut _xmlParserCtxt,
3675    filename: *const c_char,
3676) -> *mut _xmlParserInput {
3677    // SAFETY: filename must be a valid C string. ctxt may be NULL.
3678    // This function allocates a _xmlParserInput. The caller owns it.
3679    // Note: The InputBuffer backing data is NOT leaked here (no ctxt._private
3680    // to store it). Use xmlCreateFileParserCtxt + xmlParseDocument instead.
3681    if filename.is_null() {
3682        return ptr::null_mut();
3683    }
3684    crate::xml::parser::helpers::alloc_parser_input_buffer() as *mut _xmlParserInput
3685}
3686
3687/// Free a parser input.
3688///
3689/// # UPSTREAM-PARITY
3690///
3691/// ```c
3692/// void xmlFreeInputStream(xmlParserInputPtr input);
3693/// ```
3694#[no_mangle]
3695pub unsafe extern "C" fn xmlFreeInputStream(input: *mut _xmlParserInput) {
3696    if input.is_null() {
3697        return;
3698    }
3699    crate::xml::parser::helpers::free_parser_input(input);
3700}
3701
3702// ═══════════════════════════════════════════════════════════════════════════════
3703// 8. I/O
3704// ═══════════════════════════════════════════════════════════════════════════════
3705
3706/// Create an output buffer for a file.
3707///
3708/// # UPSTREAM-PARITY
3709///
3710/// ```c
3711/// xmlOutputBufferPtr xmlOutputBufferCreateFilename(const char *URI,
3712///                                                  xmlCharEncodingHandlerPtr encoder,
3713///                                                  int compression);
3714/// ```
3715#[no_mangle]
3716pub unsafe extern "C" fn xmlOutputBufferCreateFilename(
3717    URI: *const c_char,
3718    encoder: *mut c_void,
3719    compression: c_int,
3720) -> *mut _xmlOutputBuffer {
3721    let _ = compression;
3722    if URI.is_null() {
3723        return ptr::null_mut();
3724    }
3725    crate::xml::io::output_buffer_create_filename(
3726        URI,
3727        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3728        0,
3729    )
3730}
3731
3732/// Create an output buffer for a file descriptor.
3733///
3734/// # UPSTREAM-PARITY
3735///
3736/// ```c
3737/// xmlOutputBufferPtr xmlOutputBufferCreateFd(int fd,
3738///                                            xmlCharEncodingHandlerPtr encoder);
3739/// ```
3740#[no_mangle]
3741pub unsafe extern "C" fn xmlOutputBufferCreateFd(
3742    fd: c_int,
3743    encoder: *mut c_void,
3744) -> *mut _xmlOutputBuffer {
3745    if fd < 0 {
3746        return ptr::null_mut();
3747    }
3748    crate::xml::io::output_buffer_create_fd(
3749        fd,
3750        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3751    )
3752}
3753
3754/// Create an output buffer from I/O callbacks.
3755///
3756/// # UPSTREAM-PARITY
3757///
3758/// ```c
3759/// xmlOutputBufferPtr xmlOutputBufferCreateIO(
3760///     xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose,
3761///     void *ioctx, xmlCharEncodingHandlerPtr encoder);
3762/// ```
3763#[no_mangle]
3764pub unsafe extern "C" fn xmlOutputBufferCreateIO(
3765    iowrite: Option<xmlOutputWriteCallback>,
3766    ioclose: Option<xmlOutputCloseCallback>,
3767    ioctx: *mut c_void,
3768    encoder: *mut c_void,
3769) -> *mut _xmlOutputBuffer {
3770    crate::xml::io::output_buffer_create_io(
3771        iowrite,
3772        ioclose,
3773        ioctx,
3774        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3775    )
3776}
3777
3778/// Free an output buffer.
3779///
3780/// # UPSTREAM-PARITY
3781///
3782/// ```c
3783/// void xmlOutputBufferClose(xmlOutputBufferPtr out);
3784/// ```
3785#[no_mangle]
3786pub unsafe extern "C" fn xmlOutputBufferClose(out: *mut _xmlOutputBuffer) -> c_int {
3787    if out.is_null() {
3788        return -1;
3789    }
3790    crate::xml::io::output_buffer_close(out)
3791}
3792
3793/// Flush an output buffer.
3794///
3795/// # UPSTREAM-PARITY
3796///
3797/// ```c
3798/// int xmlOutputBufferFlush(xmlOutputBufferPtr out);
3799/// ```
3800#[no_mangle]
3801pub unsafe extern "C" fn xmlOutputBufferFlush(out: *mut _xmlOutputBuffer) -> c_int {
3802    if out.is_null() {
3803        return -1;
3804    }
3805    crate::xml::io::output_buffer_flush(out)
3806}
3807
3808/// Write to an output buffer.
3809///
3810/// # UPSTREAM-PARITY
3811///
3812/// ```c
3813/// int xmlOutputBufferWrite(xmlOutputBufferPtr out, int len, const char *data);
3814/// ```
3815#[no_mangle]
3816pub unsafe extern "C" fn xmlOutputBufferWrite(
3817    out: *mut _xmlOutputBuffer,
3818    len: c_int,
3819    data: *const c_char,
3820) -> c_int {
3821    if out.is_null() || data.is_null() || len <= 0 {
3822        return -1;
3823    }
3824    crate::xml::io::output_buffer_write(out, len, data)
3825}
3826
3827/// Write a string to an output buffer.
3828///
3829/// # UPSTREAM-PARITY
3830///
3831/// ```c
3832/// int xmlOutputBufferWriteString(xmlOutputBufferPtr out, const char *str);
3833/// ```
3834#[no_mangle]
3835pub unsafe extern "C" fn xmlOutputBufferWriteString(
3836    out: *mut _xmlOutputBuffer,
3837    str: *const c_char,
3838) -> c_int {
3839    if str.is_null() {
3840        return 0;
3841    }
3842    unsafe { xmlOutputBufferWrite(out, xmlStrlen(str as *const xmlChar), str) }
3843}
3844
3845/// Allocate an output buffer with no I/O target (upstream xmlAllocOutputBuffer).
3846///
3847/// # UPSTREAM-PARITY
3848///
3849/// ```c
3850/// xmlOutputBufferPtr xmlAllocOutputBuffer(xmlCharEncodingHandlerPtr encoder);
3851/// ```
3852#[no_mangle]
3853pub unsafe extern "C" fn xmlAllocOutputBuffer(encoder: *mut c_void) -> *mut _xmlOutputBuffer {
3854    crate::xml::io::output_buffer_create(
3855        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3856    )
3857}
3858
3859/// Create an output buffer that writes into a `_xmlBuffer` (upstream
3860/// xmlOutputBufferCreateBuffer).
3861///
3862/// # UPSTREAM-PARITY
3863///
3864/// ```c
3865/// xmlOutputBufferPtr xmlOutputBufferCreateBuffer(xmlBufferPtr buffer,
3866///                                                xmlCharEncodingHandlerPtr encoder);
3867/// ```
3868///
3869/// # SAFETY
3870///
3871/// - `buffer` must be a valid `_xmlBuffer`.
3872#[no_mangle]
3873pub unsafe extern "C" fn xmlOutputBufferCreateBuffer(
3874    buffer: *mut crate::abi::structs::_xmlBuffer,
3875    encoder: *mut c_void,
3876) -> *mut _xmlOutputBuffer {
3877    crate::xml::io::output_buffer_create_buffer(
3878        buffer,
3879        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3880    )
3881}
3882
3883/// Create an output buffer writing to a `FILE *` (upstream
3884/// xmlOutputBufferCreateFile): the FILE is the I/O context with a
3885/// write callback wrapping `fwrite` and a close callback wrapping `fflush`.
3886///
3887/// # SAFETY
3888///
3889/// - `file` must be a valid `FILE *` or NULL.
3890#[no_mangle]
3891pub unsafe extern "C" fn xmlOutputBufferCreateFile(
3892    file: *mut libc::FILE,
3893    encoder: *mut c_void,
3894) -> *mut _xmlOutputBuffer {
3895    crate::xml::io::output_buffer_create_file(
3896        file,
3897        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3898    )
3899}
3900
3901/// Get the current content of an output buffer (upstream xmlOutputBufferGetContent).
3902///
3903/// # SAFETY
3904///
3905/// - `out` must be a valid output buffer.
3906#[no_mangle]
3907pub unsafe extern "C" fn xmlOutputBufferGetContent(out: *mut _xmlOutputBuffer) -> *const c_char {
3908    crate::xml::io::output_buffer_get_content(out) as *const c_char
3909}
3910
3911/// Get the number of bytes currently in the output buffer (upstream
3912/// xmlOutputBufferGetSize: `size_t`, 0 on NULL/error — 11.1-Z.3 signature
3913/// court: the pre-Z.3 candidate returned `int` with -1 on error).
3914///
3915/// # SAFETY
3916///
3917/// - `out` must be a valid output buffer.
3918#[no_mangle]
3919pub unsafe extern "C" fn xmlOutputBufferGetSize(out: *mut _xmlOutputBuffer) -> usize {
3920    crate::xml::io::output_buffer_get_size(out)
3921}
3922
3923/// Write to an output buffer, escaping special characters with the given
3924/// escape function (upstream xmlOutputBufferWriteEscape).
3925///
3926/// # SAFETY
3927///
3928/// - `out` must be a valid output buffer; `str` a NUL-terminated string;
3929///   `escaping` a valid escape callback or NULL.
3930#[no_mangle]
3931pub unsafe extern "C" fn xmlOutputBufferWriteEscape(
3932    out: *mut _xmlOutputBuffer,
3933    str: *const xmlChar,
3934    escaping: Option<xmlCharEncodingOutputFunc>,
3935) -> c_int {
3936    if out.is_null() || str.is_null() {
3937        return -1;
3938    }
3939    crate::xml::io::output_buffer_write_escape(out, str, escaping)
3940}
3941
3942/// Global default `xmlOutputBufferCreateFilename` callback
3943/// (upstream xmlOutputBufferCreateFilenameDefault).
3944static mut OUTPUT_CREATE_FILENAME_DEFAULT: Option<
3945    unsafe extern "C" fn(
3946        *const c_char,
3947        *mut crate::abi::structs::_xmlCharEncodingHandler,
3948        c_int,
3949    ) -> *mut _xmlOutputBuffer,
3950> = None;
3951
3952/// Set/query the default output-buffer filename callback
3953/// (upstream xmlOutputBufferCreateFilenameDefault).
3954///
3955/// # SAFETY
3956///
3957/// - `func` must be a valid function pointer or NULL.
3958#[no_mangle]
3959pub unsafe extern "C" fn xmlOutputBufferCreateFilenameDefault(
3960    func: Option<
3961        unsafe extern "C" fn(
3962            *const c_char,
3963            *mut crate::abi::structs::_xmlCharEncodingHandler,
3964            c_int,
3965        ) -> *mut _xmlOutputBuffer,
3966    >,
3967) -> Option<
3968    unsafe extern "C" fn(
3969        *const c_char,
3970        *mut crate::abi::structs::_xmlCharEncodingHandler,
3971        c_int,
3972    ) -> *mut _xmlOutputBuffer,
3973> {
3974    let old = unsafe { OUTPUT_CREATE_FILENAME_DEFAULT };
3975    if func.is_some() {
3976        unsafe { OUTPUT_CREATE_FILENAME_DEFAULT = func };
3977    }
3978    old
3979}
3980
3981/// `__xmlOutputBufferCreateFilename` — accessor returning a pointer to the
3982/// default callback (upstream xmlIO.c).
3983#[no_mangle]
3984pub unsafe extern "C" fn __xmlOutputBufferCreateFilename() -> *mut Option<
3985    unsafe extern "C" fn(
3986        *const c_char,
3987        *mut crate::abi::structs::_xmlCharEncodingHandler,
3988        c_int,
3989    ) -> *mut _xmlOutputBuffer,
3990> {
3991    core::ptr::addr_of_mut!(OUTPUT_CREATE_FILENAME_DEFAULT)
3992}
3993
3994// ═══════════════════════════════════════════════════════════════════════════════
3995// 9. Dictionary
3996// ═══════════════════════════════════════════════════════════════════════════════
3997
3998/// Create a new dictionary.
3999///
4000/// # UPSTREAM-PARITY
4001///
4002/// ```c
4003/// xmlDictPtr xmlDictCreate(void);
4004/// ```
4005#[no_mangle]
4006pub extern "C" fn xmlDictCreate() -> *mut c_void {
4007    let d = { crate::xml::dictionary::dict_create() as *mut c_void };
4008    if !d.is_null() {
4009        // UPSTREAM-PARITY: the creator holds the base reference (count 1);
4010        // xmlDictReference adds to it and xmlDictFree decrements, freeing
4011        // the dictionary when it reaches zero.
4012        *crate::abi::exports_hash::DICT_REFS
4013            .lock()
4014            .entry(d as usize)
4015            .or_insert(0) = 1;
4016    }
4017    d
4018}
4019
4020/// Create a sub-dictionary.
4021///
4022/// # UPSTREAM-PARITY
4023///
4024/// ```c
4025/// xmlDictPtr xmlDictCreateSub(xmlDictPtr sub);
4026/// ```
4027#[no_mangle]
4028pub extern "C" fn xmlDictCreateSub(sub: *mut c_void) -> *mut c_void {
4029    let d = unsafe {
4030        crate::xml::dictionary::dict_create_sub(sub as *mut crate::xml::dictionary::Dict)
4031            as *mut c_void
4032    };
4033    if !d.is_null() {
4034        *crate::abi::exports_hash::DICT_REFS
4035            .lock()
4036            .entry(d as usize)
4037            .or_insert(0) = 1;
4038    }
4039    d
4040}
4041
4042/// Look up a string in the dictionary.
4043///
4044/// # UPSTREAM-PARITY
4045///
4046/// ```c
4047/// const xmlChar *xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len);
4048/// ```
4049///
4050/// Returns an interned string pointer (valid as long as the dictionary exists).
4051/// - If `len` < 0, `name` must be null-terminated.
4052/// - If `len` >= 0, exactly `len` bytes are used.
4053#[no_mangle]
4054pub unsafe extern "C" fn xmlDictLookup(
4055    dict: *mut c_void,
4056    name: *const xmlChar,
4057    len: c_int,
4058) -> *const xmlChar {
4059    unsafe {
4060        crate::xml::dictionary::dict_lookup(dict as *mut crate::xml::dictionary::Dict, name, len)
4061    }
4062}
4063
4064/// Check if a string exists in the dictionary.
4065///
4066/// # UPSTREAM-PARITY
4067///
4068/// ```c
4069/// const xmlChar *xmlDictExists(xmlDictPtr dict, const xmlChar *name, int len);
4070/// ```
4071#[no_mangle]
4072pub unsafe extern "C" fn xmlDictExists(
4073    dict: *mut c_void,
4074    name: *const xmlChar,
4075    len: c_int,
4076) -> *const xmlChar {
4077    unsafe {
4078        crate::xml::dictionary::dict_exists(dict as *mut crate::xml::dictionary::Dict, name, len)
4079    }
4080}
4081
4082/// Query dictionary size.
4083///
4084/// # UPSTREAM-PARITY
4085///
4086/// ```c
4087/// int xmlDictSize(const xmlDictPtr dict);
4088/// ```
4089#[no_mangle]
4090pub const extern "C" fn xmlDictSize(dict: *const c_void) -> c_int {
4091    {
4092        crate::xml::dictionary::dict_size(dict as *const crate::xml::dictionary::Dict)
4093    }
4094}
4095
4096/// Free a dictionary.
4097///
4098/// # UPSTREAM-PARITY
4099///
4100/// ```c
4101/// void xmlDictFree(xmlDictPtr dict);
4102/// ```
4103///
4104/// The reference counter added by `xmlDictReference` is honored: the
4105/// underlying dictionary is destroyed only when the last reference is
4106/// released (the base owner counts as one implicit reference).
4107#[no_mangle]
4108pub extern "C" fn xmlDictFree(dict: *mut c_void) {
4109    if dict.is_null() {
4110        return;
4111    }
4112    let mut remaining = 0u32;
4113    {
4114        let mut refs = crate::abi::exports_hash::DICT_REFS.lock();
4115        if let Some(r) = refs.get_mut(&(dict as usize)) {
4116            if *r > 0 {
4117                *r -= 1;
4118            }
4119            remaining = *r;
4120            if remaining == 0 {
4121                refs.remove(&(dict as usize));
4122            }
4123        }
4124    }
4125    if remaining == 0 {
4126        unsafe { crate::xml::dictionary::dict_free(dict as *mut crate::xml::dictionary::Dict) };
4127    }
4128}
4129
4130/// Set the dictionary size limit.
4131///
4132/// # UPSTREAM-PARITY
4133///
4134/// ```c
4135/// size_t xmlDictSetLimit(xmlDictPtr dict, size_t limit);
4136/// ```
4137#[no_mangle]
4138pub extern "C" fn xmlDictSetLimit(dict: *mut c_void, limit: usize) -> usize {
4139    {
4140        crate::xml::dictionary::dict_set_limit(dict as *mut crate::xml::dictionary::Dict, limit)
4141    }
4142}
4143
4144/// Get current dictionary usage.
4145///
4146/// # UPSTREAM-PARITY
4147///
4148/// ```c
4149/// size_t xmlDictGetUsage(const xmlDictPtr dict);
4150/// ```
4151#[no_mangle]
4152pub extern "C" fn xmlDictGetUsage(dict: *const c_void) -> usize {
4153    {
4154        crate::xml::dictionary::dict_get_usage(dict as *mut crate::xml::dictionary::Dict)
4155    }
4156}
4157
4158// ═══════════════════════════════════════════════════════════════════════════════
4159// 10. Hash Table
4160// ═══════════════════════════════════════════════════════════════════════════════
4161
4162/// Create a new hash table.
4163///
4164/// # UPSTREAM-PARITY
4165///
4166/// ```c
4167/// xmlHashTablePtr xmlHashCreate(int size);
4168/// ```
4169#[no_mangle]
4170pub extern "C" fn xmlHashCreate(size: c_int) -> *mut c_void {
4171    crate::xml::hash::hash_create(size) as *mut c_void
4172}
4173
4174/// Create a new hash table with a dictionary.
4175///
4176/// # UPSTREAM-PARITY
4177///
4178/// ```c
4179/// xmlHashTablePtr xmlHashCreateDict(int size, xmlDictPtr dict);
4180/// ```
4181#[no_mangle]
4182pub extern "C" fn xmlHashCreateDict(size: c_int, dict: *mut c_void) -> *mut c_void {
4183    crate::xml::hash::hash_create_dict(size, dict) as *mut c_void
4184}
4185
4186/// Free a hash table.
4187///
4188/// # UPSTREAM-PARITY
4189///
4190/// ```c
4191/// void xmlHashFree(xmlHashTablePtr table, xmlHashDeallocator f);
4192/// ```
4193#[no_mangle]
4194pub extern "C" fn xmlHashFree(
4195    table: *mut c_void,
4196    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4197) {
4198    unsafe { crate::xml::hash::hash_free(table as *mut crate::xml::hash::HashTable, f) }
4199}
4200
4201/// Add an entry to a hash table.
4202///
4203/// # UPSTREAM-PARITY
4204///
4205/// ```c
4206/// int xmlHashAddEntry(xmlHashTablePtr table, const xmlChar *name, void *userdata);
4207/// ```
4208#[no_mangle]
4209pub unsafe extern "C" fn xmlHashAddEntry(
4210    table: *mut c_void,
4211    name: *const xmlChar,
4212    userdata: *mut c_void,
4213) -> c_int {
4214    unsafe {
4215        crate::xml::hash::hash_add_entry(table as *mut crate::xml::hash::HashTable, name, userdata)
4216    }
4217}
4218
4219/// Add a 2-key entry.
4220///
4221/// # UPSTREAM-PARITY
4222///
4223/// ```c
4224/// int xmlHashAddEntry2(xmlHashTablePtr table, const xmlChar *name,
4225///                      const xmlChar *name2, void *userdata);
4226/// ```
4227#[no_mangle]
4228pub unsafe extern "C" fn xmlHashAddEntry2(
4229    table: *mut c_void,
4230    name: *const xmlChar,
4231    name2: *const xmlChar,
4232    userdata: *mut c_void,
4233) -> c_int {
4234    unsafe {
4235        crate::xml::hash::hash_add_entry2(
4236            table as *mut crate::xml::hash::HashTable,
4237            name,
4238            name2,
4239            userdata,
4240        )
4241    }
4242}
4243
4244/// Add a 3-key entry.
4245///
4246/// # UPSTREAM-PARITY
4247///
4248/// ```c
4249/// int xmlHashAddEntry3(xmlHashTablePtr table, const xmlChar *name,
4250///                      const xmlChar *name2, const xmlChar *name3, void *userdata);
4251/// ```
4252#[no_mangle]
4253pub unsafe extern "C" fn xmlHashAddEntry3(
4254    table: *mut c_void,
4255    name: *const xmlChar,
4256    name2: *const xmlChar,
4257    name3: *const xmlChar,
4258    userdata: *mut c_void,
4259) -> c_int {
4260    unsafe {
4261        crate::xml::hash::hash_add_entry3(
4262            table as *mut crate::xml::hash::HashTable,
4263            name,
4264            name2,
4265            name3,
4266            userdata,
4267        )
4268    }
4269}
4270
4271/// Update or add an entry.
4272///
4273/// # UPSTREAM-PARITY
4274///
4275/// ```c
4276/// int xmlHashUpdateEntry(xmlHashTablePtr table, const xmlChar *name,
4277///                        void *userdata, xmlHashDeallocator f);
4278/// ```
4279#[no_mangle]
4280pub unsafe extern "C" fn xmlHashUpdateEntry(
4281    table: *mut c_void,
4282    name: *const xmlChar,
4283    userdata: *mut c_void,
4284    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4285) -> c_int {
4286    unsafe {
4287        crate::xml::hash::hash_update_entry(
4288            table as *mut crate::xml::hash::HashTable,
4289            name,
4290            userdata,
4291            f,
4292        )
4293    }
4294}
4295
4296/// Update or add a 2-key entry.
4297#[no_mangle]
4298pub unsafe extern "C" fn xmlHashUpdateEntry2(
4299    table: *mut c_void,
4300    name: *const xmlChar,
4301    name2: *const xmlChar,
4302    userdata: *mut c_void,
4303    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4304) -> c_int {
4305    unsafe {
4306        crate::xml::hash::hash_update_entry2(
4307            table as *mut crate::xml::hash::HashTable,
4308            name,
4309            name2,
4310            userdata,
4311            f,
4312        )
4313    }
4314}
4315
4316/// Update or add a 3-key entry.
4317#[no_mangle]
4318pub unsafe extern "C" fn xmlHashUpdateEntry3(
4319    table: *mut c_void,
4320    name: *const xmlChar,
4321    name2: *const xmlChar,
4322    name3: *const xmlChar,
4323    userdata: *mut c_void,
4324    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4325) -> c_int {
4326    unsafe {
4327        crate::xml::hash::hash_update_entry3(
4328            table as *mut crate::xml::hash::HashTable,
4329            name,
4330            name2,
4331            name3,
4332            userdata,
4333            f,
4334        )
4335    }
4336}
4337
4338/// Look up an entry.
4339///
4340/// # UPSTREAM-PARITY
4341///
4342/// ```c
4343/// void *xmlHashLookup(xmlHashTablePtr table, const xmlChar *name);
4344/// ```
4345#[no_mangle]
4346pub unsafe extern "C" fn xmlHashLookup(table: *mut c_void, name: *const xmlChar) -> *mut c_void {
4347    unsafe { crate::xml::hash::hash_lookup(table as *mut crate::xml::hash::HashTable, name) }
4348}
4349
4350/// Look up a 2-key entry.
4351#[no_mangle]
4352pub unsafe extern "C" fn xmlHashLookup2(
4353    table: *mut c_void,
4354    name: *const xmlChar,
4355    name2: *const xmlChar,
4356) -> *mut c_void {
4357    unsafe {
4358        crate::xml::hash::hash_lookup2(table as *mut crate::xml::hash::HashTable, name, name2)
4359    }
4360}
4361
4362/// Look up a 3-key entry.
4363#[no_mangle]
4364pub unsafe extern "C" fn xmlHashLookup3(
4365    table: *mut c_void,
4366    name: *const xmlChar,
4367    name2: *const xmlChar,
4368    name3: *const xmlChar,
4369) -> *mut c_void {
4370    unsafe {
4371        crate::xml::hash::hash_lookup3(
4372            table as *mut crate::xml::hash::HashTable,
4373            name,
4374            name2,
4375            name3,
4376        )
4377    }
4378}
4379
4380/// Get the size of a hash table.
4381///
4382/// # UPSTREAM-PARITY
4383///
4384/// ```c
4385/// int xmlHashSize(xmlHashTablePtr table);
4386/// ```
4387#[no_mangle]
4388pub extern "C" fn xmlHashSize(table: *mut c_void) -> c_int {
4389    crate::xml::hash::hash_size(table as *mut crate::xml::hash::HashTable)
4390}
4391
4392/// Remove an entry.
4393///
4394/// # UPSTREAM-PARITY
4395///
4396/// ```c
4397/// int xmlHashRemoveEntry(xmlHashTablePtr table, const xmlChar *name,
4398///                        xmlHashDeallocator f);
4399/// ```
4400#[no_mangle]
4401pub unsafe extern "C" fn xmlHashRemoveEntry(
4402    table: *mut c_void,
4403    name: *const xmlChar,
4404    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4405) -> c_int {
4406    unsafe {
4407        crate::xml::hash::hash_remove_entry(table as *mut crate::xml::hash::HashTable, name, f)
4408    }
4409}
4410
4411/// Remove a 2-key entry.
4412#[no_mangle]
4413pub unsafe extern "C" fn xmlHashRemoveEntry2(
4414    table: *mut c_void,
4415    name: *const xmlChar,
4416    name2: *const xmlChar,
4417    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4418) -> c_int {
4419    unsafe {
4420        crate::xml::hash::hash_remove_entry2(
4421            table as *mut crate::xml::hash::HashTable,
4422            name,
4423            name2,
4424            f,
4425        )
4426    }
4427}
4428
4429/// Remove a 3-key entry.
4430#[no_mangle]
4431pub unsafe extern "C" fn xmlHashRemoveEntry3(
4432    table: *mut c_void,
4433    name: *const xmlChar,
4434    name2: *const xmlChar,
4435    name3: *const xmlChar,
4436    f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4437) -> c_int {
4438    unsafe {
4439        crate::xml::hash::hash_remove_entry3(
4440            table as *mut crate::xml::hash::HashTable,
4441            name,
4442            name2,
4443            name3,
4444            f,
4445        )
4446    }
4447}
4448
4449/// Scan a hash table with a scanner function.
4450///
4451/// # UPSTREAM-PARITY
4452///
4453/// ```c
4454/// void xmlHashScan(xmlHashTablePtr table, xmlHashScanner f, void *data);
4455/// ```
4456#[no_mangle]
4457pub extern "C" fn xmlHashScan(table: *mut c_void, f: Option<xmlHashScanner>, data: *mut c_void) {
4458    unsafe { crate::xml::hash::hash_scan(table as *mut crate::xml::hash::HashTable, f, data) }
4459}
4460
4461/// Scan a hash table with a full scanner function.
4462#[no_mangle]
4463pub extern "C" fn xmlHashScanFull(
4464    table: *mut c_void,
4465    f: Option<xmlHashScannerFull>,
4466    data: *mut c_void,
4467) {
4468    unsafe { crate::xml::hash::hash_scan_full(table as *mut crate::xml::hash::HashTable, f, data) }
4469}
4470
4471/// Copy a hash table.
4472///
4473/// # UPSTREAM-PARITY
4474///
4475/// ```c
4476/// xmlHashTablePtr xmlHashCopy(xmlHashTablePtr table, xmlHashCopier f);
4477/// ```
4478#[no_mangle]
4479pub extern "C" fn xmlHashCopy(
4480    table: *mut c_void,
4481    f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar) -> *mut c_void>,
4482) -> *mut c_void {
4483    unsafe {
4484        crate::xml::hash::hash_copy(table as *mut crate::xml::hash::HashTable, f) as *mut c_void
4485    }
4486}
4487
4488// ═══════════════════════════════════════════════════════════════════════════════
4489// 11. List
4490// ═══════════════════════════════════════════════════════════════════════════════
4491
4492/// Create a new list.
4493///
4494/// # UPSTREAM-PARITY
4495///
4496/// ```c
4497/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
4498///                          xmlListDataCompare compare);
4499/// ```
4500#[no_mangle]
4501pub extern "C" fn xmlListCreate(
4502    deallocator: Option<unsafe extern "C" fn(*mut c_void)>,
4503    compare: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
4504) -> *mut c_void {
4505    crate::xml::list::list_create(deallocator, compare) as *mut c_void
4506}
4507
4508/// Delete a list.
4509///
4510/// # UPSTREAM-PARITY
4511///
4512/// ```c
4513/// void xmlListDelete(xmlListPtr list);
4514/// ```
4515#[no_mangle]
4516pub extern "C" fn xmlListDelete(list: *mut c_void) {
4517    unsafe { crate::xml::list::list_delete(list as *mut crate::xml::list::List) }
4518}
4519
4520/// Search a list.
4521///
4522/// # UPSTREAM-PARITY
4523///
4524/// ```c
4525/// void *xmlListSearch(xmlListPtr list, void *data);
4526/// ```
4527#[no_mangle]
4528pub extern "C" fn xmlListSearch(list: *mut c_void, data: *mut c_void) -> *mut c_void {
4529    unsafe { crate::xml::list::list_search(list as *mut crate::xml::list::List, data) }
4530}
4531
4532/// Walk a list.
4533///
4534/// # UPSTREAM-PARITY
4535///
4536/// ```c
4537/// void xmlListWalk(xmlListPtr list, xmlListWalker walker, void *data);
4538/// ```
4539#[no_mangle]
4540pub extern "C" fn xmlListWalk(
4541    list: *mut c_void,
4542    walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
4543    data: *mut c_void,
4544) {
4545    unsafe { crate::xml::list::list_walk(list as *mut crate::xml::list::List, walker, data) }
4546}
4547
4548/// Push to back.
4549///
4550/// # UPSTREAM-PARITY
4551///
4552/// ```c
4553/// int xmlListPushBack(xmlListPtr list, void *data);
4554/// ```
4555#[no_mangle]
4556pub extern "C" fn xmlListPushBack(list: *mut c_void, data: *mut c_void) -> c_int {
4557    unsafe { crate::xml::list::list_push_back(list as *mut crate::xml::list::List, data) }
4558}
4559
4560/// Push to front.
4561///
4562/// # UPSTREAM-PARITY
4563///
4564/// ```c
4565/// int xmlListPushFront(xmlListPtr list, void *data);
4566/// ```
4567#[no_mangle]
4568pub extern "C" fn xmlListPushFront(list: *mut c_void, data: *mut c_void) -> c_int {
4569    unsafe { crate::xml::list::list_push_front(list as *mut crate::xml::list::List, data) }
4570}
4571
4572/// Pop from back.
4573#[no_mangle]
4574pub extern "C" fn xmlListPopBack(list: *mut c_void) {
4575    unsafe { crate::xml::list::list_pop_back(list as *mut crate::xml::list::List) }
4576}
4577
4578/// Pop from front.
4579#[no_mangle]
4580pub extern "C" fn xmlListPopFront(list: *mut c_void) {
4581    unsafe { crate::xml::list::list_pop_front(list as *mut crate::xml::list::List) }
4582}
4583
4584/// Insert into sorted list.
4585///
4586/// # UPSTREAM-PARITY
4587///
4588/// ```c
4589/// int xmlListInsert(xmlListPtr list, void *data);
4590/// ```
4591#[no_mangle]
4592pub extern "C" fn xmlListInsert(list: *mut c_void, data: *mut c_void) -> c_int {
4593    unsafe { crate::xml::list::list_insert(list as *mut crate::xml::list::List, data) }
4594}
4595
4596/// Append to list.
4597#[no_mangle]
4598pub extern "C" fn xmlListAppend(list: *mut c_void, data: *mut c_void) -> c_int {
4599    unsafe { crate::xml::list::list_append(list as *mut crate::xml::list::List, data) }
4600}
4601
4602/// Remove first matching element.
4603#[no_mangle]
4604pub extern "C" fn xmlListRemoveFirst(list: *mut c_void, data: *mut c_void) -> c_int {
4605    unsafe { crate::xml::list::list_remove_first(list as *mut crate::xml::list::List, data) }
4606}
4607
4608/// Remove last matching element.
4609#[no_mangle]
4610pub extern "C" fn xmlListRemoveLast(list: *mut c_void, data: *mut c_void) -> c_int {
4611    unsafe { crate::xml::list::list_remove_last(list as *mut crate::xml::list::List, data) }
4612}
4613
4614/// Remove all matching elements.
4615#[no_mangle]
4616pub extern "C" fn xmlListRemoveAll(list: *mut c_void, data: *mut c_void) -> c_int {
4617    unsafe { crate::xml::list::list_remove_all(list as *mut crate::xml::list::List, data) }
4618}
4619
4620/// Clear a list.
4621#[no_mangle]
4622pub extern "C" fn xmlListClear(list: *mut c_void) {
4623    unsafe { crate::xml::list::list_clear(list as *mut crate::xml::list::List) }
4624}
4625
4626/// Check if list is empty.
4627///
4628/// # UPSTREAM-PARITY
4629///
4630/// ```c
4631/// int xmlListEmpty(xmlListPtr list);
4632/// ```
4633#[no_mangle]
4634pub extern "C" fn xmlListEmpty(list: *mut c_void) -> c_int {
4635    crate::xml::list::list_empty(list as *mut crate::xml::list::List)
4636}
4637
4638/// Get front element.
4639///
4640/// # UPSTREAM-PARITY
4641///
4642/// ```c
4643/// void *xmlListFront(xmlListPtr list);
4644/// ```
4645#[no_mangle]
4646pub extern "C" fn xmlListFront(list: *mut c_void) -> *mut c_void {
4647    crate::xml::list::list_front(list as *mut crate::xml::list::List)
4648}
4649
4650/// Get back element.
4651///
4652/// # UPSTREAM-PARITY
4653///
4654/// ```c
4655/// void *xmlListBack(xmlListPtr list);
4656/// ```
4657#[no_mangle]
4658pub extern "C" fn xmlListBack(list: *mut c_void) -> *mut c_void {
4659    crate::xml::list::list_back(list as *mut crate::xml::list::List)
4660}
4661
4662/// Get list size.
4663///
4664/// # UPSTREAM-PARITY
4665///
4666/// ```c
4667/// int xmlListSize(xmlListPtr list);
4668/// ```
4669#[no_mangle]
4670pub extern "C" fn xmlListSize(list: *mut c_void) -> c_int {
4671    crate::xml::list::list_size(list as *mut crate::xml::list::List)
4672}
4673
4674/// Sort a list.
4675#[no_mangle]
4676pub extern "C" fn xmlListSort(list: *mut c_void) {
4677    unsafe { crate::xml::list::list_sort(list as *mut crate::xml::list::List) }
4678}
4679
4680/// Reverse a list.
4681#[no_mangle]
4682pub extern "C" fn xmlListReverse(list: *mut c_void) {
4683    unsafe { crate::xml::list::list_reverse(list as *mut crate::xml::list::List) }
4684}
4685
4686/// Reverse a list in-place.
4687#[no_mangle]
4688pub extern "C" fn xmlListReverseSplice(list: *mut c_void, list2: *mut c_void) {
4689    unsafe {
4690        crate::xml::list::list_reverse_splice(
4691            list as *mut crate::xml::list::List,
4692            list2 as *mut crate::xml::list::List,
4693        )
4694    }
4695}
4696
4697/// Merge two sorted lists.
4698#[no_mangle]
4699pub extern "C" fn xmlListMerge(list: *mut c_void, list2: *mut c_void) {
4700    unsafe {
4701        crate::xml::list::list_merge(
4702            list as *mut crate::xml::list::List,
4703            list2 as *mut crate::xml::list::List,
4704        )
4705    }
4706}
4707/// Return the last element of a list (upstream list.h).
4708///
4709/// # UPSTREAM-PARITY
4710///
4711/// ```c
4712/// void *xmlListEnd(xmlListPtr l);
4713/// ```
4714#[no_mangle]
4715pub unsafe extern "C" fn xmlListEnd(l: *mut c_void) -> *mut c_void {
4716    crate::xml::list::list_end(l as *mut crate::xml::list::List)
4717}
4718
4719/// Reverse-search a list (upstream list.h).
4720///
4721/// # UPSTREAM-PARITY
4722///
4723/// ```c
4724/// void *xmlListReverseSearch(xmlListPtr l, void *data);
4725/// ```
4726#[no_mangle]
4727pub unsafe extern "C" fn xmlListReverseSearch(l: *mut c_void, data: *mut c_void) -> *mut c_void {
4728    crate::xml::list::list_reverse_search(l as *mut crate::xml::list::List, data)
4729}
4730
4731/// Walk a list in reverse (upstream list.h).
4732///
4733/// # UPSTREAM-PARITY
4734///
4735/// ```c
4736/// void xmlListReverseWalk(xmlListPtr l, xmlListWalker walker, void *data);
4737/// ```
4738#[no_mangle]
4739pub unsafe extern "C" fn xmlListReverseWalk(
4740    l: *mut c_void,
4741    walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
4742    data: *mut c_void,
4743) {
4744    crate::xml::list::list_reverse_walk(l as *mut crate::xml::list::List, walker, data)
4745}
4746
4747/// Duplicate a list (upstream list.h).
4748///
4749/// # UPSTREAM-PARITY
4750///
4751/// ```c
4752/// xmlListPtr xmlListDup(xmlListPtr l);
4753/// ```
4754#[no_mangle]
4755pub unsafe extern "C" fn xmlListDup(l: *mut c_void) -> *mut c_void {
4756    crate::xml::list::list_dup(l as *mut crate::xml::list::List) as *mut c_void
4757}
4758
4759/// Copy the contents of `old` into the existing list `cur` (upstream
4760/// list.h — R-000176, the candidate previously passed a copier callback).
4761///
4762/// # UPSTREAM-PARITY
4763///
4764/// ```c
4765/// int xmlListCopy(xmlListPtr cur, const xmlListPtr old);
4766/// ```
4767///
4768/// Returns 0 on success, 1 on error (upstream list.c).
4769#[no_mangle]
4770pub unsafe extern "C" fn xmlListCopy(cur: *mut c_void, old: *mut c_void) -> c_int {
4771    crate::xml::list::list_copy(
4772        cur as *mut crate::xml::list::List,
4773        old as *mut crate::xml::list::List,
4774    )
4775}
4776
4777/// Return the data of a link (upstream list.h).
4778///
4779/// # UPSTREAM-PARITY
4780///
4781/// ```c
4782/// void *xmlLinkGetData(xmlLinkPtr lk);
4783/// ```
4784#[no_mangle]
4785pub unsafe extern "C" fn xmlLinkGetData(lk: *mut c_void) -> *mut c_void {
4786    crate::xml::list::link_get_data(lk)
4787}
4788
4789// ═══════════════════════════════════════════════════════════════════════════════
4790// 12. Buffer
4791// ═══════════════════════════════════════════════════════════════════════════════
4792
4793/// Create a new buffer.
4794///
4795/// # UPSTREAM-PARITY
4796///
4797/// ```c
4798/// xmlBufferPtr xmlBufferCreate(void);
4799/// ```
4800#[no_mangle]
4801pub extern "C" fn xmlBufferCreate() -> *mut _xmlBuffer {
4802    // UPSTREAM-PARITY (buf.c 2.15): xmlBufferCreate = size 256, alloc scheme
4803    // XML_BUFFER_ALLOC_IO, content[0] = 0. (The pre-Phase-13 path used the
4804    // internal default-size DOUBLEIT helper, which diverged from the oracle
4805    // for negative/huge CreateSize arguments — HOSTILE-ABI finding.)
4806    unsafe { xml_buffer_create_upstream(256) }
4807}
4808
4809/// Create a new buffer of a given size.
4810///
4811/// # UPSTREAM-PARITY
4812///
4813/// ```c
4814/// xmlBufferPtr xmlBufferCreateSize(size_t size);
4815/// ```
4816///
4817/// Upstream buf.c 2.15: `size >= INT_MAX` returns NULL; `size == 0` returns
4818/// a buffer with a NULL content; otherwise the content is `size + 1` bytes
4819/// (the extra byte is the NUL terminator). The alloc scheme is
4820/// XML_BUFFER_ALLOC_IO.
4821#[no_mangle]
4822pub extern "C" fn xmlBufferCreateSize(size: usize) -> *mut _xmlBuffer {
4823    // UPSTREAM-PARITY (buf.c 2.15 xmlBufferCreateSize): sizes at or beyond
4824    // INT_MAX are rejected up front — a C caller passing -1 (huge size_t)
4825    // gets NULL exactly like the oracle (HOSTILE-ABI finding).
4826    if size >= c_int::MAX as usize {
4827        return ptr::null_mut();
4828    }
4829    unsafe { xml_buffer_create_upstream(size) }
4830}
4831
4832/// Upstream `xmlBufferCreateSize` body (buf.c 2.15): allocate the struct,
4833/// then `size + 1` content bytes when size != 0 (zero-size buffers have a
4834/// NULL content), alloc scheme XML_BUFFER_ALLOC_IO, content[0] = 0.
4835///
4836/// # Safety
4837///
4838/// - No caller-provided pointers; every allocation is checked for NULL and
4839///   the struct is freed on content-allocation failure.
4840unsafe fn xml_buffer_create_upstream(size: usize) -> *mut _xmlBuffer {
4841    let ret = unsafe { xmlMallocImpl(size_of::<_xmlBuffer>()) as *mut _xmlBuffer };
4842    if ret.is_null() {
4843        return ptr::null_mut();
4844    }
4845    let sz = if size != 0 { size + 1 } else { 0 };
4846    unsafe {
4847        if sz != 0 {
4848            let content = xmlMallocImpl(sz) as *mut xmlChar;
4849            if content.is_null() {
4850                xmlFreeImpl(ret as *mut c_void);
4851                return ptr::null_mut();
4852            }
4853            *content = 0;
4854            (*ret).content = content;
4855            (*ret).contentIO = content;
4856        } else {
4857            (*ret).content = ptr::null_mut();
4858            (*ret).contentIO = ptr::null_mut();
4859        }
4860        (*ret).use_ = 0;
4861        (*ret).size = sz as c_uint;
4862        (*ret).alloc = crate::abi::types::xmlBufferAllocationScheme::XML_BUFFER_ALLOC_IO as c_int;
4863    }
4864    ret
4865}
4866
4867/// Create a buffer from a static string.
4868///
4869/// # UPSTREAM-PARITY
4870///
4871/// ```c
4872/// xmlBufferPtr xmlBufferCreateStatic(void *mem, size_t size);
4873/// ```
4874#[no_mangle]
4875pub extern "C" fn xmlBufferCreateStatic(mem: *mut c_void, size: usize) -> *mut _xmlBuffer {
4876    if mem.is_null() || size == 0 {
4877        return ptr::null_mut();
4878    }
4879    crate::xml::io::buf_create_static(mem as *const xmlChar, size as c_int)
4880}
4881
4882/// Free a buffer.
4883///
4884/// # UPSTREAM-PARITY
4885///
4886/// ```c
4887/// void xmlBufferFree(xmlBufferPtr buf);
4888/// ```
4889#[no_mangle]
4890pub extern "C" fn xmlBufferFree(buf: *mut _xmlBuffer) {
4891    crate::xml::io::buf_free(buf)
4892}
4893
4894/// Empty a buffer.
4895///
4896/// # UPSTREAM-PARITY
4897///
4898/// ```c
4899/// void xmlBufferEmpty(xmlBufferPtr buf);
4900/// ```
4901#[no_mangle]
4902pub extern "C" fn xmlBufferEmpty(buf: *mut _xmlBuffer) {
4903    if buf.is_null() {
4904        return;
4905    }
4906    unsafe {
4907        if !(*buf).content.is_null() {
4908            *(*buf).content = 0;
4909        }
4910        (*buf).use_ = 0;
4911    }
4912}
4913
4914/// Get buffer content.
4915///
4916/// # UPSTREAM-PARITY
4917///
4918/// ```c
4919/// xmlChar *xmlBufferContent(const xmlBuffer *buf);
4920/// ```
4921#[no_mangle]
4922pub extern "C" fn xmlBufferContent(buf: *const _xmlBuffer) -> *mut xmlChar {
4923    crate::xml::io::buf_content(buf as *mut _xmlBuffer)
4924}
4925
4926/// Get buffer length.
4927///
4928/// # UPSTREAM-PARITY
4929///
4930/// ```c
4931/// int xmlBufferLength(const xmlBuffer *buf);
4932/// ```
4933#[no_mangle]
4934pub extern "C" fn xmlBufferLength(buf: *const _xmlBuffer) -> c_int {
4935    crate::xml::io::buf_length(buf as *mut _xmlBuffer)
4936}
4937
4938/// Write to a buffer.
4939///
4940/// # UPSTREAM-PARITY
4941///
4942/// ```c
4943/// int xmlBufferAdd(xmlBufferPtr buf, const xmlChar *str, int len);
4944/// ```
4945#[no_mangle]
4946pub unsafe extern "C" fn xmlBufferAdd(
4947    buf: *mut _xmlBuffer,
4948    str: *const xmlChar,
4949    len: c_int,
4950) -> c_int {
4951    crate::xml::io::buf_add(buf, str, len)
4952}
4953
4954/// Write to a buffer at a position.
4955///
4956/// # UPSTREAM-PARITY
4957///
4958/// ```c
4959/// int xmlBufferAddHead(xmlBufferPtr buf, const xmlChar *str, int len);
4960/// ```
4961#[no_mangle]
4962pub unsafe extern "C" fn xmlBufferAddHead(
4963    buf: *mut _xmlBuffer,
4964    str: *const xmlChar,
4965    len: c_int,
4966) -> c_int {
4967    crate::xml::io::buf_add_head(buf, str, len)
4968}
4969
4970/// Write a C string to a buffer.
4971///
4972/// # UPSTREAM-PARITY
4973///
4974/// ```c
4975/// int xmlBufferCat(xmlBufferPtr buf, const xmlChar *str);
4976/// ```
4977#[no_mangle]
4978pub unsafe extern "C" fn xmlBufferCat(buf: *mut _xmlBuffer, str: *const xmlChar) -> c_int {
4979    if str.is_null() {
4980        return -1;
4981    }
4982    let len = crate::xml::string::xml_strlen(str) as c_int;
4983    crate::xml::io::buf_add(buf, str, len)
4984}
4985
4986/// Set buffer allocation scheme.
4987///
4988/// # UPSTREAM-PARITY
4989///
4990/// ```c
4991/// void xmlBufferSetAllocationScheme(xmlBufferPtr buf,
4992///                                    xmlBufferAllocationScheme scheme);
4993/// ```
4994#[no_mangle]
4995pub extern "C" fn xmlBufferSetAllocationScheme(buf: *mut _xmlBuffer, scheme: c_int) {
4996    if buf.is_null() {
4997        return;
4998    }
4999    unsafe {
5000        (*buf).alloc = scheme;
5001    }
5002}
5003
5004/// Shrink buffer.
5005///
5006/// # UPSTREAM-PARITY
5007///
5008/// ```c
5009/// int xmlBufferShrink(xmlBufferPtr buf, unsigned int len);
5010/// ```
5011///
5012/// Oracle buf.c semantics (11.1-Z.3 alignment): -1 on NULL or `len` larger
5013/// than the buffer content, 0 for `len == 0`, else the removed byte count.
5014#[no_mangle]
5015pub extern "C" fn xmlBufferShrink(buf: *mut _xmlBuffer, len: c_uint) -> c_int {
5016    if buf.is_null() {
5017        return -1;
5018    }
5019    if len == 0 {
5020        return 0;
5021    }
5022    unsafe {
5023        let b = &mut *buf;
5024        if len > b.use_ {
5025            return -1;
5026        }
5027        let remaining = b.use_ - len;
5028        if remaining > 0 {
5029            core::ptr::copy(b.content.add(len as usize), b.content, remaining as usize);
5030        }
5031        *b.content.add(remaining as usize) = 0;
5032        b.use_ = remaining;
5033    }
5034    len as c_int
5035}
5036
5037/// Grow buffer.
5038///
5039/// # UPSTREAM-PARITY
5040///
5041/// ```c
5042/// int xmlBufferGrow(xmlBufferPtr buf, unsigned int len);
5043/// ```
5044#[no_mangle]
5045pub extern "C" fn xmlBufferGrow(buf: *mut _xmlBuffer, len: c_uint) -> c_int {
5046    if buf.is_null() || len == 0 {
5047        return 0;
5048    }
5049    let cur_use = unsafe { (*buf).use_ };
5050    let new_size = cur_use + len + 1;
5051    crate::xml::io::buf_grow(buf, new_size)
5052}
5053
5054/// Reserve buffer space.
5055///
5056/// # UPSTREAM-PARITY
5057///
5058/// ```c
5059/// int xmlBufferReserve(xmlBufferPtr buf, int len);
5060/// ```
5061#[no_mangle]
5062pub extern "C" fn xmlBufferReserve(buf: *mut _xmlBuffer, len: c_int) -> c_int {
5063    xmlBufferGrow(buf, len as c_uint)
5064}
5065
5066/// Detach buffer content.
5067///
5068/// # UPSTREAM-PARITY
5069///
5070/// ```c
5071/// xmlChar *xmlBufferDetach(xmlBufferPtr buf);
5072/// ```
5073#[no_mangle]
5074pub extern "C" fn xmlBufferDetach(buf: *mut _xmlBuffer) -> *mut xmlChar {
5075    if buf.is_null() {
5076        return ptr::null_mut();
5077    }
5078    unsafe {
5079        let content = (*buf).content;
5080        (*buf).content = ptr::null_mut();
5081        (*buf).use_ = 0;
5082        (*buf).size = 0;
5083        content
5084    }
5085}
5086
5087// ═══════════════════════════════════════════════════════════════════════════════
5088// 13. Encoding
5089// ═══════════════════════════════════════════════════════════════════════════════
5090
5091/// Get encoding from a name string.
5092///
5093/// # UPSTREAM-PARITY
5094///
5095/// ```c
5096/// xmlCharEncoding xmlGetCharEncoding(const char *name);
5097/// ```
5098#[no_mangle]
5099pub extern "C" fn xmlGetCharEncoding(name: *const c_char) -> c_int {
5100    if name.is_null() {
5101        return 0; // XML_CHAR_ENCODING_NONE
5102    }
5103    let name_bytes = unsafe {
5104        let len = libc::strlen(name);
5105        core::slice::from_raw_parts(name as *const u8, len)
5106    };
5107    crate::xml::encoding::encoding_from_name(name_bytes) as c_int
5108}
5109
5110/// Find an encoding handler.
5111///
5112/// # UPSTREAM-PARITY
5113///
5114/// ```c
5115/// xmlCharEncodingHandlerPtr xmlFindCharEncodingHandler(const char *name);
5116/// ```
5117#[no_mangle]
5118pub extern "C" fn xmlFindCharEncodingHandler(name: *const c_char) -> *mut c_void {
5119    if name.is_null() {
5120        return ptr::null_mut();
5121    }
5122    crate::xml::encoding::find_encoding_handler(name as *const xmlChar) as *mut c_void
5123}
5124
5125/// Close an encoding handler.
5126///
5127/// # UPSTREAM-PARITY
5128///
5129/// ```c
5130/// int xmlCharEncCloseFunc(xmlCharEncodingHandlerPtr handler);
5131/// ```
5132#[no_mangle]
5133pub extern "C" fn xmlCharEncCloseFunc(handler: *mut c_void) -> c_int {
5134    if handler.is_null() {
5135        return -1;
5136    }
5137    // Free the encoding handler
5138    unsafe {
5139        let h = handler as *mut crate::abi::structs::_xmlCharEncodingHandler;
5140        if !(*h).name.is_null() {
5141            crate::abi::allocator::xmlFreeImpl((*h).name as *mut c_void);
5142        }
5143        crate::abi::allocator::xmlFreeImpl(handler);
5144    }
5145    0
5146}
5147
5148/// Convert a block of ISO-8859-1 bytes to UTF-8 (upstream encoding.c
5149/// `xmlIsolat1ToUTF8`; R-000165 closure).
5150///
5151/// `*outlen`/`*inlen` are updated with the bytes produced/consumed; returns
5152/// the number of bytes written or an xmlCharEncError code.
5153///
5154/// # SAFETY
5155///
5156/// `out`/`in` must be valid buffers for `*outlen`/`*inlen` bytes.
5157#[no_mangle]
5158pub unsafe extern "C" fn xmlIsolat1ToUTF8(
5159    out: *mut u8,
5160    outlen: *mut c_int,
5161    input: *const u8,
5162    inlen: *mut c_int,
5163) -> c_int {
5164    // xmlCharEncError (encoding.h): SUCCESS 0, INTERNAL -1, SPACE -2.
5165    const XML_ENC_ERR_SPACE: c_int = -2;
5166    const XML_ENC_ERR_INTERNAL: c_int = -1;
5167    unsafe {
5168        if out.is_null() || input.is_null() || outlen.is_null() || inlen.is_null() {
5169            return XML_ENC_ERR_INTERNAL;
5170        }
5171        let outstart = out;
5172        let instart = input;
5173        let outend = out.add(*outlen as usize);
5174        let inend = input.add(*inlen as usize);
5175        let mut cur = input;
5176        let mut o = out;
5177        while cur < inend {
5178            let c = *cur;
5179            if c < 0x80 {
5180                if o >= outend {
5181                    break;
5182                }
5183                *o = c;
5184                o = o.add(1);
5185            } else {
5186                if (outend as usize) - (o as usize) < 2 {
5187                    break;
5188                }
5189                *o = (c >> 6) | 0xC0;
5190                *o.add(1) = (c & 0x3F) | 0x80;
5191                o = o.add(2);
5192            }
5193            cur = cur.add(1);
5194        }
5195        let mut ret = XML_ENC_ERR_SPACE;
5196        if cur == inend {
5197            ret = (o as usize - outstart as usize) as c_int;
5198        }
5199        *outlen = (o as usize - outstart as usize) as c_int;
5200        *inlen = (cur as usize - instart as usize) as c_int;
5201        ret
5202    }
5203}
5204
5205/// Convert a block of UTF-8 to ISO-8859-1 (upstream encoding.c
5206/// `xmlUTF8ToIsolat1`; R-000165 closure).
5207///
5208/// # SAFETY
5209///
5210/// `out`/`in` must be valid buffers for `*outlen`/`*inlen` bytes.
5211#[no_mangle]
5212pub unsafe extern "C" fn xmlUTF8ToIsolat1(
5213    out: *mut u8,
5214    outlen: *mut c_int,
5215    input: *const u8,
5216    inlen: *mut c_int,
5217) -> c_int {
5218    const XML_ENC_ERR_SPACE: c_int = -2;
5219    const XML_ENC_ERR_INTERNAL: c_int = -1;
5220    const XML_ENC_ERR_INPUT: c_int = -3;
5221    const XML_ENC_ERR_SUCCESS: c_int = 0;
5222    unsafe {
5223        if out.is_null() || outlen.is_null() || inlen.is_null() {
5224            return XML_ENC_ERR_INTERNAL;
5225        }
5226        if input.is_null() {
5227            *inlen = 0;
5228            *outlen = 0;
5229            return XML_ENC_ERR_SUCCESS;
5230        }
5231        let outstart = out;
5232        let instart = input;
5233        let outend = out.add(*outlen as usize);
5234        let inend = input.add(*inlen as usize);
5235        let mut cur = input;
5236        let mut o = out;
5237        let mut ret = XML_ENC_ERR_SPACE;
5238        while cur < inend {
5239            if o >= outend {
5240                break;
5241            }
5242            let c = *cur;
5243            if c < 0x80 {
5244                *o = c;
5245                o = o.add(1);
5246            } else if (0xC2..=0xC3).contains(&c) {
5247                if (inend as usize) - (cur as usize) < 2 {
5248                    break;
5249                }
5250                cur = cur.add(1);
5251                *o = (c << 6) | (*cur & 0x3F);
5252                o = o.add(1);
5253            } else {
5254                ret = XML_ENC_ERR_INPUT;
5255                break;
5256            }
5257            cur = cur.add(1);
5258        }
5259        if ret != XML_ENC_ERR_INPUT {
5260            ret = (o as usize - outstart as usize) as c_int;
5261        }
5262        *outlen = (o as usize - outstart as usize) as c_int;
5263        *inlen = (cur as usize - instart as usize) as c_int;
5264        ret
5265    }
5266}
5267
5268/// Return the name of a character encoding (upstream encoding.h).
5269///
5270/// # UPSTREAM-PARITY
5271///
5272/// ```c
5273/// const char *xmlGetCharEncodingName(xmlCharEncoding enc);
5274/// ```
5275#[no_mangle]
5276pub extern "C" fn xmlGetCharEncodingName(enc: c_int) -> *const c_char {
5277    /* Values outside the local enum resolve against the upstream
5278     * defaultHandlers table (XML_CHAR_ENCODING_UTF16=23, HTML=24,
5279     * WINDOWS_1252=31); anything else is unknown (NULL). */
5280    if !(-1..=22).contains(&enc) {
5281        return match enc {
5282            23 => c"UTF-16".as_ptr(),
5283            24 => c"HTML".as_ptr(),
5284            31 => c"windows-1252".as_ptr(),
5285            _ => ptr::null(),
5286        };
5287    }
5288    let e: crate::abi::types::xmlCharEncoding = unsafe { core::mem::transmute(enc) };
5289    crate::xml::encoding::xmlGetCharEncodingName(e)
5290}
5291
5292/// Parse an encoding name into an xmlCharEncoding value (upstream encoding.h).
5293///
5294/// # UPSTREAM-PARITY
5295///
5296/// ```c
5297/// xmlCharEncoding xmlParseCharEncoding(const char *name);
5298/// ```
5299///
5300/// Returns the encoding value or XML_CHAR_ENCODING_ERROR (-1).
5301#[no_mangle]
5302pub extern "C" fn xmlParseCharEncoding(name: *const c_char) -> c_int {
5303    crate::xml::encoding::xmlParseCharEncoding(name)
5304}
5305
5306/// Add an encoding alias (upstream encoding.h).
5307///
5308/// # UPSTREAM-PARITY
5309///
5310/// ```c
5311/// int xmlAddEncodingAlias(const char *name, const char *alias);
5312/// ```
5313#[no_mangle]
5314pub extern "C" fn xmlAddEncodingAlias(name: *const c_char, alias: *const c_char) -> c_int {
5315    crate::xml::encoding::add_encoding_alias(name, alias)
5316}
5317
5318/// Delete an encoding alias (upstream encoding.h).
5319///
5320/// # UPSTREAM-PARITY
5321///
5322/// ```c
5323/// int xmlDelEncodingAlias(const char *alias);
5324/// ```
5325#[no_mangle]
5326pub extern "C" fn xmlDelEncodingAlias(alias: *const c_char) -> c_int {
5327    crate::xml::encoding::del_encoding_alias(alias)
5328}
5329
5330/// Look up an encoding alias (upstream encoding.h).
5331///
5332/// # UPSTREAM-PARITY
5333///
5334/// ```c
5335/// const char *xmlGetEncodingAlias(const char *alias);
5336/// ```
5337#[no_mangle]
5338pub extern "C" fn xmlGetEncodingAlias(alias: *const c_char) -> *const c_char {
5339    crate::xml::encoding::get_encoding_alias(alias)
5340}
5341
5342/// Clean up the encoding alias table (upstream encoding.h).
5343///
5344/// # UPSTREAM-PARITY
5345///
5346/// ```c
5347/// void xmlCleanupEncodingAliases(void);
5348/// ```
5349#[no_mangle]
5350pub extern "C" fn xmlCleanupEncodingAliases() {
5351    crate::xml::encoding::cleanup_encoding_aliases();
5352}
5353
5354/// Convert the input buffer using an encoding handler (upstream encoding.h).
5355///
5356/// # UPSTREAM-PARITY
5357///
5358/// ```c
5359/// int xmlCharEncInFunc(xmlCharEncodingHandler *handler,
5360///                      xmlBufferPtr out, xmlBufferPtr in);
5361/// ```
5362#[no_mangle]
5363pub extern "C" fn xmlCharEncInFunc(
5364    handler: *mut c_void,
5365    out: *mut c_void,
5366    in_: *mut c_void,
5367) -> c_int {
5368    crate::xml::encoding::xmlCharEncInFunc(
5369        handler as *mut crate::abi::structs::_xmlCharEncodingHandler,
5370        out as *mut crate::abi::structs::_xmlBuffer,
5371        in_ as *mut crate::abi::structs::_xmlBuffer,
5372    )
5373}
5374
5375/// Convert the output buffer using an encoding handler (upstream encoding.h).
5376///
5377/// # UPSTREAM-PARITY
5378///
5379/// ```c
5380/// int xmlCharEncOutFunc(xmlCharEncodingHandler *handler,
5381///                       xmlBufferPtr out, xmlBufferPtr in);
5382/// ```
5383#[no_mangle]
5384pub extern "C" fn xmlCharEncOutFunc(
5385    handler: *mut c_void,
5386    out: *mut c_void,
5387    in_: *mut c_void,
5388) -> c_int {
5389    crate::xml::encoding::xmlCharEncOutFunc(
5390        handler as *mut crate::abi::structs::_xmlCharEncodingHandler,
5391        out as *mut crate::abi::structs::_xmlBuffer,
5392        in_ as *mut crate::abi::structs::_xmlBuffer,
5393    )
5394}
5395
5396/// Create a new encoding handler (upstream encoding.h).
5397///
5398/// # UPSTREAM-PARITY
5399///
5400/// ```c
5401/// xmlCharEncodingHandlerPtr xmlNewCharEncodingHandler(
5402///     const char *name, xmlCharEncodingInputFunc input,
5403///     xmlCharEncodingOutputFunc output);
5404/// ```
5405#[no_mangle]
5406pub extern "C" fn xmlNewCharEncodingHandler(
5407    name: *const c_char,
5408    input: crate::abi::callbacks::xmlCharEncodingInputFunc,
5409    output: crate::abi::callbacks::xmlCharEncodingOutputFunc,
5410) -> *mut c_void {
5411    crate::xml::encoding::xmlNewCharEncodingHandler(name, input, output) as *mut c_void
5412}
5413
5414/// Initialize the built-in encoding handlers (upstream encoding.h).
5415///
5416/// # UPSTREAM-PARITY
5417///
5418/// ```c
5419/// void xmlInitCharEncodingHandlers(void);
5420/// ```
5421#[no_mangle]
5422pub extern "C" fn xmlInitCharEncodingHandlers() {
5423    crate::xml::encoding::xmlInitCharEncodingHandlers();
5424}
5425
5426/// Clean up the encoding handlers (upstream encoding.h).
5427///
5428/// # UPSTREAM-PARITY
5429///
5430/// ```c
5431/// void xmlCleanupCharEncodingHandlers(void);
5432/// ```
5433#[no_mangle]
5434pub extern "C" fn xmlCleanupCharEncodingHandlers() {
5435    crate::xml::encoding::xmlCleanupCharEncodingHandlers();
5436}
5437
5438/// Look up a built-in encoding handler by `xmlCharEncoding` value.
5439///
5440/// Returns an `xmlParserErrors` code; on success `*out` receives the static
5441/// handler (NULL for UTF-8, which needs no conversion).
5442///
5443/// # UPSTREAM-PARITY
5444///
5445/// ```c
5446/// xmlParserErrors xmlLookupCharEncodingHandler(xmlCharEncoding enc,
5447///                                              xmlCharEncodingHandler **out);
5448/// ```
5449#[no_mangle]
5450pub extern "C" fn xmlLookupCharEncodingHandler(enc: c_int, out: *mut *mut c_void) -> c_int {
5451    crate::xml::encoding::xmlLookupCharEncodingHandler(enc, out)
5452}
5453
5454/// Get the encoding handler for an `xmlCharEncoding` value (deprecated).
5455///
5456/// # UPSTREAM-PARITY
5457///
5458/// ```c
5459/// xmlCharEncodingHandler *xmlGetCharEncodingHandler(xmlCharEncoding enc);
5460/// ```
5461#[no_mangle]
5462pub extern "C" fn xmlGetCharEncodingHandler(enc: c_int) -> *mut c_void {
5463    crate::xml::encoding::xmlGetCharEncodingHandler(enc)
5464}
5465
5466/// Find or create an encoding handler by name for one conversion direction.
5467///
5468/// # UPSTREAM-PARITY
5469///
5470/// ```c
5471/// xmlParserErrors xmlOpenCharEncodingHandler(const char *name, int output,
5472///                                            xmlCharEncodingHandler **out);
5473/// ```
5474#[no_mangle]
5475pub extern "C" fn xmlOpenCharEncodingHandler(
5476    name: *const c_char,
5477    output: c_int,
5478    out: *mut *mut c_void,
5479) -> c_int {
5480    crate::xml::encoding::xmlOpenCharEncodingHandler(name, output, out)
5481}
5482
5483/// Find or create an encoding handler by name with flags and an optional
5484/// custom conversion implementation.
5485///
5486/// # UPSTREAM-PARITY
5487///
5488/// ```c
5489/// xmlParserErrors xmlCreateCharEncodingHandler(
5490///     const char *name, xmlCharEncFlags flags, xmlCharEncConvImpl impl,
5491///     void *implCtxt, xmlCharEncodingHandler **out);
5492/// ```
5493#[no_mangle]
5494pub extern "C" fn xmlCreateCharEncodingHandler(
5495    name: *const c_char,
5496    flags: c_int,
5497    impl_: Option<crate::abi::callbacks::xmlCharEncConvImpl>,
5498    implCtxt: *mut c_void,
5499    out: *mut *mut c_void,
5500) -> c_int {
5501    crate::xml::encoding::xmlCreateCharEncodingHandler(name, flags, impl_, implCtxt, out)
5502}
5503
5504/// Create an encoding handler backed by modern conversion callbacks.
5505///
5506/// # UPSTREAM-PARITY
5507///
5508/// ```c
5509/// xmlParserErrors xmlCharEncNewCustomHandler(
5510///     const char *name, xmlCharEncConvFunc input, xmlCharEncConvFunc output,
5511///     xmlCharEncConvCtxtDtor ctxtDtor, void *inputCtxt, void *outputCtxt,
5512///     xmlCharEncodingHandler **out);
5513/// ```
5514#[no_mangle]
5515pub extern "C" fn xmlCharEncNewCustomHandler(
5516    name: *const c_char,
5517    input: crate::abi::callbacks::xmlCharEncConvFunc,
5518    output: crate::abi::callbacks::xmlCharEncConvFunc,
5519    ctxtDtor: Option<crate::abi::callbacks::xmlCharEncConvCtxtDtor>,
5520    inputCtxt: *mut c_void,
5521    outputCtxt: *mut c_void,
5522    out: *mut *mut c_void,
5523) -> c_int {
5524    crate::xml::encoding::xmlCharEncNewCustomHandler(
5525        name, input, output, ctxtDtor, inputCtxt, outputCtxt, out,
5526    )
5527}
5528
5529/// Convert an input buffer's encoding.
5530///
5531/// # UPSTREAM-PARITY
5532///
5533/// ```c
5534/// int xmlCharEncInput(xmlParserInputBufferPtr input, int to);
5535/// ```
5536#[no_mangle]
5537pub unsafe extern "C" fn xmlCharEncInput(input: *mut _xmlParserInputBuffer, _to: c_int) -> c_int {
5538    if input.is_null() {
5539        return -1;
5540    }
5541    let handler = (*input).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
5542    if handler.is_null() {
5543        return -1;
5544    }
5545    let raw = (*input).raw as *mut crate::abi::structs::_xmlBuffer;
5546    let buf = (*input).buffer as *mut crate::abi::structs::_xmlBuffer;
5547    if raw.is_null() || buf.is_null() {
5548        return -1;
5549    }
5550    crate::xml::encoding::char_enc_in(handler, buf, raw)
5551}
5552
5553/// Convert an output buffer's encoding.
5554///
5555/// # UPSTREAM-PARITY
5556///
5557/// ```c
5558/// int xmlCharEncOutput(xmlOutputBufferPtr output, int to);
5559/// ```
5560#[no_mangle]
5561pub unsafe extern "C" fn xmlCharEncOutput(output: *mut _xmlOutputBuffer, _to: c_int) -> c_int {
5562    if output.is_null() {
5563        return -1;
5564    }
5565    let handler = (*output).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
5566    if handler.is_null() {
5567        return -1;
5568    }
5569    let buf = (*output).buffer as *mut crate::abi::structs::_xmlBuffer;
5570    let conv = (*output).conv as *mut crate::abi::structs::_xmlBuffer;
5571    if buf.is_null() || conv.is_null() {
5572        return -1;
5573    }
5574    crate::xml::encoding::char_enc_out(handler, conv, buf)
5575}
5576
5577// ═══════════════════════════════════════════════════════════════════════════════
5578// URI
5579// ═══════════════════════════════════════════════════════════════════════════════
5580
5581/// Parse a URI string.
5582///
5583/// # UPSTREAM-PARITY
5584///
5585/// ```c
5586/// xmlURIPtr xmlParseURI(const char *str);
5587/// ```
5588#[no_mangle]
5589pub unsafe extern "C" fn xmlParseURI(str: *const c_char) -> *mut c_void {
5590    crate::xml::uri::xmlParseURI(str)
5591}
5592
5593/// Parse a URI string (raw version).
5594///
5595/// # UPSTREAM-PARITY
5596///
5597/// ```c
5598/// xmlURIPtr xmlParseURIRaw(const char *str, int raw);
5599/// ```
5600#[no_mangle]
5601pub unsafe extern "C" fn xmlParseURIRaw(str: *const c_char, raw: c_int) -> *mut c_void {
5602    let _ = raw;
5603    crate::xml::uri::xmlParseURI(str)
5604}
5605
5606/// Free a URI structure.
5607///
5608/// # UPSTREAM-PARITY
5609///
5610/// ```c
5611/// void xmlFreeURI(xmlURIPtr uri);
5612/// ```
5613#[no_mangle]
5614pub unsafe extern "C" fn xmlFreeURI(uri: *mut c_void) {
5615    crate::xml::uri::xmlFreeURI(uri)
5616}
5617
5618/// Create an empty URI.
5619///
5620/// # UPSTREAM-PARITY
5621///
5622/// ```c
5623/// xmlURIPtr xmlCreateURI(void);
5624/// ```
5625#[no_mangle]
5626pub extern "C" fn xmlCreateURI() -> *mut c_void {
5627    crate::xml::uri::xmlCreateURI()
5628}
5629
5630/// Save a URI structure to a string.
5631///
5632/// # UPSTREAM-PARITY
5633///
5634/// ```c
5635/// xmlChar *xmlSaveUri(xmlURIPtr uri);
5636/// ```
5637#[no_mangle]
5638pub unsafe extern "C" fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
5639    crate::xml::uri::xmlSaveUri(uri)
5640}
5641
5642/// Parse a URI string into an existing URI structure (upstream uri.h).
5643///
5644/// # UPSTREAM-PARITY
5645///
5646/// ```c
5647/// int xmlParseURIReference(xmlURIPtr uri, const char *str);
5648/// ```
5649///
5650/// Returns 0 on success, -1 on failure (the URI structure is left
5651/// untouched on failure).
5652///
5653/// # Safety
5654///
5655/// - `uri` must be a valid pointer from `xmlParseURI`/`xmlCreateURI`.
5656/// - `str` must be a valid null-terminated C string.
5657#[no_mangle]
5658pub unsafe extern "C" fn xmlParseURIReference(uri: *mut c_void, str: *const c_char) -> c_int {
5659    crate::xml::uri::xmlParseURIReference(uri, str)
5660}
5661
5662/// Normalize a URI path in place (upstream uri.h).
5663///
5664/// # UPSTREAM-PARITY
5665///
5666/// ```c
5667/// int xmlNormalizeURIPath(char *path);
5668/// ```
5669///
5670/// Returns 0 on success, -1 if the path is NULL, not absolute, or contains
5671/// `..` segments that climb above the root.
5672///
5673/// # Safety
5674///
5675/// `path` must be a valid writable null-terminated C string buffer.
5676#[no_mangle]
5677pub unsafe extern "C" fn xmlNormalizeURIPath(path: *mut c_char) -> c_int {
5678    crate::xml::uri::xmlNormalizeURIPath(path)
5679}
5680
5681/// Escape a URI string.
5682///
5683/// # UPSTREAM-PARITY
5684///
5685/// ```c
5686/// xmlChar *xmlURIEscapeStr(const xmlChar *str, const xmlChar *list);
5687/// ```
5688#[no_mangle]
5689pub unsafe extern "C" fn xmlURIEscapeStr(
5690    str: *const xmlChar,
5691    list: *const xmlChar,
5692) -> *mut xmlChar {
5693    crate::xml::uri::xmlURIEscapeStr(str, list)
5694}
5695
5696/// Unescape a URI string.
5697///
5698/// # UPSTREAM-PARITY
5699///
5700/// ```c
5701/// char *xmlURIUnescapeString(const char *str, int len, char *target);
5702/// ```
5703#[no_mangle]
5704pub unsafe extern "C" fn xmlURIUnescapeString(
5705    str: *const c_char,
5706    len: c_int,
5707    target: *mut c_char,
5708) -> *mut c_char {
5709    crate::xml::uri::xmlURIUnescapeString(str, len, target)
5710}
5711
5712// ═══════════════════════════════════════════════════════════════════════════════
5713// 14. XPath
5714// ═══════════════════════════════════════════════════════════════════════════════
5715
5716// ── Helper functions ────────────────────────────────────────────────────
5717
5718/// Convert an internal `XPathValue` to a C ABI `_xmlXPathObject`.
5719///
5720/// The returned pointer is heap-allocated via `xmlMallocZero` and must be
5721/// freed with `xmlXPathFreeObject`.
5722///
5723/// # Safety
5724///
5725/// Must be called from a context where `xmlMalloc` is safe to call.
5726unsafe fn xpath_to_object(val: XPathValue) -> *mut _xmlXPathObject {
5727    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
5728    if obj.is_null() {
5729        return ptr::null_mut();
5730    }
5731    match val {
5732        XPathValue::NodeSet(ns) => {
5733            (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
5734            (*obj).nodesetval = ns.to_raw() as *mut c_void;
5735        }
5736        XPathValue::Boolean(b) => {
5737            (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
5738            (*obj).boolval = if b { 1 } else { 0 };
5739        }
5740        XPathValue::Number(n) => {
5741            (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
5742            (*obj).floatval = n;
5743        }
5744        XPathValue::String(s) => {
5745            (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
5746            let bytes = s.as_bytes();
5747            let len = bytes.len();
5748            let buf = xmlMallocImpl(len + 1) as *mut xmlChar;
5749            if !buf.is_null() {
5750                ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
5751                *buf.add(len) = 0; // null terminator
5752            }
5753            (*obj).stringval = buf;
5754        }
5755    }
5756    obj
5757}
5758
5759/// Extract an internal `XPathValue` from a C ABI `_xmlXPathObject`.
5760///
5761/// # Safety
5762///
5763/// `obj` must be a valid, non-null pointer to a properly initialised
5764/// `_xmlXPathObject`.
5765unsafe fn object_to_xpathvalue(obj: *mut _xmlXPathObject) -> XPathValue {
5766    let typ = (*obj).type_;
5767    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
5768        let ns_ptr = (*obj).nodesetval as *mut _xmlNodeSet;
5769        if ns_ptr.is_null() {
5770            return XPathValue::NodeSet(NodeSet::new());
5771        }
5772        let node_nr = (*ns_ptr).nodeNr;
5773        let node_tab = (*ns_ptr).nodeTab;
5774        let mut ns = NodeSet::new();
5775        if !node_tab.is_null() {
5776            for i in 0..node_nr as isize {
5777                let node = *node_tab.add(i as usize);
5778                ns.push(node);
5779            }
5780        }
5781        XPathValue::NodeSet(ns)
5782    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
5783        XPathValue::Boolean((*obj).boolval != 0)
5784    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
5785        XPathValue::Number((*obj).floatval)
5786    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
5787        let s_ptr = (*obj).stringval;
5788        if s_ptr.is_null() {
5789            XPathValue::String(String::new())
5790        } else {
5791            let s = CStr::from_ptr(s_ptr as *const c_char)
5792                .to_string_lossy()
5793                .into_owned();
5794            XPathValue::String(s)
5795        }
5796    } else if typ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int {
5797        // A result tree fragment: node-set containing the fragment's
5798        // document node (matching how global RTF variables are bound), so
5799        // local RTF variables stringify to their text and remain navigable
5800        // via exsl:node-set.
5801        let frag_doc = (*obj).nodesetval as *mut _xmlDoc;
5802        if frag_doc.is_null() {
5803            XPathValue::NodeSet(NodeSet::new())
5804        } else {
5805            let mut ns = NodeSet::new();
5806            ns.push(frag_doc as *mut _xmlNode);
5807            XPathValue::NodeSet(ns)
5808        }
5809    } else {
5810        // Undefined / unknown type — return boolean false as a safe default.
5811        XPathValue::Boolean(false)
5812    }
5813}
5814
5815/// Public wrapper for `xpath_to_object` (used by the XPath export bridge).
5816///
5817/// # Safety
5818///
5819/// - `val` is consumed and converted into a heap-allocated `_xmlXPathObject`.
5820pub unsafe fn xpath_to_object_pub(val: XPathValue) -> *mut _xmlXPathObject {
5821    xpath_to_object(val)
5822}
5823
5824/// Public wrapper for `object_to_xpathvalue` (used by the XSLT engine).
5825///
5826/// # Safety
5827///
5828/// `obj` must be a valid, non-null pointer to a properly initialised
5829/// `_xmlXPathObject`.
5830pub unsafe fn object_to_xpathvalue_pub(obj: *mut _xmlXPathObject) -> XPathValue {
5831    object_to_xpathvalue(obj)
5832}
5833
5834// ── Compiled expression registry ────────────────────────────────────────
5835//
5836// Compiled XPath expressions are opaque pointers returned by xmlXPathCompile.
5837// We store them in a global registry keyed by a monotonically increasing ID.
5838
5839static COMPILED_EXPRS: Lazy<Mutex<HashMap<u64, Box<CompiledExpr>>>> =
5840    Lazy::new(|| Mutex::new(HashMap::new()));
5841static NEXT_COMPILED_KEY: Lazy<Mutex<u64>> = Lazy::new(|| Mutex::new(1));
5842
5843/// Accessor for the compiled-expression registry (used by the XPath export
5844/// bridge for `xmlXPathCompiledEval` / `xmlXPathCompiledEvalToBoolean`).
5845pub(crate) fn xpath_compiled_registry() -> &'static Mutex<HashMap<u64, Box<CompiledExpr>>> {
5846    &COMPILED_EXPRS
5847}
5848
5849// ── C extension-function registry ──────────────────────────────────────
5850//
5851// C extension functions registered via xmlXPathRegisterFunc / RegisterFuncNS
5852// are stored here because the Rust XPathFunction signature is incompatible
5853// with the C xmlXPathFunction calling convention (the C function expects a
5854// parser context, not pre-evaluated argument slices). The registration is
5855// stored faithfully; invoking registered C functions from within the Rust
5856// evaluator requires a bridge that is not yet implemented.
5857
5858type CXPathFunc = unsafe extern "C" fn(*mut c_void, c_int);
5859
5860/// Wrapper around `*mut c_void` that implements `Send` + `Sync` so it can
5861/// be used as a key in a `Mutex`-protected global `HashMap`.
5862#[derive(Clone, Copy, PartialEq, Eq, Hash)]
5863struct SendSyncPtr(*mut c_void);
5864unsafe impl Send for SendSyncPtr {}
5865unsafe impl Sync for SendSyncPtr {}
5866
5867static C_FUNCTIONS: Lazy<Mutex<HashMap<(SendSyncPtr, String), CXPathFunc>>> =
5868    Lazy::new(|| Mutex::new(HashMap::new()));
5869
5870/// Look up a C-registered extension function for the context identified by
5871/// `extra` (the internal XPathContext pointer). Used by
5872/// `xmlXPathFunctionLookupNS`.
5873pub(crate) fn xpath_cfunc_lookup(extra: *mut c_void, qualified: &str) -> Option<CXPathFunc> {
5874    C_FUNCTIONS
5875        .lock()
5876        .get(&(SendSyncPtr(extra), qualified.to_string()))
5877        .copied()
5878}
5879
5880/// Drop every C extension-function registration belonging to the context
5881/// identified by `extra` (upstream `xmlXPathRegisteredFuncsCleanup`).
5882pub(crate) fn xpath_cfunc_cleanup(extra: *mut c_void) {
5883    C_FUNCTIONS.lock().retain(|(k, _), _| k.0 != extra);
5884}
5885
5886/// Build the Rust-side closure that bridges a C-registered XPath function
5887/// into the Rust evaluator (see `c_func_call_bridge`). Returns a
5888/// `BoxedXPathFunction` so the closure is coerced with the higher-ranked
5889/// signature the evaluator requires.
5890fn c_func_bridge_closure(c_ctxt: SendSyncPtr, qualified: String) -> BoxedXPathFunction {
5891    Box::new(move |_ctx: &mut XPathContext, args: &[XPathValue]| {
5892        let cc = c_ctxt;
5893        unsafe { c_func_call_bridge(cc.0 as *mut _xmlXPathContext, &qualified, args) }
5894    })
5895}
5896
5897/// Call a C-ABI `xmlXPathFunction` through a synthesized
5898/// `xmlXPathParserContext`: push the evaluated arguments as XPath objects,
5899/// invoke the function, pop and convert the result — the upstream
5900/// `xmlXPathCompOpEval` function-call sequence (xpath.c).
5901///
5902/// # SAFETY
5903///
5904/// - `fnptr` must be a valid C callback (or None).
5905/// - `c_ctxt` must be the live C XPath context the callback belongs to.
5906pub(crate) unsafe fn call_c_xpath_function(
5907    fnptr: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
5908    c_ctxt: *mut _xmlXPathContext,
5909    args: &[XPathValue],
5910) -> Result<XPathValue, String> {
5911    let func = match fnptr {
5912        Some(f) => f,
5913        None => return Err("XPath: missing C function pointer".to_string()),
5914    };
5915    let pc = crate::xml::xpath::parser_context::new_parser_context(ptr::null(), c_ctxt);
5916    if pc.is_null() {
5917        return Err("XPath: parser-context allocation failure".to_string());
5918    }
5919    let mut push_ok = true;
5920    for v in args {
5921        let obj = xpath_to_object(v.clone());
5922        if obj.is_null() || crate::xml::xpath::parser_context::value_push(pc, obj).is_null() {
5923            push_ok = false;
5924            break;
5925        }
5926    }
5927    let result = if push_ok {
5928        // SAFETY: `func` is a valid C callback; the arguments are on the
5929        // parser-context value stack exactly as upstream would leave them.
5930        unsafe { func(pc as *mut c_void, args.len() as c_int) };
5931        let ret = crate::xml::xpath::parser_context::value_pop(pc);
5932        if ret.is_null() {
5933            Err("XPath: C function returned no value".to_string())
5934        } else {
5935            let v = object_to_xpathvalue(ret);
5936            // The popped object is heap-allocated; free it after converting.
5937            unsafe { xmlXPathFreeObject(ret) };
5938            Ok(v)
5939        }
5940    } else {
5941        Err("XPath: failed to push arguments to C function".to_string())
5942    };
5943    // Free any objects the C function left on the stack, then the context.
5944    unsafe {
5945        loop {
5946            let leftover = crate::xml::xpath::parser_context::value_pop(pc);
5947            if leftover.is_null() {
5948                break;
5949            }
5950            xmlXPathFreeObject(leftover);
5951        }
5952        crate::xml::xpath::parser_context::free_parser_context(pc);
5953    }
5954    result
5955}
5956
5957/// Rust-side wrapper registered in the internal XPathContext when a C
5958/// extension function is registered (`xmlXPathRegisterFunc[NS]`). This is the
5959/// parser-context bridge: it synthesises the upstream `xmlXPathParserContext`
5960/// (value stack + context pointer), pushes the evaluated arguments as XPath
5961/// objects, invokes the C function, then pops and converts the result — the
5962/// upstream `xmlXPathCompOpEval` function-call sequence (xpath.c).
5963unsafe fn c_func_call_bridge(
5964    c_ctxt: *mut _xmlXPathContext,
5965    qualified: &str,
5966    args: &[XPathValue],
5967) -> Result<XPathValue, String> {
5968    if c_ctxt.is_null() {
5969        return Err("XPath: null context in C function bridge".to_string());
5970    }
5971    let func = xpath_cfunc_lookup((*c_ctxt).extra, qualified);
5972    if func.is_none() {
5973        return Err(format!("XPath: unknown C function '{}'", qualified));
5974    }
5975    unsafe { call_c_xpath_function(func, c_ctxt, args) }
5976}
5977
5978// ── Public API ─────────────────────────────────────────────────────────
5979
5980/// Create a new XPath context.
5981///
5982/// Allocates a `_xmlXPathContext` and an internal `XPathContext`, storing
5983/// the latter's pointer in the `extra` field.
5984///
5985/// # UPSTREAM-PARITY
5986///
5987/// ```c
5988/// xmlXPathContextPtr xmlXPathNewContext(xmlDocPtr doc);
5989/// ```
5990#[no_mangle]
5991pub unsafe extern "C" fn xmlXPathNewContext(doc: *mut _xmlDoc) -> *mut _xmlXPathContext {
5992    let ctxt = xmlMallocZero(size_of::<_xmlXPathContext>()) as *mut _xmlXPathContext;
5993    if ctxt.is_null() {
5994        return ptr::null_mut();
5995    }
5996
5997    // Initialise the C ABI context fields.
5998    (*ctxt).doc = doc;
5999    (*ctxt).node = ptr::null_mut();
6000    (*ctxt).contextSize = 1;
6001    (*ctxt).proximityPosition = 1;
6002
6003    // Create the internal XPathContext and store it in `extra`.
6004    let mut internal = Box::new(XPathContext::new(doc));
6005    // UPSTREAM-PARITY: the standard function library is implicitly available
6006    // in every context (upstream compiles it in; xmlXPathRegisterAllFunctions
6007    // is a no-op since 2.14.0). Without this, core-function calls such as
6008    // count() would fail as unknown functions.
6009    for (name, func) in crate::xml::xpath::functions::core_functions() {
6010        internal.register_function(&name, func);
6011    }
6012    (*ctxt).extra = Box::into_raw(internal) as *mut c_void;
6013
6014    ctxt
6015}
6016
6017/// Free an XPath context.
6018///
6019/// # UPSTREAM-PARITY
6020///
6021/// ```c
6022/// void xmlXPathFreeContext(xmlXPathContextPtr ctxt);
6023/// ```
6024#[no_mangle]
6025pub unsafe extern "C" fn xmlXPathFreeContext(ctxt: *mut _xmlXPathContext) {
6026    if ctxt.is_null() {
6027        return;
6028    }
6029    // Drop the internal XPathContext.
6030    if !(*ctxt).extra.is_null() {
6031        let _ = Box::from_raw((*ctxt).extra as *mut XPathContext);
6032        (*ctxt).extra = ptr::null_mut();
6033    }
6034    // Drop the registered-namespace C-string hash (xmlXPathNsLookup pointers).
6035    if !(*ctxt).nsHash.is_null() {
6036        drop(Box::from_raw(
6037            (*ctxt).nsHash as *mut HashMap<String, CString>,
6038        ));
6039        (*ctxt).nsHash = ptr::null_mut();
6040    }
6041    // Free the C ABI context struct.
6042    xmlFreeImpl(ctxt as *mut c_void);
6043}
6044
6045/// Evaluate an XPath expression.
6046///
6047/// # UPSTREAM-PARITY
6048///
6049/// ```c
6050/// xmlXPathObjectPtr xmlXPathEvalExpression(const xmlChar *str,
6051///                                          xmlXPathContextPtr ctxt);
6052/// ```
6053#[no_mangle]
6054pub unsafe extern "C" fn xmlXPathEvalExpression(
6055    str_: *const xmlChar,
6056    ctxt: *mut _xmlXPathContext,
6057) -> *mut _xmlXPathObject {
6058    if str_.is_null() || ctxt.is_null() {
6059        return ptr::null_mut();
6060    }
6061    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
6062        Ok(s) => s,
6063        Err(_) => return ptr::null_mut(),
6064    };
6065    let internal = (*ctxt).extra as *mut XPathContext;
6066    if internal.is_null() {
6067        return ptr::null_mut();
6068    }
6069    let internal = &mut *internal;
6070    // Clear any stale error so a fresh evaluation either succeeds or records
6071    // its own failure message (the XSLT layer surfaces it verbatim).
6072    internal.clear_error();
6073
6074    match crate::xml::xpath::evaluate_str(expr_str, internal) {
6075        Some(val) => xpath_to_object(val),
6076        None => {
6077            // UPSTREAM-PARITY: libxml2 reports a failed compile/eval with
6078            // "XPath error : Invalid expression" (xmlXPathErr,
6079            // XPATH_EXPR_ERROR). The precise per-expression diagnostics are
6080            // tracked as RESIDUAL R-XPATH-ERRMSG.
6081            if internal.error.is_none() {
6082                internal.set_error("Invalid expression");
6083            }
6084            ptr::null_mut()
6085        }
6086    }
6087}
6088
6089/// Evaluate an XPath expression (simplified alias).
6090///
6091/// # UPSTREAM-PARITY
6092///
6093/// ```c
6094/// xmlXPathObjectPtr xmlXPathEval(const xmlChar *str, xmlXPathContextPtr ctxt);
6095/// ```
6096#[no_mangle]
6097pub unsafe extern "C" fn xmlXPathEval(
6098    str_: *const xmlChar,
6099    ctxt: *mut _xmlXPathContext,
6100) -> *mut _xmlXPathObject {
6101    xmlXPathEvalExpression(str_, ctxt)
6102}
6103
6104/// Free an XPath object.
6105///
6106/// Releases the internal members (string buffer or node-set) and then frees
6107/// the object struct itself.
6108///
6109/// # UPSTREAM-PARITY
6110///
6111/// ```c
6112/// void xmlXPathFreeObject(xmlXPathObjectPtr obj);
6113/// ```
6114#[no_mangle]
6115pub unsafe extern "C" fn xmlXPathFreeObject(obj: *mut _xmlXPathObject) {
6116    if obj.is_null() {
6117        return;
6118    }
6119    let typ = (*obj).type_;
6120    // Free string storage.
6121    if typ == xmlXPathObjectType::XPATH_STRING as c_int && !(*obj).stringval.is_null() {
6122        xmlFreeImpl((*obj).stringval as *mut c_void);
6123        (*obj).stringval = ptr::null_mut();
6124    }
6125    // Free node-set storage.
6126    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6127        let ns = (*obj).nodesetval as *mut _xmlNodeSet;
6128        if !ns.is_null() {
6129            if !(*ns).nodeTab.is_null() {
6130                xmlFreeImpl((*ns).nodeTab as *mut c_void);
6131            }
6132            xmlFreeImpl(ns as *mut c_void);
6133        }
6134        (*obj).nodesetval = ptr::null_mut();
6135    }
6136    xmlFreeImpl(obj as *mut c_void);
6137}
6138
6139/// Copy an XPath object (deep copy).
6140///
6141/// # UPSTREAM-PARITY
6142///
6143/// ```c
6144/// xmlXPathObjectPtr xmlXPathObjectCopy(xmlXPathObjectPtr val);
6145/// ```
6146///
6147/// Oracle behavior: returns a newly allocated object with the same type
6148/// and value. Node-sets are copied element-by-element; strings are
6149/// duplicated; numbers and booleans are copied by value.
6150#[no_mangle]
6151pub unsafe extern "C" fn xmlXPathObjectCopy(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
6152    if val.is_null() {
6153        return ptr::null_mut();
6154    }
6155    let typ = (*val).type_;
6156    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
6157    if obj.is_null() {
6158        return ptr::null_mut();
6159    }
6160    (*obj).type_ = typ;
6161    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6162        let src_ns = (*val).nodesetval as *mut _xmlNodeSet;
6163        if !src_ns.is_null() {
6164            let nr = (*src_ns).nodeNr;
6165            let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
6166            if ns.is_null() {
6167                xmlFreeImpl(obj as *mut c_void);
6168                return ptr::null_mut();
6169            }
6170            (*ns).nodeNr = nr;
6171            (*ns).nodeMax = nr;
6172            if nr > 0 && !(*src_ns).nodeTab.is_null() {
6173                let tab = xmlMallocImpl((nr as usize) * core::mem::size_of::<*mut _xmlNode>())
6174                    as *mut *mut _xmlNode;
6175                if tab.is_null() {
6176                    xmlFreeImpl(ns as *mut c_void);
6177                    xmlFreeImpl(obj as *mut c_void);
6178                    return ptr::null_mut();
6179                }
6180                ptr::copy_nonoverlapping((*src_ns).nodeTab, tab, nr as usize);
6181                (*ns).nodeTab = tab;
6182            } else {
6183                (*ns).nodeTab = ptr::null_mut();
6184            }
6185            (*obj).nodesetval = ns as *mut c_void;
6186        }
6187    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
6188        (*obj).boolval = (*val).boolval;
6189    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
6190        (*obj).floatval = (*val).floatval;
6191    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
6192        let src = (*val).stringval;
6193        if !src.is_null() {
6194            let len = libc::strlen(src as *const libc::c_char);
6195            let buf = xmlMallocImpl(len + 1) as *mut xmlChar;
6196            if !buf.is_null() {
6197                ptr::copy_nonoverlapping(src, buf, len);
6198                *buf.add(len) = 0;
6199            }
6200            (*obj).stringval = buf;
6201        }
6202    }
6203    obj
6204}
6205
6206/// Cast an XPath object to its string value.
6207///
6208/// Returns a newly allocated string (caller frees with `xmlFree`).
6209///
6210/// # UPSTREAM-PARITY
6211///
6212/// ```c
6213/// xmlChar *xmlXPathCastToString(xmlXPathObjectPtr val);
6214/// ```
6215#[no_mangle]
6216pub unsafe extern "C" fn xmlXPathCastToString(val: *mut _xmlXPathObject) -> *mut xmlChar {
6217    if val.is_null() {
6218        return ptr::null_mut();
6219    }
6220    let typ = (*val).type_;
6221    let mut result: Vec<u8> = Vec::new();
6222    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
6223        if !(*val).stringval.is_null() {
6224            let len = libc::strlen((*val).stringval as *const libc::c_char);
6225            result.extend_from_slice(core::slice::from_raw_parts((*val).stringval, len));
6226        }
6227    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
6228        // Number → string conversion per XPath 1.0 §4.2:
6229        // - NaN → "NaN"
6230        // - +0/-0 → "0"
6231        // - infinity → "Infinity" / "-Infinity"
6232        // - integer → decimal representation without exponent
6233        let n = (*val).floatval;
6234        result.extend_from_slice(xml_number_to_string(n).as_bytes());
6235    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
6236        result.extend_from_slice(if (*val).boolval != 0 {
6237            b"true"
6238        } else {
6239            b"false"
6240        });
6241    } else if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6242        // String value of a node-set is the string value of the first node
6243        // in document order (or empty if empty).
6244        let ns = (*val).nodesetval as *mut _xmlNodeSet;
6245        if !ns.is_null() && (*ns).nodeNr > 0 && !(*ns).nodeTab.is_null() {
6246            let node = *(*ns).nodeTab;
6247            if !node.is_null() {
6248                let content = crate::xml::tree::node_get_content(node);
6249                if !content.is_null() {
6250                    let len = libc::strlen(content as *const libc::c_char);
6251                    result.extend_from_slice(core::slice::from_raw_parts(content, len));
6252                    xmlFreeImpl(content as *mut c_void);
6253                }
6254            }
6255        }
6256    }
6257    // Allocate the C string.
6258    let buf = xmlMallocImpl(result.len() + 1) as *mut xmlChar;
6259    if buf.is_null() {
6260        return ptr::null_mut();
6261    }
6262    if !result.is_empty() {
6263        ptr::copy_nonoverlapping(result.as_ptr(), buf, result.len());
6264    }
6265    *buf.add(result.len()) = 0;
6266    buf
6267}
6268
6269/// Convert an XPath number to its string representation (XPath 1.0 §4.2).
6270///
6271/// Canonical implementation lives in `crate::xml::xpath::types::number_to_string`
6272/// (a port of upstream `xmlXPathCastNumberToString` / `xmlXPathFormatNumber`,
6273/// R-000166); this ABI helper delegates so every number→string conversion
6274/// shares exactly one oracle-verified code path.
6275pub fn xml_number_to_string(n: f64) -> String {
6276    crate::xml::xpath::types::number_to_string(n)
6277}
6278
6279/// Port of upstream xpath.c `xmlXPathStringEvalNumber` (R-000166): see
6280/// `crate::xml::xpath::types::string_bytes_to_number` — the oracle
6281/// accumulates digits directly, caps the fraction at MAX_FRAC=20 digits
6282/// after any leading zeros, applies the exponent with `pow(10.0, exp)`
6283/// (underflowing to 0 below the smallest subnormal), accepts XML whitespace
6284/// around the number, and returns NaN for anything else — including a
6285/// leading '+'.
6286fn xpath_string_eval_number(bytes: &[u8]) -> f64 {
6287    crate::xml::xpath::types::string_bytes_to_number(bytes)
6288}
6289
6290/// Cast a C string to a number per XPath 1.0 §4.2 conversion rules.
6291///
6292/// # UPSTREAM-PARITY
6293///
6294/// ```c
6295/// double xmlXPathCastStringToNumber(const xmlChar *val);
6296/// ```
6297#[no_mangle]
6298pub unsafe extern "C" fn xmlXPathCastStringToNumber(val: *const xmlChar) -> f64 {
6299    if val.is_null() {
6300        return f64::NAN;
6301    }
6302    let len = libc::strlen(val as *const libc::c_char);
6303    let bytes = core::slice::from_raw_parts(val, len);
6304    xpath_string_eval_number(bytes)
6305}
6306
6307/// Compare two nodes in document order.
6308///
6309/// UPSTREAM-PARITY (xpath.c `xmlXPathCmpNodes`): returns **1** when
6310/// `node1` precedes `node2` in document order, **-1** when `node1` follows
6311/// `node2`, 0 for the same node, and -2 for NULL or cross-document
6312/// comparisons. The sign convention was verified against the system oracle
6313/// (libxml2 2.15.3): `xmlXPathCmpNodes(book1, book2)` returns 1.
6314///
6315/// # UPSTREAM-PARITY
6316///
6317/// ```c
6318/// int xmlXPathCmpNodes(xmlNodePtr node1, xmlNodePtr node2);
6319/// ```
6320#[no_mangle]
6321pub unsafe extern "C" fn xmlXPathCmpNodes(node1: *mut _xmlNode, node2: *mut _xmlNode) -> c_int {
6322    if node1.is_null() || node2.is_null() {
6323        return -2;
6324    }
6325    if node1 == node2 {
6326        return 0;
6327    }
6328    // Build ancestor chains.
6329    let mut chain1: Vec<*mut _xmlNode> = Vec::new();
6330    let mut chain2: Vec<*mut _xmlNode> = Vec::new();
6331    let mut n = node1;
6332    while !n.is_null() {
6333        chain1.push(n);
6334        n = (*n).parent;
6335    }
6336    let mut n = node2;
6337    while !n.is_null() {
6338        chain2.push(n);
6339        n = (*n).parent;
6340    }
6341    // Distinct documents (or entities) case.
6342    if chain1[chain1.len() - 1] != chain2[chain2.len() - 1] {
6343        return -2;
6344    }
6345    // Find the nearest common ancestor.
6346    let mut i = chain1.len();
6347    let mut j = chain2.len();
6348    while i > 0 && j > 0 && chain1[i - 1] == chain2[j - 1] {
6349        i -= 1;
6350        j -= 1;
6351    }
6352    // node1 is an ancestor of node2 -> node1 precedes it -> 1.
6353    if i == 0 {
6354        return 1;
6355    }
6356    // node2 is an ancestor of node1 -> node1 follows it -> -1.
6357    if j == 0 {
6358        return -1;
6359    }
6360    // Compare sibling order at the divergence point.
6361    let mut a = chain1[i - 1];
6362    let mut b = chain2[j - 1];
6363    // Climb to the same level.
6364    while !a.is_null() && !b.is_null() {
6365        let pa = (*a).parent;
6366        let pb = (*b).parent;
6367        if pa == pb {
6368            break;
6369        }
6370        a = pa;
6371        b = pb;
6372    }
6373    // Walk forward from the first child of the common parent.
6374    let parent = (*a).parent;
6375    let mut child = if parent.is_null() {
6376        ptr::null_mut()
6377    } else {
6378        (*parent).children
6379    };
6380    while !child.is_null() {
6381        if child == a {
6382            return 1; // a precedes b
6383        }
6384        if child == b {
6385            return -1; // b precedes a
6386        }
6387        child = (*child).next;
6388    }
6389    0
6390}
6391
6392/// Create a node-set from a range of an existing node-set.
6393///
6394/// # UPSTREAM-PARITY
6395///
6396/// ```c
6397/// xmlNodeSetPtr xmlXPathNodeSetCreate(xmlNodePtr val);
6398/// ```
6399///
6400/// With a null `val`, creates an empty node-set.
6401#[no_mangle]
6402pub unsafe extern "C" fn xmlXPathNodeSetCreate(val: *mut _xmlNode) -> *mut _xmlNodeSet {
6403    let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
6404    if ns.is_null() {
6405        return ptr::null_mut();
6406    }
6407    if val.is_null() {
6408        return ns;
6409    }
6410    let tab = xmlMallocImpl(core::mem::size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
6411    if tab.is_null() {
6412        xmlFreeImpl(ns as *mut c_void);
6413        return ptr::null_mut();
6414    }
6415    *tab = val;
6416    (*ns).nodeTab = tab;
6417    (*ns).nodeNr = 1;
6418    (*ns).nodeMax = 1;
6419    ns
6420}
6421
6422/// Free a node-set allocated by `xmlXPathNodeSetCreate` or a node-set
6423/// builder in this library.
6424///
6425/// Frees the node-set structure and its node table; the nodes themselves
6426/// are owned by their document and are not freed.
6427///
6428/// # UPSTREAM-PARITY
6429///
6430/// ```c
6431/// void xmlXPathFreeNodeSet(xmlNodeSetPtr ns);
6432/// ```
6433#[no_mangle]
6434pub unsafe extern "C" fn xmlXPathFreeNodeSet(ns: *mut _xmlNodeSet) {
6435    if ns.is_null() {
6436        return;
6437    }
6438    if !(*ns).nodeTab.is_null() {
6439        xmlFreeImpl((*ns).nodeTab as *mut c_void);
6440        (*ns).nodeTab = ptr::null_mut();
6441    }
6442    (*ns).nodeNr = 0;
6443    (*ns).nodeMax = 0;
6444    xmlFreeImpl(ns as *mut c_void);
6445}
6446
6447/// Compile an XPath expression.
6448///
6449/// Returns an opaque pointer that can be passed to `xmlXPathEvalExpression`
6450/// (via the compiled-expr infrastructure) or freed with `xmlXPathFreeCompExpr`.
6451///
6452/// # UPSTREAM-PARITY
6453///
6454/// ```c
6455/// xmlXPathCompExprPtr xmlXPathCompile(const xmlChar *str);
6456/// ```
6457#[no_mangle]
6458pub unsafe extern "C" fn xmlXPathCompile(str_: *const xmlChar) -> *mut c_void {
6459    if str_.is_null() {
6460        return ptr::null_mut();
6461    }
6462    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
6463        Ok(s) => s,
6464        Err(_) => return ptr::null_mut(),
6465    };
6466
6467    match crate::xml::xpath::compile_result(expr_str) {
6468        Ok(compiled) => {
6469            let mut map = COMPILED_EXPRS.lock();
6470            let mut counter = NEXT_COMPILED_KEY.lock();
6471            let key = *counter;
6472            *counter += 1;
6473            map.insert(key, Box::new(compiled));
6474            key as *mut c_void
6475        }
6476        Err(e) => {
6477            // UPSTREAM-PARITY (xpath.c xmlXPathErrFmt): a failed compile
6478            // reports "XPath error : Invalid expression\n" plus the
6479            // expression and a caret at the error offset through the generic
6480            // channel (HOSTILE-FAILURE F3).
6481            let msg_cstr = std::ffi::CString::new("Invalid expression\n").unwrap_or_default();
6482            let expr_cstr = std::ffi::CString::new(expr_str).unwrap_or_default();
6483            let off = e.pos;
6484            let window = if off < 100 && off < expr_str.len() {
6485                Some((expr_str.as_bytes(), off))
6486            } else {
6487                None
6488            };
6489            unsafe {
6490                crate::xml::errors::raise_error_streamed(
6491                    ptr::null_mut(),
6492                    crate::abi::types::XML_FROM_XPATH,
6493                    crate::abi::types::XPATH_EXPR_ERROR,
6494                    crate::abi::types::xmlErrorLevel::XML_ERR_ERROR as c_int,
6495                    ptr::null(),
6496                    0,
6497                    0,
6498                    expr_cstr.as_ptr(),
6499                    ptr::null(),
6500                    ptr::null(),
6501                    off as c_int,
6502                    msg_cstr.as_ptr(),
6503                    window,
6504                    None,
6505                    crate::xml::errors::GenericDelivery::Stream,
6506                    None,
6507                );
6508            }
6509            ptr::null_mut()
6510        }
6511    }
6512}
6513
6514/// Free a compiled XPath expression.
6515///
6516/// # UPSTREAM-PARITY
6517///
6518/// ```c
6519/// void xmlXPathFreeCompExpr(xmlXPathCompExprPtr comp);
6520/// ```
6521#[no_mangle]
6522pub unsafe extern "C" fn xmlXPathFreeCompExpr(comp: *mut c_void) {
6523    if comp.is_null() {
6524        return;
6525    }
6526    let mut map = COMPILED_EXPRS.lock();
6527    map.remove(&(comp as u64));
6528}
6529
6530/// Register an XPath namespace.
6531///
6532/// # UPSTREAM-PARITY
6533///
6534/// ```c
6535/// int xmlXPathRegisterNs(xmlXPathContextPtr ctxt,
6536///                        const xmlChar *prefix, const xmlChar *ns_uri);
6537/// ```
6538#[no_mangle]
6539pub unsafe extern "C" fn xmlXPathRegisterNs(
6540    ctxt: *mut _xmlXPathContext,
6541    prefix: *const xmlChar,
6542    ns_uri: *const xmlChar,
6543) -> c_int {
6544    if ctxt.is_null() || prefix.is_null() || ns_uri.is_null() {
6545        return -1;
6546    }
6547    let internal = (*ctxt).extra as *mut XPathContext;
6548    if internal.is_null() {
6549        return -1;
6550    }
6551    let internal = &mut *internal;
6552
6553    let prefix_str = match CStr::from_ptr(prefix as *const c_char).to_str() {
6554        Ok(s) => s,
6555        Err(_) => return -1,
6556    };
6557    let uri_str = match CStr::from_ptr(ns_uri as *const c_char).to_str() {
6558        Ok(s) => s,
6559        Err(_) => return -1,
6560    };
6561
6562    internal.register_namespace(prefix_str, uri_str);
6563
6564    // Mirror the registration into the C context's nsHash (Box<HashMap<
6565    // String, CString>>): xmlXPathNsLookup hands out pointers into these
6566    // owned C strings, matching upstream ownership (strdup'd in nsHash,
6567    // freed by xmlXPathRegisteredNsCleanup / xmlXPathFreeContext).
6568    let map: &mut HashMap<String, CString> = if (*ctxt).nsHash.is_null() {
6569        let b: Box<HashMap<String, CString>> = Box::default();
6570        (*ctxt).nsHash = Box::into_raw(b) as *mut c_void;
6571        &mut *((*ctxt).nsHash as *mut HashMap<String, CString>)
6572    } else {
6573        &mut *((*ctxt).nsHash as *mut HashMap<String, CString>)
6574    };
6575    map.insert(
6576        prefix_str.to_string(),
6577        CString::new(uri_str.as_bytes()).unwrap_or_default(),
6578    );
6579    0
6580}
6581
6582/// Register an XPath function.
6583///
6584/// The C function pointer is stored in a side table keyed by the context.
6585/// A Rust-side stub is registered in the internal context so that the Rust
6586/// evaluator is aware of the function; however, calling the C function
6587/// directly from the Rust evaluator is not yet supported.
6588///
6589/// # UPSTREAM-PARITY
6590///
6591/// ```c
6592/// int xmlXPathRegisterFunc(xmlXPathContextPtr ctxt,
6593///                          const xmlChar *name, xmlXPathFunction f);
6594/// ```
6595#[no_mangle]
6596pub unsafe extern "C" fn xmlXPathRegisterFunc(
6597    ctxt: *mut _xmlXPathContext,
6598    name: *const xmlChar,
6599    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
6600) -> c_int {
6601    if ctxt.is_null() || name.is_null() {
6602        return -1;
6603    }
6604    let internal = (*ctxt).extra as *mut XPathContext;
6605    if internal.is_null() {
6606        return -1;
6607    }
6608    let internal = &mut *internal;
6609
6610    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
6611        Ok(s) => s,
6612        Err(_) => return -1,
6613    };
6614
6615    if let Some(func) = f {
6616        // Store the C function pointer in the side table.
6617        let key = (SendSyncPtr((*ctxt).extra), name_str.to_string());
6618        C_FUNCTIONS.lock().insert(key, func);
6619        // Register a Rust closure that bridges to the C function through a
6620        // synthesized xmlXPathParserContext (upstream function-call ABI).
6621        let c_ctxt = SendSyncPtr(ctxt as *mut c_void);
6622        let name_owned = name_str.to_string();
6623        internal.register_function(name_str, c_func_bridge_closure(c_ctxt, name_owned));
6624    }
6625    0
6626}
6627
6628/// Register an XPath function with namespace.
6629///
6630/// # UPSTREAM-PARITY
6631///
6632/// ```c
6633/// int xmlXPathRegisterFuncNS(xmlXPathContextPtr ctxt,
6634///                            const xmlChar *name, const xmlChar *ns_uri,
6635///                            xmlXPathFunction f);
6636/// ```
6637#[no_mangle]
6638pub unsafe extern "C" fn xmlXPathRegisterFuncNS(
6639    ctxt: *mut _xmlXPathContext,
6640    name: *const xmlChar,
6641    ns_uri: *const xmlChar,
6642    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
6643) -> c_int {
6644    if ctxt.is_null() || name.is_null() {
6645        return -1;
6646    }
6647    let internal = (*ctxt).extra as *mut XPathContext;
6648    if internal.is_null() {
6649        return -1;
6650    }
6651    let internal = &mut *internal;
6652
6653    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
6654        Ok(s) => s,
6655        Err(_) => return -1,
6656    };
6657    let ns_str = if ns_uri.is_null() {
6658        String::new()
6659    } else {
6660        match CStr::from_ptr(ns_uri as *const c_char).to_str() {
6661            Ok(s) => s.to_string(),
6662            Err(_) => return -1,
6663        }
6664    };
6665
6666    // Use "{ns}:" prefix as part of the key to keep functions unique.
6667    let qualified = if ns_str.is_empty() {
6668        name_str.to_string()
6669    } else {
6670        format!("{{{}}}{}", ns_str, name_str)
6671    };
6672
6673    if let Some(func) = f {
6674        let key = (SendSyncPtr((*ctxt).extra), qualified.clone());
6675        C_FUNCTIONS.lock().insert(key, func);
6676        let c_ctxt = SendSyncPtr(ctxt as *mut c_void);
6677        let qualified_owned = qualified.clone();
6678        internal.register_function(&qualified, c_func_bridge_closure(c_ctxt, qualified_owned));
6679    }
6680    0
6681}
6682
6683/// Register an XPath variable.
6684///
6685/// # UPSTREAM-PARITY
6686///
6687/// ```c
6688/// int xmlXPathRegisterVariable(xmlXPathContextPtr ctxt,
6689///                              const xmlChar *name, xmlXPathObjectPtr value);
6690/// ```
6691#[no_mangle]
6692pub unsafe extern "C" fn xmlXPathRegisterVariable(
6693    ctxt: *mut _xmlXPathContext,
6694    name: *const xmlChar,
6695    value: *mut _xmlXPathObject,
6696) -> c_int {
6697    if ctxt.is_null() || name.is_null() || value.is_null() {
6698        return -1;
6699    }
6700    let internal = (*ctxt).extra as *mut XPathContext;
6701    if internal.is_null() {
6702        return -1;
6703    }
6704    let internal = &mut *internal;
6705
6706    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
6707        Ok(s) => s,
6708        Err(_) => return -1,
6709    };
6710
6711    let xpath_val = object_to_xpathvalue(value);
6712    internal.register_variable(name_str, xpath_val);
6713    0
6714}
6715
6716/// Create an XPath object wrapping a single node in a node-set.
6717///
6718/// # UPSTREAM-PARITY
6719///
6720/// ```c
6721/// xmlXPathObjectPtr xmlXPathNewNodeSet(xmlNodePtr val);
6722/// ```
6723#[no_mangle]
6724pub unsafe extern "C" fn xmlXPathNewNodeSet(val: *mut _xmlNode) -> *mut _xmlXPathObject {
6725    let ns = if val.is_null() {
6726        NodeSet::new()
6727    } else {
6728        NodeSet::singleton(val)
6729    };
6730    xpath_to_object(XPathValue::NodeSet(ns))
6731}
6732
6733/// Create an XPath object from a C string value.
6734///
6735/// # UPSTREAM-PARITY
6736///
6737/// ```c
6738/// xmlXPathObjectPtr xmlXPathNewCString(const xmlChar *val);
6739/// ```
6740#[no_mangle]
6741pub unsafe extern "C" fn xmlXPathNewCString(val: *const xmlChar) -> *mut _xmlXPathObject {
6742    if val.is_null() {
6743        return xpath_to_object(XPathValue::String(String::new()));
6744    }
6745    let s = match CStr::from_ptr(val as *const c_char).to_str() {
6746        Ok(s) => s.to_string(),
6747        Err(_) => return ptr::null_mut(),
6748    };
6749    xpath_to_object(XPathValue::String(s))
6750}
6751
6752/// Create an XPath number object.
6753///
6754/// # UPSTREAM-PARITY
6755///
6756/// ```c
6757/// xmlXPathObjectPtr xmlXPathNewFloat(double val);
6758/// ```
6759#[no_mangle]
6760pub extern "C" fn xmlXPathNewFloat(val: f64) -> *mut _xmlXPathObject {
6761    unsafe { xpath_to_object(XPathValue::Number(val)) }
6762}
6763
6764/// Create an XPath boolean object.
6765///
6766/// # UPSTREAM-PARITY
6767///
6768/// ```c
6769/// xmlXPathObjectPtr xmlXPathNewBoolean(int val);
6770/// ```
6771#[no_mangle]
6772pub extern "C" fn xmlXPathNewBoolean(val: c_int) -> *mut _xmlXPathObject {
6773    unsafe { xpath_to_object(XPathValue::Boolean(val != 0)) }
6774}
6775
6776// ═══════════════════════════════════════════════════════════════════════════════
6777// 14.5. XPointer
6778// ═══════════════════════════════════════════════════════════════════════════════
6779
6780/// Evaluate an XPointer expression.
6781///
6782/// Delegates to the xpointer module.
6783///
6784/// # UPSTREAM-PARITY
6785///
6786/// ```c
6787/// xmlNodePtr xmlXPtrEval(const xmlChar *expr, xmlDocPtr doc);
6788/// ```
6789#[no_mangle]
6790pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
6791    crate::xml::xpointer::xmlXPtrEval(expr, doc)
6792}
6793
6794// ═══════════════════════════════════════════════════════════════════════════════
6795// 15. XInclude
6796// ═══════════════════════════════════════════════════════════════════════════════
6797
6798/// Process XInclude nodes in a document.
6799///
6800/// # UPSTREAM-PARITY
6801///
6802/// ```c
6803/// int xmlXIncludeProcess(xmlDocPtr doc);
6804/// ```
6805#[no_mangle]
6806pub unsafe extern "C" fn xmlXIncludeProcess(doc: *mut _xmlDoc) -> c_int {
6807    crate::xml::xinclude::xinclude_process(doc)
6808}
6809
6810/// Process XInclude nodes with flags.
6811///
6812/// # UPSTREAM-PARITY
6813///
6814/// ```c
6815/// int xmlXIncludeProcessFlags(xmlDocPtr doc, int flags);
6816/// ```
6817#[no_mangle]
6818pub unsafe extern "C" fn xmlXIncludeProcessFlags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
6819    crate::xml::xinclude::xinclude_process_flags(doc, flags)
6820}
6821
6822// ═══════════════════════════════════════════════════════════════════════════════
6823// 16. Catalog
6824// ═══════════════════════════════════════════════════════════════════════════════
6825
6826/// Load a catalog.
6827///
6828/// # UPSTREAM-PARITY
6829///
6830/// ```c
6831/// xmlCatalogPtr xmlCatalogLoad(const char *catalogs);
6832/// ```
6833#[no_mangle]
6834pub extern "C" fn xmlCatalogLoad(catalogs: *const c_char) -> *mut c_void {
6835    if catalogs.is_null() {
6836        return ptr::null_mut();
6837    }
6838    crate::xml::catalog::load_catalog(catalogs)
6839}
6840
6841/// Resolve a public ID.
6842///
6843/// # UPSTREAM-PARITY
6844///
6845/// ```c
6846/// xmlCharPtr xmlCatalogResolvePublic(const xmlChar *pubID);
6847/// ```
6848#[no_mangle]
6849pub unsafe extern "C" fn xmlCatalogResolvePublic(pubID: *const xmlChar) -> *mut xmlChar {
6850    if pubID.is_null() {
6851        return ptr::null_mut();
6852    }
6853    crate::xml::catalog::resolve_public(pubID)
6854}
6855
6856/// Resolve a system ID.
6857///
6858/// # UPSTREAM-PARITY
6859///
6860/// ```c
6861/// xmlCharPtr xmlCatalogResolveSystem(const xmlChar *sysID);
6862/// ```
6863#[no_mangle]
6864pub unsafe extern "C" fn xmlCatalogResolveSystem(sysID: *const xmlChar) -> *mut xmlChar {
6865    if sysID.is_null() {
6866        return ptr::null_mut();
6867    }
6868    crate::xml::catalog::resolve_system(sysID)
6869}
6870
6871/// Resolve a URI.
6872///
6873/// # UPSTREAM-PARITY
6874///
6875/// ```c
6876/// xmlCharPtr xmlCatalogResolveURI(const xmlChar *URI);
6877/// ```
6878#[no_mangle]
6879pub unsafe extern "C" fn xmlCatalogResolveURI(URI: *const xmlChar) -> *mut xmlChar {
6880    if URI.is_null() {
6881        return ptr::null_mut();
6882    }
6883    crate::xml::catalog::resolve_uri(URI)
6884}
6885
6886/// Set catalog defaults.
6887///
6888/// # UPSTREAM-PARITY
6889///
6890/// ```c
6891/// void xmlCatalogSetDefaults(xmlCatalogAllowValue allow);
6892/// ```
6893#[no_mangle]
6894pub extern "C" fn xmlCatalogSetDefaults(allow: c_int) {
6895    crate::xml::catalog::set_defaults(allow)
6896}
6897
6898/// Get catalog defaults.
6899///
6900/// # UPSTREAM-PARITY
6901///
6902/// ```c
6903/// xmlCatalogAllowValue xmlCatalogGetDefaults(void);
6904/// ```
6905#[no_mangle]
6906pub extern "C" fn xmlCatalogGetDefaults() -> c_int {
6907    crate::xml::catalog::get_defaults()
6908}
6909
6910/// Add a catalog.
6911///
6912/// # UPSTREAM-PARITY
6913///
6914/// ```c
6915/// int xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace);
6916/// ```
6917#[no_mangle]
6918pub unsafe extern "C" fn xmlCatalogAdd(
6919    type_: *const xmlChar,
6920    orig: *const xmlChar,
6921    replace: *const xmlChar,
6922) -> c_int {
6923    if type_.is_null() || orig.is_null() || replace.is_null() {
6924        return -1;
6925    }
6926    crate::xml::catalog::add(type_, orig, replace)
6927}
6928
6929/// Remove a catalog entry.
6930///
6931/// # UPSTREAM-PARITY
6932///
6933/// ```c
6934/// int xmlCatalogRemove(const xmlChar *value);
6935/// ```
6936#[no_mangle]
6937pub unsafe extern "C" fn xmlCatalogRemove(value: *const xmlChar) -> c_int {
6938    if value.is_null() {
6939        return 0;
6940    }
6941    crate::xml::catalog::remove(value)
6942}
6943
6944/// Dump the catalog in XML format to a FILE* (upstream catalog.h:
6945/// 1-argument form — R-000176, the candidate previously took a spurious
6946/// second `xmlCatalogPtr` argument).
6947///
6948/// # UPSTREAM-PARITY
6949///
6950/// ```c
6951/// void xmlCatalogDump(FILE *out);
6952/// ```
6953#[no_mangle]
6954pub unsafe extern "C" fn xmlCatalogDump(output: *mut c_void) {
6955    if output.is_null() {
6956        return;
6957    }
6958    let doc = crate::xml::catalog::dump_doc();
6959    if doc.is_null() {
6960        return;
6961    }
6962    let mut mem: *mut xmlChar = ptr::null_mut();
6963    let mut size: c_int = 0;
6964    crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
6965    if !mem.is_null() {
6966        libc::fwrite(
6967            mem as *const c_void,
6968            1,
6969            size as usize,
6970            output as *mut libc::FILE,
6971        );
6972        xmlFreeImpl(mem as *mut c_void);
6973    }
6974    crate::xml::tree::free_doc(doc);
6975}
6976
6977/// Save the catalog to a file (upstream `xmlCatalogSave`).
6978///
6979/// Returns 0 on success, -1 on failure.
6980///
6981/// # UPSTREAM-PARITY
6982///
6983/// ```c
6984/// int xmlCatalogSave(const char *filename);
6985/// ```
6986#[no_mangle]
6987pub unsafe extern "C" fn xmlCatalogSave(filename: *const c_char) -> c_int {
6988    if filename.is_null() {
6989        return -1;
6990    }
6991    let doc = crate::xml::catalog::dump_doc();
6992    if doc.is_null() {
6993        return -1;
6994    }
6995    let mut mem: *mut xmlChar = ptr::null_mut();
6996    let mut size: c_int = 0;
6997    crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
6998    let mut ret: c_int = -1;
6999    if !mem.is_null() {
7000        let fp = libc::fopen(filename, c"w".as_ptr() as *const c_char);
7001        if !fp.is_null() {
7002            let written = libc::fwrite(mem as *const c_void, 1, size as usize, fp);
7003            ret = if written == size as usize { 0 } else { -1 };
7004            libc::fclose(fp);
7005        }
7006        xmlFreeImpl(mem as *mut c_void);
7007    }
7008    crate::xml::tree::free_doc(doc);
7009    ret
7010}
7011
7012/// Clean up the catalog subsystem.
7013///
7014/// # UPSTREAM-PARITY
7015///
7016/// ```c
7017/// void xmlCatalogCleanup(void);
7018/// ```
7019#[no_mangle]
7020pub extern "C" fn xmlCatalogCleanup() {
7021    crate::xml::catalog::cleanup();
7022}
7023
7024/// Convert all the SGML catalog entries as XML ones (upstream catalog.c:
7025/// returns 0 on success, -1 otherwise — R-000176, the candidate previously
7026/// returned a dump document).
7027///
7028/// # UPSTREAM-PARITY
7029///
7030/// ```c
7031/// int xmlCatalogConvert(void);
7032/// ```
7033///
7034/// The candidate's catalog stores XML-flavored entries natively (there is no
7035/// separate SGML table to convert), so once the catalog is initialized the
7036/// conversion succeeds as a no-op, matching upstream's successful-return
7037/// contract.
7038#[no_mangle]
7039pub extern "C" fn xmlCatalogConvert() -> c_int {
7040    if !crate::xml::catalog::is_initialized() {
7041        return -1;
7042    }
7043    0
7044}
7045
7046// ═══════════════════════════════════════════════════════════════════════════════
7047// 17. HTML
7048// ═══════════════════════════════════════════════════════════════════════════════
7049
7050/// Parse an HTML document from a file.
7051///
7052/// # UPSTREAM-PARITY
7053///
7054/// ```c
7055/// htmlDocPtr htmlParseFile(const char *filename, const char *encoding);
7056/// ```
7057#[no_mangle]
7058pub const unsafe extern "C" fn htmlParseFile(
7059    _filename: *const c_char,
7060    _encoding: *const c_char,
7061) -> *mut _xmlDoc {
7062    // Phase 1: STUB
7063    ptr::null_mut()
7064}
7065
7066/// Parse an HTML document from memory.
7067///
7068/// # UPSTREAM-PARITY
7069///
7070/// ```c
7071/// htmlDocPtr htmlParseMemory(const char *buffer, int size);
7072/// ```
7073#[no_mangle]
7074pub const unsafe extern "C" fn htmlParseMemory(
7075    _buffer: *const c_char,
7076    _size: c_int,
7077) -> *mut _xmlDoc {
7078    // Phase 1: STUB
7079    ptr::null_mut()
7080}
7081
7082/// Parse an HTML document from a document string.
7083///
7084/// # UPSTREAM-PARITY
7085///
7086/// ```c
7087/// htmlDocPtr htmlParseDoc(const xmlChar *cur, const char *encoding);
7088/// ```
7089#[no_mangle]
7090pub const unsafe extern "C" fn htmlParseDoc(
7091    _cur: *const xmlChar,
7092    _encoding: *const c_char,
7093) -> *mut _xmlDoc {
7094    // Phase 1: STUB
7095    ptr::null_mut()
7096}
7097
7098/// Create an HTML parser context.
7099///
7100/// # UPSTREAM-PARITY
7101///
7102/// ```c
7103/// htmlParserCtxtPtr htmlCreateFileParserCtxt(const char *filename,
7104///                                            const char *encoding);
7105/// ```
7106#[no_mangle]
7107pub const unsafe extern "C" fn htmlCreateFileParserCtxt(
7108    _filename: *const c_char,
7109    _encoding: *const c_char,
7110) -> *mut c_void {
7111    // Phase 1: STUB
7112    ptr::null_mut()
7113}
7114
7115/// Free an HTML parser context.
7116///
7117/// # UPSTREAM-PARITY
7118///
7119/// ```c
7120/// void htmlFreeParserCtxt(htmlParserCtxtPtr ctxt);
7121/// ```
7122#[no_mangle]
7123pub extern "C" fn htmlFreeParserCtxt(ctxt: *mut c_void) {
7124    unsafe { crate::xml::html::free_parser_ctxt(ctxt) }
7125}
7126
7127/// Initialize the HTML parser.
7128///
7129/// # UPSTREAM-PARITY
7130///
7131/// ```c
7132/// void htmlInitParser(void);
7133/// ```
7134#[no_mangle]
7135pub const extern "C" fn htmlInitParser() {
7136    // Phase 1: STUB
7137}
7138
7139/// Clean up the HTML parser.
7140///
7141/// # UPSTREAM-PARITY
7142///
7143/// ```c
7144/// void htmlCleanupParser(void);
7145/// ```
7146#[no_mangle]
7147pub const extern "C" fn htmlCleanupParser() {
7148    // Phase 1: STUB
7149}
7150
7151// ═══════════════════════════════════════════════════════════════════════════════
7152// 17.5. Validation (DTD)
7153// ═══════════════════════════════════════════════════════════════════════════════
7154
7155/// Create a new validation context.
7156///
7157/// # UPSTREAM-PARITY
7158///
7159/// ```c
7160/// xmlValidCtxtPtr xmlNewValidCtxt(void);
7161/// ```
7162#[no_mangle]
7163pub unsafe extern "C" fn xmlNewValidCtxt() -> *mut _xmlValidCtxt {
7164    crate::xml::validation::new_valid_ctxt()
7165}
7166
7167/// Free a validation context.
7168///
7169/// # UPSTREAM-PARITY
7170///
7171/// ```c
7172/// void xmlFreeValidCtxt(xmlValidCtxtPtr ctxt);
7173/// ```
7174#[no_mangle]
7175pub unsafe extern "C" fn xmlFreeValidCtxt(ctxt: *mut _xmlValidCtxt) {
7176    crate::xml::validation::free_valid_ctxt(ctxt);
7177}
7178
7179/// Set error and warning callbacks on a validation context.
7180///
7181/// # UPSTREAM-PARITY
7182///
7183/// ```c
7184/// void xmlSetValidErrors(xmlValidCtxtPtr ctxt,
7185///                        xmlGenericErrorFunc err,
7186///                        xmlGenericErrorFunc warn,
7187///                        void *data);
7188/// ```
7189#[no_mangle]
7190pub unsafe extern "C" fn xmlSetValidErrors(
7191    ctxt: *mut _xmlValidCtxt,
7192    err: Option<xmlGenericErrorFunc>,
7193    warn: Option<xmlGenericErrorFunc>,
7194    data: *mut c_void,
7195) {
7196    crate::xml::validation::set_valid_errors(ctxt, err, warn, data);
7197}
7198
7199/// Validate a document against its DTD.
7200///
7201/// # UPSTREAM-PARITY
7202///
7203/// ```c
7204/// int xmlValidateDocument(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7205/// ```
7206#[no_mangle]
7207pub unsafe extern "C" fn xmlValidateDocument(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
7208    crate::xml::validation::validate_document(ctxt, doc)
7209}
7210
7211/// Final validation pass (check ID/IDREF consistency).
7212///
7213/// # UPSTREAM-PARITY
7214///
7215/// ```c
7216/// int xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7217/// ```
7218#[no_mangle]
7219pub unsafe extern "C" fn xmlValidateDocumentFinal(
7220    ctxt: *mut _xmlValidCtxt,
7221    doc: *mut _xmlDoc,
7222) -> c_int {
7223    crate::xml::validation::validate_document_final(ctxt, doc)
7224}
7225
7226/// Validate an element node against its DTD declarations.
7227///
7228/// # UPSTREAM-PARITY
7229///
7230/// ```c
7231/// int xmlValidateElement(xmlValidCtxtPtr ctxt,
7232///                        xmlDocPtr doc,
7233///                        xmlNodePtr elem);
7234/// ```
7235#[no_mangle]
7236pub unsafe extern "C" fn xmlValidateElement(
7237    ctxt: *mut _xmlValidCtxt,
7238    doc: *mut _xmlDoc,
7239    elem: *mut _xmlNode,
7240) -> c_int {
7241    crate::xml::validation::validate_element(ctxt, doc, elem)
7242}
7243
7244/// Validate an attribute declaration.
7245///
7246/// # UPSTREAM-PARITY
7247///
7248/// ```c
7249/// int xmlValidateAttributeDecl(xmlValidCtxt *ctxt,
7250///                              xmlDoc *doc,
7251///                              xmlAttribute *attr);
7252/// ```
7253///
7254/// 11.1-Z.3 signature court: the pre-Z.3 candidate declared a fourth
7255/// `xmlNodePtr elem` argument that does not exist upstream (valid.h 2.15.3).
7256#[no_mangle]
7257pub unsafe extern "C" fn xmlValidateAttributeDecl(
7258    ctxt: *mut _xmlValidCtxt,
7259    doc: *mut _xmlDoc,
7260    attr: *mut _xmlAttribute,
7261) -> c_int {
7262    crate::xml::validation::validate_attribute_decl(ctxt, doc, attr)
7263}
7264
7265/// Validate an attribute value against its declared type.
7266///
7267/// # UPSTREAM-PARITY
7268///
7269/// ```c
7270/// int xmlValidateAttributeValue(int type, const xmlChar *value);
7271/// ```
7272#[no_mangle]
7273pub unsafe extern "C" fn xmlValidateAttributeValue(atype: c_int, value: *const xmlChar) -> c_int {
7274    crate::xml::validation::validate_attribute_value(atype, value)
7275}
7276
7277/// Validate a NOTATION reference.
7278///
7279/// # UPSTREAM-PARITY
7280///
7281/// ```c
7282/// int xmlValidateNotationUse(xmlValidCtxtPtr ctxt,
7283///                            xmlDocPtr doc,
7284///                            const xmlChar *notationName);
7285/// ```
7286#[no_mangle]
7287pub unsafe extern "C" fn xmlValidateNotationUse(
7288    ctxt: *mut _xmlValidCtxt,
7289    doc: *mut _xmlDoc,
7290    notation_name: *const xmlChar,
7291) -> c_int {
7292    crate::xml::validation::validate_notation_use(ctxt, doc, notation_name)
7293}
7294
7295/// Validate an ID value (check uniqueness).
7296///
7297/// # UPSTREAM-PARITY
7298///
7299/// ```c
7300/// int xmlValidateID(xmlValidCtxtPtr ctxt,
7301///                   xmlDocPtr doc,
7302///                   xmlNodePtr node,
7303///                   const xmlChar *value);
7304/// ```
7305#[no_mangle]
7306pub unsafe extern "C" fn xmlValidateID(
7307    ctxt: *mut _xmlValidCtxt,
7308    doc: *mut _xmlDoc,
7309    node: *mut _xmlNode,
7310    value: *const xmlChar,
7311) -> c_int {
7312    crate::xml::validation::validate_id(ctxt, doc, node, value)
7313}
7314
7315/// Validate an IDREF value (check it references a known ID; upstream
7316/// valid.h 2.15 has no `node` argument — R-000176).
7317///
7318/// # UPSTREAM-PARITY
7319///
7320/// ```c
7321/// int xmlValidateIDRef(xmlValidCtxt *ctxt, xmlDoc *doc,
7322///                      const xmlChar *value);
7323/// ```
7324#[no_mangle]
7325pub unsafe extern "C" fn xmlValidateIDRef(
7326    ctxt: *mut _xmlValidCtxt,
7327    doc: *mut _xmlDoc,
7328    value: *const xmlChar,
7329) -> c_int {
7330    crate::xml::validation::validate_id_ref(ctxt, doc, ptr::null_mut(), value)
7331}
7332
7333/// Validate IDREFS (whitespace-separated list of IDREFs; upstream valid.h
7334/// 2.15 has no `node` argument — R-000176).
7335///
7336/// # UPSTREAM-PARITY
7337///
7338/// ```c
7339/// int xmlValidateIDRefs(xmlValidCtxt *ctxt, xmlDoc *doc,
7340///                       const xmlChar *value);
7341/// ```
7342#[no_mangle]
7343pub unsafe extern "C" fn xmlValidateIDRefs(
7344    ctxt: *mut _xmlValidCtxt,
7345    doc: *mut _xmlDoc,
7346    value: *const xmlChar,
7347) -> c_int {
7348    crate::xml::validation::validate_id_refs(ctxt, doc, ptr::null_mut(), value)
7349}
7350
7351/// Validate an NCName value (modern 2-arg form, upstream tree.c).
7352///
7353/// # UPSTREAM-PARITY
7354///
7355/// ```c
7356/// int xmlValidateNCName(const xmlChar *value, int space);
7357/// ```
7358///
7359/// Returns -1 on NULL, 0 if valid, 1 if invalid.
7360#[no_mangle]
7361pub unsafe extern "C" fn xmlValidateNCName(value: *const xmlChar, space: c_int) -> c_int {
7362    crate::xml::validation::validate_ncname(value, space)
7363}
7364
7365/// Validate a QName value (modern 2-arg form, upstream tree.c).
7366///
7367/// # UPSTREAM-PARITY
7368///
7369/// ```c
7370/// int xmlValidateQName(const xmlChar *value, int space);
7371/// ```
7372#[no_mangle]
7373pub unsafe extern "C" fn xmlValidateQName(value: *const xmlChar, space: c_int) -> c_int {
7374    crate::xml::validation::validate_qname(value, space)
7375}
7376
7377/// Validate an XML Name value (modern 2-arg form, upstream tree.c).
7378///
7379/// # UPSTREAM-PARITY / HISTORICAL
7380///
7381/// Since libxml2 2.12 the DSO symbol carries a second `int space` parameter
7382/// with inverted return semantics (0 valid / 1 invalid / -1 NULL); the
7383/// pre-2.12 1-arg form no longer exists in the DSO. The candidate matches
7384/// the current oracle. (The 1-arg semantics live on as xmlValidateNameValue.)
7385///
7386/// ```c
7387/// int xmlValidateName(const xmlChar *value, int space);
7388/// ```
7389#[no_mangle]
7390pub unsafe extern "C" fn xmlValidateName(value: *const xmlChar, space: c_int) -> c_int {
7391    crate::xml::validation::validate_name_space(value, space)
7392}
7393
7394/// Validate an NMToken value (modern 2-arg form, upstream tree.c).
7395///
7396/// # UPSTREAM-PARITY
7397///
7398/// ```c
7399/// int xmlValidateNMToken(const xmlChar *value, int space);
7400/// ```
7401#[no_mangle]
7402pub unsafe extern "C" fn xmlValidateNMToken(value: *const xmlChar, space: c_int) -> c_int {
7403    crate::xml::validation::validate_nmtoken_space(value, space)
7404}
7405
7406/// Validate a Name value (1-arg form, upstream valid.c).
7407///
7408/// # UPSTREAM-PARITY
7409///
7410/// ```c
7411/// int xmlValidateNameValue(const xmlChar *value);
7412/// ```
7413///
7414/// Returns 1 if valid, 0 if not (NULL included).
7415#[no_mangle]
7416pub unsafe extern "C" fn xmlValidateNameValue(value: *const xmlChar) -> c_int {
7417    crate::xml::validation::validate_name_value(value)
7418}
7419
7420/// Validate a whitespace-separated list of Names (separator is exactly
7421/// 0x20, upstream erratum E20).
7422///
7423/// # UPSTREAM-PARITY
7424///
7425/// ```c
7426/// int xmlValidateNamesValue(const xmlChar *value);
7427/// ```
7428#[no_mangle]
7429pub unsafe extern "C" fn xmlValidateNamesValue(value: *const xmlChar) -> c_int {
7430    crate::xml::validation::validate_names_value(value)
7431}
7432
7433/// Validate an Nmtoken value (1-arg form, upstream valid.c).
7434///
7435/// # UPSTREAM-PARITY
7436///
7437/// ```c
7438/// int xmlValidateNmtokenValue(const xmlChar *value);
7439/// ```
7440#[no_mangle]
7441pub unsafe extern "C" fn xmlValidateNmtokenValue(value: *const xmlChar) -> c_int {
7442    crate::xml::validation::validate_nmtoken_value(value)
7443}
7444
7445/// Validate a whitespace-separated list of Nmtokens.
7446///
7447/// # UPSTREAM-PARITY
7448///
7449/// ```c
7450/// int xmlValidateNmtokensValue(const xmlChar *value);
7451/// ```
7452#[no_mangle]
7453pub unsafe extern "C" fn xmlValidateNmtokensValue(value: *const xmlChar) -> c_int {
7454    crate::xml::validation::validate_nmtokens_value(value)
7455}
7456
7457/// Validate a single element declaration (VC: Unique Element Type
7458/// Declaration, VC: No Duplicate Types).
7459///
7460/// # UPSTREAM-PARITY
7461///
7462/// ```c
7463/// int xmlValidateElementDecl(xmlValidCtxtPtr ctxt,
7464///                            xmlDocPtr doc,
7465///                            xmlElementPtr elem);
7466/// ```
7467#[no_mangle]
7468pub unsafe extern "C" fn xmlValidateElementDecl(
7469    ctxt: *mut _xmlValidCtxt,
7470    doc: *mut _xmlDoc,
7471    elem: *mut _xmlElement,
7472) -> c_int {
7473    crate::xml::validation::validate_element_decl(ctxt, doc, elem)
7474}
7475
7476/// Validate a notation declaration.
7477///
7478/// # UPSTREAM-PARITY
7479///
7480/// Modern libxml2 has no validity constraint on notation declarations; the
7481/// oracle returns 1 unconditionally (verified by DSO disassembly).
7482///
7483/// ```c
7484/// int xmlValidateNotationDecl(xmlValidCtxtPtr ctxt,
7485///                             xmlDocPtr doc,
7486///                             xmlNotationPtr nota);
7487/// ```
7488#[no_mangle]
7489pub const unsafe extern "C" fn xmlValidateNotationDecl(
7490    ctxt: *mut _xmlValidCtxt,
7491    doc: *mut _xmlDoc,
7492    nota: *mut _xmlNotation,
7493) -> c_int {
7494    crate::xml::validation::validate_notation_decl(ctxt, doc, nota)
7495}
7496
7497/// Validate a single attribute against its declaration.
7498///
7499/// # UPSTREAM-PARITY
7500///
7501/// ```c
7502/// int xmlValidateOneAttribute(xmlValidCtxtPtr ctxt,
7503///                             xmlDocPtr doc,
7504///                             xmlNodePtr elem,
7505///                             xmlAttrPtr attr,
7506///                             const xmlChar *value);
7507/// ```
7508#[no_mangle]
7509pub unsafe extern "C" fn xmlValidateOneAttribute(
7510    ctxt: *mut _xmlValidCtxt,
7511    doc: *mut _xmlDoc,
7512    elem: *mut _xmlNode,
7513    attr: *mut _xmlAttr,
7514    value: *const xmlChar,
7515) -> c_int {
7516    crate::xml::validation::validate_one_attribute(ctxt, doc, elem, attr, value)
7517}
7518
7519/// Validate a single element against its declaration (without recursing).
7520///
7521/// # UPSTREAM-PARITY
7522///
7523/// ```c
7524/// int xmlValidateOneElement(xmlValidCtxtPtr ctxt,
7525///                           xmlDocPtr doc,
7526///                           xmlNodePtr elem);
7527/// ```
7528#[no_mangle]
7529pub unsafe extern "C" fn xmlValidateOneElement(
7530    ctxt: *mut _xmlValidCtxt,
7531    doc: *mut _xmlDoc,
7532    elem: *mut _xmlNode,
7533) -> c_int {
7534    crate::xml::validation::validate_one_element(ctxt, doc, elem)
7535}
7536
7537/// Validate a namespace declaration attribute.
7538///
7539/// # UPSTREAM-PARITY
7540///
7541/// ```c
7542/// int xmlValidateOneNamespace(xmlValidCtxtPtr ctxt,
7543///                             xmlDocPtr doc,
7544///                             xmlNodePtr elem,
7545///                             const xmlChar *prefix,
7546///                             xmlNsPtr ns,
7547///                             const xmlChar *value);
7548/// ```
7549#[no_mangle]
7550pub unsafe extern "C" fn xmlValidateOneNamespace(
7551    ctxt: *mut _xmlValidCtxt,
7552    doc: *mut _xmlDoc,
7553    elem: *mut _xmlNode,
7554    prefix: *const xmlChar,
7555    ns: *mut _xmlNs,
7556    value: *const xmlChar,
7557) -> c_int {
7558    crate::xml::validation::validate_one_namespace(ctxt, doc, elem, prefix, ns, value)
7559}
7560
7561/// Push a new element start onto the validation stack (streaming DTD
7562/// validation).
7563///
7564/// # UPSTREAM-PARITY
7565///
7566/// ```c
7567/// int xmlValidatePushElement(xmlValidCtxtPtr ctxt,
7568///                            xmlDocPtr doc,
7569///                            xmlNodePtr elem,
7570///                            const xmlChar *qname);
7571/// ```
7572#[no_mangle]
7573pub unsafe extern "C" fn xmlValidatePushElement(
7574    ctxt: *mut _xmlValidCtxt,
7575    doc: *mut _xmlDoc,
7576    elem: *mut _xmlNode,
7577    qname: *const xmlChar,
7578) -> c_int {
7579    crate::xml::validation::validate_push_element(ctxt, doc, elem, qname)
7580}
7581
7582/// Push character data onto the validation stack.
7583///
7584/// # UPSTREAM-PARITY
7585///
7586/// ```c
7587/// int xmlValidatePushCData(xmlValidCtxtPtr ctxt,
7588///                          const xmlChar *data,
7589///                          int len);
7590/// ```
7591#[no_mangle]
7592pub unsafe extern "C" fn xmlValidatePushCData(
7593    ctxt: *mut _xmlValidCtxt,
7594    data: *const xmlChar,
7595    len: c_int,
7596) -> c_int {
7597    crate::xml::validation::validate_push_cdata(ctxt, data, len)
7598}
7599
7600/// Pop an element end from the validation stack.
7601///
7602/// # UPSTREAM-PARITY
7603///
7604/// ```c
7605/// int xmlValidatePopElement(xmlValidCtxtPtr ctxt,
7606///                           xmlDocPtr doc,
7607///                           xmlNodePtr elem,
7608///                           const xmlChar *qname);
7609/// ```
7610#[no_mangle]
7611pub unsafe extern "C" fn xmlValidatePopElement(
7612    ctxt: *mut _xmlValidCtxt,
7613    doc: *mut _xmlDoc,
7614    elem: *mut _xmlNode,
7615    qname: *const xmlChar,
7616) -> c_int {
7617    crate::xml::validation::validate_pop_element(ctxt, doc, elem, qname)
7618}
7619
7620/// Build the content-model automaton for an element declaration.
7621///
7622/// # UPSTREAM-PARITY
7623///
7624/// ```c
7625/// int xmlValidBuildContentModel(xmlValidCtxtPtr ctxt,
7626///                               xmlElementPtr elem);
7627/// ```
7628#[no_mangle]
7629pub unsafe extern "C" fn xmlValidBuildContentModel(
7630    ctxt: *mut _xmlValidCtxt,
7631    elem: *mut _xmlElement,
7632) -> c_int {
7633    crate::xml::validation::validate_build_content_model(ctxt, elem)
7634}
7635
7636/// Add an attribute to the document's ID table.
7637///
7638/// # UPSTREAM-PARITY
7639///
7640/// ```c
7641/// xmlIDPtr xmlAddID(xmlValidCtxtPtr ctxt,
7642///                   xmlDocPtr doc,
7643///                   const xmlChar *value,
7644///                   xmlAttrPtr attr);
7645/// ```
7646#[no_mangle]
7647pub unsafe extern "C" fn xmlAddID(
7648    ctxt: *mut _xmlValidCtxt,
7649    doc: *mut _xmlDoc,
7650    value: *const xmlChar,
7651    attr: *mut _xmlAttr,
7652) -> *mut _xmlID {
7653    crate::xml::validation::add_id(ctxt, doc, value, attr)
7654}
7655
7656/// Remove an attribute from the document's ID table.
7657///
7658/// # UPSTREAM-PARITY
7659///
7660/// ```c
7661/// int xmlRemoveID(xmlDocPtr doc, xmlAttrPtr attr);
7662/// ```
7663#[no_mangle]
7664pub unsafe extern "C" fn xmlRemoveID(doc: *mut _xmlDoc, attr: *mut _xmlAttr) -> c_int {
7665    crate::xml::validation::remove_id(doc, attr)
7666}
7667
7668/// Register an IDREF in the document's ref table.
7669///
7670/// # UPSTREAM-PARITY
7671///
7672/// ```c
7673/// xmlRefPtr xmlAddRef(xmlValidCtxtPtr ctxt,
7674///                     xmlDocPtr doc,
7675///                     const xmlChar *value,
7676///                     xmlAttrPtr attr);
7677/// ```
7678#[no_mangle]
7679pub unsafe extern "C" fn xmlAddRef(
7680    ctxt: *mut _xmlValidCtxt,
7681    doc: *mut _xmlDoc,
7682    value: *const xmlChar,
7683    attr: *mut _xmlAttr,
7684) -> *mut _xmlRef {
7685    crate::xml::validation::add_ref(ctxt, doc, value, attr)
7686}
7687
7688/// Remove an attribute's IDREF entries.
7689///
7690/// # UPSTREAM-PARITY
7691///
7692/// ```c
7693/// int xmlRemoveRef(xmlDocPtr doc, xmlAttrPtr attr);
7694/// ```
7695#[no_mangle]
7696pub unsafe extern "C" fn xmlRemoveRef(doc: *mut _xmlDoc, attr: *mut _xmlAttr) -> c_int {
7697    crate::xml::validation::remove_ref(doc, attr)
7698}
7699
7700/// Add an ID without a validation context (2.13+).
7701///
7702/// # UPSTREAM-PARITY
7703///
7704/// ```c
7705/// int xmlAddIDSafe(xmlAttrPtr attr, const xmlChar *value);
7706/// ```
7707#[no_mangle]
7708pub unsafe extern "C" fn xmlAddIDSafe(attr: *mut _xmlAttr, value: *const xmlChar) -> c_int {
7709    crate::xml::validation::add_id_safe(attr, value)
7710}
7711
7712/// Free an ID hash table.
7713///
7714/// # UPSTREAM-PARITY
7715///
7716/// ```c
7717/// void xmlFreeIDTable(xmlIDTablePtr table);
7718/// ```
7719#[no_mangle]
7720pub unsafe extern "C" fn xmlFreeIDTable(table: *mut c_void) {
7721    crate::xml::validation::free_id_table(table as *mut crate::xml::hash::HashTable);
7722}
7723
7724/// Free an IDREF hash table.
7725///
7726/// # UPSTREAM-PARITY
7727///
7728/// ```c
7729/// void xmlFreeRefTable(xmlRefTablePtr table);
7730/// ```
7731#[no_mangle]
7732pub unsafe extern "C" fn xmlFreeRefTable(table: *mut c_void) {
7733    crate::xml::validation::free_ref_table(table as *mut crate::xml::hash::HashTable);
7734}
7735
7736/// Look up the attribute holding an ID.
7737///
7738/// # UPSTREAM-PARITY
7739///
7740/// ```c
7741/// xmlAttrPtr xmlGetID(xmlDocPtr doc, const xmlChar *ID);
7742/// ```
7743#[no_mangle]
7744pub unsafe extern "C" fn xmlGetID(doc: *mut _xmlDoc, id: *const xmlChar) -> *mut _xmlAttr {
7745    crate::xml::validation::get_id(doc, id)
7746}
7747
7748/// Look up the list of references for an ID.
7749///
7750/// # UPSTREAM-PARITY
7751///
7752/// ```c
7753/// xmlListPtr xmlGetRefs(xmlDocPtr doc, const xmlChar *ID);
7754/// ```
7755#[no_mangle]
7756pub unsafe extern "C" fn xmlGetRefs(doc: *mut _xmlDoc, id: *const xmlChar) -> *mut c_void {
7757    crate::xml::validation::get_refs(doc, id) as *mut c_void
7758}
7759
7760/// Is this attribute an ID?
7761///
7762/// # UPSTREAM-PARITY
7763///
7764/// ```c
7765/// int xmlIsID(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr);
7766/// ```
7767#[no_mangle]
7768pub unsafe extern "C" fn xmlIsID(
7769    doc: *mut _xmlDoc,
7770    elem: *mut _xmlNode,
7771    attr: *mut _xmlAttr,
7772) -> c_int {
7773    crate::xml::validation::is_id(doc, elem, attr)
7774}
7775
7776/// Is this attribute an IDREF?
7777///
7778/// # UPSTREAM-PARITY
7779///
7780/// ```c
7781/// int xmlIsRef(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr);
7782/// ```
7783#[no_mangle]
7784pub unsafe extern "C" fn xmlIsRef(
7785    doc: *mut _xmlDoc,
7786    elem: *mut _xmlNode,
7787    attr: *mut _xmlAttr,
7788) -> c_int {
7789    crate::xml::validation::is_ref(doc, elem, attr)
7790}
7791
7792/// Search a DTD for an element declaration (with QName splitting).
7793///
7794/// # UPSTREAM-PARITY
7795///
7796/// ```c
7797/// xmlElementPtr xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name);
7798/// ```
7799#[no_mangle]
7800pub unsafe extern "C" fn xmlGetDtdElementDesc(
7801    dtd: *mut _xmlDtd,
7802    name: *const xmlChar,
7803) -> *mut _xmlElement {
7804    crate::xml::validation::get_dtd_element_desc(dtd, name)
7805}
7806
7807/// Search a DTD for an attribute declaration (with QName splitting).
7808///
7809/// # UPSTREAM-PARITY
7810///
7811/// ```c
7812/// xmlAttributePtr xmlGetDtdAttrDesc(xmlDtdPtr dtd,
7813///                                   const xmlChar *elem,
7814///                                   const xmlChar *name);
7815/// ```
7816#[no_mangle]
7817pub unsafe extern "C" fn xmlGetDtdAttrDesc(
7818    dtd: *mut _xmlDtd,
7819    elem: *const xmlChar,
7820    name: *const xmlChar,
7821) -> *mut _xmlAttribute {
7822    crate::xml::validation::get_dtd_attr_desc(dtd, elem, name)
7823}
7824
7825/// Search a DTD for a qualified element declaration.
7826///
7827/// # UPSTREAM-PARITY
7828///
7829/// ```c
7830/// xmlElementPtr xmlGetDtdQElementDesc(xmlDtdPtr dtd,
7831///                                     const xmlChar *name,
7832///                                     const xmlChar *prefix);
7833/// ```
7834#[no_mangle]
7835pub unsafe extern "C" fn xmlGetDtdQElementDesc(
7836    dtd: *mut _xmlDtd,
7837    name: *const xmlChar,
7838    prefix: *const xmlChar,
7839) -> *mut _xmlElement {
7840    crate::xml::validation::get_dtd_qelement_desc(dtd, name, prefix)
7841}
7842
7843/// Search a DTD for a qualified attribute declaration.
7844///
7845/// # UPSTREAM-PARITY
7846///
7847/// ```c
7848/// xmlAttributePtr xmlGetDtdQAttrDesc(xmlDtdPtr dtd,
7849///                                    const xmlChar *elem,
7850///                                    const xmlChar *name,
7851///                                    const xmlChar *prefix);
7852/// ```
7853#[no_mangle]
7854pub unsafe extern "C" fn xmlGetDtdQAttrDesc(
7855    dtd: *mut _xmlDtd,
7856    elem: *const xmlChar,
7857    name: *const xmlChar,
7858    prefix: *const xmlChar,
7859) -> *mut _xmlAttribute {
7860    crate::xml::validation::get_dtd_qattr_desc(dtd, elem, name, prefix)
7861}
7862
7863/// Search a DTD for a notation declaration.
7864///
7865/// # UPSTREAM-PARITY
7866///
7867/// ```c
7868/// xmlNotationPtr xmlGetDtdNotationDesc(xmlDtdPtr dtd, const xmlChar *name);
7869/// ```
7870#[no_mangle]
7871pub unsafe extern "C" fn xmlGetDtdNotationDesc(
7872    dtd: *mut _xmlDtd,
7873    name: *const xmlChar,
7874) -> *mut _xmlNotation {
7875    crate::xml::validation::get_dtd_notation_desc(dtd, name)
7876}
7877
7878/// Validate the root element of a document.
7879///
7880/// # UPSTREAM-PARITY
7881///
7882/// ```c
7883/// int xmlValidateRoot(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7884/// ```
7885#[no_mangle]
7886pub unsafe extern "C" fn xmlValidateRoot(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
7887    crate::xml::validation::validate_root(ctxt, doc)
7888}
7889
7890/// Validate element content against its content model.
7891///
7892/// # UPSTREAM-PARITY
7893///
7894/// ```c
7895/// int xmlValidateContent(xmlValidCtxtPtr ctxt,
7896///                        xmlNodePtr node,
7897///                        xmlDocPtr doc);
7898/// ```
7899#[no_mangle]
7900pub unsafe extern "C" fn xmlValidateContent(
7901    ctxt: *mut _xmlValidCtxt,
7902    node: *mut _xmlNode,
7903    doc: *mut _xmlDoc,
7904) -> c_int {
7905    crate::xml::validation::validate_content(ctxt, node, doc)
7906}
7907
7908/// Check if an element is declared as mixed content.
7909///
7910/// # UPSTREAM-PARITY
7911///
7912/// ```c
7913/// int xmlIsMixedElement(xmlDocPtr doc, const xmlChar *name);
7914/// ```
7915#[no_mangle]
7916pub unsafe extern "C" fn xmlIsMixedElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
7917    crate::xml::validation::is_mixed_element(doc, name)
7918}
7919
7920/// Check if an element is declared as EMPTY.
7921///
7922/// # UPSTREAM-PARITY
7923///
7924/// ```c
7925/// int xmlIsEmptyElement(xmlDocPtr doc, const xmlChar *name);
7926/// ```
7927#[no_mangle]
7928pub unsafe extern "C" fn xmlIsEmptyElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
7929    crate::xml::validation::is_empty_element(doc, name)
7930}
7931
7932/// Validate a DTD's declarations.
7933///
7934/// # UPSTREAM-PARITY
7935///
7936/// ```c
7937/// int xmlValidateDtd(xmlValidCtxtPtr ctxt,
7938///                    xmlDocPtr doc,
7939///                    xmlDtdPtr dtd);
7940/// ```
7941#[no_mangle]
7942pub unsafe extern "C" fn xmlValidateDtd(
7943    ctxt: *mut _xmlValidCtxt,
7944    doc: *mut _xmlDoc,
7945    dtd: *mut _xmlDtd,
7946) -> c_int {
7947    crate::xml::validation::validate_dtd(ctxt, doc, dtd)
7948}
7949
7950/// Final DTD validation (ID/IDREF consistency).
7951///
7952/// # UPSTREAM-PARITY
7953///
7954/// ```c
7955/// int xmlValidateDtdFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7956/// ```
7957#[no_mangle]
7958pub unsafe extern "C" fn xmlValidateDtdFinal(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
7959    crate::xml::validation::validate_dtd_final(ctxt, doc)
7960}
7961
7962/// Validate that a value is in an enumeration.
7963///
7964/// # UPSTREAM-PARITY
7965///
7966/// ```c
7967/// int xmlValidateEnumeration(xmlValidCtxtPtr ctxt,
7968///                            const xmlChar *value,
7969///                            xmlEnumerationPtr tree);
7970/// ```
7971#[no_mangle]
7972pub unsafe extern "C" fn xmlValidateEnumeration(
7973    ctxt: *mut _xmlValidCtxt,
7974    value: *const xmlChar,
7975    tree: *mut _xmlEnumeration,
7976) -> c_int {
7977    crate::xml::validation::validate_enumeration(ctxt, value, tree)
7978}
7979
7980// ═══════════════════════════════════════════════════════════════════════════════
7981// 18. Debug / Miscellaneous
7982// ═══════════════════════════════════════════════════════════════════════════════
7983
7984/// Dump a document to a file for debugging.
7985/// Get the path to the current executable.
7986///
7987/// # UPSTREAM-PARITY
7988///
7989/// ```c
7990/// char *xmlGetBinaryPath(void);
7991/// ```
7992#[no_mangle]
7993pub const extern "C" fn xmlGetBinaryPath() -> *mut c_char {
7994    // Phase 1: STUB
7995    ptr::null_mut()
7996}
7997
7998/// Get the path to the current executable's home directory.
7999///
8000/// # UPSTREAM-PARITY
8001///
8002/// ```c
8003/// char *xmlGetHomeOfBinary(void);
8004/// ```
8005#[no_mangle]
8006pub const extern "C" fn xmlGetHomeOfBinary() -> *mut c_char {
8007    // Phase 1: STUB
8008    ptr::null_mut()
8009}
8010
8011// ═══════════════════════════════════════════════════════════════════════════════
8012// SAX2 default callback entry points (upstream SAX2.c)
8013// ═══════════════════════════════════════════════════════════════════════════════
8014//
8015// These are the public `xmlSAX2*` callback functions that downstream code
8016// installs into `xmlSAXHandler` structures. They are the same implementations
8017// the candidate's default SAX handler uses; exporting them under the
8018// upstream names is required for ABI parity (R-000136 closure).
8019
8020/// Upstream SAX2.c `xmlSAX2StartDocument` — public entry point of the default handler.
8021#[no_mangle]
8022pub unsafe extern "C" fn xmlSAX2StartDocument(ctx: *mut c_void) {
8023    crate::xml::sax::default::default_sax_handler::startDocument(ctx)
8024}
8025
8026/// Upstream SAX2.c `xmlSAX2EndDocument` — public entry point of the default handler.
8027#[no_mangle]
8028pub unsafe extern "C" fn xmlSAX2EndDocument(ctx: *mut c_void) {
8029    crate::xml::sax::default::default_sax_handler::endDocument(ctx)
8030}
8031
8032/// Upstream SAX2.c `xmlSAX2StartElementNs` — public entry point of the default handler.
8033#[no_mangle]
8034pub unsafe extern "C" fn xmlSAX2StartElementNs(
8035    ctx: *mut c_void,
8036    localname: *const xmlChar,
8037    prefix: *const xmlChar,
8038    URI: *const xmlChar,
8039    nb_namespaces: c_int,
8040    namespaces: *mut *const xmlChar,
8041    nb_attributes: c_int,
8042    nb_defaulted: c_int,
8043    attributes: *mut *const xmlChar,
8044) {
8045    crate::xml::sax::default::default_sax_handler::startElementNs(
8046        ctx,
8047        localname,
8048        prefix,
8049        URI,
8050        nb_namespaces,
8051        namespaces,
8052        nb_attributes,
8053        nb_defaulted,
8054        attributes,
8055    )
8056}
8057
8058/// Upstream SAX2.c `xmlSAX2EndElementNs` — public entry point of the default handler.
8059#[no_mangle]
8060pub unsafe extern "C" fn xmlSAX2EndElementNs(
8061    ctx: *mut c_void,
8062    localname: *const xmlChar,
8063    prefix: *const xmlChar,
8064    URI: *const xmlChar,
8065) {
8066    crate::xml::sax::default::default_sax_handler::endElementNs(ctx, localname, prefix, URI)
8067}
8068
8069/// Upstream SAX2.c `xmlSAX2Characters` — public entry point of the default handler.
8070#[no_mangle]
8071pub unsafe extern "C" fn xmlSAX2Characters(ctx: *mut c_void, ch: *const xmlChar, len: c_int) {
8072    crate::xml::sax::default::default_sax_handler::characters(ctx, ch, len)
8073}
8074
8075/// Upstream SAX2.c `xmlSAX2IgnorableWhitespace` — public entry point of the default handler.
8076#[no_mangle]
8077pub unsafe extern "C" fn xmlSAX2IgnorableWhitespace(
8078    ctx: *mut c_void,
8079    ch: *const xmlChar,
8080    len: c_int,
8081) {
8082    crate::xml::sax::default::default_sax_handler::ignorableWhitespace(ctx, ch, len)
8083}
8084
8085/// Upstream SAX2.c `xmlSAX2Comment` — public entry point of the default handler.
8086#[no_mangle]
8087pub unsafe extern "C" fn xmlSAX2Comment(ctx: *mut c_void, value: *const xmlChar) {
8088    crate::xml::sax::default::default_sax_handler::comment(ctx, value)
8089}
8090
8091/// Upstream SAX2.c `xmlSAX2ProcessingInstruction` — public entry point of the default handler.
8092#[no_mangle]
8093pub unsafe extern "C" fn xmlSAX2ProcessingInstruction(
8094    ctx: *mut c_void,
8095    target: *const xmlChar,
8096    data: *const xmlChar,
8097) {
8098    crate::xml::sax::default::default_sax_handler::processingInstruction(ctx, target, data)
8099}
8100
8101/// Upstream SAX2.c `xmlSAX2CDataBlock` — public entry point of the default handler.
8102#[no_mangle]
8103pub unsafe extern "C" fn xmlSAX2CDataBlock(ctx: *mut c_void, value: *const xmlChar, len: c_int) {
8104    crate::xml::sax::default::default_sax_handler::cdataBlock(ctx, value, len)
8105}
8106
8107/// Upstream SAX2.c `xmlSAX2InternalSubset` — public entry point of the default handler.
8108#[no_mangle]
8109pub unsafe extern "C" fn xmlSAX2InternalSubset(
8110    ctx: *mut c_void,
8111    name: *const xmlChar,
8112    ExternalID: *const xmlChar,
8113    SystemID: *const xmlChar,
8114) {
8115    crate::xml::sax::default::default_sax_handler::internalSubset(ctx, name, ExternalID, SystemID)
8116}
8117
8118/// Upstream SAX2.c `xmlSAX2ExternalSubset` — public entry point of the default handler.
8119#[no_mangle]
8120pub unsafe extern "C" fn xmlSAX2ExternalSubset(
8121    ctx: *mut c_void,
8122    name: *const xmlChar,
8123    ExternalID: *const xmlChar,
8124    SystemID: *const xmlChar,
8125) {
8126    crate::xml::sax::default::default_sax_handler::externalSubset(ctx, name, ExternalID, SystemID)
8127}
8128
8129/// Upstream SAX2.c `xmlSAX2EntityDecl` — public entry point of the default handler.
8130#[no_mangle]
8131pub unsafe extern "C" fn xmlSAX2EntityDecl(
8132    ctx: *mut c_void,
8133    name: *const xmlChar,
8134    type_: c_int,
8135    publicId: *const xmlChar,
8136    systemId: *const xmlChar,
8137    content: *mut xmlChar,
8138) {
8139    crate::xml::sax::default::default_sax_handler::entityDecl(
8140        ctx, name, type_, publicId, systemId, content,
8141    )
8142}
8143
8144/// Upstream SAX2.c `xmlSAX2AttributeDecl` — public entry point of the default handler.
8145#[no_mangle]
8146pub const unsafe extern "C" fn xmlSAX2AttributeDecl(
8147    ctx: *mut c_void,
8148    elem: *const xmlChar,
8149    fullname: *const xmlChar,
8150    type_: c_int,
8151    def: c_int,
8152    defaultValue: *const xmlChar,
8153    tree: *mut crate::abi::structs::_xmlEnumeration,
8154) {
8155    crate::xml::sax::default::default_sax_handler::attributeDecl(
8156        ctx,
8157        elem,
8158        fullname,
8159        type_,
8160        def,
8161        defaultValue,
8162        tree,
8163    )
8164}
8165
8166/// Upstream SAX2.c `xmlSAX2ElementDecl` — public entry point of the default handler.
8167#[no_mangle]
8168pub const unsafe extern "C" fn xmlSAX2ElementDecl(
8169    ctx: *mut c_void,
8170    name: *const xmlChar,
8171    type_: c_int,
8172    content: *mut crate::abi::structs::_xmlElementContent,
8173) {
8174    crate::xml::sax::default::default_sax_handler::elementDecl(ctx, name, type_, content)
8175}
8176
8177/// Upstream SAX2.c `xmlSAX2NotationDecl` — public entry point of the default handler.
8178#[no_mangle]
8179pub const unsafe extern "C" fn xmlSAX2NotationDecl(
8180    ctx: *mut c_void,
8181    name: *const xmlChar,
8182    publicId: *const xmlChar,
8183    systemId: *const xmlChar,
8184) {
8185    crate::xml::sax::default::default_sax_handler::notationDecl(ctx, name, publicId, systemId)
8186}
8187
8188/// Upstream SAX2.c `xmlSAX2UnparsedEntityDecl` — public entry point of the default handler.
8189#[no_mangle]
8190pub const unsafe extern "C" fn xmlSAX2UnparsedEntityDecl(
8191    ctx: *mut c_void,
8192    name: *const xmlChar,
8193    publicId: *const xmlChar,
8194    systemId: *const xmlChar,
8195    notationName: *const xmlChar,
8196) {
8197    crate::xml::sax::default::default_sax_handler::unparsedEntityDecl(
8198        ctx,
8199        name,
8200        publicId,
8201        systemId,
8202        notationName,
8203    )
8204}
8205
8206/// Upstream SAX2.c `xmlSAX2ResolveEntity` — public entry point of the default handler.
8207#[no_mangle]
8208pub const unsafe extern "C" fn xmlSAX2ResolveEntity(
8209    ctx: *mut c_void,
8210    publicId: *const xmlChar,
8211    systemId: *const xmlChar,
8212) -> *mut crate::abi::structs::_xmlParserInput {
8213    crate::xml::sax::default::default_sax_handler::resolveEntity(ctx, publicId, systemId)
8214}
8215
8216/// Upstream SAX2.c `xmlSAX2IsStandalone` — public entry point of the default handler.
8217#[no_mangle]
8218pub const unsafe extern "C" fn xmlSAX2IsStandalone(ctx: *mut c_void) -> c_int {
8219    crate::xml::sax::default::default_sax_handler::isStandalone(ctx)
8220}
8221
8222/// Upstream SAX2.c `xmlSAX2HasInternalSubset` — public entry point of the default handler.
8223#[no_mangle]
8224pub unsafe extern "C" fn xmlSAX2HasInternalSubset(ctx: *mut c_void) -> c_int {
8225    crate::xml::sax::default::default_sax_handler::hasInternalSubset(ctx)
8226}
8227
8228/// Upstream SAX2.c `xmlSAX2HasExternalSubset` — public entry point of the default handler.
8229#[no_mangle]
8230pub unsafe extern "C" fn xmlSAX2HasExternalSubset(ctx: *mut c_void) -> c_int {
8231    crate::xml::sax::default::default_sax_handler::hasExternalSubset(ctx)
8232}
8233
8234/// Upstream SAX2.c `xmlSAX2GetEntity` — public entry point of the default handler.
8235#[no_mangle]
8236pub unsafe extern "C" fn xmlSAX2GetEntity(
8237    ctx: *mut c_void,
8238    name: *const xmlChar,
8239) -> *mut crate::abi::structs::_xmlEntity {
8240    crate::xml::sax::default::default_sax_handler::getEntity(ctx, name)
8241}
8242
8243/// Upstream SAX2.c `xmlSAX2GetParameterEntity` — public entry point of the default handler.
8244#[no_mangle]
8245pub unsafe extern "C" fn xmlSAX2GetParameterEntity(
8246    ctx: *mut c_void,
8247    name: *const xmlChar,
8248) -> *mut crate::abi::structs::_xmlEntity {
8249    crate::xml::sax::default::default_sax_handler::getParameterEntity(ctx, name)
8250}
8251
8252/// Upstream SAX2.c `xmlSAX2GetLineNumber` — public entry point of the
8253/// default handler (SAX locator callback).
8254#[no_mangle]
8255pub unsafe extern "C" fn xmlSAX2GetLineNumber(ctx: *mut c_void) -> c_int {
8256    crate::xml::sax::default::default_sax_handler::getLineNumber(ctx)
8257}
8258
8259/// Upstream SAX2.c `xmlSAX2GetColumnNumber`.
8260#[no_mangle]
8261pub unsafe extern "C" fn xmlSAX2GetColumnNumber(ctx: *mut c_void) -> c_int {
8262    crate::xml::sax::default::default_sax_handler::getColumnNumber(ctx)
8263}
8264
8265/// Upstream SAX2.c `xmlSAX2GetPublicId`.
8266#[no_mangle]
8267pub const unsafe extern "C" fn xmlSAX2GetPublicId(ctx: *mut c_void) -> *const xmlChar {
8268    crate::xml::sax::default::default_sax_handler::getPublicId(ctx)
8269}
8270
8271/// Upstream SAX2.c `xmlSAX2GetSystemId`.
8272#[no_mangle]
8273pub unsafe extern "C" fn xmlSAX2GetSystemId(ctx: *mut c_void) -> *const xmlChar {
8274    crate::xml::sax::default::default_sax_handler::getSystemId(ctx)
8275}
8276
8277/// Upstream SAX2.c `xmlSAX2StartElement` — SAX1 start-element entry point.
8278/// The candidate parser dispatches through the SAX2 (namespaced) callbacks;
8279/// this wrapper maps to the SAX1 handler when installed.
8280#[no_mangle]
8281pub unsafe extern "C" fn xmlSAX2StartElement(
8282    ctx: *mut c_void,
8283    name: *const xmlChar,
8284    atts: *mut *const xmlChar,
8285) {
8286    // The parser core invokes startElementNs; the SAX1 shim is provided by
8287    // the dispatch layer. When this entry point is installed directly on a
8288    // handler, route through the internal SAX1 path.
8289    crate::xml::sax::dispatch::SaxDispatcher::sax1_start_element(ctx, name, atts);
8290}
8291
8292/// Upstream SAX2.c `xmlSAX2EndElement` — SAX1 end-element entry point.
8293#[no_mangle]
8294pub unsafe extern "C" fn xmlSAX2EndElement(ctx: *mut c_void, name: *const xmlChar) {
8295    crate::xml::sax::dispatch::SaxDispatcher::sax1_end_element(ctx, name);
8296}
8297
8298/// Upstream SAX2.c `xmlSAX2SetDocumentLocator` — public entry point of the default handler.
8299#[no_mangle]
8300pub const unsafe extern "C" fn xmlSAX2SetDocumentLocator(
8301    ctx: *mut c_void,
8302    loc: *mut crate::abi::callbacks::_xmlSAXLocator,
8303) {
8304    crate::xml::sax::default::default_sax_handler::setDocumentLocator(ctx, loc)
8305}
8306
8307/// Upstream SAX2.c `xmlSAX2Reference` — public entry point of the default handler.
8308#[no_mangle]
8309pub unsafe extern "C" fn xmlSAX2Reference(ctx: *mut c_void, name: *const xmlChar) {
8310    crate::xml::sax::default::default_sax_handler::reference(ctx, name)
8311}
8312
8313#[cfg(test)]
8314mod tests {
8315    use super::xml_number_to_string;
8316
8317    /// R-000166: number-to-string follows upstream xmlXPathFormatNumber —
8318    /// verified byte-identical against the oracle (xsltproc) on the t4/n3
8319    /// differential corpora. Cases here are exact doubles or
8320    /// rounding-robust formats (parser-dependent literals are covered by the
8321    /// differential corpora, not unit tests).
8322    #[allow(clippy::approx_constant)]
8323    #[test]
8324    fn test_xml_number_to_string_parity_cases() {
8325        let cases: &[(f64, &str)] = &[
8326            (1234567.891, "1234567.891"),
8327            (0.1 + 0.2, "0.3"),
8328            (1.0 / 3.0, "0.333333333333333"),
8329            (1e20, "1e+20"),
8330            (1e-5, "0.00001"),
8331            (123456789012345678901234567890.0, "1.23456789012346e+29"),
8332            (1e100, "1e+100"),
8333            (-1e100, "-1e+100"),
8334            (1.5e-100, "1.5e-100"),
8335            (1e9, "1000000000"),
8336            (0.00001, "0.00001"),
8337            (9.99e-6, "9.99e-06"),
8338            (2147483646.0, "2147483646"),
8339            (2147483648.0, "2.147483648e+09"),
8340            (-2147483647.0, "-2147483647"),
8341            (-2147483649.0, "-2.147483649e+09"),
8342            (0.5, "0.5"),
8343            (1.0 / 7.0, "0.142857142857143"),
8344            (2.675, "2.675"),
8345            (3.141592653589793, "3.141592653589793"),
8346            (-0.0, "0"),
8347            (0.0, "0"),
8348            (f64::INFINITY, "Infinity"),
8349            (f64::NEG_INFINITY, "-Infinity"),
8350            (f64::NAN, "NaN"),
8351            (0.30000000000000004, "0.3"),
8352            (2.2250738585072014e-308, "2.2250738585072e-308"),
8353            (5e-324, "4.94065645841247e-324"),
8354        ];
8355        for (n, expected) in cases {
8356            assert_eq!(&xml_number_to_string(*n), expected, "value: {}", n);
8357        }
8358    }
8359
8360    /// xmlNewChild with a non-NULL content creates the element and appends a
8361    /// text child (upstream tree.c xmlNewChild -> xmlNewDocNode ->
8362    /// xmlNewDocText + xmlAddChild; tree2.c relies on it — Phase-12
8363    /// EXTERNAL-CONSUMERS court).
8364    ///
8365    /// # Safety
8366    ///
8367    /// - The doc and nodes are created and freed exactly once within the
8368    ///   test; pointers are asserted non-NULL before dereference.
8369    #[test]
8370    fn test_xml_new_child_with_content() {
8371        unsafe {
8372            let doc =
8373                crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
8374            assert!(!doc.is_null());
8375            let root = crate::xml::tree::new_node(
8376                core::ptr::null_mut(),
8377                c"root".as_ptr() as *const crate::abi::types::xmlChar,
8378            );
8379            assert!(!root.is_null());
8380            crate::xml::tree::doc_set_root_element(doc, root);
8381
8382            let child = super::xmlNewChild(
8383                root,
8384                core::ptr::null_mut(),
8385                c"node1".as_ptr() as *const crate::abi::types::xmlChar,
8386                c"content of node 1".as_ptr() as *const crate::abi::types::xmlChar,
8387            );
8388            assert!(!child.is_null());
8389            assert!(
8390                !(*child).children.is_null(),
8391                "content must become a text child"
8392            );
8393            assert_eq!(
8394                crate::abi::types::xmlElementType::XML_TEXT_NODE as i32,
8395                (*(*child).children).type_
8396            );
8397            let text = crate::xml::string::xmlstr_to_bytes((*(*child).children).content);
8398            assert_eq!(text, b"content of node 1");
8399
8400            // NULL content stays childless
8401            let empty = super::xmlNewChild(
8402                root,
8403                core::ptr::null_mut(),
8404                c"node2".as_ptr() as *const crate::abi::types::xmlChar,
8405                core::ptr::null(),
8406            );
8407            assert!(!empty.is_null());
8408            assert!((*empty).children.is_null());
8409
8410            crate::xml::tree::free_doc(doc);
8411        }
8412    }
8413}