Skip to main content

libxml_rs/abi/
exports_xml2.rs

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