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