Skip to main content

libxml_rs/abi/
exports_xml2.rs

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