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