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