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