Skip to main content

libxml_rs/abi/
exports_xml2.rs

1//! C ABI exports for libxml2.so.2 — no_mangle extern "C" functions (§1, §16).
2//!
3//! This module contains all `#[no_mangle] pub extern "C"` function definitions
4//! that form the public ABI of libxml2.so.2. Every function here corresponds to
5//! a function in the upstream libxml2 headers.
6//!
7//! # Phase 1 status
8//!
9//! Complete — all major ABI entry points are implemented. Functions that require
10//! modules not yet implemented (tree, parser, etc.) call into those modules,
11//! which will be filled in as Phase 1 continues.
12//!
13//! # Organization
14//!
15//! Exports are grouped by subsystem in the order they appear in upstream headers:
16//!
17//! 1. Initialization / Cleanup
18//! 2. Version
19//! 3. Memory / Allocator
20//! 4. Error handling
21//! 5. String utilities
22//! 6. Tree (document, node, attribute, namespace, DTD, entity)
23//! 7. Parser (SAX, DOM, push, reader)
24//! 8. I/O
25//! 9. Dictionary
26//! 10. Hash table
27//! 11. List
28//! 12. Buffer
29//! 13. Encoding
30//! 14. XPath
31//! 15. XInclude
32//! 16. Catalog
33//! 17. HTML
34//! 18. Debug/misc
35
36#![allow(non_snake_case)]
37#![allow(unused_variables)]
38#![allow(clippy::missing_safety_doc)]
39#![allow(clippy::not_unsafe_ptr_arg_deref)]
40
41use core::ffi::c_void;
42use core::ptr;
43use once_cell::sync::Lazy;
44use parking_lot::Mutex;
45use std::collections::HashMap;
46use std::ffi::CStr;
47use std::mem::size_of;
48use std::os::raw::{c_char, c_int, c_uint};
49
50use crate::xml::xinclude;
51use crate::xml::xpath::ast::CompiledExpr;
52use crate::xml::xpath::context::XPathContext;
53use crate::xml::xpath::types::{NodeSet, XPathValue};
54use crate::xml::xpointer;
55
56use crate::abi::allocator::*;
57use crate::abi::callbacks::*;
58use crate::abi::structs::*;
59use crate::abi::types::*;
60
61// ═══════════════════════════════════════════════════════════════════════════════
62// 1. Initialization / Cleanup
63// ═══════════════════════════════════════════════════════════════════════════════
64
65/// Initialize the parser library.
66///
67/// Must be called before any other libxml2 functions.
68/// Safe to call multiple times (reference-counted in modern libxml2).
69///
70/// # UPSTREAM-PARITY
71///
72/// ```c
73/// void xmlInitParser(void);
74/// ```
75#[no_mangle]
76pub unsafe extern "C" fn xmlInitParser() {
77    crate::internal::globals::init_parser();
78}
79
80/// Clean up the parser library.
81///
82/// Should be called when the library is no longer needed.
83///
84/// # UPSTREAM-PARITY
85///
86/// ```c
87/// void xmlCleanupParser(void);
88/// ```
89#[no_mangle]
90pub unsafe extern "C" fn xmlCleanupParser() {
91    crate::internal::globals::cleanup_parser();
92}
93
94/// Initialize threading support.
95///
96/// # UPSTREAM-PARITY
97///
98/// ```c
99/// int xmlInitThreads(void);
100/// ```
101///
102/// Returns 0 on success.
103#[no_mangle]
104pub unsafe extern "C" fn xmlInitThreads() -> c_int {
105    crate::internal::globals::init_threads()
106}
107
108/// Clean up threading support.
109///
110/// # UPSTREAM-PARITY
111///
112/// ```c
113/// void xmlCleanupThreads(void);
114/// ```
115#[no_mangle]
116pub unsafe extern "C" fn xmlCleanupThreads() {
117    crate::xml::threads::cleanup_threads();
118}
119
120/// Check whether the library has been initialized.
121///
122/// # UPSTREAM-PARITY
123///
124/// ```c
125/// int xmlIsInitialized(void);
126/// ```
127#[no_mangle]
128pub extern "C" fn xmlIsInitialized() -> c_int {
129    if crate::abi::versioning::is_initialized() {
130        1
131    } else {
132        0
133    }
134}
135
136/// Initialize a set of threads (libxml2 compat).
137///
138/// # UPSTREAM-PARITY
139///
140/// ```c
141/// int xmlInitThreads(void);
142/// ```
143/// This is an alias.
144#[no_mangle]
145pub unsafe extern "C" fn xmlLockLibrary() {
146    crate::xml::threads::lock_library();
147}
148
149/// Unlock the library (libxml2 compat).
150///
151/// # UPSTREAM-PARITY
152///
153/// ```c
154/// void xmlUnlockLibrary(void);
155/// ```
156#[no_mangle]
157pub unsafe extern "C" fn xmlUnlockLibrary() {
158    crate::xml::threads::unlock_library();
159}
160
161// ═══════════════════════════════════════════════════════════════════════════════
162// 4. Error Handling
163// ═══════════════════════════════════════════════════════════════════════════════
164
165/// Set the generic error handler.
166///
167/// # UPSTREAM-PARITY
168///
169/// ```c
170/// void xmlSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc handler);
171/// ```
172///
173/// # SAFETY
174///
175/// - `handler` must be a valid function pointer or NULL (to reset to default).
176/// - If non-NULL, the handler may be called at any time with `ctx`.
177#[no_mangle]
178pub unsafe extern "C" fn xmlSetGenericErrorFunc(
179    ctx: *mut c_void,
180    handler: Option<xmlGenericErrorFunc>,
181) {
182    // SAFETY: Delegates to xml::errors with same safety contract.
183    unsafe { crate::xml::errors::set_generic_error_func(ctx, handler) };
184}
185
186/// Set the structured error handler.
187///
188/// # UPSTREAM-PARITY
189///
190/// ```c
191/// void xmlSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc handler);
192/// ```
193///
194/// # SAFETY
195///
196/// - `handler` must be a valid function pointer or NULL.
197#[no_mangle]
198pub unsafe extern "C" fn xmlSetStructuredErrorFunc(
199    ctx: *mut c_void,
200    handler: Option<xmlStructuredErrorFunc>,
201) {
202    // SAFETY: Delegates to xml::errors with same safety contract.
203    unsafe { crate::xml::errors::set_structured_error_func(ctx, handler) };
204}
205
206/// Get the last error for the current thread.
207///
208/// # UPSTREAM-PARITY
209///
210/// ```c
211/// xmlErrorPtr xmlGetLastError(void);
212/// ```
213///
214/// Returns a pointer to the last error, or NULL if no error occurred.
215/// The returned pointer is valid until the next libxml2 call in this thread.
216#[no_mangle]
217pub extern "C" fn xmlGetLastError() -> *mut _xmlError {
218    crate::xml::errors::get_last_error()
219}
220
221/// Get a copy of the last error for the current thread.
222///
223/// # UPSTREAM-PARITY
224///
225/// ```c
226/// xmlErrorPtr xmlCopyError(xmlErrorPtr from, xmlErrorPtr to);
227/// ```
228///
229/// Copies `from` into `to`. Returns 0 on success, -1 on error.
230///
231/// # SAFETY
232///
233/// - `from` and `to` must be valid pointers to `_xmlError` structs, or NULL.
234#[no_mangle]
235pub unsafe extern "C" fn xmlCopyError(from: *const _xmlError, to: *mut _xmlError) -> c_int {
236    // SAFETY: Delegates to xml::errors with same safety contract.
237    unsafe { crate::xml::errors::copy_error(from, to) }
238}
239
240/// Reset an error structure.
241///
242/// # UPSTREAM-PARITY
243///
244/// ```c
245/// void xmlResetError(xmlErrorPtr err);
246/// ```
247///
248/// # SAFETY
249///
250/// - `err` must be a valid pointer to `_xmlError`, or NULL.
251#[no_mangle]
252pub unsafe extern "C" fn xmlResetError(err: *mut _xmlError) {
253    // SAFETY: Delegates to xml::errors with same safety contract.
254    unsafe { crate::xml::errors::reset_error(err) };
255}
256
257/// Raise a structured error.
258///
259/// This is called internally when an error occurs. It updates the last error
260/// and invokes the structured error handler if one is set.
261///
262/// # SAFETY
263///
264/// - `ctxt` may be NULL (context of the error).
265/// - `domain`, `code`, `level`: valid error codes.
266/// - `msg` must be a valid C string or NULL.
267/// - `file` must be a valid C string or NULL.
268/// - `str1`, `str2`, `str3`: error-related strings (may be NULL).
269#[no_mangle]
270pub unsafe extern "C" fn xmlRaiseError(
271    ctxt: *mut c_void,
272    ctxt2: *mut c_void,
273    ctxt3: *mut c_void,
274    ctxt4: *mut c_void,
275    ctxt5: *mut c_void,
276    domain: c_int,
277    code: c_int,
278    level: c_int,
279    file: *const c_char,
280    line: c_int,
281    str1: *const c_char,
282    str2: *const c_char,
283    str3: *const c_char,
284    int1: c_int,
285    int2: c_int,
286    msg: *const c_char,
287) {
288    // SAFETY: Delegates to xml::errors with same safety contract.
289    unsafe {
290        crate::xml::errors::raise_error(
291            ctxt, ctxt2, ctxt3, ctxt4, ctxt5, domain, code, level, file, line, str1, str2, str3,
292            int1, int2, msg,
293        );
294    }
295}
296
297/// Remove any error from the last error stack.
298///
299/// # UPSTREAM-PARITY
300///
301/// ```c
302/// void xmlResetLastError(void);
303/// ```
304#[no_mangle]
305pub extern "C" fn xmlResetLastError() {
306    crate::xml::errors::reset_last_error();
307}
308
309// ═══════════════════════════════════════════════════════════════════════════════
310// 5. String Utilities
311// ═══════════════════════════════════════════════════════════════════════════════
312
313/// Duplicate a string using xmlChar.
314///
315/// # UPSTREAM-PARITY
316///
317/// ```c
318/// xmlChar *xmlStrdup(const xmlChar *cur);
319/// ```
320///
321/// # SAFETY
322///
323/// - `cur` must be a valid null-terminated xmlChar string or NULL.
324#[no_mangle]
325pub unsafe extern "C" fn xmlStrdup(cur: *const xmlChar) -> *mut xmlChar {
326    if cur.is_null() {
327        return ptr::null_mut();
328    }
329    let len = unsafe { xmlStrlen(cur) };
330    let size = len + 1;
331    let new_ptr = unsafe { xmlMalloc(size as usize) };
332    if new_ptr.is_null() {
333        return ptr::null_mut();
334    }
335    unsafe {
336        ptr::copy_nonoverlapping(cur as *const u8, new_ptr as *mut u8, size as usize);
337    }
338    new_ptr as *mut xmlChar
339}
340
341/// Duplicate a substring.
342///
343/// # UPSTREAM-PARITY
344///
345/// ```c
346/// xmlChar *xmlStrndup(const xmlChar *cur, int len);
347/// ```
348///
349/// # SAFETY
350///
351/// - `cur` must be a valid pointer or NULL.
352#[no_mangle]
353pub unsafe extern "C" fn xmlStrndup(cur: *const xmlChar, len: c_int) -> *mut xmlChar {
354    if cur.is_null() || len <= 0 {
355        return ptr::null_mut();
356    }
357    let size = len as usize + 1;
358    let new_ptr = unsafe { xmlMalloc(size) };
359    if new_ptr.is_null() {
360        return ptr::null_mut();
361    }
362    unsafe {
363        ptr::copy_nonoverlapping(cur as *const u8, new_ptr as *mut u8, len as usize);
364        *(new_ptr.add(len as usize) as *mut u8) = 0;
365    }
366    new_ptr as *mut xmlChar
367}
368
369/// Get the length of an xmlChar string.
370///
371/// # UPSTREAM-PARITY
372///
373/// ```c
374/// int xmlStrlen(const xmlChar *str);
375/// ```
376///
377/// # SAFETY
378///
379/// - `str` must be a valid null-terminated string or NULL (returns 0).
380#[no_mangle]
381pub unsafe extern "C" fn xmlStrlen(str: *const xmlChar) -> c_int {
382    if str.is_null() {
383        return 0;
384    }
385    unsafe { libc::strlen(str as *const c_char) as c_int }
386}
387
388/// Compare two xmlChar strings.
389///
390/// # UPSTREAM-PARITY
391///
392/// ```c
393/// int xmlStrcmp(const xmlChar *str1, const xmlChar *str2);
394/// ```
395///
396/// Returns 0 if equal, <0 if str1 < str2, >0 if str1 > str2.
397/// NULL-safe: NULL sorts before any non-NULL string.
398#[no_mangle]
399pub unsafe extern "C" fn xmlStrcmp(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
400    if str1.is_null() && str2.is_null() {
401        return 0;
402    }
403    if str1.is_null() {
404        return -1;
405    }
406    if str2.is_null() {
407        return 1;
408    }
409    unsafe { libc::strcmp(str1 as *const c_char, str2 as *const c_char) as c_int }
410}
411
412/// Compare two xmlChar strings up to a given length.
413///
414/// # UPSTREAM-PARITY
415///
416/// ```c
417/// int xmlStrncmp(const xmlChar *str1, const xmlChar *str2, int len);
418/// ```
419#[no_mangle]
420pub unsafe extern "C" fn xmlStrncmp(
421    str1: *const xmlChar,
422    str2: *const xmlChar,
423    len: c_int,
424) -> c_int {
425    if len <= 0 {
426        return 0;
427    }
428    if str1.is_null() && str2.is_null() {
429        return 0;
430    }
431    if str1.is_null() {
432        return -1;
433    }
434    if str2.is_null() {
435        return 1;
436    }
437    unsafe { libc::strncmp(str1 as *const c_char, str2 as *const c_char, len as usize) as c_int }
438}
439
440/// Case-insensitive comparison of two xmlChar strings.
441///
442/// # UPSTREAM-PARITY
443///
444/// ```c
445/// int xmlStrcasecmp(const xmlChar *str1, const xmlChar *str2);
446/// ```
447#[no_mangle]
448pub unsafe extern "C" fn xmlStrcasecmp(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
449    if str1.is_null() && str2.is_null() {
450        return 0;
451    }
452    if str1.is_null() {
453        return -1;
454    }
455    if str2.is_null() {
456        return 1;
457    }
458    unsafe { libc::strcasecmp(str1 as *const c_char, str2 as *const c_char) as c_int }
459}
460
461/// Case-insensitive comparison with length limit.
462///
463/// # UPSTREAM-PARITY
464///
465/// ```c
466/// int xmlStrncasecmp(const xmlChar *str1, const xmlChar *str2, int len);
467/// ```
468#[no_mangle]
469pub unsafe extern "C" fn xmlStrncasecmp(
470    str1: *const xmlChar,
471    str2: *const xmlChar,
472    len: c_int,
473) -> c_int {
474    if len <= 0 {
475        return 0;
476    }
477    if str1.is_null() && str2.is_null() {
478        return 0;
479    }
480    if str1.is_null() {
481        return -1;
482    }
483    if str2.is_null() {
484        return 1;
485    }
486    unsafe {
487        libc::strncasecmp(str1 as *const c_char, str2 as *const c_char, len as usize) as c_int
488    }
489}
490
491/// Check if two xmlChar strings are equal.
492///
493/// # UPSTREAM-PARITY
494///
495/// ```c
496/// int xmlStrEqual(const xmlChar *str1, const xmlChar *str2);
497/// ```
498///
499/// Returns 1 if equal, 0 if not. NULL-safe.
500#[no_mangle]
501pub unsafe extern "C" fn xmlStrEqual(str1: *const xmlChar, str2: *const xmlChar) -> c_int {
502    if str1.is_null() && str2.is_null() {
503        return 1;
504    }
505    if str1.is_null() || str2.is_null() {
506        return 0;
507    }
508    unsafe { (libc::strcmp(str1 as *const c_char, str2 as *const c_char) == 0) as c_int }
509}
510
511/// Check if an xmlChar string equals a qualified name.
512///
513/// # UPSTREAM-PARITY
514///
515/// ```c
516/// int xmlStrQEqual(const xmlChar *pref, const xmlChar *name, const xmlChar *str);
517/// ```
518///
519/// Returns 1 if `pref:name` equals `str`, 0 otherwise.
520/// `pref` may be NULL (compares only name).
521#[no_mangle]
522pub unsafe extern "C" fn xmlStrQEqual(
523    pref: *const xmlChar,
524    name: *const xmlChar,
525    str: *const xmlChar,
526) -> c_int {
527    if name.is_null() || str.is_null() {
528        return 0;
529    }
530    if pref.is_null() {
531        return unsafe { xmlStrEqual(name, str) };
532    }
533    // Compare "pref:name" with str
534    let pref_len = unsafe { xmlStrlen(pref) };
535    let name_len = unsafe { xmlStrlen(name) };
536    let total_len = pref_len + 1 + name_len;
537    let str_len = unsafe { xmlStrlen(str) };
538    if total_len != str_len {
539        return 0;
540    }
541    // Compare prefix part
542    if unsafe {
543        libc::strncmp(
544            pref as *const c_char,
545            str as *const c_char,
546            pref_len as usize,
547        )
548    } != 0
549    {
550        return 0;
551    }
552    // Check colon
553    if unsafe { *str.add(pref_len as usize) } != b':' as xmlChar {
554        return 0;
555    }
556    // Compare name part
557    (unsafe {
558        libc::strncmp(
559            name as *const c_char,
560            str.add((pref_len + 1) as usize) as *const c_char,
561            name_len as usize,
562        ) == 0
563    }) as c_int
564}
565
566/// Concatenate two strings.
567///
568/// # UPSTREAM-PARITY
569///
570/// ```c
571/// xmlChar *xmlStrcat(xmlChar *cur, const xmlChar *add);
572/// ```
573///
574/// # SAFETY
575///
576/// - `cur` must be a valid xmlMalloc'd string or NULL.
577/// - `add` must be a valid string or NULL.
578/// - If `cur` is NULL, behaves like xmlStrdup(add).
579#[no_mangle]
580pub unsafe extern "C" fn xmlStrcat(cur: *mut xmlChar, add: *const xmlChar) -> *mut xmlChar {
581    if add.is_null() {
582        return cur;
583    }
584    if cur.is_null() {
585        return unsafe { xmlStrdup(add) };
586    }
587    let cur_len = unsafe { xmlStrlen(cur) } as usize;
588    let add_len = unsafe { xmlStrlen(add) } as usize;
589    let new_size = cur_len + add_len + 1;
590    let new_ptr = unsafe { xmlRealloc(cur as *mut c_void, new_size) };
591    if new_ptr.is_null() {
592        return ptr::null_mut();
593    }
594    unsafe {
595        ptr::copy_nonoverlapping(add as *const u8, (new_ptr as *mut u8).add(cur_len), add_len);
596        *((new_ptr as *mut u8).add(cur_len + add_len)) = 0;
597    }
598    new_ptr as *mut xmlChar
599}
600
601/// Concatenate up to `len` characters.
602///
603/// # UPSTREAM-PARITY
604///
605/// ```c
606/// xmlChar *xmlStrncat(xmlChar *cur, const xmlChar *add, int len);
607/// ```
608///
609/// # SAFETY
610///
611/// Same as xmlStrcat, but only copies up to `len` characters from `add`.
612#[no_mangle]
613pub unsafe extern "C" fn xmlStrncat(
614    cur: *mut xmlChar,
615    add: *const xmlChar,
616    len: c_int,
617) -> *mut xmlChar {
618    if add.is_null() || len <= 0 {
619        return cur;
620    }
621    let len = len as usize;
622    if cur.is_null() {
623        return unsafe { xmlStrndup(add, len as c_int) };
624    }
625    let cur_len = unsafe { xmlStrlen(cur) } as usize;
626    let new_size = cur_len + len + 1;
627    let new_ptr = unsafe { xmlRealloc(cur as *mut c_void, new_size) };
628    if new_ptr.is_null() {
629        return ptr::null_mut();
630    }
631    unsafe {
632        ptr::copy_nonoverlapping(add as *const u8, (new_ptr as *mut u8).add(cur_len), len);
633        *((new_ptr as *mut u8).add(cur_len + len)) = 0;
634    }
635    new_ptr as *mut xmlChar
636}
637
638/// Create a new string by concatenating up to `len` characters.
639///
640/// # UPSTREAM-PARITY
641///
642/// ```c
643/// xmlChar *xmlStrncatNew(const xmlChar *str1, const xmlChar *str2, int len);
644/// ```
645#[no_mangle]
646pub unsafe extern "C" fn xmlStrncatNew(
647    str1: *const xmlChar,
648    str2: *const xmlChar,
649    len: c_int,
650) -> *mut xmlChar {
651    let mut result: *mut xmlChar = ptr::null_mut();
652    if !str1.is_null() {
653        result = unsafe { xmlStrdup(str1) };
654    }
655    if !str2.is_null() && len > 0 {
656        result = unsafe { xmlStrncat(result, str2, len) };
657    }
658    result
659}
660
661/// Copy a string.
662///
663/// # UPSTREAM-PARITY
664///
665/// ```c
666/// xmlChar *xmlStrcpy(xmlChar *dst, const xmlChar *src);
667/// ```
668///
669/// # SAFETY
670///
671/// - `dst` must be a valid xmlMalloc'd buffer large enough to hold `src`.
672/// - `src` must be a valid string.
673#[no_mangle]
674pub unsafe extern "C" fn xmlStrcpy(dst: *mut xmlChar, src: *const xmlChar) -> *mut xmlChar {
675    if dst.is_null() || src.is_null() {
676        return dst;
677    }
678    let len = unsafe { xmlStrlen(src) } as usize + 1;
679    unsafe {
680        ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, len);
681    }
682    dst
683}
684
685/// Copy up to `len` characters.
686///
687/// # UPSTREAM-PARITY
688///
689/// ```c
690/// xmlChar *xmlStrncpy(xmlChar *dst, const xmlChar *src, int len);
691/// ```
692#[no_mangle]
693pub unsafe extern "C" fn xmlStrncpy(
694    dst: *mut xmlChar,
695    src: *const xmlChar,
696    len: c_int,
697) -> *mut xmlChar {
698    if dst.is_null() || src.is_null() || len <= 0 {
699        return dst;
700    }
701    let len = len as usize;
702    let src_len = unsafe { xmlStrlen(src) } as usize;
703    let copy_len = if src_len < len { src_len } else { len - 1 };
704    unsafe {
705        ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, copy_len);
706        *dst.add(copy_len) = 0;
707    }
708    dst
709}
710
711/// Extract a substring.
712///
713/// # UPSTREAM-PARITY
714///
715/// ```c
716/// xmlChar *xmlStrsub(const xmlChar *str, int start, int len);
717/// ```
718///
719/// Returns a newly allocated substring, or NULL on error.
720#[no_mangle]
721pub unsafe extern "C" fn xmlStrsub(str: *const xmlChar, start: c_int, len: c_int) -> *mut xmlChar {
722    if str.is_null() || start < 0 || len < 0 {
723        return ptr::null_mut();
724    }
725    let str_len = unsafe { xmlStrlen(str) };
726    if start >= str_len {
727        return unsafe { xmlStrdup(b"\0" as *const u8 as *const xmlChar) };
728    }
729    let actual_len = if start + len > str_len {
730        str_len - start
731    } else {
732        len
733    };
734    unsafe { xmlStrndup(str.add(start as usize), actual_len) }
735}
736
737// ═══════════════════════════════════════════════════════════════════════════════
738// 6. Tree — Document, Node, Attribute, Namespace, DTD, Entity
739// ═══════════════════════════════════════════════════════════════════════════════
740
741/// Create a new document.
742///
743/// # UPSTREAM-PARITY
744///
745/// ```c
746/// xmlDocPtr xmlNewDoc(const xmlChar *version);
747/// ```
748///
749/// # SAFETY
750///
751/// - `version` must be a valid string or NULL (defaults to "1.0").
752/// - Returns a newly allocated document. Caller must free with `xmlFreeDoc`.
753#[no_mangle]
754pub unsafe extern "C" fn xmlNewDoc(version: *const xmlChar) -> *mut _xmlDoc {
755    crate::xml::tree::new_doc(version)
756}
757
758/// Free a document.
759///
760/// # UPSTREAM-PARITY
761///
762/// ```c
763/// void xmlFreeDoc(xmlDocPtr doc);
764/// ```
765///
766/// # SAFETY
767///
768/// - `doc` must be a valid document pointer or NULL.
769#[no_mangle]
770pub unsafe extern "C" fn xmlFreeDoc(doc: *mut _xmlDoc) {
771    crate::xml::tree::free_doc(doc);
772}
773
774/// Create a new node.
775///
776/// # UPSTREAM-PARITY
777///
778/// ```c
779/// xmlNodePtr xmlNewNode(xmlNsPtr ns, const xmlChar *name);
780/// ```
781///
782/// # SAFETY
783///
784/// - `ns` may be NULL.
785/// - `name` must be a valid string.
786/// - Returns a newly allocated node. Caller must free with `xmlFreeNode`.
787#[no_mangle]
788pub unsafe extern "C" fn xmlNewNode(ns: *mut _xmlNs, name: *const xmlChar) -> *mut _xmlNode {
789    crate::xml::tree::new_node(ns, name)
790}
791
792/// Free a node.
793///
794/// # UPSTREAM-PARITY
795///
796/// ```c
797/// void xmlFreeNode(xmlNodePtr node);
798/// ```
799///
800/// # SAFETY
801///
802/// - `node` must be a valid node pointer or NULL.
803/// - The node must NOT be part of a document tree (must be unlinked first).
804#[no_mangle]
805pub unsafe extern "C" fn xmlFreeNode(node: *mut _xmlNode) {
806    crate::xml::tree::free_node(node);
807}
808
809/// Unlink a node from its tree.
810///
811/// # UPSTREAM-PARITY
812///
813/// ```c
814/// void xmlUnlinkNode(xmlNodePtr node);
815/// ```
816///
817/// # SAFETY
818///
819/// - `node` must be a valid node pointer or NULL.
820#[no_mangle]
821pub unsafe extern "C" fn xmlUnlinkNode(node: *mut _xmlNode) {
822    crate::xml::tree::unlink_node(node);
823}
824
825/// Add a child node.
826///
827/// # UPSTREAM-PARITY
828///
829/// ```c
830/// xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur);
831/// ```
832///
833/// # SAFETY
834///
835/// - `parent` must be a valid node.
836/// - `cur` must be a valid node (ownership transfers to parent).
837/// - Returns pointer to the added child (borrowed).
838#[no_mangle]
839pub unsafe extern "C" fn xmlAddChild(parent: *mut _xmlNode, cur: *mut _xmlNode) -> *mut _xmlNode {
840    crate::xml::tree::add_child(parent, cur)
841}
842
843/// Add a sibling node.
844///
845/// # UPSTREAM-PARITY
846///
847/// ```c
848/// xmlNodePtr xmlAddSibling(xmlNodePtr cur, xmlNodePtr sibling);
849/// ```
850///
851/// # SAFETY
852///
853/// Same as xmlAddChild, but adds after `cur` instead of as a child.
854#[no_mangle]
855pub unsafe extern "C" fn xmlAddSibling(
856    cur: *mut _xmlNode,
857    sibling: *mut _xmlNode,
858) -> *mut _xmlNode {
859    crate::xml::tree::add_sibling(cur, sibling)
860}
861
862/// Create a new child element.
863///
864/// # UPSTREAM-PARITY
865///
866/// ```c
867/// xmlNodePtr xmlNewChild(xmlNodePtr parent, xmlNsPtr ns,
868///                        const xmlChar *name, const xmlChar *content);
869/// ```
870///
871/// Creates a new element node, adds it as a child of `parent`, and
872/// sets its content if `content` is non-NULL.
873///
874/// # SAFETY
875///
876/// - `parent` must be a valid node (may be NULL).
877/// - `ns` may be NULL.
878/// - `name` must be a valid string.
879/// - Returns a newly allocated node (owned by parent).
880#[no_mangle]
881pub unsafe extern "C" fn xmlNewChild(
882    parent: *mut _xmlNode,
883    ns: *mut _xmlNs,
884    name: *const xmlChar,
885    content: *const xmlChar,
886) -> *mut _xmlNode {
887    crate::xml::tree::new_child(parent, ns, name)
888}
889
890/// Set the root element of a document.
891///
892/// # UPSTREAM-PARITY
893///
894/// ```c
895/// xmlNodePtr xmlDocSetRootElement(xmlDocPtr doc, xmlNodePtr root);
896/// ```
897///
898/// Returns the old root element (if any), which the caller must free.
899///
900/// # SAFETY
901///
902/// - `doc` must be a valid document.
903/// - `root` must be a valid node (ownership transfers to doc).
904#[no_mangle]
905pub unsafe extern "C" fn xmlDocSetRootElement(
906    doc: *mut _xmlDoc,
907    root: *mut _xmlNode,
908) -> *mut _xmlNode {
909    crate::xml::tree::doc_set_root_element(doc, root)
910}
911
912/// Get the root element of a document.
913///
914/// # UPSTREAM-PARITY
915///
916/// ```c
917/// xmlNodePtr xmlDocGetRootElement(const xmlDoc *doc);
918/// ```
919///
920/// Returns a borrowed pointer (do not free).
921#[no_mangle]
922pub extern "C" fn xmlDocGetRootElement(doc: *const _xmlDoc) -> *mut _xmlNode {
923    crate::xml::tree::doc_get_root_element(doc as *mut _xmlDoc)
924}
925
926/// Copy a node.
927///
928/// # UPSTREAM-PARITY
929///
930/// ```c
931/// xmlNodePtr xmlCopyNode(const xmlNodePtr node, int extended);
932/// ```
933///
934/// If `extended` is 1, copies recursively (deep copy).
935/// If `extended` is 0, copies only the node itself (shallow copy).
936///
937/// Returns a newly allocated copy. Caller must free with `xmlFreeNode`.
938#[no_mangle]
939pub unsafe extern "C" fn xmlCopyNode(node: *const _xmlNode, extended: c_int) -> *mut _xmlNode {
940    crate::xml::tree::copy_node(node, extended)
941}
942
943/// Copy a document.
944///
945/// # UPSTREAM-PARITY
946///
947/// ```c
948/// xmlDocPtr xmlCopyDoc(const xmlDocPtr doc, int recursive);
949/// ```
950///
951/// Returns a newly allocated copy. Caller must free with `xmlFreeDoc`.
952#[no_mangle]
953pub unsafe extern "C" fn xmlCopyDoc(doc: *const _xmlDoc, recursive: c_int) -> *mut _xmlDoc {
954    crate::xml::tree::copy_doc(doc, recursive)
955}
956
957/// Create a text node.
958///
959/// # UPSTREAM-PARITY
960///
961/// ```c
962/// xmlNodePtr xmlNewText(const xmlChar *content);
963/// ```
964///
965/// Creates a new text node with the given content.
966/// If `content` is NULL, creates an empty text node.
967#[no_mangle]
968pub unsafe extern "C" fn xmlNewText(content: *const xmlChar) -> *mut _xmlNode {
969    crate::xml::tree::new_text(content)
970}
971
972/// Create a new comment node.
973///
974/// # UPSTREAM-PARITY
975///
976/// ```c
977/// xmlNodePtr xmlNewComment(const xmlChar *content);
978/// ```
979#[no_mangle]
980pub unsafe extern "C" fn xmlNewComment(content: *const xmlChar) -> *mut _xmlNode {
981    crate::xml::tree::new_comment(content)
982}
983
984/// Create a new PI node.
985///
986/// # UPSTREAM-PARITY
987///
988/// ```c
989/// xmlNodePtr xmlNewPI(const xmlChar *name, const xmlChar *content);
990/// ```
991#[no_mangle]
992pub unsafe extern "C" fn xmlNewPI(name: *const xmlChar, content: *const xmlChar) -> *mut _xmlNode {
993    crate::xml::tree::new_pi(name, content)
994}
995
996/// Create a new CDATA node.
997///
998/// # UPSTREAM-PARITY
999///
1000/// ```c
1001/// xmlNodePtr xmlNewCDataBlock(xmlDocPtr doc, const xmlChar *content, int len);
1002/// ```
1003#[no_mangle]
1004pub unsafe extern "C" fn xmlNewCDataBlock(
1005    doc: *mut _xmlDoc,
1006    content: *const xmlChar,
1007    len: c_int,
1008) -> *mut _xmlNode {
1009    crate::xml::tree::new_cdata_block(doc, content, len)
1010}
1011
1012/// Create a new namespace definition.
1013///
1014/// # UPSTREAM-PARITY
1015///
1016/// ```c
1017/// xmlNsPtr xmlNewNs(xmlNodePtr node, const xmlChar *href, const xmlChar *prefix);
1018/// ```
1019///
1020/// # SAFETY
1021///
1022/// - `node` may be NULL.
1023/// - `href` and `prefix` are copied.
1024/// - Returns a borrowed pointer (namespace is owned by the node).
1025#[no_mangle]
1026pub unsafe extern "C" fn xmlNewNs(
1027    node: *mut _xmlNode,
1028    href: *const xmlChar,
1029    prefix: *const xmlChar,
1030) -> *mut _xmlNs {
1031    crate::xml::tree::new_ns(node, href, prefix)
1032}
1033
1034/// Set the namespace of a node.
1035///
1036/// # UPSTREAM-PARITY
1037///
1038/// ```c
1039/// void xmlSetNs(xmlNodePtr node, xmlNsPtr ns);
1040/// ```
1041#[no_mangle]
1042pub unsafe extern "C" fn xmlSetNs(node: *mut _xmlNode, ns: *mut _xmlNs) {
1043    crate::xml::tree::set_ns(node, ns);
1044}
1045
1046/// Get the namespace of a node.
1047///
1048/// # UPSTREAM-PARITY
1049///
1050/// ```c
1051/// xmlNsPtr xmlGetNsList(xmlDocPtr doc, const xmlNode *node);
1052/// ```
1053#[no_mangle]
1054pub unsafe extern "C" fn xmlGetNsList(
1055    doc: *mut _xmlDoc,
1056    node: *const _xmlNode,
1057) -> *mut *mut _xmlNs {
1058    crate::xml::tree::get_ns_list(doc, node as *mut _xmlNode)
1059}
1060
1061/// Search for a namespace by href.
1062///
1063/// # UPSTREAM-PARITY
1064///
1065/// ```c
1066/// xmlNsPtr xmlSearchNs(xmlDocPtr doc, xmlNodePtr node, const xmlChar *nameSpace);
1067/// ```
1068#[no_mangle]
1069pub unsafe extern "C" fn xmlSearchNs(
1070    doc: *mut _xmlDoc,
1071    node: *mut _xmlNode,
1072    nameSpace: *const xmlChar,
1073) -> *mut _xmlNs {
1074    crate::xml::tree::search_ns(doc, node, nameSpace)
1075}
1076
1077/// Search for a namespace by href, using the full in-scope chain.
1078///
1079/// # UPSTREAM-PARITY
1080///
1081/// ```c
1082/// xmlNsPtr xmlSearchNsByHref(xmlDocPtr doc, xmlNodePtr node, const xmlChar *href);
1083/// ```
1084#[no_mangle]
1085pub unsafe extern "C" fn xmlSearchNsByHref(
1086    doc: *mut _xmlDoc,
1087    node: *mut _xmlNode,
1088    href: *const xmlChar,
1089) -> *mut _xmlNs {
1090    crate::xml::tree::search_ns_by_href(doc, node, href)
1091}
1092
1093/// Set a property (attribute) on a node.
1094///
1095/// # UPSTREAM-PARITY
1096///
1097/// ```c
1098/// xmlAttrPtr xmlSetProp(xmlNodePtr node, const xmlChar *name, const xmlChar *value);
1099/// ```
1100///
1101/// If the attribute already exists, its value is updated.
1102/// Returns a borrowed pointer to the attribute.
1103///
1104/// # SAFETY
1105///
1106/// - `node` must be a valid element node.
1107/// - `name` must be a valid string.
1108/// - `value` may be NULL.
1109#[no_mangle]
1110pub unsafe extern "C" fn xmlSetProp(
1111    node: *mut _xmlNode,
1112    name: *const xmlChar,
1113    value: *const xmlChar,
1114) -> *mut _xmlAttr {
1115    crate::xml::tree::set_prop(node, name, value)
1116}
1117
1118/// Get a property value by name.
1119///
1120/// # UPSTREAM-PARITY
1121///
1122/// ```c
1123/// xmlChar *xmlGetProp(const xmlNode *node, const xmlChar *name);
1124/// ```
1125///
1126/// Returns a newly allocated string. Caller must free with `xmlFree`.
1127#[no_mangle]
1128pub unsafe extern "C" fn xmlGetProp(node: *const _xmlNode, name: *const xmlChar) -> *mut xmlChar {
1129    crate::xml::tree::get_prop(node as *mut _xmlNode, name)
1130}
1131
1132/// Get a namespaced property value.
1133///
1134/// # UPSTREAM-PARITY
1135///
1136/// ```c
1137/// xmlChar *xmlGetNsProp(const xmlNode *node, const xmlChar *name, const xmlChar *nameSpace);
1138/// ```
1139#[no_mangle]
1140pub unsafe extern "C" fn xmlGetNsProp(
1141    node: *const _xmlNode,
1142    name: *const xmlChar,
1143    nameSpace: *const xmlChar,
1144) -> *mut xmlChar {
1145    crate::xml::tree::get_ns_prop(node as *mut _xmlNode, name, nameSpace)
1146}
1147
1148/// Set a namespaced property.
1149///
1150/// # UPSTREAM-PARITY
1151///
1152/// ```c
1153/// xmlAttrPtr xmlSetNsProp(xmlNodePtr node, xmlNsPtr ns,
1154///                         const xmlChar *name, const xmlChar *value);
1155/// ```
1156#[no_mangle]
1157pub unsafe extern "C" fn xmlSetNsProp(
1158    node: *mut _xmlNode,
1159    ns: *mut _xmlNs,
1160    name: *const xmlChar,
1161    value: *const xmlChar,
1162) -> *mut _xmlAttr {
1163    crate::xml::tree::set_ns_prop(node, ns, name, value)
1164}
1165
1166/// Remove a property by name.
1167///
1168/// # UPSTREAM-PARITY
1169///
1170/// ```c
1171/// int xmlRemoveProp(xmlAttrPtr attr);
1172/// ```
1173///
1174/// Returns 0 on success, -1 on error.
1175#[no_mangle]
1176pub unsafe extern "C" fn xmlRemoveProp(attr: *mut _xmlAttr) -> c_int {
1177    crate::xml::tree::remove_prop(attr)
1178}
1179
1180/// Get a DTD from a document, creating one if needed.
1181///
1182/// # UPSTREAM-PARITY
1183///
1184/// ```c
1185/// xmlDtdPtr xmlGetIntSubset(const xmlDoc *doc);
1186/// ```
1187#[no_mangle]
1188pub extern "C" fn xmlGetIntSubset(doc: *const _xmlDoc) -> *mut _xmlDtd {
1189    crate::xml::tree::get_int_subset(doc)
1190}
1191
1192/// Create a new DTD.
1193///
1194/// # UPSTREAM-PARITY
1195///
1196/// ```c
1197/// xmlDtdPtr xmlNewDtd(xmlDocPtr doc, const xmlChar *name,
1198///                     const xmlChar *ExternalID, const xmlChar *SystemID);
1199/// ```
1200#[no_mangle]
1201pub unsafe extern "C" fn xmlNewDtd(
1202    doc: *mut _xmlDoc,
1203    name: *const xmlChar,
1204    ExternalID: *const xmlChar,
1205    SystemID: *const xmlChar,
1206) -> *mut _xmlDtd {
1207    crate::xml::tree::new_dtd(doc, name, ExternalID, SystemID)
1208}
1209
1210/// Create a new entity.
1211///
1212/// # UPSTREAM-PARITY
1213///
1214/// ```c
1215/// xmlEntityPtr xmlNewEntity(xmlDocPtr doc, const xmlChar *name, int type,
1216///                           const xmlChar *ExternalID, const xmlChar *SystemID,
1217///                           const xmlChar *content);
1218/// ```
1219#[no_mangle]
1220pub unsafe extern "C" fn xmlNewEntity(
1221    doc: *mut _xmlDoc,
1222    name: *const xmlChar,
1223    type_: c_int,
1224    ExternalID: *const xmlChar,
1225    SystemID: *const xmlChar,
1226    content: *const xmlChar,
1227) -> *mut _xmlEntity {
1228    crate::xml::tree::new_entity(doc, name, type_, ExternalID, SystemID, content)
1229}
1230
1231/// Get an entity by name.
1232///
1233/// # UPSTREAM-PARITY
1234///
1235/// ```c
1236/// xmlEntityPtr xmlGetDocEntity(const xmlDoc *doc, const xmlChar *name);
1237/// ```
1238#[no_mangle]
1239pub unsafe extern "C" fn xmlGetDocEntity(
1240    doc: *const _xmlDoc,
1241    name: *const xmlChar,
1242) -> *mut _xmlEntity {
1243    crate::xml::tree::get_doc_entity(doc, name)
1244}
1245
1246/// Get a parameter entity by name.
1247///
1248/// # UPSTREAM-PARITY
1249///
1250/// ```c
1251/// xmlEntityPtr xmlGetParameterEntity(const xmlDoc *doc, const xmlChar *name);
1252/// ```
1253#[no_mangle]
1254pub unsafe extern "C" fn xmlGetParameterEntity(
1255    doc: *const _xmlDoc,
1256    name: *const xmlChar,
1257) -> *mut _xmlEntity {
1258    crate::xml::tree::get_parameter_entity(doc, name)
1259}
1260
1261// ── DTD Declaration Exports ────────────────────────────────────────────
1262
1263/// Create an internal subset (DTD).
1264///
1265/// # UPSTREAM-PARITY
1266///
1267/// ```c
1268/// xmlDtdPtr xmlCreateIntSubset(xmlDocPtr doc, const xmlChar *name,
1269///                              const xmlChar *ExternalID, const xmlChar *SystemID);
1270/// ```
1271#[no_mangle]
1272pub unsafe extern "C" fn xmlCreateIntSubset(
1273    doc: *mut _xmlDoc,
1274    name: *const xmlChar,
1275    ExternalID: *const xmlChar,
1276    SystemID: *const xmlChar,
1277) -> *mut _xmlDtd {
1278    crate::xml::dtd::create_int_subset(doc, name, ExternalID, SystemID)
1279}
1280
1281/// Free a DTD.
1282///
1283/// # UPSTREAM-PARITY
1284///
1285/// ```c
1286/// void xmlFreeDtd(xmlDtdPtr dtd);
1287/// ```
1288#[no_mangle]
1289pub unsafe extern "C" fn xmlFreeDtd(dtd: *mut _xmlDtd) {
1290    crate::xml::dtd::free_dtd(dtd);
1291}
1292
1293/// Add a notation declaration.
1294///
1295/// # UPSTREAM-PARITY
1296///
1297/// ```c
1298/// xmlNotationPtr xmlAddNotationDecl(xmlDtdPtr dtd, const xmlChar *name,
1299///                                   const xmlChar *PublicID,
1300///                                   const xmlChar *SystemID);
1301/// ```
1302#[no_mangle]
1303pub unsafe extern "C" fn xmlAddNotationDecl(
1304    dtd: *mut _xmlDtd,
1305    name: *const xmlChar,
1306    PublicID: *const xmlChar,
1307    SystemID: *const xmlChar,
1308) -> *mut _xmlNotation {
1309    crate::xml::dtd::add_notation_decl(dtd, name, PublicID, SystemID)
1310}
1311
1312/// Look up a notation declaration.
1313///
1314/// # UPSTREAM-PARITY
1315///
1316/// ```c
1317/// xmlNotationPtr xmlGetNotationDecl(xmlDtdPtr dtd, const xmlChar *name);
1318/// ```
1319#[no_mangle]
1320pub unsafe extern "C" fn xmlGetNotationDecl(
1321    dtd: *mut _xmlDtd,
1322    name: *const xmlChar,
1323) -> *mut _xmlNotation {
1324    crate::xml::dtd::get_notation_decl(dtd, name)
1325}
1326
1327/// Copy a notation declaration.
1328///
1329/// # UPSTREAM-PARITY
1330///
1331/// ```c
1332/// xmlNotationPtr xmlCopyNotation(xmlNotationPtr notation);
1333/// ```
1334#[no_mangle]
1335pub unsafe extern "C" fn xmlCopyNotation(notation: *mut _xmlNotation) -> *mut _xmlNotation {
1336    crate::xml::dtd::copy_notation(notation)
1337}
1338
1339/// Free a notation declaration.
1340///
1341/// # UPSTREAM-PARITY
1342///
1343/// ```c
1344/// void xmlFreeNotation(xmlNotationPtr notation);
1345/// ```
1346#[no_mangle]
1347pub unsafe extern "C" fn xmlFreeNotation(notation: *mut _xmlNotation) {
1348    crate::xml::dtd::free_notation(notation);
1349}
1350
1351/// Add an element declaration.
1352///
1353/// # UPSTREAM-PARITY
1354///
1355/// ```c
1356/// xmlElementPtr xmlAddElementDecl(xmlDtdPtr dtd, const xmlChar *name, int type,
1357///                                 xmlElementContentPtr content);
1358/// ```
1359#[no_mangle]
1360pub unsafe extern "C" fn xmlAddElementDecl(
1361    dtd: *mut _xmlDtd,
1362    name: *const xmlChar,
1363    type_: c_int,
1364    content: *mut _xmlElementContent,
1365) -> *mut _xmlElement {
1366    crate::xml::dtd::add_element_decl(dtd, name, type_, content)
1367}
1368
1369/// Look up an element declaration.
1370///
1371/// # UPSTREAM-PARITY
1372///
1373/// ```c
1374/// xmlElementPtr xmlGetElementDecl(xmlDtdPtr dtd, const xmlChar *name);
1375/// ```
1376#[no_mangle]
1377pub unsafe extern "C" fn xmlGetElementDecl(
1378    dtd: *mut _xmlDtd,
1379    name: *const xmlChar,
1380) -> *mut _xmlElement {
1381    crate::xml::dtd::get_element_decl(dtd, name)
1382}
1383
1384/// Copy an element declaration.
1385///
1386/// # UPSTREAM-PARITY
1387///
1388/// ```c
1389/// xmlElementPtr xmlCopyElement(xmlElementPtr elem);
1390/// ```
1391#[no_mangle]
1392pub unsafe extern "C" fn xmlCopyElement(elem: *mut _xmlElement) -> *mut _xmlElement {
1393    crate::xml::dtd::copy_element(elem)
1394}
1395
1396/// Free an element declaration.
1397///
1398/// # UPSTREAM-PARITY
1399///
1400/// ```c
1401/// void xmlFreeElement(xmlElementPtr elem);
1402/// ```
1403#[no_mangle]
1404pub unsafe extern "C" fn xmlFreeElement(elem: *mut _xmlElement) {
1405    crate::xml::dtd::free_element(elem);
1406}
1407
1408/// Add an attribute declaration.
1409///
1410/// # UPSTREAM-PARITY
1411///
1412/// ```c
1413/// xmlAttributePtr xmlAddAttributeDecl(xmlDtdPtr dtd, xmlElementPtr elem,
1414///                                     const xmlChar *name, int type, int def,
1415///                                     const xmlChar *defaultValue,
1416///                                     xmlEnumerationPtr tree);
1417/// ```
1418#[no_mangle]
1419pub unsafe extern "C" fn xmlAddAttributeDecl(
1420    dtd: *mut _xmlDtd,
1421    elem: *mut _xmlElement,
1422    name: *const xmlChar,
1423    type_: c_int,
1424    def: c_int,
1425    defaultValue: *const xmlChar,
1426    tree: *mut _xmlEnumeration,
1427) -> *mut _xmlAttribute {
1428    crate::xml::dtd::add_attribute_decl(dtd, elem, name, type_, def, defaultValue, tree)
1429}
1430
1431/// Look up an attribute declaration.
1432///
1433/// # UPSTREAM-PARITY
1434///
1435/// ```c
1436/// xmlAttributePtr xmlGetAttributeDecl(xmlDtdPtr dtd, xmlElementPtr elem,
1437///                                     const xmlChar *name, int namePrefix);
1438/// ```
1439#[no_mangle]
1440pub unsafe extern "C" fn xmlGetAttributeDecl(
1441    dtd: *mut _xmlDtd,
1442    elem: *mut _xmlElement,
1443    name: *const xmlChar,
1444    namePrefix: c_int,
1445) -> *mut _xmlAttribute {
1446    crate::xml::dtd::get_attribute_decl(dtd, elem, name, namePrefix)
1447}
1448
1449/// Copy an attribute declaration.
1450///
1451/// # UPSTREAM-PARITY
1452///
1453/// ```c
1454/// xmlAttributePtr xmlCopyAttribute(xmlAttributePtr attr);
1455/// ```
1456#[no_mangle]
1457pub unsafe extern "C" fn xmlCopyAttribute(attr: *mut _xmlAttribute) -> *mut _xmlAttribute {
1458    crate::xml::dtd::copy_attribute_decl(attr)
1459}
1460
1461/// Free an attribute declaration.
1462///
1463/// # UPSTREAM-PARITY
1464///
1465/// ```c
1466/// void xmlFreeAttribute(xmlAttributePtr attr);
1467/// ```
1468#[no_mangle]
1469pub unsafe extern "C" fn xmlFreeAttribute(attr: *mut _xmlAttribute) {
1470    crate::xml::dtd::free_attribute(attr);
1471}
1472
1473/// Create a new element content model.
1474///
1475/// # UPSTREAM-PARITY
1476///
1477/// ```c
1478/// xmlElementContentPtr xmlNewElementContent(const xmlChar *name, int type);
1479/// ```
1480#[no_mangle]
1481pub unsafe extern "C" fn xmlNewElementContent(
1482    name: *const xmlChar,
1483    type_: c_int,
1484) -> *mut _xmlElementContent {
1485    crate::xml::dtd::create_content_model(name, type_)
1486}
1487
1488/// Copy an element content model.
1489///
1490/// # UPSTREAM-PARITY
1491///
1492/// ```c
1493/// xmlElementContentPtr xmlCopyElementContent(xmlElementContentPtr content);
1494/// ```
1495#[no_mangle]
1496pub unsafe extern "C" fn xmlCopyElementContent(
1497    content: *mut _xmlElementContent,
1498) -> *mut _xmlElementContent {
1499    crate::xml::dtd::copy_content_model(content)
1500}
1501
1502/// Free an element content model.
1503///
1504/// # UPSTREAM-PARITY
1505///
1506/// ```c
1507/// void xmlFreeElementContent(xmlElementContentPtr cur);
1508/// ```
1509#[no_mangle]
1510pub unsafe extern "C" fn xmlFreeElementContent(cur: *mut _xmlElementContent) {
1511    crate::xml::dtd::free_content_model(cur);
1512}
1513
1514// ── Entity Exports ─────────────────────────────────────────────────────
1515
1516/// Add an entity declaration.
1517///
1518/// # UPSTREAM-PARITY
1519///
1520/// ```c
1521/// xmlEntityPtr xmlAddEntity(xmlDtdPtr dtd, const xmlChar *name, int type,
1522///                           const xmlChar *ExternalID, const xmlChar *SystemID,
1523///                           const xmlChar *content);
1524/// ```
1525#[no_mangle]
1526pub unsafe extern "C" fn xmlAddEntity(
1527    dtd: *mut _xmlDtd,
1528    name: *const xmlChar,
1529    type_: c_int,
1530    ExternalID: *const xmlChar,
1531    SystemID: *const xmlChar,
1532    content: *const xmlChar,
1533) -> *mut _xmlEntity {
1534    crate::xml::entities::add_entity(dtd, name, type_, ExternalID, SystemID, content)
1535}
1536
1537/// Get an entity by name.
1538///
1539/// # UPSTREAM-PARITY
1540///
1541/// ```c
1542/// xmlEntityPtr xmlGetEntity(xmlDocPtr doc, const xmlChar *name);
1543/// ```
1544#[no_mangle]
1545pub unsafe extern "C" fn xmlGetEntity(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlEntity {
1546    crate::xml::entities::get_entity(doc, name)
1547}
1548
1549/// Copy an entity.
1550///
1551/// # UPSTREAM-PARITY
1552///
1553/// ```c
1554/// xmlEntityPtr xmlCopyEntity(xmlEntityPtr entity);
1555/// ```
1556#[no_mangle]
1557pub unsafe extern "C" fn xmlCopyEntity(entity: *mut _xmlEntity) -> *mut _xmlEntity {
1558    crate::xml::entities::copy_entity(entity)
1559}
1560
1561/// Free an entity.
1562///
1563/// # UPSTREAM-PARITY
1564///
1565/// ```c
1566/// void xmlFreeEntity(xmlEntityPtr entity);
1567/// ```
1568#[no_mangle]
1569pub unsafe extern "C" fn xmlFreeEntity(entity: *mut _xmlEntity) {
1570    crate::xml::entities::free_entity(entity);
1571}
1572
1573/// Encode entities for reentrant output.
1574///
1575/// # UPSTREAM-PARITY
1576///
1577/// ```c
1578/// xmlChar *xmlEncodeEntitiesReentrant(xmlDocPtr doc, const xmlChar *input);
1579/// ```
1580#[no_mangle]
1581pub unsafe extern "C" fn xmlEncodeEntitiesReentrant(
1582    doc: *mut _xmlDoc,
1583    input: *const xmlChar,
1584) -> *mut xmlChar {
1585    crate::xml::entities::encode_entities_reentrant(doc, input)
1586}
1587
1588/// Get the line number of a node.
1589///
1590/// # UPSTREAM-PARITY
1591///
1592/// ```c
1593/// long xmlGetLineNo(const xmlNode *node);
1594/// ```
1595#[no_mangle]
1596pub extern "C" fn xmlGetLineNo(node: *const _xmlNode) -> c_int {
1597    crate::xml::tree::get_line_no(node)
1598}
1599
1600// ═══════════════════════════════════════════════════════════════════════════════
1601// Serialization — xmlNodeDump, xmlDocDump, xmlSaveFile, etc.
1602// ═══════════════════════════════════════════════════════════════════════════════
1603
1604/// Dump a node to a buffer.
1605///
1606/// # UPSTREAM-PARITY
1607///
1608/// ```c
1609/// int xmlNodeDump(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur, int level, int format);
1610/// ```
1611#[no_mangle]
1612pub unsafe extern "C" fn xmlNodeDump(
1613    buf: *mut _xmlBuffer,
1614    doc: *mut _xmlDoc,
1615    cur: *mut _xmlNode,
1616    level: c_int,
1617    format: c_int,
1618) -> c_int {
1619    if buf.is_null() || cur.is_null() {
1620        return -1;
1621    }
1622    crate::xml::tree::xmlNodeDump(buf, doc, cur, level, format)
1623}
1624
1625/// Dump a document to a file pointer.
1626///
1627/// # UPSTREAM-PARITY
1628///
1629/// ```c
1630/// int xmlDocDump(FILE *f, xmlDocPtr doc);
1631/// ```
1632#[no_mangle]
1633pub unsafe extern "C" fn xmlDocDump(fp: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1634    if fp.is_null() || doc.is_null() {
1635        return -1;
1636    }
1637    crate::xml::tree::xmlDocDump(fp, doc)
1638}
1639
1640/// Dump a document to memory with format.
1641///
1642/// # UPSTREAM-PARITY
1643///
1644/// ```c
1645/// void xmlDocDumpFormatMemory(xmlDocPtr doc, xmlChar **mem, int *size, int format);
1646/// ```
1647#[no_mangle]
1648pub unsafe extern "C" fn xmlDocDumpFormatMemory(
1649    doc: *mut _xmlDoc,
1650    mem: *mut *mut xmlChar,
1651    size: *mut c_int,
1652    format: c_int,
1653) {
1654    if doc.is_null() || mem.is_null() || size.is_null() {
1655        return;
1656    }
1657    crate::xml::tree::xmlDocDumpFormatMemory(doc, mem, size, format)
1658}
1659
1660/// Dump a document to memory (unformatted).
1661///
1662/// # UPSTREAM-PARITY
1663///
1664/// ```c
1665/// void xmlDocDumpMemory(xmlDocPtr doc, xmlChar **mem, int *size);
1666/// ```
1667#[no_mangle]
1668pub unsafe extern "C" fn xmlDocDumpMemory(
1669    doc: *mut _xmlDoc,
1670    mem: *mut *mut xmlChar,
1671    size: *mut c_int,
1672) {
1673    if doc.is_null() || mem.is_null() || size.is_null() {
1674        return;
1675    }
1676    crate::xml::tree::xmlDocDumpMemory(doc, mem, size)
1677}
1678
1679/// Save a document to a file.
1680///
1681/// # UPSTREAM-PARITY
1682///
1683/// ```c
1684/// int xmlSaveFile(const char *filename, xmlDocPtr cur);
1685/// ```
1686#[no_mangle]
1687pub unsafe extern "C" fn xmlSaveFile(filename: *const c_char, cur: *mut _xmlDoc) -> c_int {
1688    if filename.is_null() || cur.is_null() {
1689        return -1;
1690    }
1691    crate::xml::tree::xmlSaveFile(filename, cur)
1692}
1693
1694/// Save a document to a file with encoding.
1695///
1696/// # UPSTREAM-PARITY
1697///
1698/// ```c
1699/// int xmlSaveFileEnc(const char *filename, xmlDocPtr cur, const char *encoding);
1700/// ```
1701#[no_mangle]
1702pub unsafe extern "C" fn xmlSaveFileEnc(
1703    filename: *const c_char,
1704    cur: *mut _xmlDoc,
1705    encoding: *const c_char,
1706) -> c_int {
1707    if filename.is_null() || cur.is_null() {
1708        return -1;
1709    }
1710    crate::xml::tree::xmlSaveFileEnc(filename, cur, encoding)
1711}
1712
1713/// Save a document to a file with format.
1714///
1715/// # UPSTREAM-PARITY
1716///
1717/// ```c
1718/// int xmlSaveFormatFile(const char *filename, xmlDocPtr cur, int format);
1719/// ```
1720#[no_mangle]
1721pub unsafe extern "C" fn xmlSaveFormatFile(
1722    filename: *const c_char,
1723    cur: *mut _xmlDoc,
1724    format: c_int,
1725) -> c_int {
1726    if filename.is_null() || cur.is_null() {
1727        return -1;
1728    }
1729    crate::xml::tree::xmlSaveFormatFile(filename, cur, format)
1730}
1731
1732/// Save a document to a file with encoding and format.
1733///
1734/// # UPSTREAM-PARITY
1735///
1736/// ```c
1737/// int xmlSaveFormatFileEnc(const char *filename, xmlDocPtr cur, const char *encoding, int format);
1738/// ```
1739#[no_mangle]
1740pub unsafe extern "C" fn xmlSaveFormatFileEnc(
1741    filename: *const c_char,
1742    cur: *mut _xmlDoc,
1743    encoding: *const c_char,
1744    format: c_int,
1745) -> c_int {
1746    if filename.is_null() || cur.is_null() {
1747        return -1;
1748    }
1749    crate::xml::tree::xmlSaveFormatFileEnc(filename, cur, encoding, format)
1750}
1751
1752// ═══════════════════════════════════════════════════════════════════════════════
1753// 7. Parser — SAX, DOM, Push, Reader
1754// ═══════════════════════════════════════════════════════════════════════════════
1755
1756/// Read an XML document from a string.
1757///
1758/// # UPSTREAM-PARITY
1759///
1760/// ```c
1761/// xmlDocPtr xmlReadDoc(const xmlChar *cur, const char *URL,
1762///                      const char *encoding, int options);
1763/// ```
1764///
1765/// Returns a parsed document. Caller must free with `xmlFreeDoc`.
1766#[no_mangle]
1767pub unsafe extern "C" fn xmlReadDoc(
1768    cur: *const xmlChar,
1769    URL: *const c_char,
1770    encoding: *const c_char,
1771    options: c_int,
1772) -> *mut _xmlDoc {
1773    // SAFETY: cur must be a valid null-terminated xmlChar string if non-null.
1774    if cur.is_null() {
1775        return ptr::null_mut();
1776    }
1777    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1778    if ctxt.is_null() {
1779        return ptr::null_mut();
1780    }
1781    let len = crate::xml::string::xml_strlen(cur);
1782    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
1783    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1784    (*ctxt).options = options;
1785    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1786        let doc = (*ctxt).myDoc;
1787        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1788        return doc;
1789    }
1790    let doc = (*ctxt).myDoc;
1791    if !doc.is_null() {
1792        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1793    }
1794    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1795    doc
1796}
1797
1798/// Read an XML document from a file.
1799///
1800/// # UPSTREAM-PARITY
1801///
1802/// ```c
1803/// xmlDocPtr xmlReadFile(const char *URL, const char *encoding, int options);
1804/// ```
1805#[no_mangle]
1806pub unsafe extern "C" fn xmlReadFile(
1807    URL: *const c_char,
1808    encoding: *const c_char,
1809    options: c_int,
1810) -> *mut _xmlDoc {
1811    // SAFETY: URL must be a valid C string or NULL.
1812    if URL.is_null() {
1813        return ptr::null_mut();
1814    }
1815    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1816    if ctxt.is_null() {
1817        return ptr::null_mut();
1818    }
1819    let input = match crate::xml::parser::helpers::input_from_file(URL) {
1820        Ok(input) => input,
1821        Err(_) => {
1822            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1823            return ptr::null_mut();
1824        }
1825    };
1826    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1827    (*ctxt).options = options;
1828    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1829        let doc = (*ctxt).myDoc;
1830        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1831        // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
1832        // partially built document is discarded and NULL is returned; with
1833        // XML_PARSE_RECOVER the partial tree is kept.
1834        if options & 1 << 0 != 0 {
1835            return doc;
1836        }
1837        if !doc.is_null() {
1838            crate::xml::tree::free_doc(doc);
1839        }
1840        return ptr::null_mut();
1841    }
1842    let doc = (*ctxt).myDoc;
1843    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1844    doc
1845}
1846
1847/// Read an XML document from memory.
1848///
1849/// # UPSTREAM-PARITY
1850///
1851/// ```c
1852/// xmlDocPtr xmlReadMemory(const char *buffer, int size,
1853///                         const char *URL, const char *encoding, int options);
1854/// ```
1855#[no_mangle]
1856pub unsafe extern "C" fn xmlReadMemory(
1857    buffer: *const c_char,
1858    size: c_int,
1859    URL: *const c_char,
1860    encoding: *const c_char,
1861    options: c_int,
1862) -> *mut _xmlDoc {
1863    // SAFETY: buffer must be a valid pointer with at least `size` readable bytes.
1864    if buffer.is_null() || size <= 0 {
1865        return ptr::null_mut();
1866    }
1867    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1868    if ctxt.is_null() {
1869        return ptr::null_mut();
1870    }
1871    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
1872    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1873    (*ctxt).options = options;
1874    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1875        let doc = (*ctxt).myDoc;
1876        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1877        // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
1878        // partially built document is discarded and NULL is returned; with
1879        // XML_PARSE_RECOVER the partial tree is kept.
1880        if options & 1 << 0 != 0 {
1881            return doc;
1882        }
1883        if !doc.is_null() {
1884            crate::xml::tree::free_doc(doc);
1885        }
1886        return ptr::null_mut();
1887    }
1888    let doc = (*ctxt).myDoc;
1889    if !doc.is_null() && !URL.is_null() {
1890        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1891    }
1892    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1893    doc
1894}
1895
1896/// Load a list of catalogs (upstream `xmlLoadCatalogs`).
1897///
1898/// # SAFETY
1899///
1900/// - `catalogs` must be a valid NUL-terminated string or NULL.
1901#[no_mangle]
1902pub unsafe extern "C" fn xmlLoadCatalogs(catalogs: *const c_char) {
1903    if !catalogs.is_null() {
1904        crate::xml::catalog::load_catalog(catalogs);
1905    }
1906}
1907
1908/// Load a single catalog (upstream `xmlLoadCatalog`).
1909///
1910/// # SAFETY
1911///
1912/// - `catalogs` must be a valid NUL-terminated string or NULL.
1913#[no_mangle]
1914pub unsafe extern "C" fn xmlLoadCatalog(catalogs: *const c_char) -> *mut c_void {
1915    crate::xml::catalog::load_catalog(catalogs)
1916}
1917
1918/// Read an XML document from a file descriptor.
1919///
1920/// # UPSTREAM-PARITY
1921///
1922/// ```c
1923/// xmlDocPtr xmlReadFd(int fd, const char *URL, const char *encoding, int options);
1924/// ```
1925#[no_mangle]
1926pub unsafe extern "C" fn xmlReadFd(
1927    fd: c_int,
1928    URL: *const c_char,
1929    encoding: *const c_char,
1930    options: c_int,
1931) -> *mut _xmlDoc {
1932    // SAFETY: fd must be a valid open file descriptor.
1933    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1934    if ctxt.is_null() {
1935        return ptr::null_mut();
1936    }
1937    // Read all data from the fd
1938    let mut buf = Vec::new();
1939    let mut tmp = [0u8; 4096];
1940    loop {
1941        let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
1942        if n <= 0 {
1943            break;
1944        }
1945        buf.extend_from_slice(&tmp[..n as usize]);
1946    }
1947    let input = crate::xml::parser::helpers::input_from_memory(
1948        buf.as_ptr() as *const c_char,
1949        buf.len() as c_int,
1950    );
1951    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1952    (*ctxt).options = options;
1953    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1954        let doc = (*ctxt).myDoc;
1955        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1956        return doc;
1957    }
1958    let doc = (*ctxt).myDoc;
1959    if !doc.is_null() && !URL.is_null() {
1960        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1961    }
1962    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1963    doc
1964}
1965
1966/// Read an XML document from I/O callbacks.
1967///
1968/// # UPSTREAM-PARITY
1969///
1970/// ```c
1971/// xmlDocPtr xmlReadIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
1972///                     void *ioctx, const char *URL, const char *encoding, int options);
1973/// ```
1974#[no_mangle]
1975pub unsafe extern "C" fn xmlReadIO(
1976    ioread: Option<xmlInputReadCallback>,
1977    ioclose: Option<xmlInputCloseCallback>,
1978    ioctx: *mut c_void,
1979    URL: *const c_char,
1980    encoding: *const c_char,
1981    options: c_int,
1982) -> *mut _xmlDoc {
1983    // SAFETY: callbacks must be valid function pointers if non-NULL.
1984    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1985    if ctxt.is_null() {
1986        return ptr::null_mut();
1987    }
1988    let input = crate::xml::parser::helpers::input_from_io(ioread, ioclose, ioctx);
1989    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1990    (*ctxt).options = options;
1991    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1992        let doc = (*ctxt).myDoc;
1993        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1994        return doc;
1995    }
1996    let doc = (*ctxt).myDoc;
1997    if !doc.is_null() && !URL.is_null() {
1998        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1999    }
2000    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2001    doc
2002}
2003
2004/// Parse an XML document (SAX1).
2005///
2006/// # UPSTREAM-PARITY
2007///
2008/// ```c
2009/// xmlDocPtr xmlSAXParseDoc(xmlSAXHandlerPtr sax, const xmlChar *cur, int recovery);
2010/// ```
2011#[no_mangle]
2012pub unsafe extern "C" fn xmlSAXParseDoc(
2013    sax: *mut _xmlSAXHandler,
2014    cur: *const xmlChar,
2015    recovery: c_int,
2016) -> *mut _xmlDoc {
2017    // SAFETY: cur must be a valid null-terminated xmlChar string.
2018    if cur.is_null() {
2019        return ptr::null_mut();
2020    }
2021    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2022    if ctxt.is_null() {
2023        return ptr::null_mut();
2024    }
2025    if !sax.is_null() {
2026        (*ctxt).sax = sax;
2027        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2028    }
2029    if recovery != 0 {
2030        (*ctxt).recovery = 1;
2031        (*ctxt).options |= 1; // XML_PARSE_RECOVER
2032    }
2033    let len = crate::xml::string::xml_strlen(cur);
2034    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
2035    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2036    crate::xml::parser::helpers::parse_document(ctxt);
2037    let doc = (*ctxt).myDoc;
2038    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2039    doc
2040}
2041
2042/// Parse an XML file (SAX1).
2043///
2044/// # UPSTREAM-PARITY
2045///
2046/// ```c
2047/// xmlDocPtr xmlSAXParseFile(xmlSAXHandlerPtr sax, const char *filename, int recovery);
2048/// ```
2049#[no_mangle]
2050pub unsafe extern "C" fn xmlSAXParseFile(
2051    sax: *mut _xmlSAXHandler,
2052    filename: *const c_char,
2053    recovery: c_int,
2054) -> *mut _xmlDoc {
2055    // SAFETY: filename must be a valid C string.
2056    if filename.is_null() {
2057        return ptr::null_mut();
2058    }
2059    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2060    if ctxt.is_null() {
2061        return ptr::null_mut();
2062    }
2063    if !sax.is_null() {
2064        (*ctxt).sax = sax;
2065        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2066    }
2067    if recovery != 0 {
2068        (*ctxt).recovery = 1;
2069        (*ctxt).options |= 1;
2070    }
2071    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2072        Ok(input) => input,
2073        Err(_) => {
2074            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2075            return ptr::null_mut();
2076        }
2077    };
2078    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2079    crate::xml::parser::helpers::parse_document(ctxt);
2080    let doc = (*ctxt).myDoc;
2081    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2082    doc
2083}
2084
2085/// Parse an XML document from memory (SAX1).
2086///
2087/// # UPSTREAM-PARITY
2088///
2089/// ```c
2090/// xmlDocPtr xmlSAXParseMemory(xmlSAXHandlerPtr sax,
2091///                             const char *buffer, int size, int recovery);
2092/// ```
2093#[no_mangle]
2094pub unsafe extern "C" fn xmlSAXParseMemory(
2095    sax: *mut _xmlSAXHandler,
2096    buffer: *const c_char,
2097    size: c_int,
2098    recovery: c_int,
2099) -> *mut _xmlDoc {
2100    // SAFETY: buffer must be valid with at least `size` bytes.
2101    if buffer.is_null() || size <= 0 {
2102        return ptr::null_mut();
2103    }
2104    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2105    if ctxt.is_null() {
2106        return ptr::null_mut();
2107    }
2108    if !sax.is_null() {
2109        (*ctxt).sax = sax;
2110        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2111    }
2112    if recovery != 0 {
2113        (*ctxt).recovery = 1;
2114        (*ctxt).options |= 1;
2115    }
2116    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
2117    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2118    crate::xml::parser::helpers::parse_document(ctxt);
2119    let doc = (*ctxt).myDoc;
2120    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2121    doc
2122}
2123
2124/// SAX user parse file.
2125///
2126/// # UPSTREAM-PARITY
2127///
2128/// ```c
2129/// int xmlSAXUserParseFile(xmlSAXHandlerPtr sax, void *user_data,
2130///                         const char *filename);
2131/// ```
2132#[no_mangle]
2133pub unsafe extern "C" fn xmlSAXUserParseFile(
2134    sax: *mut _xmlSAXHandler,
2135    user_data: *mut c_void,
2136    filename: *const c_char,
2137) -> c_int {
2138    // SAFETY: filename must be a valid C string. sax and user_data may be NULL.
2139    if filename.is_null() {
2140        return -1;
2141    }
2142    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2143    if ctxt.is_null() {
2144        return -1;
2145    }
2146    if !sax.is_null() {
2147        (*ctxt).sax = sax;
2148    }
2149    (*ctxt).userData = if !user_data.is_null() {
2150        user_data
2151    } else {
2152        ctxt as *mut c_void
2153    };
2154    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2155        Ok(input) => input,
2156        Err(_) => {
2157            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2158            return -1;
2159        }
2160    };
2161    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2162    let ret = crate::xml::parser::helpers::parse_document(ctxt);
2163    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2164    ret
2165}
2166
2167/// SAX user parse memory.
2168///
2169/// # UPSTREAM-PARITY
2170///
2171/// ```c
2172/// int xmlSAXUserParseMemory(xmlSAXHandlerPtr sax, void *user_data,
2173///                           const char *buffer, int size);
2174/// ```
2175#[no_mangle]
2176pub unsafe extern "C" fn xmlSAXUserParseMemory(
2177    sax: *mut _xmlSAXHandler,
2178    user_data: *mut c_void,
2179    buffer: *const c_char,
2180    size: c_int,
2181) -> c_int {
2182    // SAFETY: buffer must be valid with at least `size` bytes.
2183    if buffer.is_null() || size <= 0 {
2184        return -1;
2185    }
2186    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2187    if ctxt.is_null() {
2188        return -1;
2189    }
2190    if !sax.is_null() {
2191        (*ctxt).sax = sax;
2192    }
2193    (*ctxt).userData = if !user_data.is_null() {
2194        user_data
2195    } else {
2196        ctxt as *mut c_void
2197    };
2198    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
2199    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2200    let ret = crate::xml::parser::helpers::parse_document(ctxt);
2201    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2202    ret
2203}
2204
2205/// Parse an XML document from a string (DOM).
2206///
2207/// # UPSTREAM-PARITY
2208///
2209/// ```c
2210/// xmlDocPtr xmlParseDoc(const xmlChar *cur);
2211/// ```
2212#[no_mangle]
2213pub unsafe extern "C" fn xmlParseDoc(cur: *const xmlChar) -> *mut _xmlDoc {
2214    // SAFETY: cur must be a valid null-terminated xmlChar string.
2215    if cur.is_null() {
2216        return ptr::null_mut();
2217    }
2218    xmlReadDoc(cur, ptr::null(), ptr::null(), 0)
2219}
2220
2221/// Parse an XML file (DOM).
2222///
2223/// # UPSTREAM-PARITY
2224///
2225/// ```c
2226/// xmlDocPtr xmlParseFile(const char *filename);
2227/// ```
2228#[no_mangle]
2229pub unsafe extern "C" fn xmlParseFile(filename: *const c_char) -> *mut _xmlDoc {
2230    // SAFETY: filename must be a valid C string.
2231    if filename.is_null() {
2232        return ptr::null_mut();
2233    }
2234    xmlReadFile(filename, ptr::null(), 0)
2235}
2236
2237/// Parse an XML document from memory (DOM).
2238///
2239/// # UPSTREAM-PARITY
2240///
2241/// ```c
2242/// xmlDocPtr xmlParseMemory(const char *buffer, int size);
2243/// ```
2244#[no_mangle]
2245pub unsafe extern "C" fn xmlParseMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
2246    // SAFETY: buffer must be valid with at least `size` bytes.
2247    if buffer.is_null() || size <= 0 {
2248        return ptr::null_mut();
2249    }
2250    xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 0)
2251}
2252
2253/// Create a file parser context.
2254///
2255/// # UPSTREAM-PARITY
2256///
2257/// ```c
2258/// xmlParserCtxtPtr xmlCreateFileParserCtxt(const char *filename);
2259/// ```
2260#[no_mangle]
2261pub unsafe extern "C" fn xmlCreateFileParserCtxt(filename: *const c_char) -> *mut _xmlParserCtxt {
2262    // SAFETY: filename must be a valid C string.
2263    if filename.is_null() {
2264        return ptr::null_mut();
2265    }
2266    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2267    if ctxt.is_null() {
2268        return ptr::null_mut();
2269    }
2270    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2271        Ok(input) => input,
2272        Err(_) => {
2273            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2274            return ptr::null_mut();
2275        }
2276    };
2277    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2278    ctxt
2279}
2280
2281/// Create a document parser context.
2282///
2283/// # UPSTREAM-PARITY
2284///
2285/// ```c
2286/// xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar *cur);
2287/// ```
2288#[no_mangle]
2289pub unsafe extern "C" fn xmlCreateDocParserCtxt(cur: *const xmlChar) -> *mut _xmlParserCtxt {
2290    // SAFETY: cur must be a valid null-terminated xmlChar string.
2291    if cur.is_null() {
2292        return ptr::null_mut();
2293    }
2294    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2295    if ctxt.is_null() {
2296        return ptr::null_mut();
2297    }
2298    let len = crate::xml::string::xml_strlen(cur);
2299    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
2300    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2301    ctxt
2302}
2303
2304/// Parse a document using an existing parser context.
2305///
2306/// # UPSTREAM-PARITY
2307///
2308/// ```c
2309/// int xmlParseDocument(xmlParserCtxtPtr ctxt);
2310/// ```
2311#[no_mangle]
2312pub unsafe extern "C" fn xmlParseDocument(ctxt: *mut _xmlParserCtxt) -> c_int {
2313    // SAFETY: ctxt must be a valid parser context.
2314    if ctxt.is_null() {
2315        return -1;
2316    }
2317    crate::xml::parser::helpers::parse_document(ctxt)
2318}
2319
2320/// Free a parser context.
2321///
2322/// # UPSTREAM-PARITY
2323///
2324/// ```c
2325/// void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt);
2326/// ```
2327#[no_mangle]
2328pub unsafe extern "C" fn xmlFreeParserCtxt(ctxt: *mut _xmlParserCtxt) {
2329    if ctxt.is_null() {
2330        return;
2331    }
2332    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2333}
2334
2335/// Set parser options.
2336///
2337/// # UPSTREAM-PARITY
2338///
2339/// ```c
2340/// int xmlCtxtUseOptions(xmlParserCtxtPtr ctxt, int options);
2341/// ```
2342#[no_mangle]
2343pub unsafe extern "C" fn xmlCtxtUseOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
2344    if ctxt.is_null() {
2345        return -1;
2346    }
2347    // Phase 1: STUB
2348    unsafe {
2349        (*ctxt).options = options;
2350    }
2351    0
2352}
2353
2354/// Parse a well-balanced chunk (for push parsing).
2355///
2356/// # UPSTREAM-PARITY
2357///
2358/// ```c
2359/// xmlParserErrors xmlParseChunk(xmlParserCtxtPtr ctxt,
2360///                               const char *chunk, int size, int terminate);
2361/// ```
2362#[no_mangle]
2363pub unsafe extern "C" fn xmlParseChunk(
2364    ctxt: *mut _xmlParserCtxt,
2365    chunk: *const c_char,
2366    size: c_int,
2367    terminate: c_int,
2368) -> c_int {
2369    // SAFETY: ctxt must be a valid parser context.
2370    // chunk may be NULL if terminate is set (finalize without data).
2371    if ctxt.is_null() {
2372        return -1;
2373    }
2374    crate::xml::parser::helpers::parse_chunk(ctxt, chunk, size, terminate)
2375}
2376
2377/// Create a memory parser input buffer.
2378///
2379/// # UPSTREAM-PARITY
2380///
2381/// ```c
2382/// xmlParserInputBufferPtr xmlParserInputBufferCreateMem(const char *buffer, int size, int enc);
2383/// ```
2384#[no_mangle]
2385pub unsafe extern "C" fn xmlParserInputBufferCreateMem(
2386    buffer: *const c_char,
2387    size: c_int,
2388    enc: c_int,
2389) -> *mut _xmlParserInputBuffer {
2390    // SAFETY: buffer must be valid with at least `size` bytes.
2391    if buffer.is_null() || size <= 0 {
2392        return ptr::null_mut();
2393    }
2394    crate::xml::parser::helpers::alloc_parser_input_buffer()
2395}
2396
2397/// Create a file parser input buffer.
2398///
2399/// # UPSTREAM-PARITY
2400///
2401/// ```c
2402/// xmlParserInputBufferPtr xmlParserInputBufferCreateFilename(const char *URI, int enc);
2403/// ```
2404#[no_mangle]
2405pub unsafe extern "C" fn xmlParserInputBufferCreateFilename(
2406    URI: *const c_char,
2407    enc: c_int,
2408) -> *mut _xmlParserInputBuffer {
2409    // SAFETY: URI must be a valid C string or NULL.
2410    if URI.is_null() {
2411        return ptr::null_mut();
2412    }
2413    crate::xml::parser::helpers::alloc_parser_input_buffer()
2414}
2415
2416/// Create an I/O parser input buffer.
2417///
2418/// # UPSTREAM-PARITY
2419///
2420/// ```c
2421/// xmlParserInputBufferPtr xmlParserInputBufferCreateIO(
2422///     xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
2423///     void *ioctx, int enc);
2424/// ```
2425#[no_mangle]
2426pub unsafe extern "C" fn xmlParserInputBufferCreateIO(
2427    ioread: Option<xmlInputReadCallback>,
2428    ioclose: Option<xmlInputCloseCallback>,
2429    ioctx: *mut c_void,
2430    enc: c_int,
2431) -> *mut _xmlParserInputBuffer {
2432    // SAFETY: ioread must be a valid callback if Some. ioctx may be NULL.
2433    let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
2434    if !buf.is_null() {
2435        (*buf).readcallback = ioread;
2436        (*buf).closecallback = ioclose;
2437        (*buf).context = ioctx;
2438    }
2439    buf
2440}
2441
2442/// Free a parser input buffer.
2443///
2444/// # UPSTREAM-PARITY
2445///
2446/// ```c
2447/// void xmlFreeParserInputBuffer(xmlParserInputBufferPtr buf);
2448/// ```
2449#[no_mangle]
2450pub unsafe extern "C" fn xmlFreeParserInputBuffer(buf: *mut _xmlParserInputBuffer) {
2451    if buf.is_null() {
2452        return;
2453    }
2454    crate::xml::parser::helpers::free_parser_input_buffer(buf);
2455}
2456
2457/// Create a new parser input.
2458///
2459/// # UPSTREAM-PARITY
2460///
2461/// ```c
2462/// xmlParserInputPtr xmlNewInputFromFile(xmlParserCtxtPtr ctxt, const char *filename);
2463/// ```
2464#[no_mangle]
2465pub unsafe extern "C" fn xmlNewInputFromFile(
2466    ctxt: *mut _xmlParserCtxt,
2467    filename: *const c_char,
2468) -> *mut _xmlParserInput {
2469    // SAFETY: filename must be a valid C string. ctxt may be NULL.
2470    // This function allocates a _xmlParserInput. The caller owns it.
2471    // Note: The InputBuffer backing data is NOT leaked here (no ctxt._private
2472    // to store it). Use xmlCreateFileParserCtxt + xmlParseDocument instead.
2473    if filename.is_null() {
2474        return ptr::null_mut();
2475    }
2476    crate::xml::parser::helpers::alloc_parser_input_buffer() as *mut _xmlParserInput
2477}
2478
2479/// Free a parser input.
2480///
2481/// # UPSTREAM-PARITY
2482///
2483/// ```c
2484/// void xmlFreeInputStream(xmlParserInputPtr input);
2485/// ```
2486#[no_mangle]
2487pub unsafe extern "C" fn xmlFreeInputStream(input: *mut _xmlParserInput) {
2488    if input.is_null() {
2489        return;
2490    }
2491    crate::xml::parser::helpers::free_parser_input(input);
2492}
2493
2494// ═══════════════════════════════════════════════════════════════════════════════
2495// 8. I/O
2496// ═══════════════════════════════════════════════════════════════════════════════
2497
2498/// Create an output buffer for a file.
2499///
2500/// # UPSTREAM-PARITY
2501///
2502/// ```c
2503/// xmlOutputBufferPtr xmlOutputBufferCreateFilename(const char *URI,
2504///                                                  xmlCharEncodingHandlerPtr encoder,
2505///                                                  int compression);
2506/// ```
2507#[no_mangle]
2508pub unsafe extern "C" fn xmlOutputBufferCreateFilename(
2509    URI: *const c_char,
2510    encoder: *mut c_void,
2511    compression: c_int,
2512) -> *mut _xmlOutputBuffer {
2513    let _ = compression;
2514    if URI.is_null() {
2515        return ptr::null_mut();
2516    }
2517    crate::xml::io::output_buffer_create_filename(
2518        URI,
2519        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2520        0,
2521    )
2522}
2523
2524/// Create an output buffer for a file descriptor.
2525///
2526/// # UPSTREAM-PARITY
2527///
2528/// ```c
2529/// xmlOutputBufferPtr xmlOutputBufferCreateFd(int fd,
2530///                                            xmlCharEncodingHandlerPtr encoder);
2531/// ```
2532#[no_mangle]
2533pub unsafe extern "C" fn xmlOutputBufferCreateFd(
2534    fd: c_int,
2535    encoder: *mut c_void,
2536) -> *mut _xmlOutputBuffer {
2537    if fd < 0 {
2538        return ptr::null_mut();
2539    }
2540    crate::xml::io::output_buffer_create_fd(
2541        fd,
2542        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2543    )
2544}
2545
2546/// Create an output buffer from I/O callbacks.
2547///
2548/// # UPSTREAM-PARITY
2549///
2550/// ```c
2551/// xmlOutputBufferPtr xmlOutputBufferCreateIO(
2552///     xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose,
2553///     void *ioctx, xmlCharEncodingHandlerPtr encoder);
2554/// ```
2555#[no_mangle]
2556pub unsafe extern "C" fn xmlOutputBufferCreateIO(
2557    iowrite: Option<xmlOutputWriteCallback>,
2558    ioclose: Option<xmlOutputCloseCallback>,
2559    ioctx: *mut c_void,
2560    encoder: *mut c_void,
2561) -> *mut _xmlOutputBuffer {
2562    crate::xml::io::output_buffer_create_io(
2563        iowrite,
2564        ioclose,
2565        ioctx,
2566        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2567    )
2568}
2569
2570/// Free an output buffer.
2571///
2572/// # UPSTREAM-PARITY
2573///
2574/// ```c
2575/// void xmlOutputBufferClose(xmlOutputBufferPtr out);
2576/// ```
2577#[no_mangle]
2578pub unsafe extern "C" fn xmlOutputBufferClose(out: *mut _xmlOutputBuffer) -> c_int {
2579    if out.is_null() {
2580        return -1;
2581    }
2582    crate::xml::io::output_buffer_close(out)
2583}
2584
2585/// Flush an output buffer.
2586///
2587/// # UPSTREAM-PARITY
2588///
2589/// ```c
2590/// int xmlOutputBufferFlush(xmlOutputBufferPtr out);
2591/// ```
2592#[no_mangle]
2593pub unsafe extern "C" fn xmlOutputBufferFlush(out: *mut _xmlOutputBuffer) -> c_int {
2594    if out.is_null() {
2595        return -1;
2596    }
2597    crate::xml::io::output_buffer_flush(out)
2598}
2599
2600/// Write to an output buffer.
2601///
2602/// # UPSTREAM-PARITY
2603///
2604/// ```c
2605/// int xmlOutputBufferWrite(xmlOutputBufferPtr out, int len, const char *data);
2606/// ```
2607#[no_mangle]
2608pub unsafe extern "C" fn xmlOutputBufferWrite(
2609    out: *mut _xmlOutputBuffer,
2610    len: c_int,
2611    data: *const c_char,
2612) -> c_int {
2613    if out.is_null() || data.is_null() || len <= 0 {
2614        return -1;
2615    }
2616    crate::xml::io::output_buffer_write(out, len, data)
2617}
2618
2619/// Write a string to an output buffer.
2620///
2621/// # UPSTREAM-PARITY
2622///
2623/// ```c
2624/// int xmlOutputBufferWriteString(xmlOutputBufferPtr out, const char *str);
2625/// ```
2626#[no_mangle]
2627pub unsafe extern "C" fn xmlOutputBufferWriteString(
2628    out: *mut _xmlOutputBuffer,
2629    str: *const c_char,
2630) -> c_int {
2631    if str.is_null() {
2632        return 0;
2633    }
2634    unsafe { xmlOutputBufferWrite(out, xmlStrlen(str as *const xmlChar), str) }
2635}
2636
2637// ═══════════════════════════════════════════════════════════════════════════════
2638// 9. Dictionary
2639// ═══════════════════════════════════════════════════════════════════════════════
2640
2641/// Create a new dictionary.
2642///
2643/// # UPSTREAM-PARITY
2644///
2645/// ```c
2646/// xmlDictPtr xmlDictCreate(void);
2647/// ```
2648#[no_mangle]
2649pub extern "C" fn xmlDictCreate() -> *mut c_void {
2650    // Phase 1: STUB — will be implemented in xml/dictionary module.
2651    ptr::null_mut()
2652}
2653
2654/// Create a sub-dictionary.
2655///
2656/// # UPSTREAM-PARITY
2657///
2658/// ```c
2659/// xmlDictPtr xmlDictCreateSub(xmlDictPtr sub);
2660/// ```
2661#[no_mangle]
2662pub extern "C" fn xmlDictCreateSub(_sub: *mut c_void) -> *mut c_void {
2663    // Phase 1: STUB
2664    ptr::null_mut()
2665}
2666
2667/// Look up a string in the dictionary.
2668///
2669/// # UPSTREAM-PARITY
2670///
2671/// ```c
2672/// const xmlChar *xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len);
2673/// ```
2674///
2675/// Returns an interned string pointer (valid as long as the dictionary exists).
2676/// - If `len` < 0, `name` must be null-terminated.
2677/// - If `len` >= 0, exactly `len` bytes are used.
2678#[no_mangle]
2679pub unsafe extern "C" fn xmlDictLookup(
2680    dict: *mut c_void,
2681    name: *const xmlChar,
2682    len: c_int,
2683) -> *const xmlChar {
2684    // Phase 1: STUB
2685    name
2686}
2687
2688/// Check if a string exists in the dictionary.
2689///
2690/// # UPSTREAM-PARITY
2691///
2692/// ```c
2693/// const xmlChar *xmlDictExists(xmlDictPtr dict, const xmlChar *name, int len);
2694/// ```
2695#[no_mangle]
2696pub unsafe extern "C" fn xmlDictExists(
2697    dict: *mut c_void,
2698    name: *const xmlChar,
2699    len: c_int,
2700) -> *const xmlChar {
2701    // Phase 1: STUB
2702    ptr::null()
2703}
2704
2705/// Query dictionary size.
2706///
2707/// # UPSTREAM-PARITY
2708///
2709/// ```c
2710/// unsigned int xmlDictSize(const xmlDictPtr dict);
2711/// ```
2712#[no_mangle]
2713pub extern "C" fn xmlDictSize(dict: *const c_void) -> c_uint {
2714    // Phase 1: STUB
2715    0
2716}
2717
2718/// Free a dictionary.
2719///
2720/// # UPSTREAM-PARITY
2721///
2722/// ```c
2723/// void xmlDictFree(xmlDictPtr dict);
2724/// ```
2725#[no_mangle]
2726pub extern "C" fn xmlDictFree(_dict: *mut c_void) {
2727    // Phase 1: STUB
2728}
2729
2730/// Set the dictionary size limit.
2731///
2732/// # UPSTREAM-PARITY
2733///
2734/// ```c
2735/// unsigned int xmlDictSetLimit(xmlDictPtr dict, unsigned int limit);
2736/// ```
2737#[no_mangle]
2738pub extern "C" fn xmlDictSetLimit(_dict: *mut c_void, _limit: c_uint) -> c_uint {
2739    // Phase 1: STUB
2740    0
2741}
2742
2743/// Get current dictionary usage.
2744///
2745/// # UPSTREAM-PARITY
2746///
2747/// ```c
2748/// unsigned int xmlDictGetUsage(const xmlDictPtr dict);
2749/// ```
2750#[no_mangle]
2751pub extern "C" fn xmlDictGetUsage(_dict: *const c_void) -> c_uint {
2752    // Phase 1: STUB
2753    0
2754}
2755
2756// ═══════════════════════════════════════════════════════════════════════════════
2757// 10. Hash Table
2758// ═══════════════════════════════════════════════════════════════════════════════
2759
2760/// Create a new hash table.
2761///
2762/// # UPSTREAM-PARITY
2763///
2764/// ```c
2765/// xmlHashTablePtr xmlHashCreate(int size);
2766/// ```
2767#[no_mangle]
2768pub extern "C" fn xmlHashCreate(_size: c_int) -> *mut c_void {
2769    // Phase 1: STUB
2770    ptr::null_mut()
2771}
2772
2773/// Create a new hash table with a dictionary.
2774///
2775/// # UPSTREAM-PARITY
2776///
2777/// ```c
2778/// xmlHashTablePtr xmlHashCreateDict(int size, xmlDictPtr dict);
2779/// ```
2780#[no_mangle]
2781pub extern "C" fn xmlHashCreateDict(_size: c_int, _dict: *mut c_void) -> *mut c_void {
2782    // Phase 1: STUB
2783    ptr::null_mut()
2784}
2785
2786/// Free a hash table.
2787///
2788/// # UPSTREAM-PARITY
2789///
2790/// ```c
2791/// void xmlHashFree(xmlHashTablePtr table, xmlHashDeallocator f);
2792/// ```
2793#[no_mangle]
2794pub extern "C" fn xmlHashFree(
2795    _table: *mut c_void,
2796    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2797) {
2798    // Phase 1: STUB
2799}
2800
2801/// Add an entry to a hash table.
2802///
2803/// # UPSTREAM-PARITY
2804///
2805/// ```c
2806/// int xmlHashAddEntry(xmlHashTablePtr table, const xmlChar *name, void *userdata);
2807/// ```
2808#[no_mangle]
2809pub unsafe extern "C" fn xmlHashAddEntry(
2810    _table: *mut c_void,
2811    _name: *const xmlChar,
2812    _userdata: *mut c_void,
2813) -> c_int {
2814    // Phase 1: STUB
2815    0
2816}
2817
2818/// Add a 2-key entry.
2819///
2820/// # UPSTREAM-PARITY
2821///
2822/// ```c
2823/// int xmlHashAddEntry2(xmlHashTablePtr table, const xmlChar *name,
2824///                      const xmlChar *name2, void *userdata);
2825/// ```
2826#[no_mangle]
2827pub unsafe extern "C" fn xmlHashAddEntry2(
2828    _table: *mut c_void,
2829    _name: *const xmlChar,
2830    _name2: *const xmlChar,
2831    _userdata: *mut c_void,
2832) -> c_int {
2833    // Phase 1: STUB
2834    0
2835}
2836
2837/// Add a 3-key entry.
2838///
2839/// # UPSTREAM-PARITY
2840///
2841/// ```c
2842/// int xmlHashAddEntry3(xmlHashTablePtr table, const xmlChar *name,
2843///                      const xmlChar *name2, const xmlChar *name3, void *userdata);
2844/// ```
2845#[no_mangle]
2846pub unsafe extern "C" fn xmlHashAddEntry3(
2847    _table: *mut c_void,
2848    _name: *const xmlChar,
2849    _name2: *const xmlChar,
2850    _name3: *const xmlChar,
2851    _userdata: *mut c_void,
2852) -> c_int {
2853    // Phase 1: STUB
2854    0
2855}
2856
2857/// Update or add an entry.
2858///
2859/// # UPSTREAM-PARITY
2860///
2861/// ```c
2862/// int xmlHashUpdateEntry(xmlHashTablePtr table, const xmlChar *name,
2863///                        void *userdata, xmlHashDeallocator f);
2864/// ```
2865#[no_mangle]
2866pub unsafe extern "C" fn xmlHashUpdateEntry(
2867    _table: *mut c_void,
2868    _name: *const xmlChar,
2869    _userdata: *mut c_void,
2870    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2871) -> c_int {
2872    // Phase 1: STUB
2873    0
2874}
2875
2876/// Update or add a 2-key entry.
2877#[no_mangle]
2878pub unsafe extern "C" fn xmlHashUpdateEntry2(
2879    _table: *mut c_void,
2880    _name: *const xmlChar,
2881    _name2: *const xmlChar,
2882    _userdata: *mut c_void,
2883    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2884) -> c_int {
2885    // Phase 1: STUB
2886    0
2887}
2888
2889/// Update or add a 3-key entry.
2890#[no_mangle]
2891pub unsafe extern "C" fn xmlHashUpdateEntry3(
2892    _table: *mut c_void,
2893    _name: *const xmlChar,
2894    _name2: *const xmlChar,
2895    _name3: *const xmlChar,
2896    _userdata: *mut c_void,
2897    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2898) -> c_int {
2899    // Phase 1: STUB
2900    0
2901}
2902
2903/// Look up an entry.
2904///
2905/// # UPSTREAM-PARITY
2906///
2907/// ```c
2908/// void *xmlHashLookup(xmlHashTablePtr table, const xmlChar *name);
2909/// ```
2910#[no_mangle]
2911pub unsafe extern "C" fn xmlHashLookup(_table: *mut c_void, _name: *const xmlChar) -> *mut c_void {
2912    // Phase 1: STUB
2913    ptr::null_mut()
2914}
2915
2916/// Look up a 2-key entry.
2917#[no_mangle]
2918pub unsafe extern "C" fn xmlHashLookup2(
2919    _table: *mut c_void,
2920    _name: *const xmlChar,
2921    _name2: *const xmlChar,
2922) -> *mut c_void {
2923    // Phase 1: STUB
2924    ptr::null_mut()
2925}
2926
2927/// Look up a 3-key entry.
2928#[no_mangle]
2929pub unsafe extern "C" fn xmlHashLookup3(
2930    _table: *mut c_void,
2931    _name: *const xmlChar,
2932    _name2: *const xmlChar,
2933    _name3: *const xmlChar,
2934) -> *mut c_void {
2935    // Phase 1: STUB
2936    ptr::null_mut()
2937}
2938
2939/// Get the size of a hash table.
2940///
2941/// # UPSTREAM-PARITY
2942///
2943/// ```c
2944/// int xmlHashSize(xmlHashTablePtr table);
2945/// ```
2946#[no_mangle]
2947pub extern "C" fn xmlHashSize(_table: *mut c_void) -> c_int {
2948    // Phase 1: STUB
2949    0
2950}
2951
2952/// Remove an entry.
2953///
2954/// # UPSTREAM-PARITY
2955///
2956/// ```c
2957/// int xmlHashRemoveEntry(xmlHashTablePtr table, const xmlChar *name,
2958///                        xmlHashDeallocator f);
2959/// ```
2960#[no_mangle]
2961pub unsafe extern "C" fn xmlHashRemoveEntry(
2962    _table: *mut c_void,
2963    _name: *const xmlChar,
2964    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2965) -> c_int {
2966    // Phase 1: STUB
2967    0
2968}
2969
2970/// Remove a 2-key entry.
2971#[no_mangle]
2972pub unsafe extern "C" fn xmlHashRemoveEntry2(
2973    _table: *mut c_void,
2974    _name: *const xmlChar,
2975    _name2: *const xmlChar,
2976    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2977) -> c_int {
2978    // Phase 1: STUB
2979    0
2980}
2981
2982/// Remove a 3-key entry.
2983#[no_mangle]
2984pub unsafe extern "C" fn xmlHashRemoveEntry3(
2985    _table: *mut c_void,
2986    _name: *const xmlChar,
2987    _name2: *const xmlChar,
2988    _name3: *const xmlChar,
2989    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2990) -> c_int {
2991    // Phase 1: STUB
2992    0
2993}
2994
2995/// Scan a hash table with a scanner function.
2996///
2997/// # UPSTREAM-PARITY
2998///
2999/// ```c
3000/// void xmlHashScan(xmlHashTablePtr table, xmlHashScanner f, void *data);
3001/// ```
3002#[no_mangle]
3003pub extern "C" fn xmlHashScan(
3004    _table: *mut c_void,
3005    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar, *mut c_void)>,
3006    _data: *mut c_void,
3007) {
3008    // Phase 1: STUB
3009}
3010
3011/// Scan a hash table with a full scanner function.
3012#[no_mangle]
3013pub extern "C" fn xmlHashScanFull(
3014    _table: *mut c_void,
3015    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar, *mut c_void, *mut c_void)>,
3016    _data: *mut c_void,
3017) {
3018    // Phase 1: STUB
3019}
3020
3021/// Copy a hash table.
3022///
3023/// # UPSTREAM-PARITY
3024///
3025/// ```c
3026/// xmlHashTablePtr xmlHashCopy(xmlHashTablePtr table, xmlHashCopier f);
3027/// ```
3028#[no_mangle]
3029pub extern "C" fn xmlHashCopy(
3030    _table: *mut c_void,
3031    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar) -> *mut c_void>,
3032) -> *mut c_void {
3033    // Phase 1: STUB
3034    ptr::null_mut()
3035}
3036
3037// ═══════════════════════════════════════════════════════════════════════════════
3038// 11. List
3039// ═══════════════════════════════════════════════════════════════════════════════
3040
3041/// Create a new list.
3042///
3043/// # UPSTREAM-PARITY
3044///
3045/// ```c
3046/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
3047///                          xmlListDataCompare compare);
3048/// ```
3049#[no_mangle]
3050pub extern "C" fn xmlListCreate(
3051    _deallocator: Option<unsafe extern "C" fn(*mut c_void)>,
3052    _compare: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
3053) -> *mut c_void {
3054    // Phase 1: STUB
3055    ptr::null_mut()
3056}
3057
3058/// Delete a list.
3059///
3060/// # UPSTREAM-PARITY
3061///
3062/// ```c
3063/// void xmlListDelete(xmlListPtr list);
3064/// ```
3065#[no_mangle]
3066pub extern "C" fn xmlListDelete(_list: *mut c_void) {
3067    // Phase 1: STUB
3068}
3069
3070/// Search a list.
3071///
3072/// # UPSTREAM-PARITY
3073///
3074/// ```c
3075/// void *xmlListSearch(xmlListPtr list, void *data);
3076/// ```
3077#[no_mangle]
3078pub extern "C" fn xmlListSearch(_list: *mut c_void, _data: *mut c_void) -> *mut c_void {
3079    // Phase 1: STUB
3080    ptr::null_mut()
3081}
3082
3083/// Walk a list.
3084///
3085/// # UPSTREAM-PARITY
3086///
3087/// ```c
3088/// void xmlListWalk(xmlListPtr list, xmlListWalker walker, void *data);
3089/// ```
3090#[no_mangle]
3091pub extern "C" fn xmlListWalk(
3092    _list: *mut c_void,
3093    _walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
3094    _data: *mut c_void,
3095) {
3096    // Phase 1: STUB
3097}
3098
3099/// Push to back.
3100///
3101/// # UPSTREAM-PARITY
3102///
3103/// ```c
3104/// int xmlListPushBack(xmlListPtr list, void *data);
3105/// ```
3106#[no_mangle]
3107pub extern "C" fn xmlListPushBack(_list: *mut c_void, _data: *mut c_void) -> c_int {
3108    // Phase 1: STUB
3109    0
3110}
3111
3112/// Push to front.
3113///
3114/// # UPSTREAM-PARITY
3115///
3116/// ```c
3117/// int xmlListPushFront(xmlListPtr list, void *data);
3118/// ```
3119#[no_mangle]
3120pub extern "C" fn xmlListPushFront(_list: *mut c_void, _data: *mut c_void) -> c_int {
3121    // Phase 1: STUB
3122    0
3123}
3124
3125/// Pop from back.
3126#[no_mangle]
3127pub extern "C" fn xmlListPopBack(_list: *mut c_void) {
3128    // Phase 1: STUB
3129}
3130
3131/// Pop from front.
3132#[no_mangle]
3133pub extern "C" fn xmlListPopFront(_list: *mut c_void) {
3134    // Phase 1: STUB
3135}
3136
3137/// Insert into sorted list.
3138///
3139/// # UPSTREAM-PARITY
3140///
3141/// ```c
3142/// int xmlListInsert(xmlListPtr list, void *data);
3143/// ```
3144#[no_mangle]
3145pub extern "C" fn xmlListInsert(_list: *mut c_void, _data: *mut c_void) -> c_int {
3146    // Phase 1: STUB
3147    0
3148}
3149
3150/// Append to list.
3151#[no_mangle]
3152pub extern "C" fn xmlListAppend(_list: *mut c_void, _data: *mut c_void) -> c_int {
3153    // Phase 1: STUB
3154    0
3155}
3156
3157/// Remove first matching element.
3158#[no_mangle]
3159pub extern "C" fn xmlListRemoveFirst(_list: *mut c_void, _data: *mut c_void) -> c_int {
3160    // Phase 1: STUB
3161    0
3162}
3163
3164/// Remove last matching element.
3165#[no_mangle]
3166pub extern "C" fn xmlListRemoveLast(_list: *mut c_void, _data: *mut c_void) -> c_int {
3167    // Phase 1: STUB
3168    0
3169}
3170
3171/// Remove all matching elements.
3172#[no_mangle]
3173pub extern "C" fn xmlListRemoveAll(_list: *mut c_void, _data: *mut c_void) -> c_int {
3174    // Phase 1: STUB
3175    0
3176}
3177
3178/// Clear a list.
3179#[no_mangle]
3180pub extern "C" fn xmlListClear(_list: *mut c_void) {
3181    // Phase 1: STUB
3182}
3183
3184/// Check if list is empty.
3185///
3186/// # UPSTREAM-PARITY
3187///
3188/// ```c
3189/// int xmlListEmpty(xmlListPtr list);
3190/// ```
3191#[no_mangle]
3192pub extern "C" fn xmlListEmpty(_list: *mut c_void) -> c_int {
3193    // Phase 1: STUB
3194    1
3195}
3196
3197/// Get front element.
3198///
3199/// # UPSTREAM-PARITY
3200///
3201/// ```c
3202/// void *xmlListFront(xmlListPtr list);
3203/// ```
3204#[no_mangle]
3205pub extern "C" fn xmlListFront(_list: *mut c_void) -> *mut c_void {
3206    // Phase 1: STUB
3207    ptr::null_mut()
3208}
3209
3210/// Get back element.
3211///
3212/// # UPSTREAM-PARITY
3213///
3214/// ```c
3215/// void *xmlListBack(xmlListPtr list);
3216/// ```
3217#[no_mangle]
3218pub extern "C" fn xmlListBack(_list: *mut c_void) -> *mut c_void {
3219    // Phase 1: STUB
3220    ptr::null_mut()
3221}
3222
3223/// Get list size.
3224///
3225/// # UPSTREAM-PARITY
3226///
3227/// ```c
3228/// int xmlListSize(xmlListPtr list);
3229/// ```
3230#[no_mangle]
3231pub extern "C" fn xmlListSize(_list: *mut c_void) -> c_int {
3232    // Phase 1: STUB
3233    0
3234}
3235
3236/// Sort a list.
3237#[no_mangle]
3238pub extern "C" fn xmlListSort(_list: *mut c_void) {
3239    // Phase 1: STUB
3240}
3241
3242/// Reverse a list.
3243#[no_mangle]
3244pub extern "C" fn xmlListReverse(_list: *mut c_void) {
3245    // Phase 1: STUB
3246}
3247
3248/// Reverse a list in-place.
3249#[no_mangle]
3250pub extern "C" fn xmlListReverseSplice(_list: *mut c_void, _list2: *mut c_void) {
3251    // Phase 1: STUB
3252}
3253
3254/// Merge two sorted lists.
3255#[no_mangle]
3256pub extern "C" fn xmlListMerge(_list: *mut c_void, _list2: *mut c_void) {
3257    // Phase 1: STUB
3258}
3259
3260// ═══════════════════════════════════════════════════════════════════════════════
3261// 12. Buffer
3262// ═══════════════════════════════════════════════════════════════════════════════
3263
3264/// Create a new buffer.
3265///
3266/// # UPSTREAM-PARITY
3267///
3268/// ```c
3269/// xmlBufferPtr xmlBufferCreate(void);
3270/// ```
3271#[no_mangle]
3272pub extern "C" fn xmlBufferCreate() -> *mut _xmlBuffer {
3273    crate::xml::io::buf_create(-1)
3274}
3275
3276/// Create a new buffer of a given size.
3277///
3278/// # UPSTREAM-PARITY
3279///
3280/// ```c
3281/// xmlBufferPtr xmlBufferCreateSize(size_t size);
3282/// ```
3283#[no_mangle]
3284pub extern "C" fn xmlBufferCreateSize(size: usize) -> *mut _xmlBuffer {
3285    crate::xml::io::buf_create(size as c_int)
3286}
3287
3288/// Create a buffer from a static string.
3289///
3290/// # UPSTREAM-PARITY
3291///
3292/// ```c
3293/// xmlBufferPtr xmlBufferCreateStatic(void *mem, size_t size);
3294/// ```
3295#[no_mangle]
3296pub extern "C" fn xmlBufferCreateStatic(mem: *mut c_void, size: usize) -> *mut _xmlBuffer {
3297    if mem.is_null() || size == 0 {
3298        return ptr::null_mut();
3299    }
3300    crate::xml::io::buf_create_static(mem as *const xmlChar, size as c_int)
3301}
3302
3303/// Free a buffer.
3304///
3305/// # UPSTREAM-PARITY
3306///
3307/// ```c
3308/// void xmlBufferFree(xmlBufferPtr buf);
3309/// ```
3310#[no_mangle]
3311pub extern "C" fn xmlBufferFree(buf: *mut _xmlBuffer) {
3312    crate::xml::io::buf_free(buf)
3313}
3314
3315/// Empty a buffer.
3316///
3317/// # UPSTREAM-PARITY
3318///
3319/// ```c
3320/// void xmlBufferEmpty(xmlBufferPtr buf);
3321/// ```
3322#[no_mangle]
3323pub extern "C" fn xmlBufferEmpty(buf: *mut _xmlBuffer) {
3324    if buf.is_null() {
3325        return;
3326    }
3327    unsafe {
3328        if !(*buf).content.is_null() {
3329            *(*buf).content = 0;
3330        }
3331        (*buf).use_ = 0;
3332    }
3333}
3334
3335/// Get buffer content.
3336///
3337/// # UPSTREAM-PARITY
3338///
3339/// ```c
3340/// xmlChar *xmlBufferContent(const xmlBuffer *buf);
3341/// ```
3342#[no_mangle]
3343pub extern "C" fn xmlBufferContent(buf: *const _xmlBuffer) -> *mut xmlChar {
3344    crate::xml::io::buf_content(buf as *mut _xmlBuffer)
3345}
3346
3347/// Get buffer length.
3348///
3349/// # UPSTREAM-PARITY
3350///
3351/// ```c
3352/// int xmlBufferLength(const xmlBuffer *buf);
3353/// ```
3354#[no_mangle]
3355pub extern "C" fn xmlBufferLength(buf: *const _xmlBuffer) -> c_int {
3356    crate::xml::io::buf_length(buf as *mut _xmlBuffer)
3357}
3358
3359/// Write to a buffer.
3360///
3361/// # UPSTREAM-PARITY
3362///
3363/// ```c
3364/// int xmlBufferAdd(xmlBufferPtr buf, const xmlChar *str, int len);
3365/// ```
3366#[no_mangle]
3367pub unsafe extern "C" fn xmlBufferAdd(
3368    buf: *mut _xmlBuffer,
3369    str: *const xmlChar,
3370    len: c_int,
3371) -> c_int {
3372    crate::xml::io::buf_add(buf, str, len)
3373}
3374
3375/// Write to a buffer at a position.
3376///
3377/// # UPSTREAM-PARITY
3378///
3379/// ```c
3380/// int xmlBufferAddHead(xmlBufferPtr buf, const xmlChar *str, int len);
3381/// ```
3382#[no_mangle]
3383pub unsafe extern "C" fn xmlBufferAddHead(
3384    buf: *mut _xmlBuffer,
3385    str: *const xmlChar,
3386    len: c_int,
3387) -> c_int {
3388    crate::xml::io::buf_add_head(buf, str, len)
3389}
3390
3391/// Write a C string to a buffer.
3392///
3393/// # UPSTREAM-PARITY
3394///
3395/// ```c
3396/// int xmlBufferCat(xmlBufferPtr buf, const xmlChar *str);
3397/// ```
3398#[no_mangle]
3399pub unsafe extern "C" fn xmlBufferCat(buf: *mut _xmlBuffer, str: *const xmlChar) -> c_int {
3400    if str.is_null() {
3401        return -1;
3402    }
3403    let len = crate::xml::string::xml_strlen(str) as c_int;
3404    crate::xml::io::buf_add(buf, str, len)
3405}
3406
3407/// Set buffer allocation scheme.
3408///
3409/// # UPSTREAM-PARITY
3410///
3411/// ```c
3412/// void xmlBufferSetAllocationScheme(xmlBufferPtr buf,
3413///                                    xmlBufferAllocationScheme scheme);
3414/// ```
3415#[no_mangle]
3416pub extern "C" fn xmlBufferSetAllocationScheme(buf: *mut _xmlBuffer, scheme: c_int) {
3417    if buf.is_null() {
3418        return;
3419    }
3420    unsafe {
3421        (*buf).alloc = scheme;
3422    }
3423}
3424
3425/// Shrink buffer.
3426///
3427/// # UPSTREAM-PARITY
3428///
3429/// ```c
3430/// int xmlBufferShrink(xmlBufferPtr buf, int len);
3431/// ```
3432#[no_mangle]
3433pub extern "C" fn xmlBufferShrink(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3434    if buf.is_null() || len <= 0 {
3435        return 0;
3436    }
3437    unsafe {
3438        let b = &mut *buf;
3439        let shrink_len = (len as c_uint).min(b.use_);
3440        if shrink_len > 0 {
3441            let remaining = b.use_ - shrink_len;
3442            if remaining > 0 {
3443                core::ptr::copy(
3444                    b.content.add(shrink_len as usize),
3445                    b.content,
3446                    remaining as usize,
3447                );
3448            }
3449            *b.content.add(remaining as usize) = 0;
3450            b.use_ = remaining;
3451        }
3452    }
3453    len
3454}
3455
3456/// Grow buffer.
3457///
3458/// # UPSTREAM-PARITY
3459///
3460/// ```c
3461/// int xmlBufferGrow(xmlBufferPtr buf, int len);
3462/// ```
3463#[no_mangle]
3464pub extern "C" fn xmlBufferGrow(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3465    if buf.is_null() || len <= 0 {
3466        return 0;
3467    }
3468    let cur_use = unsafe { (*buf).use_ };
3469    let new_size = cur_use + len as c_uint + 1;
3470    crate::xml::io::buf_grow(buf, new_size)
3471}
3472
3473/// Reserve buffer space.
3474///
3475/// # UPSTREAM-PARITY
3476///
3477/// ```c
3478/// int xmlBufferReserve(xmlBufferPtr buf, int len);
3479/// ```
3480#[no_mangle]
3481pub extern "C" fn xmlBufferReserve(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3482    xmlBufferGrow(buf, len)
3483}
3484
3485/// Detach buffer content.
3486///
3487/// # UPSTREAM-PARITY
3488///
3489/// ```c
3490/// xmlChar *xmlBufferDetach(xmlBufferPtr buf);
3491/// ```
3492#[no_mangle]
3493pub extern "C" fn xmlBufferDetach(buf: *mut _xmlBuffer) -> *mut xmlChar {
3494    if buf.is_null() {
3495        return ptr::null_mut();
3496    }
3497    unsafe {
3498        let content = (*buf).content;
3499        (*buf).content = ptr::null_mut();
3500        (*buf).use_ = 0;
3501        (*buf).size = 0;
3502        content
3503    }
3504}
3505
3506// ═══════════════════════════════════════════════════════════════════════════════
3507// 13. Encoding
3508// ═══════════════════════════════════════════════════════════════════════════════
3509
3510/// Get encoding from a name string.
3511///
3512/// # UPSTREAM-PARITY
3513///
3514/// ```c
3515/// xmlCharEncoding xmlGetCharEncoding(const char *name);
3516/// ```
3517#[no_mangle]
3518pub extern "C" fn xmlGetCharEncoding(name: *const c_char) -> c_int {
3519    if name.is_null() {
3520        return 0; // XML_CHAR_ENCODING_NONE
3521    }
3522    let name_bytes = unsafe {
3523        let len = libc::strlen(name);
3524        core::slice::from_raw_parts(name as *const u8, len)
3525    };
3526    crate::xml::encoding::encoding_from_name(name_bytes) as c_int
3527}
3528
3529/// Find an encoding handler.
3530///
3531/// # UPSTREAM-PARITY
3532///
3533/// ```c
3534/// xmlCharEncodingHandlerPtr xmlFindCharEncodingHandler(const char *name);
3535/// ```
3536#[no_mangle]
3537pub extern "C" fn xmlFindCharEncodingHandler(name: *const c_char) -> *mut c_void {
3538    if name.is_null() {
3539        return ptr::null_mut();
3540    }
3541    crate::xml::encoding::find_encoding_handler(name as *const xmlChar) as *mut c_void
3542}
3543
3544/// Close an encoding handler.
3545///
3546/// # UPSTREAM-PARITY
3547///
3548/// ```c
3549/// int xmlCharEncCloseFunc(xmlCharEncodingHandlerPtr handler);
3550/// ```
3551#[no_mangle]
3552pub extern "C" fn xmlCharEncCloseFunc(handler: *mut c_void) -> c_int {
3553    if handler.is_null() {
3554        return -1;
3555    }
3556    // Free the encoding handler
3557    unsafe {
3558        let h = handler as *mut crate::abi::structs::_xmlCharEncodingHandler;
3559        if !(*h).name.is_null() {
3560            crate::abi::allocator::xmlFree((*h).name as *mut c_void);
3561        }
3562        crate::abi::allocator::xmlFree(handler);
3563    }
3564    0
3565}
3566
3567/// Convert an input buffer's encoding.
3568///
3569/// # UPSTREAM-PARITY
3570///
3571/// ```c
3572/// int xmlCharEncInput(xmlParserInputBufferPtr input, int to);
3573/// ```
3574#[no_mangle]
3575pub unsafe extern "C" fn xmlCharEncInput(input: *mut _xmlParserInputBuffer, _to: c_int) -> c_int {
3576    if input.is_null() {
3577        return -1;
3578    }
3579    let handler = (*input).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
3580    if handler.is_null() {
3581        return -1;
3582    }
3583    let raw = (*input).raw as *mut crate::abi::structs::_xmlBuffer;
3584    let buf = (*input).buffer as *mut crate::abi::structs::_xmlBuffer;
3585    if raw.is_null() || buf.is_null() {
3586        return -1;
3587    }
3588    crate::xml::encoding::char_enc_in(handler, buf, raw)
3589}
3590
3591/// Convert an output buffer's encoding.
3592///
3593/// # UPSTREAM-PARITY
3594///
3595/// ```c
3596/// int xmlCharEncOutput(xmlOutputBufferPtr output, int to);
3597/// ```
3598#[no_mangle]
3599pub unsafe extern "C" fn xmlCharEncOutput(output: *mut _xmlOutputBuffer, _to: c_int) -> c_int {
3600    if output.is_null() {
3601        return -1;
3602    }
3603    let handler = (*output).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
3604    if handler.is_null() {
3605        return -1;
3606    }
3607    let buf = (*output).buffer as *mut crate::abi::structs::_xmlBuffer;
3608    let conv = (*output).conv as *mut crate::abi::structs::_xmlBuffer;
3609    if buf.is_null() || conv.is_null() {
3610        return -1;
3611    }
3612    crate::xml::encoding::char_enc_out(handler, conv, buf)
3613}
3614
3615// ═══════════════════════════════════════════════════════════════════════════════
3616// URI
3617// ═══════════════════════════════════════════════════════════════════════════════
3618
3619/// Parse a URI string.
3620///
3621/// # UPSTREAM-PARITY
3622///
3623/// ```c
3624/// xmlURIPtr xmlParseURI(const char *str);
3625/// ```
3626#[no_mangle]
3627pub unsafe extern "C" fn xmlParseURI(str: *const c_char) -> *mut c_void {
3628    crate::xml::uri::xmlParseURI(str)
3629}
3630
3631/// Parse a URI string (raw version).
3632///
3633/// # UPSTREAM-PARITY
3634///
3635/// ```c
3636/// xmlURIPtr xmlParseURIRaw(const char *str, int raw);
3637/// ```
3638#[no_mangle]
3639pub unsafe extern "C" fn xmlParseURIRaw(str: *const c_char, raw: c_int) -> *mut c_void {
3640    let _ = raw;
3641    crate::xml::uri::xmlParseURI(str)
3642}
3643
3644/// Free a URI structure.
3645///
3646/// # UPSTREAM-PARITY
3647///
3648/// ```c
3649/// void xmlFreeURI(xmlURIPtr uri);
3650/// ```
3651#[no_mangle]
3652pub unsafe extern "C" fn xmlFreeURI(uri: *mut c_void) {
3653    crate::xml::uri::xmlFreeURI(uri)
3654}
3655
3656/// Create an empty URI.
3657///
3658/// # UPSTREAM-PARITY
3659///
3660/// ```c
3661/// xmlURIPtr xmlCreateURI(void);
3662/// ```
3663#[no_mangle]
3664pub extern "C" fn xmlCreateURI() -> *mut c_void {
3665    crate::xml::uri::xmlCreateURI()
3666}
3667
3668/// Save a URI structure to a string.
3669///
3670/// # UPSTREAM-PARITY
3671///
3672/// ```c
3673/// xmlChar *xmlSaveUri(xmlURIPtr uri);
3674/// ```
3675#[no_mangle]
3676pub unsafe extern "C" fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
3677    crate::xml::uri::xmlSaveUri(uri)
3678}
3679
3680/// Escape a URI string.
3681///
3682/// # UPSTREAM-PARITY
3683///
3684/// ```c
3685/// xmlChar *xmlURIEscapeStr(const xmlChar *str, const xmlChar *list);
3686/// ```
3687#[no_mangle]
3688pub unsafe extern "C" fn xmlURIEscapeStr(
3689    str: *const xmlChar,
3690    list: *const xmlChar,
3691) -> *mut xmlChar {
3692    crate::xml::uri::xmlURIEscapeStr(str, list)
3693}
3694
3695/// Unescape a URI string.
3696///
3697/// # UPSTREAM-PARITY
3698///
3699/// ```c
3700/// char *xmlURIUnescapeString(const char *str, int len, char *target);
3701/// ```
3702#[no_mangle]
3703pub unsafe extern "C" fn xmlURIUnescapeString(
3704    str: *const c_char,
3705    len: c_int,
3706    target: *mut c_char,
3707) -> *mut c_char {
3708    crate::xml::uri::xmlURIUnescapeString(str, len, target)
3709}
3710
3711// ═══════════════════════════════════════════════════════════════════════════════
3712// 14. XPath
3713// ═══════════════════════════════════════════════════════════════════════════════
3714
3715// ── Helper functions ────────────────────────────────────────────────────
3716
3717/// Convert an internal `XPathValue` to a C ABI `_xmlXPathObject`.
3718///
3719/// The returned pointer is heap-allocated via `xmlMallocZero` and must be
3720/// freed with `xmlXPathFreeObject`.
3721///
3722/// # Safety
3723///
3724/// Must be called from a context where `xmlMalloc` is safe to call.
3725unsafe fn xpath_to_object(val: XPathValue) -> *mut _xmlXPathObject {
3726    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
3727    if obj.is_null() {
3728        return ptr::null_mut();
3729    }
3730    match val {
3731        XPathValue::NodeSet(ns) => {
3732            (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
3733            (*obj).nodesetval = ns.to_raw() as *mut c_void;
3734        }
3735        XPathValue::Boolean(b) => {
3736            (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
3737            (*obj).boolval = if b { 1 } else { 0 };
3738        }
3739        XPathValue::Number(n) => {
3740            (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
3741            (*obj).floatval = n;
3742        }
3743        XPathValue::String(s) => {
3744            (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
3745            let bytes = s.as_bytes();
3746            let len = bytes.len();
3747            let buf = xmlMalloc(len + 1) as *mut xmlChar;
3748            if !buf.is_null() {
3749                ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
3750                *buf.add(len) = 0; // null terminator
3751            }
3752            (*obj).stringval = buf;
3753        }
3754    }
3755    obj
3756}
3757
3758/// Extract an internal `XPathValue` from a C ABI `_xmlXPathObject`.
3759///
3760/// # Safety
3761///
3762/// `obj` must be a valid, non-null pointer to a properly initialised
3763/// `_xmlXPathObject`.
3764unsafe fn object_to_xpathvalue(obj: *mut _xmlXPathObject) -> XPathValue {
3765    let typ = (*obj).type_;
3766    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
3767        let ns_ptr = (*obj).nodesetval as *mut _xmlNodeSet;
3768        if ns_ptr.is_null() {
3769            return XPathValue::NodeSet(NodeSet::new());
3770        }
3771        let node_nr = (*ns_ptr).nodeNr;
3772        let node_tab = (*ns_ptr).nodeTab;
3773        let mut ns = NodeSet::new();
3774        if !node_tab.is_null() {
3775            for i in 0..node_nr as isize {
3776                let node = *node_tab.add(i as usize);
3777                ns.push(node);
3778            }
3779        }
3780        XPathValue::NodeSet(ns)
3781    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
3782        XPathValue::Boolean((*obj).boolval != 0)
3783    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
3784        XPathValue::Number((*obj).floatval)
3785    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
3786        let s_ptr = (*obj).stringval;
3787        if s_ptr.is_null() {
3788            XPathValue::String(String::new())
3789        } else {
3790            let s = CStr::from_ptr(s_ptr as *const c_char)
3791                .to_string_lossy()
3792                .into_owned();
3793            XPathValue::String(s)
3794        }
3795    } else if typ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int {
3796        // A result tree fragment: node-set containing the fragment's
3797        // document node (matching how global RTF variables are bound), so
3798        // local RTF variables stringify to their text and remain navigable
3799        // via exsl:node-set.
3800        let frag_doc = (*obj).nodesetval as *mut _xmlDoc;
3801        if frag_doc.is_null() {
3802            XPathValue::NodeSet(NodeSet::new())
3803        } else {
3804            let mut ns = NodeSet::new();
3805            ns.push(frag_doc as *mut _xmlNode);
3806            XPathValue::NodeSet(ns)
3807        }
3808    } else {
3809        // Undefined / unknown type — return boolean false as a safe default.
3810        XPathValue::Boolean(false)
3811    }
3812}
3813
3814/// Public wrapper for `object_to_xpathvalue` (used by the XSLT engine).
3815///
3816/// # Safety
3817///
3818/// `obj` must be a valid, non-null pointer to a properly initialised
3819/// `_xmlXPathObject`.
3820pub unsafe fn object_to_xpathvalue_pub(obj: *mut _xmlXPathObject) -> XPathValue {
3821    object_to_xpathvalue(obj)
3822}
3823
3824// ── Compiled expression registry ────────────────────────────────────────
3825//
3826// Compiled XPath expressions are opaque pointers returned by xmlXPathCompile.
3827// We store them in a global registry keyed by a monotonically increasing ID.
3828
3829static COMPILED_EXPRS: Lazy<Mutex<HashMap<u64, Box<CompiledExpr>>>> =
3830    Lazy::new(|| Mutex::new(HashMap::new()));
3831static NEXT_COMPILED_KEY: Lazy<Mutex<u64>> = Lazy::new(|| Mutex::new(1));
3832
3833// ── C extension-function registry ──────────────────────────────────────
3834//
3835// C extension functions registered via xmlXPathRegisterFunc / RegisterFuncNS
3836// are stored here because the Rust XPathFunction signature is incompatible
3837// with the C xmlXPathFunction calling convention (the C function expects a
3838// parser context, not pre-evaluated argument slices). The registration is
3839// stored faithfully; invoking registered C functions from within the Rust
3840// evaluator requires a bridge that is not yet implemented.
3841
3842type CXPathFunc = unsafe extern "C" fn(*mut c_void, c_int);
3843
3844/// Wrapper around `*mut c_void` that implements `Send` + `Sync` so it can
3845/// be used as a key in a `Mutex`-protected global `HashMap`.
3846#[derive(Clone, Copy, PartialEq, Eq, Hash)]
3847struct SendSyncPtr(*mut c_void);
3848unsafe impl Send for SendSyncPtr {}
3849unsafe impl Sync for SendSyncPtr {}
3850
3851static C_FUNCTIONS: Lazy<Mutex<HashMap<(SendSyncPtr, String), CXPathFunc>>> =
3852    Lazy::new(|| Mutex::new(HashMap::new()));
3853
3854/// Rust-side wrapper that is registered in the internal XPathContext when a
3855/// C extension function is registered. It looks up the C function pointer and
3856/// attempts to call it, but the calling-convention mismatch means this is a
3857/// stub that returns an error for now.
3858fn c_func_stub(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
3859    Err(
3860        "C extension function cannot be called from Rust evaluator without a parser-context bridge"
3861            .to_string(),
3862    )
3863}
3864
3865// ── Public API ─────────────────────────────────────────────────────────
3866
3867/// Create a new XPath context.
3868///
3869/// Allocates a `_xmlXPathContext` and an internal `XPathContext`, storing
3870/// the latter's pointer in the `extra` field.
3871///
3872/// # UPSTREAM-PARITY
3873///
3874/// ```c
3875/// xmlXPathContextPtr xmlXPathNewContext(xmlDocPtr doc);
3876/// ```
3877#[no_mangle]
3878pub unsafe extern "C" fn xmlXPathNewContext(doc: *mut _xmlDoc) -> *mut _xmlXPathContext {
3879    let ctxt = xmlMallocZero(size_of::<_xmlXPathContext>()) as *mut _xmlXPathContext;
3880    if ctxt.is_null() {
3881        return ptr::null_mut();
3882    }
3883
3884    // Initialise the C ABI context fields.
3885    (*ctxt).doc = doc;
3886    (*ctxt).node = ptr::null_mut();
3887    (*ctxt).contextSize = 1;
3888    (*ctxt).proximityPosition = 1;
3889
3890    // Create the internal XPathContext and store it in `extra`.
3891    let internal = Box::new(XPathContext::new(doc));
3892    (*ctxt).extra = Box::into_raw(internal) as *mut c_void;
3893
3894    ctxt
3895}
3896
3897/// Free an XPath context.
3898///
3899/// # UPSTREAM-PARITY
3900///
3901/// ```c
3902/// void xmlXPathFreeContext(xmlXPathContextPtr ctxt);
3903/// ```
3904#[no_mangle]
3905pub unsafe extern "C" fn xmlXPathFreeContext(ctxt: *mut _xmlXPathContext) {
3906    if ctxt.is_null() {
3907        return;
3908    }
3909    // Drop the internal XPathContext.
3910    if !(*ctxt).extra.is_null() {
3911        let _ = Box::from_raw((*ctxt).extra as *mut XPathContext);
3912        (*ctxt).extra = ptr::null_mut();
3913    }
3914    // Free the C ABI context struct.
3915    xmlFree(ctxt as *mut c_void);
3916}
3917
3918/// Evaluate an XPath expression.
3919///
3920/// # UPSTREAM-PARITY
3921///
3922/// ```c
3923/// xmlXPathObjectPtr xmlXPathEvalExpression(const xmlChar *str,
3924///                                          xmlXPathContextPtr ctxt);
3925/// ```
3926#[no_mangle]
3927pub unsafe extern "C" fn xmlXPathEvalExpression(
3928    str_: *const xmlChar,
3929    ctxt: *mut _xmlXPathContext,
3930) -> *mut _xmlXPathObject {
3931    if str_.is_null() || ctxt.is_null() {
3932        return ptr::null_mut();
3933    }
3934    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
3935        Ok(s) => s,
3936        Err(_) => return ptr::null_mut(),
3937    };
3938    let internal = (*ctxt).extra as *mut XPathContext;
3939    if internal.is_null() {
3940        return ptr::null_mut();
3941    }
3942    let internal = &mut *internal;
3943
3944    match crate::xml::xpath::evaluate_str(expr_str, internal) {
3945        Some(val) => xpath_to_object(val),
3946        None => {
3947            // UPSTREAM-PARITY: libxml2 reports a failed compile/eval with
3948            // "XPath error : Invalid expression" (xmlXPathErr,
3949            // XPATH_EXPR_ERROR). The precise per-expression diagnostics are
3950            // tracked as RESIDUAL R-XPATH-ERRMSG.
3951            if internal.error.is_none() {
3952                internal.set_error("Invalid expression");
3953            }
3954            ptr::null_mut()
3955        }
3956    }
3957}
3958
3959/// Evaluate an XPath expression (simplified alias).
3960///
3961/// # UPSTREAM-PARITY
3962///
3963/// ```c
3964/// xmlXPathObjectPtr xmlXPathEval(const xmlChar *str, xmlXPathContextPtr ctxt);
3965/// ```
3966#[no_mangle]
3967pub unsafe extern "C" fn xmlXPathEval(
3968    str_: *const xmlChar,
3969    ctxt: *mut _xmlXPathContext,
3970) -> *mut _xmlXPathObject {
3971    xmlXPathEvalExpression(str_, ctxt)
3972}
3973
3974/// Free an XPath object.
3975///
3976/// Releases the internal members (string buffer or node-set) and then frees
3977/// the object struct itself.
3978///
3979/// # UPSTREAM-PARITY
3980///
3981/// ```c
3982/// void xmlXPathFreeObject(xmlXPathObjectPtr obj);
3983/// ```
3984#[no_mangle]
3985pub unsafe extern "C" fn xmlXPathFreeObject(obj: *mut _xmlXPathObject) {
3986    if obj.is_null() {
3987        return;
3988    }
3989    let typ = (*obj).type_;
3990    // Free string storage.
3991    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
3992        if !(*obj).stringval.is_null() {
3993            xmlFree((*obj).stringval as *mut c_void);
3994            (*obj).stringval = ptr::null_mut();
3995        }
3996    }
3997    // Free node-set storage.
3998    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
3999        let ns = (*obj).nodesetval as *mut _xmlNodeSet;
4000        if !ns.is_null() {
4001            if !(*ns).nodeTab.is_null() {
4002                xmlFree((*ns).nodeTab as *mut c_void);
4003            }
4004            xmlFree(ns as *mut c_void);
4005        }
4006        (*obj).nodesetval = ptr::null_mut();
4007    }
4008    xmlFree(obj as *mut c_void);
4009}
4010
4011/// Copy an XPath object (deep copy).
4012///
4013/// # UPSTREAM-PARITY
4014///
4015/// ```c
4016/// xmlXPathObjectPtr xmlXPathObjectCopy(xmlXPathObjectPtr val);
4017/// ```
4018///
4019/// Oracle behavior: returns a newly allocated object with the same type
4020/// and value. Node-sets are copied element-by-element; strings are
4021/// duplicated; numbers and booleans are copied by value.
4022#[no_mangle]
4023pub unsafe extern "C" fn xmlXPathObjectCopy(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
4024    if val.is_null() {
4025        return ptr::null_mut();
4026    }
4027    let typ = (*val).type_;
4028    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
4029    if obj.is_null() {
4030        return ptr::null_mut();
4031    }
4032    (*obj).type_ = typ;
4033    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
4034        let src_ns = (*val).nodesetval as *mut _xmlNodeSet;
4035        if !src_ns.is_null() {
4036            let nr = (*src_ns).nodeNr;
4037            let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
4038            if ns.is_null() {
4039                xmlFree(obj as *mut c_void);
4040                return ptr::null_mut();
4041            }
4042            (*ns).nodeNr = nr;
4043            (*ns).nodeMax = nr;
4044            if nr > 0 && !(*src_ns).nodeTab.is_null() {
4045                let tab = xmlMalloc((nr as usize) * core::mem::size_of::<*mut _xmlNode>())
4046                    as *mut *mut _xmlNode;
4047                if tab.is_null() {
4048                    xmlFree(ns as *mut c_void);
4049                    xmlFree(obj as *mut c_void);
4050                    return ptr::null_mut();
4051                }
4052                ptr::copy_nonoverlapping((*src_ns).nodeTab, tab, nr as usize);
4053                (*ns).nodeTab = tab;
4054            } else {
4055                (*ns).nodeTab = ptr::null_mut();
4056            }
4057            (*obj).nodesetval = ns as *mut c_void;
4058        }
4059    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
4060        (*obj).boolval = (*val).boolval;
4061    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
4062        (*obj).floatval = (*val).floatval;
4063    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
4064        let src = (*val).stringval;
4065        if !src.is_null() {
4066            let len = libc::strlen(src as *const libc::c_char);
4067            let buf = xmlMalloc(len + 1) as *mut xmlChar;
4068            if !buf.is_null() {
4069                ptr::copy_nonoverlapping(src, buf, len);
4070                *buf.add(len) = 0;
4071            }
4072            (*obj).stringval = buf;
4073        }
4074    }
4075    obj
4076}
4077
4078/// Cast an XPath object to its string value.
4079///
4080/// Returns a newly allocated string (caller frees with `xmlFree`).
4081///
4082/// # UPSTREAM-PARITY
4083///
4084/// ```c
4085/// xmlChar *xmlXPathCastToString(xmlXPathObjectPtr val);
4086/// ```
4087#[no_mangle]
4088pub unsafe extern "C" fn xmlXPathCastToString(val: *mut _xmlXPathObject) -> *mut xmlChar {
4089    if val.is_null() {
4090        return ptr::null_mut();
4091    }
4092    let typ = (*val).type_;
4093    let mut result: Vec<u8> = Vec::new();
4094    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
4095        if !(*val).stringval.is_null() {
4096            let len = libc::strlen((*val).stringval as *const libc::c_char);
4097            result.extend_from_slice(core::slice::from_raw_parts((*val).stringval, len));
4098        }
4099    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
4100        // Number → string conversion per XPath 1.0 §4.2:
4101        // - NaN → "NaN"
4102        // - +0/-0 → "0"
4103        // - infinity → "Infinity" / "-Infinity"
4104        // - integer → decimal representation without exponent
4105        let n = (*val).floatval;
4106        result.extend_from_slice(xml_number_to_string(n).as_bytes());
4107    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
4108        result.extend_from_slice(if (*val).boolval != 0 {
4109            b"true"
4110        } else {
4111            b"false"
4112        });
4113    } else if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
4114        // String value of a node-set is the string value of the first node
4115        // in document order (or empty if empty).
4116        let ns = (*val).nodesetval as *mut _xmlNodeSet;
4117        if !ns.is_null() && (*ns).nodeNr > 0 && !(*ns).nodeTab.is_null() {
4118            let node = *(*ns).nodeTab;
4119            if !node.is_null() {
4120                let content = crate::xml::tree::node_get_content(node);
4121                if !content.is_null() {
4122                    let len = libc::strlen(content as *const libc::c_char);
4123                    result.extend_from_slice(core::slice::from_raw_parts(content, len));
4124                    xmlFree(content as *mut c_void);
4125                }
4126            }
4127        }
4128    }
4129    // Allocate the C string.
4130    let buf = xmlMalloc(result.len() + 1) as *mut xmlChar;
4131    if buf.is_null() {
4132        return ptr::null_mut();
4133    }
4134    if !result.is_empty() {
4135        ptr::copy_nonoverlapping(result.as_ptr(), buf, result.len());
4136    }
4137    *buf.add(result.len()) = 0;
4138    buf
4139}
4140
4141/// Convert an XPath number to its string representation (XPath 1.0 §4.2).
4142///
4143/// Exposed as a helper; matches upstream `xmlXPathCastNumberToString`.
4144pub fn xml_number_to_string(n: f64) -> String {
4145    if n.is_nan() {
4146        return "NaN".to_string();
4147    }
4148    if n.is_infinite() {
4149        return if n > 0.0 {
4150            "Infinity".to_string()
4151        } else {
4152            "-Infinity".to_string()
4153        };
4154    }
4155    if n == 0.0 {
4156        // Both +0 and -0 serialize as "0" per XPath 1.0.
4157        return "0".to_string();
4158    }
4159    // Integer values serialize without a decimal point or exponent.
4160    if n.fract() == 0.0 && n.abs() < 1e15 {
4161        return format!("{:.0}", n);
4162    }
4163    // For other values, upstream uses %.15g-ish formatting with adjustments.
4164    // Try to find the shortest representation that round-trips (%.17g then
4165    // trim), which matches upstream's effective behavior for most inputs.
4166    let mut s = format!("{:.15}", n);
4167    // Trim trailing zeros and possible trailing dot.
4168    if s.contains('.') {
4169        while s.ends_with('0') {
4170            s.pop();
4171        }
4172        if s.ends_with('.') {
4173            s.pop();
4174        }
4175    }
4176    if s == "-0" {
4177        return "0".to_string();
4178    }
4179    s
4180}
4181
4182/// Cast a C string to a number per XPath 1.0 §4.2 conversion rules.
4183///
4184/// # UPSTREAM-PARITY
4185///
4186/// ```c
4187/// double xmlXPathCastStringToNumber(const xmlChar *val);
4188/// ```
4189#[no_mangle]
4190pub unsafe extern "C" fn xmlXPathCastStringToNumber(val: *const xmlChar) -> f64 {
4191    if val.is_null() {
4192        return f64::NAN;
4193    }
4194    let len = libc::strlen(val as *const libc::c_char);
4195    let bytes = core::slice::from_raw_parts(val, len);
4196    // Skip leading whitespace (XML whitespace per XPath 1.0).
4197    let mut i = 0;
4198    while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r') {
4199        i += 1;
4200    }
4201    let s = &bytes[i..];
4202    if s.is_empty() {
4203        return f64::NAN;
4204    }
4205    // Parse an optional sign.
4206    let (sign, rest) = match s[0] {
4207        b'+' => (1.0f64, &s[1..]),
4208        b'-' => (-1.0f64, &s[1..]),
4209        _ => (1.0f64, s),
4210    };
4211    if rest.is_empty() {
4212        return f64::NAN;
4213    }
4214    // Try full parse; if it fails (trailing junk), the value is NaN per spec.
4215    // XPath 1.0: a string that does not conform to the Number production is NaN.
4216    let num_str = core::str::from_utf8(rest);
4217    match num_str {
4218        Ok(s) => {
4219            // Accept only valid Number productions: digits with optional
4220            // fraction/exponent.
4221            let valid = is_xpath_number(s);
4222            if !valid {
4223                f64::NAN
4224            } else {
4225                s.trim()
4226                    .parse::<f64>()
4227                    .map(|v| v * sign)
4228                    .unwrap_or(f64::NAN)
4229            }
4230        }
4231        Err(_) => f64::NAN,
4232    }
4233}
4234
4235/// Check whether a string conforms to the XPath 1.0 Number production.
4236fn is_xpath_number(s: &str) -> bool {
4237    let b = s.as_bytes();
4238    if b.is_empty() {
4239        return false;
4240    }
4241    let mut i = 0;
4242    let mut saw_digit = false;
4243    while i < b.len() && b[i].is_ascii_digit() {
4244        saw_digit = true;
4245        i += 1;
4246    }
4247    if i < b.len() && b[i] == b'.' {
4248        i += 1;
4249        while i < b.len() && b[i].is_ascii_digit() {
4250            saw_digit = true;
4251            i += 1;
4252        }
4253    }
4254    if !saw_digit {
4255        return false;
4256    }
4257    if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
4258        i += 1;
4259        if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
4260            i += 1;
4261        }
4262        let mut saw_exp = false;
4263        while i < b.len() && b[i].is_ascii_digit() {
4264            saw_exp = true;
4265            i += 1;
4266        }
4267        if !saw_exp {
4268            return false;
4269        }
4270    }
4271    i == b.len()
4272}
4273
4274/// Compare two nodes in document order.
4275///
4276/// Returns negative if `node1` precedes `node2`, positive if it follows,
4277/// 0 if they are the same node.
4278///
4279/// # UPSTREAM-PARITY
4280///
4281/// ```c
4282/// int xmlXPathCmpNodes(xmlNodePtr node1, xmlNodePtr node2);
4283/// ```
4284///
4285/// Oracle behavior: uses ancestor comparison — if one node is an ancestor
4286/// of the other, the ancestor comes first; otherwise the nearest common
4287/// ancestor's child order determines the result.
4288#[no_mangle]
4289pub unsafe extern "C" fn xmlXPathCmpNodes(node1: *mut _xmlNode, node2: *mut _xmlNode) -> c_int {
4290    if node1.is_null() || node2.is_null() {
4291        return 0;
4292    }
4293    if node1 == node2 {
4294        return 0;
4295    }
4296    // Build ancestor chains.
4297    let mut chain1: Vec<*mut _xmlNode> = Vec::new();
4298    let mut chain2: Vec<*mut _xmlNode> = Vec::new();
4299    let mut n = node1;
4300    while !n.is_null() {
4301        chain1.push(n);
4302        n = (*n).parent as *mut _xmlNode;
4303    }
4304    let mut n = node2;
4305    while !n.is_null() {
4306        chain2.push(n);
4307        n = (*n).parent as *mut _xmlNode;
4308    }
4309    // Find the nearest common ancestor.
4310    let mut i = chain1.len();
4311    let mut j = chain2.len();
4312    while i > 0 && j > 0 && chain1[i - 1] == chain2[j - 1] {
4313        i -= 1;
4314        j -= 1;
4315    }
4316    if i == 0 && j == 0 {
4317        return 0; // Same node (already handled) or disjoint trees treated as equal
4318    }
4319    if i == 0 {
4320        return -1; // node1 is an ancestor of node2
4321    }
4322    if j == 0 {
4323        return 1; // node2 is an ancestor of node1
4324    }
4325    // Compare sibling order at the divergence point.
4326    let mut a = chain1[i - 1];
4327    let mut b = chain2[j - 1];
4328    // Climb to the same level.
4329    while !a.is_null() && !b.is_null() {
4330        let pa = (*a).parent as *mut _xmlNode;
4331        let pb = (*b).parent as *mut _xmlNode;
4332        if pa == pb {
4333            break;
4334        }
4335        a = pa;
4336        b = pb;
4337    }
4338    // Walk forward from the first child of the common parent.
4339    let parent = (*a).parent as *mut _xmlNode;
4340    let mut child = if parent.is_null() {
4341        ptr::null_mut()
4342    } else {
4343        (*parent).children
4344    };
4345    while !child.is_null() {
4346        if child == a {
4347            return -1;
4348        }
4349        if child == b {
4350            return 1;
4351        }
4352        child = (*child).next;
4353    }
4354    0
4355}
4356
4357/// Create a node-set from a range of an existing node-set.
4358///
4359/// # UPSTREAM-PARITY
4360///
4361/// ```c
4362/// xmlNodeSetPtr xmlXPathNodeSetCreate(xmlNodePtr val);
4363/// ```
4364///
4365/// With a null `val`, creates an empty node-set.
4366#[no_mangle]
4367pub unsafe extern "C" fn xmlXPathNodeSetCreate(val: *mut _xmlNode) -> *mut _xmlNodeSet {
4368    let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
4369    if ns.is_null() {
4370        return ptr::null_mut();
4371    }
4372    if val.is_null() {
4373        return ns;
4374    }
4375    let tab = xmlMalloc(core::mem::size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
4376    if tab.is_null() {
4377        xmlFree(ns as *mut c_void);
4378        return ptr::null_mut();
4379    }
4380    *tab = val;
4381    (*ns).nodeTab = tab;
4382    (*ns).nodeNr = 1;
4383    (*ns).nodeMax = 1;
4384    ns
4385}
4386
4387/// Free a node-set allocated by `xmlXPathNodeSetCreate` or a node-set
4388/// builder in this library.
4389///
4390/// Frees the node-set structure and its node table; the nodes themselves
4391/// are owned by their document and are not freed.
4392///
4393/// # UPSTREAM-PARITY
4394///
4395/// ```c
4396/// void xmlXPathFreeNodeSet(xmlNodeSetPtr ns);
4397/// ```
4398#[no_mangle]
4399pub unsafe extern "C" fn xmlXPathFreeNodeSet(ns: *mut _xmlNodeSet) {
4400    if ns.is_null() {
4401        return;
4402    }
4403    if !(*ns).nodeTab.is_null() {
4404        xmlFree((*ns).nodeTab as *mut c_void);
4405        (*ns).nodeTab = ptr::null_mut();
4406    }
4407    (*ns).nodeNr = 0;
4408    (*ns).nodeMax = 0;
4409    xmlFree(ns as *mut c_void);
4410}
4411
4412/// Compile an XPath expression.
4413///
4414/// Returns an opaque pointer that can be passed to `xmlXPathEvalExpression`
4415/// (via the compiled-expr infrastructure) or freed with `xmlXPathFreeCompExpr`.
4416///
4417/// # UPSTREAM-PARITY
4418///
4419/// ```c
4420/// xmlXPathCompExprPtr xmlXPathCompile(const xmlChar *str);
4421/// ```
4422#[no_mangle]
4423pub unsafe extern "C" fn xmlXPathCompile(str_: *const xmlChar) -> *mut c_void {
4424    if str_.is_null() {
4425        return ptr::null_mut();
4426    }
4427    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
4428        Ok(s) => s,
4429        Err(_) => return ptr::null_mut(),
4430    };
4431
4432    match crate::xml::xpath::compile(expr_str) {
4433        Some(compiled) => {
4434            let mut map = COMPILED_EXPRS.lock();
4435            let mut counter = NEXT_COMPILED_KEY.lock();
4436            let key = *counter;
4437            *counter += 1;
4438            map.insert(key, Box::new(compiled));
4439            key as *mut c_void
4440        }
4441        None => ptr::null_mut(),
4442    }
4443}
4444
4445/// Free a compiled XPath expression.
4446///
4447/// # UPSTREAM-PARITY
4448///
4449/// ```c
4450/// void xmlXPathFreeCompExpr(xmlXPathCompExprPtr comp);
4451/// ```
4452#[no_mangle]
4453pub unsafe extern "C" fn xmlXPathFreeCompExpr(comp: *mut c_void) {
4454    if comp.is_null() {
4455        return;
4456    }
4457    let mut map = COMPILED_EXPRS.lock();
4458    map.remove(&(comp as u64));
4459}
4460
4461/// Register an XPath namespace.
4462///
4463/// # UPSTREAM-PARITY
4464///
4465/// ```c
4466/// int xmlXPathRegisterNs(xmlXPathContextPtr ctxt,
4467///                        const xmlChar *prefix, const xmlChar *ns_uri);
4468/// ```
4469#[no_mangle]
4470pub unsafe extern "C" fn xmlXPathRegisterNs(
4471    ctxt: *mut _xmlXPathContext,
4472    prefix: *const xmlChar,
4473    ns_uri: *const xmlChar,
4474) -> c_int {
4475    if ctxt.is_null() || prefix.is_null() || ns_uri.is_null() {
4476        return -1;
4477    }
4478    let internal = (*ctxt).extra as *mut XPathContext;
4479    if internal.is_null() {
4480        return -1;
4481    }
4482    let internal = &mut *internal;
4483
4484    let prefix_str = match CStr::from_ptr(prefix as *const c_char).to_str() {
4485        Ok(s) => s,
4486        Err(_) => return -1,
4487    };
4488    let uri_str = match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4489        Ok(s) => s,
4490        Err(_) => return -1,
4491    };
4492
4493    internal.register_namespace(prefix_str, uri_str);
4494    0
4495}
4496
4497/// Register an XPath function.
4498///
4499/// The C function pointer is stored in a side table keyed by the context.
4500/// A Rust-side stub is registered in the internal context so that the Rust
4501/// evaluator is aware of the function; however, calling the C function
4502/// directly from the Rust evaluator is not yet supported.
4503///
4504/// # UPSTREAM-PARITY
4505///
4506/// ```c
4507/// int xmlXPathRegisterFunc(xmlXPathContextPtr ctxt,
4508///                          const xmlChar *name, xmlXPathFunction f);
4509/// ```
4510#[no_mangle]
4511pub unsafe extern "C" fn xmlXPathRegisterFunc(
4512    ctxt: *mut _xmlXPathContext,
4513    name: *const xmlChar,
4514    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
4515) -> c_int {
4516    if ctxt.is_null() || name.is_null() {
4517        return -1;
4518    }
4519    let internal = (*ctxt).extra as *mut XPathContext;
4520    if internal.is_null() {
4521        return -1;
4522    }
4523    let internal = &mut *internal;
4524
4525    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4526        Ok(s) => s,
4527        Err(_) => return -1,
4528    };
4529
4530    if let Some(func) = f {
4531        // Store the C function pointer in the side table.
4532        let key = (SendSyncPtr((*ctxt).extra), name_str.to_string());
4533        C_FUNCTIONS.lock().insert(key, func);
4534        // Register a Rust stub so the evaluator knows the function exists.
4535        internal.register_function(name_str, c_func_stub);
4536    }
4537    0
4538}
4539
4540/// Register an XPath function with namespace.
4541///
4542/// # UPSTREAM-PARITY
4543///
4544/// ```c
4545/// int xmlXPathRegisterFuncNS(xmlXPathContextPtr ctxt,
4546///                            const xmlChar *name, const xmlChar *ns_uri,
4547///                            xmlXPathFunction f);
4548/// ```
4549#[no_mangle]
4550pub unsafe extern "C" fn xmlXPathRegisterFuncNS(
4551    ctxt: *mut _xmlXPathContext,
4552    name: *const xmlChar,
4553    ns_uri: *const xmlChar,
4554    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
4555) -> c_int {
4556    if ctxt.is_null() || name.is_null() {
4557        return -1;
4558    }
4559    let internal = (*ctxt).extra as *mut XPathContext;
4560    if internal.is_null() {
4561        return -1;
4562    }
4563    let internal = &mut *internal;
4564
4565    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4566        Ok(s) => s,
4567        Err(_) => return -1,
4568    };
4569    let ns_str = if ns_uri.is_null() {
4570        String::new()
4571    } else {
4572        match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4573            Ok(s) => s.to_string(),
4574            Err(_) => return -1,
4575        }
4576    };
4577
4578    // Use "{ns}:" prefix as part of the key to keep functions unique.
4579    let qualified = if ns_str.is_empty() {
4580        name_str.to_string()
4581    } else {
4582        format!("{{{}}}{}", ns_str, name_str)
4583    };
4584
4585    if let Some(func) = f {
4586        let key = (SendSyncPtr((*ctxt).extra), qualified.clone());
4587        C_FUNCTIONS.lock().insert(key, func);
4588        internal.register_function(&qualified, c_func_stub);
4589    }
4590    0
4591}
4592
4593/// Register an XPath variable.
4594///
4595/// # UPSTREAM-PARITY
4596///
4597/// ```c
4598/// int xmlXPathRegisterVariable(xmlXPathContextPtr ctxt,
4599///                              const xmlChar *name, xmlXPathObjectPtr value);
4600/// ```
4601#[no_mangle]
4602pub unsafe extern "C" fn xmlXPathRegisterVariable(
4603    ctxt: *mut _xmlXPathContext,
4604    name: *const xmlChar,
4605    value: *mut _xmlXPathObject,
4606) -> c_int {
4607    if ctxt.is_null() || name.is_null() || value.is_null() {
4608        return -1;
4609    }
4610    let internal = (*ctxt).extra as *mut XPathContext;
4611    if internal.is_null() {
4612        return -1;
4613    }
4614    let internal = &mut *internal;
4615
4616    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4617        Ok(s) => s,
4618        Err(_) => return -1,
4619    };
4620
4621    let xpath_val = object_to_xpathvalue(value);
4622    internal.register_variable(name_str, xpath_val);
4623    0
4624}
4625
4626/// Create an XPath object wrapping a single node in a node-set.
4627///
4628/// # UPSTREAM-PARITY
4629///
4630/// ```c
4631/// xmlXPathObjectPtr xmlXPathNewNodeSet(xmlNodePtr val);
4632/// ```
4633#[no_mangle]
4634pub unsafe extern "C" fn xmlXPathNewNodeSet(val: *mut _xmlNode) -> *mut _xmlXPathObject {
4635    let ns = if val.is_null() {
4636        NodeSet::new()
4637    } else {
4638        NodeSet::singleton(val)
4639    };
4640    xpath_to_object(XPathValue::NodeSet(ns))
4641}
4642
4643/// Create an XPath object from a C string value.
4644///
4645/// # UPSTREAM-PARITY
4646///
4647/// ```c
4648/// xmlXPathObjectPtr xmlXPathNewCString(const xmlChar *val);
4649/// ```
4650#[no_mangle]
4651pub unsafe extern "C" fn xmlXPathNewCString(val: *const xmlChar) -> *mut _xmlXPathObject {
4652    if val.is_null() {
4653        return xpath_to_object(XPathValue::String(String::new()));
4654    }
4655    let s = match CStr::from_ptr(val as *const c_char).to_str() {
4656        Ok(s) => s.to_string(),
4657        Err(_) => return ptr::null_mut(),
4658    };
4659    xpath_to_object(XPathValue::String(s))
4660}
4661
4662/// Create an XPath number object.
4663///
4664/// # UPSTREAM-PARITY
4665///
4666/// ```c
4667/// xmlXPathObjectPtr xmlXPathNewFloat(double val);
4668/// ```
4669#[no_mangle]
4670pub extern "C" fn xmlXPathNewFloat(val: f64) -> *mut _xmlXPathObject {
4671    unsafe { xpath_to_object(XPathValue::Number(val)) }
4672}
4673
4674/// Create an XPath boolean object.
4675///
4676/// # UPSTREAM-PARITY
4677///
4678/// ```c
4679/// xmlXPathObjectPtr xmlXPathNewBoolean(int val);
4680/// ```
4681#[no_mangle]
4682pub extern "C" fn xmlXPathNewBoolean(val: c_int) -> *mut _xmlXPathObject {
4683    unsafe { xpath_to_object(XPathValue::Boolean(val != 0)) }
4684}
4685
4686// ═══════════════════════════════════════════════════════════════════════════════
4687// 14.5. XPointer
4688// ═══════════════════════════════════════════════════════════════════════════════
4689
4690/// Evaluate an XPointer expression.
4691///
4692/// Delegates to the xpointer module.
4693///
4694/// # UPSTREAM-PARITY
4695///
4696/// ```c
4697/// xmlNodePtr xmlXPtrEval(const xmlChar *expr, xmlDocPtr doc);
4698/// ```
4699#[no_mangle]
4700pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
4701    crate::xml::xpointer::xmlXPtrEval(expr, doc)
4702}
4703
4704// ═══════════════════════════════════════════════════════════════════════════════
4705// 15. XInclude
4706// ═══════════════════════════════════════════════════════════════════════════════
4707
4708/// Process XInclude nodes in a document.
4709///
4710/// # UPSTREAM-PARITY
4711///
4712/// ```c
4713/// int xmlXIncludeProcess(xmlDocPtr doc);
4714/// ```
4715#[no_mangle]
4716pub unsafe extern "C" fn xmlXIncludeProcess(doc: *mut _xmlDoc) -> c_int {
4717    crate::xml::xinclude::xinclude_process(doc)
4718}
4719
4720/// Process XInclude nodes with flags.
4721///
4722/// # UPSTREAM-PARITY
4723///
4724/// ```c
4725/// int xmlXIncludeProcessFlags(xmlDocPtr doc, int flags);
4726/// ```
4727#[no_mangle]
4728pub unsafe extern "C" fn xmlXIncludeProcessFlags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
4729    crate::xml::xinclude::xinclude_process_flags(doc, flags)
4730}
4731
4732// ═══════════════════════════════════════════════════════════════════════════════
4733// 16. Catalog
4734// ═══════════════════════════════════════════════════════════════════════════════
4735
4736/// Load a catalog.
4737///
4738/// # UPSTREAM-PARITY
4739///
4740/// ```c
4741/// xmlCatalogPtr xmlCatalogLoad(const char *catalogs);
4742/// ```
4743#[no_mangle]
4744pub extern "C" fn xmlCatalogLoad(catalogs: *const c_char) -> *mut c_void {
4745    if catalogs.is_null() {
4746        return ptr::null_mut();
4747    }
4748    crate::xml::catalog::load_catalog(catalogs)
4749}
4750
4751/// Resolve a public ID.
4752///
4753/// # UPSTREAM-PARITY
4754///
4755/// ```c
4756/// xmlCharPtr xmlCatalogResolvePublic(const xmlChar *pubID);
4757/// ```
4758#[no_mangle]
4759pub unsafe extern "C" fn xmlCatalogResolvePublic(pubID: *const xmlChar) -> *mut xmlChar {
4760    if pubID.is_null() {
4761        return ptr::null_mut();
4762    }
4763    crate::xml::catalog::resolve_public(pubID)
4764}
4765
4766/// Resolve a system ID.
4767///
4768/// # UPSTREAM-PARITY
4769///
4770/// ```c
4771/// xmlCharPtr xmlCatalogResolveSystem(const xmlChar *sysID);
4772/// ```
4773#[no_mangle]
4774pub unsafe extern "C" fn xmlCatalogResolveSystem(sysID: *const xmlChar) -> *mut xmlChar {
4775    if sysID.is_null() {
4776        return ptr::null_mut();
4777    }
4778    crate::xml::catalog::resolve_system(sysID)
4779}
4780
4781/// Resolve a URI.
4782///
4783/// # UPSTREAM-PARITY
4784///
4785/// ```c
4786/// xmlCharPtr xmlCatalogResolveURI(const xmlChar *URI);
4787/// ```
4788#[no_mangle]
4789pub unsafe extern "C" fn xmlCatalogResolveURI(URI: *const xmlChar) -> *mut xmlChar {
4790    if URI.is_null() {
4791        return ptr::null_mut();
4792    }
4793    crate::xml::catalog::resolve_uri(URI)
4794}
4795
4796/// Set catalog defaults.
4797///
4798/// # UPSTREAM-PARITY
4799///
4800/// ```c
4801/// void xmlCatalogSetDefaults(xmlCatalogAllowValue allow);
4802/// ```
4803#[no_mangle]
4804pub extern "C" fn xmlCatalogSetDefaults(allow: c_int) {
4805    crate::xml::catalog::set_defaults(allow)
4806}
4807
4808/// Get catalog defaults.
4809///
4810/// # UPSTREAM-PARITY
4811///
4812/// ```c
4813/// xmlCatalogAllowValue xmlCatalogGetDefaults(void);
4814/// ```
4815#[no_mangle]
4816pub extern "C" fn xmlCatalogGetDefaults() -> c_int {
4817    crate::xml::catalog::get_defaults()
4818}
4819
4820/// Add a catalog.
4821///
4822/// # UPSTREAM-PARITY
4823///
4824/// ```c
4825/// int xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace);
4826/// ```
4827#[no_mangle]
4828pub unsafe extern "C" fn xmlCatalogAdd(
4829    type_: *const xmlChar,
4830    orig: *const xmlChar,
4831    replace: *const xmlChar,
4832) -> c_int {
4833    if type_.is_null() || orig.is_null() || replace.is_null() {
4834        return -1;
4835    }
4836    crate::xml::catalog::add(type_, orig, replace)
4837}
4838
4839/// Remove a catalog entry.
4840///
4841/// # UPSTREAM-PARITY
4842///
4843/// ```c
4844/// int xmlCatalogRemove(const xmlChar *value);
4845/// ```
4846#[no_mangle]
4847pub unsafe extern "C" fn xmlCatalogRemove(value: *const xmlChar) -> c_int {
4848    if value.is_null() {
4849        return 0;
4850    }
4851    crate::xml::catalog::remove(value)
4852}
4853
4854/// Dump the catalog in XML format to a FILE* (upstream `xmlCatalogDump`).
4855///
4856/// # UPSTREAM-PARITY
4857///
4858/// ```c
4859/// void xmlCatalogDump(FILE *out, xmlCatalogPtr catal);
4860/// ```
4861#[no_mangle]
4862pub unsafe extern "C" fn xmlCatalogDump(output: *mut c_void, _catal: *mut c_void) {
4863    if output.is_null() {
4864        return;
4865    }
4866    let doc = crate::xml::catalog::dump_doc();
4867    if doc.is_null() {
4868        return;
4869    }
4870    let mut mem: *mut xmlChar = ptr::null_mut();
4871    let mut size: c_int = 0;
4872    crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
4873    if !mem.is_null() {
4874        libc::fwrite(
4875            mem as *const c_void,
4876            1,
4877            size as usize,
4878            output as *mut libc::FILE,
4879        );
4880        xmlFree(mem as *mut c_void);
4881    }
4882    crate::xml::tree::free_doc(doc);
4883}
4884
4885/// Save the catalog to a file (upstream `xmlCatalogSave`).
4886///
4887/// Returns 0 on success, -1 on failure.
4888///
4889/// # UPSTREAM-PARITY
4890///
4891/// ```c
4892/// int xmlCatalogSave(const char *filename);
4893/// ```
4894#[no_mangle]
4895pub unsafe extern "C" fn xmlCatalogSave(filename: *const c_char) -> c_int {
4896    if filename.is_null() {
4897        return -1;
4898    }
4899    let doc = crate::xml::catalog::dump_doc();
4900    if doc.is_null() {
4901        return -1;
4902    }
4903    let mut mem: *mut xmlChar = ptr::null_mut();
4904    let mut size: c_int = 0;
4905    crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
4906    let mut ret: c_int = -1;
4907    if !mem.is_null() {
4908        let fp = libc::fopen(filename, b"w\0".as_ptr() as *const c_char);
4909        if !fp.is_null() {
4910            let written = libc::fwrite(mem as *const c_void, 1, size as usize, fp);
4911            ret = if written == size as usize { 0 } else { -1 };
4912            libc::fclose(fp);
4913        }
4914        xmlFree(mem as *mut c_void);
4915    }
4916    crate::xml::tree::free_doc(doc);
4917    ret
4918}
4919
4920/// Clean up the catalog subsystem.
4921///
4922/// # UPSTREAM-PARITY
4923///
4924/// ```c
4925/// void xmlCatalogCleanup(void);
4926/// ```
4927#[no_mangle]
4928pub extern "C" fn xmlCatalogCleanup() {
4929    crate::xml::catalog::cleanup();
4930}
4931
4932/// Convert an SGML catalog to XML.
4933///
4934/// # UPSTREAM-PARITY
4935///
4936/// ```c
4937/// xmlDocPtr xmlCatalogConvert(void);
4938/// ```
4939#[no_mangle]
4940pub extern "C" fn xmlCatalogConvert() -> *mut _xmlDoc {
4941    // SAFETY: catalog::convert() allocates and builds an XML document tree.
4942    unsafe { crate::xml::catalog::convert() }
4943}
4944
4945// ═══════════════════════════════════════════════════════════════════════════════
4946// 17. HTML
4947// ═══════════════════════════════════════════════════════════════════════════════
4948
4949/// Parse an HTML document from a file.
4950///
4951/// # UPSTREAM-PARITY
4952///
4953/// ```c
4954/// htmlDocPtr htmlParseFile(const char *filename, const char *encoding);
4955/// ```
4956#[no_mangle]
4957pub unsafe extern "C" fn htmlParseFile(
4958    _filename: *const c_char,
4959    _encoding: *const c_char,
4960) -> *mut _xmlDoc {
4961    // Phase 1: STUB
4962    ptr::null_mut()
4963}
4964
4965/// Parse an HTML document from memory.
4966///
4967/// # UPSTREAM-PARITY
4968///
4969/// ```c
4970/// htmlDocPtr htmlParseMemory(const char *buffer, int size);
4971/// ```
4972#[no_mangle]
4973pub unsafe extern "C" fn htmlParseMemory(_buffer: *const c_char, _size: c_int) -> *mut _xmlDoc {
4974    // Phase 1: STUB
4975    ptr::null_mut()
4976}
4977
4978/// Parse an HTML document from a document string.
4979///
4980/// # UPSTREAM-PARITY
4981///
4982/// ```c
4983/// htmlDocPtr htmlParseDoc(const xmlChar *cur, const char *encoding);
4984/// ```
4985#[no_mangle]
4986pub unsafe extern "C" fn htmlParseDoc(
4987    _cur: *const xmlChar,
4988    _encoding: *const c_char,
4989) -> *mut _xmlDoc {
4990    // Phase 1: STUB
4991    ptr::null_mut()
4992}
4993
4994/// Create an HTML parser context.
4995///
4996/// # UPSTREAM-PARITY
4997///
4998/// ```c
4999/// htmlParserCtxtPtr htmlCreateFileParserCtxt(const char *filename,
5000///                                            const char *encoding);
5001/// ```
5002#[no_mangle]
5003pub unsafe extern "C" fn htmlCreateFileParserCtxt(
5004    _filename: *const c_char,
5005    _encoding: *const c_char,
5006) -> *mut c_void {
5007    // Phase 1: STUB
5008    ptr::null_mut()
5009}
5010
5011/// Free an HTML parser context.
5012///
5013/// # UPSTREAM-PARITY
5014///
5015/// ```c
5016/// void htmlFreeParserCtxt(htmlParserCtxtPtr ctxt);
5017/// ```
5018#[no_mangle]
5019pub extern "C" fn htmlFreeParserCtxt(_ctxt: *mut c_void) {
5020    // Phase 1: STUB
5021}
5022
5023/// Initialize the HTML parser.
5024///
5025/// # UPSTREAM-PARITY
5026///
5027/// ```c
5028/// void htmlInitParser(void);
5029/// ```
5030#[no_mangle]
5031pub extern "C" fn htmlInitParser() {
5032    // Phase 1: STUB
5033}
5034
5035/// Clean up the HTML parser.
5036///
5037/// # UPSTREAM-PARITY
5038///
5039/// ```c
5040/// void htmlCleanupParser(void);
5041/// ```
5042#[no_mangle]
5043pub extern "C" fn htmlCleanupParser() {
5044    // Phase 1: STUB
5045}
5046
5047// ═══════════════════════════════════════════════════════════════════════════════
5048// 17.5. Validation (DTD)
5049// ═══════════════════════════════════════════════════════════════════════════════
5050
5051/// Create a new validation context.
5052///
5053/// # UPSTREAM-PARITY
5054///
5055/// ```c
5056/// xmlValidCtxtPtr xmlNewValidCtxt(void);
5057/// ```
5058#[no_mangle]
5059pub unsafe extern "C" fn xmlNewValidCtxt() -> *mut _xmlValidCtxt {
5060    crate::xml::validation::new_valid_ctxt()
5061}
5062
5063/// Free a validation context.
5064///
5065/// # UPSTREAM-PARITY
5066///
5067/// ```c
5068/// void xmlFreeValidCtxt(xmlValidCtxtPtr ctxt);
5069/// ```
5070#[no_mangle]
5071pub unsafe extern "C" fn xmlFreeValidCtxt(ctxt: *mut _xmlValidCtxt) {
5072    crate::xml::validation::free_valid_ctxt(ctxt);
5073}
5074
5075/// Set error and warning callbacks on a validation context.
5076///
5077/// # UPSTREAM-PARITY
5078///
5079/// ```c
5080/// void xmlSetValidErrors(xmlValidCtxtPtr ctxt,
5081///                        xmlGenericErrorFunc err,
5082///                        xmlGenericErrorFunc warn,
5083///                        void *data);
5084/// ```
5085#[no_mangle]
5086pub unsafe extern "C" fn xmlSetValidErrors(
5087    ctxt: *mut _xmlValidCtxt,
5088    err: Option<xmlGenericErrorFunc>,
5089    warn: Option<xmlGenericErrorFunc>,
5090    data: *mut c_void,
5091) {
5092    crate::xml::validation::set_valid_errors(ctxt, err, warn, data);
5093}
5094
5095/// Validate a document against its DTD.
5096///
5097/// # UPSTREAM-PARITY
5098///
5099/// ```c
5100/// int xmlValidateDocument(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5101/// ```
5102#[no_mangle]
5103pub unsafe extern "C" fn xmlValidateDocument(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5104    crate::xml::validation::validate_document(ctxt, doc)
5105}
5106
5107/// Final validation pass (check ID/IDREF consistency).
5108///
5109/// # UPSTREAM-PARITY
5110///
5111/// ```c
5112/// int xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5113/// ```
5114#[no_mangle]
5115pub unsafe extern "C" fn xmlValidateDocumentFinal(
5116    ctxt: *mut _xmlValidCtxt,
5117    doc: *mut _xmlDoc,
5118) -> c_int {
5119    crate::xml::validation::validate_document_final(ctxt, doc)
5120}
5121
5122/// Validate an element node against its DTD declarations.
5123///
5124/// # UPSTREAM-PARITY
5125///
5126/// ```c
5127/// int xmlValidateElement(xmlValidCtxtPtr ctxt,
5128///                        xmlDocPtr doc,
5129///                        xmlNodePtr elem);
5130/// ```
5131#[no_mangle]
5132pub unsafe extern "C" fn xmlValidateElement(
5133    ctxt: *mut _xmlValidCtxt,
5134    doc: *mut _xmlDoc,
5135    elem: *mut _xmlNode,
5136) -> c_int {
5137    crate::xml::validation::validate_element(ctxt, doc, elem)
5138}
5139
5140/// Validate an attribute declaration.
5141///
5142/// # UPSTREAM-PARITY
5143///
5144/// ```c
5145/// int xmlValidateAttributeDecl(xmlValidCtxtPtr ctxt,
5146///                              xmlDocPtr doc,
5147///                              xmlNodePtr elem,
5148///                              xmlAttributePtr attr);
5149/// ```
5150#[no_mangle]
5151pub unsafe extern "C" fn xmlValidateAttributeDecl(
5152    ctxt: *mut _xmlValidCtxt,
5153    doc: *mut _xmlDoc,
5154    elem: *mut _xmlNode,
5155    attr: *mut _xmlAttribute,
5156) -> c_int {
5157    crate::xml::validation::validate_attribute_decl(ctxt, doc, elem, attr)
5158}
5159
5160/// Validate an attribute value against its declared type.
5161///
5162/// # UPSTREAM-PARITY
5163///
5164/// ```c
5165/// int xmlValidateAttributeValue(int type, const xmlChar *value);
5166/// ```
5167#[no_mangle]
5168pub unsafe extern "C" fn xmlValidateAttributeValue(atype: c_int, value: *const xmlChar) -> c_int {
5169    crate::xml::validation::validate_attribute_value(atype, value)
5170}
5171
5172/// Validate a NOTATION reference.
5173///
5174/// # UPSTREAM-PARITY
5175///
5176/// ```c
5177/// int xmlValidateNotationUse(xmlValidCtxtPtr ctxt,
5178///                            xmlDocPtr doc,
5179///                            const xmlChar *notationName);
5180/// ```
5181#[no_mangle]
5182pub unsafe extern "C" fn xmlValidateNotationUse(
5183    ctxt: *mut _xmlValidCtxt,
5184    doc: *mut _xmlDoc,
5185    notation_name: *const xmlChar,
5186) -> c_int {
5187    crate::xml::validation::validate_notation_use(ctxt, doc, notation_name)
5188}
5189
5190/// Validate an ID value (check uniqueness).
5191///
5192/// # UPSTREAM-PARITY
5193///
5194/// ```c
5195/// int xmlValidateID(xmlValidCtxtPtr ctxt,
5196///                   xmlDocPtr doc,
5197///                   xmlNodePtr node,
5198///                   const xmlChar *value);
5199/// ```
5200#[no_mangle]
5201pub unsafe extern "C" fn xmlValidateID(
5202    ctxt: *mut _xmlValidCtxt,
5203    doc: *mut _xmlDoc,
5204    node: *mut _xmlNode,
5205    value: *const xmlChar,
5206) -> c_int {
5207    crate::xml::validation::validate_id(ctxt, doc, node, value)
5208}
5209
5210/// Validate an IDREF value (check it references a known ID).
5211///
5212/// # UPSTREAM-PARITY
5213///
5214/// ```c
5215/// int xmlValidateIDRef(xmlValidCtxtPtr ctxt,
5216///                      xmlDocPtr doc,
5217///                      xmlNodePtr node,
5218///                      const xmlChar *value);
5219/// ```
5220#[no_mangle]
5221pub unsafe extern "C" fn xmlValidateIDRef(
5222    ctxt: *mut _xmlValidCtxt,
5223    doc: *mut _xmlDoc,
5224    node: *mut _xmlNode,
5225    value: *const xmlChar,
5226) -> c_int {
5227    crate::xml::validation::validate_id_ref(ctxt, doc, node, value)
5228}
5229
5230/// Validate IDREFS (whitespace-separated list of IDREFs).
5231///
5232/// # UPSTREAM-PARITY
5233///
5234/// ```c
5235/// int xmlValidateIDRefs(xmlValidCtxtPtr ctxt,
5236///                       xmlDocPtr doc,
5237///                       xmlNodePtr node,
5238///                       const xmlChar *value);
5239/// ```
5240#[no_mangle]
5241pub unsafe extern "C" fn xmlValidateIDRefs(
5242    ctxt: *mut _xmlValidCtxt,
5243    doc: *mut _xmlDoc,
5244    node: *mut _xmlNode,
5245    value: *const xmlChar,
5246) -> c_int {
5247    crate::xml::validation::validate_id_refs(ctxt, doc, node, value)
5248}
5249
5250/// Validate an NMTOKEN value.
5251///
5252/// # UPSTREAM-PARITY
5253///
5254/// ```c
5255/// int xmlValidateNmtoken(const xmlChar *value);
5256/// ```
5257#[no_mangle]
5258pub unsafe extern "C" fn xmlValidateNmtoken(value: *const xmlChar) -> c_int {
5259    crate::xml::validation::validate_nmtoken(value)
5260}
5261
5262/// Validate a whitespace-separated list of NMTOKENs.
5263///
5264/// # UPSTREAM-PARITY
5265///
5266/// ```c
5267/// int xmlValidateNmtokens(const xmlChar *value);
5268/// ```
5269#[no_mangle]
5270pub unsafe extern "C" fn xmlValidateNmtokens(value: *const xmlChar) -> c_int {
5271    crate::xml::validation::validate_nmtokens(value)
5272}
5273
5274/// Validate an XML Name value.
5275///
5276/// # UPSTREAM-PARITY
5277///
5278/// ```c
5279/// int xmlValidateName(const xmlChar *value);
5280/// ```
5281#[no_mangle]
5282pub unsafe extern "C" fn xmlValidateName(value: *const xmlChar) -> c_int {
5283    crate::xml::validation::validate_name(value)
5284}
5285
5286/// Validate a whitespace-separated list of XML Names.
5287///
5288/// # UPSTREAM-PARITY
5289///
5290/// ```c
5291/// int xmlValidateNames(const xmlChar *value);
5292/// ```
5293#[no_mangle]
5294pub unsafe extern "C" fn xmlValidateNames(value: *const xmlChar) -> c_int {
5295    crate::xml::validation::validate_names(value)
5296}
5297
5298/// Validate the root element of a document.
5299///
5300/// # UPSTREAM-PARITY
5301///
5302/// ```c
5303/// int xmlValidateRoot(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5304/// ```
5305#[no_mangle]
5306pub unsafe extern "C" fn xmlValidateRoot(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5307    crate::xml::validation::validate_root(ctxt, doc)
5308}
5309
5310/// Validate element content against its content model.
5311///
5312/// # UPSTREAM-PARITY
5313///
5314/// ```c
5315/// int xmlValidateContent(xmlValidCtxtPtr ctxt,
5316///                        xmlNodePtr node,
5317///                        xmlDocPtr doc);
5318/// ```
5319#[no_mangle]
5320pub unsafe extern "C" fn xmlValidateContent(
5321    ctxt: *mut _xmlValidCtxt,
5322    node: *mut _xmlNode,
5323    doc: *mut _xmlDoc,
5324) -> c_int {
5325    crate::xml::validation::validate_content(ctxt, node, doc)
5326}
5327
5328/// Check if an element is declared as mixed content.
5329///
5330/// # UPSTREAM-PARITY
5331///
5332/// ```c
5333/// int xmlIsMixedElement(xmlDocPtr doc, const xmlChar *name);
5334/// ```
5335#[no_mangle]
5336pub unsafe extern "C" fn xmlIsMixedElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
5337    crate::xml::validation::is_mixed_element(doc, name)
5338}
5339
5340/// Check if an element is declared as EMPTY.
5341///
5342/// # UPSTREAM-PARITY
5343///
5344/// ```c
5345/// int xmlIsEmptyElement(xmlDocPtr doc, const xmlChar *name);
5346/// ```
5347#[no_mangle]
5348pub unsafe extern "C" fn xmlIsEmptyElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
5349    crate::xml::validation::is_empty_element(doc, name)
5350}
5351
5352/// Validate a DTD's declarations.
5353///
5354/// # UPSTREAM-PARITY
5355///
5356/// ```c
5357/// int xmlValidateDtd(xmlValidCtxtPtr ctxt,
5358///                    xmlDocPtr doc,
5359///                    xmlDtdPtr dtd);
5360/// ```
5361#[no_mangle]
5362pub unsafe extern "C" fn xmlValidateDtd(
5363    ctxt: *mut _xmlValidCtxt,
5364    doc: *mut _xmlDoc,
5365    dtd: *mut _xmlDtd,
5366) -> c_int {
5367    crate::xml::validation::validate_dtd(ctxt, doc, dtd)
5368}
5369
5370/// Final DTD validation (ID/IDREF consistency).
5371///
5372/// # UPSTREAM-PARITY
5373///
5374/// ```c
5375/// int xmlValidateDtdFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5376/// ```
5377#[no_mangle]
5378pub unsafe extern "C" fn xmlValidateDtdFinal(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5379    crate::xml::validation::validate_dtd_final(ctxt, doc)
5380}
5381
5382/// Validate that a value is in an enumeration.
5383///
5384/// # UPSTREAM-PARITY
5385///
5386/// ```c
5387/// int xmlValidateEnumeration(xmlValidCtxtPtr ctxt,
5388///                            const xmlChar *value,
5389///                            xmlEnumerationPtr tree);
5390/// ```
5391#[no_mangle]
5392pub unsafe extern "C" fn xmlValidateEnumeration(
5393    ctxt: *mut _xmlValidCtxt,
5394    value: *const xmlChar,
5395    tree: *mut _xmlEnumeration,
5396) -> c_int {
5397    crate::xml::validation::validate_enumeration(ctxt, value, tree)
5398}
5399
5400// ═══════════════════════════════════════════════════════════════════════════════
5401// 18. Debug / Miscellaneous
5402// ═══════════════════════════════════════════════════════════════════════════════
5403
5404/// Dump a document to a file for debugging.
5405
5406/// Get the path to the current executable.
5407///
5408/// # UPSTREAM-PARITY
5409///
5410/// ```c
5411/// char *xmlGetBinaryPath(void);
5412/// ```
5413#[no_mangle]
5414pub extern "C" fn xmlGetBinaryPath() -> *mut c_char {
5415    // Phase 1: STUB
5416    ptr::null_mut()
5417}
5418
5419/// Get the path to the current executable's home directory.
5420///
5421/// # UPSTREAM-PARITY
5422///
5423/// ```c
5424/// char *xmlGetHomeOfBinary(void);
5425/// ```
5426#[no_mangle]
5427pub extern "C" fn xmlGetHomeOfBinary() -> *mut c_char {
5428    // Phase 1: STUB
5429    ptr::null_mut()
5430}