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