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        return doc;
1832    }
1833    let doc = (*ctxt).myDoc;
1834    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1835    doc
1836}
1837
1838/// Read an XML document from memory.
1839///
1840/// # UPSTREAM-PARITY
1841///
1842/// ```c
1843/// xmlDocPtr xmlReadMemory(const char *buffer, int size,
1844///                         const char *URL, const char *encoding, int options);
1845/// ```
1846#[no_mangle]
1847pub unsafe extern "C" fn xmlReadMemory(
1848    buffer: *const c_char,
1849    size: c_int,
1850    URL: *const c_char,
1851    encoding: *const c_char,
1852    options: c_int,
1853) -> *mut _xmlDoc {
1854    // SAFETY: buffer must be a valid pointer with at least `size` readable bytes.
1855    if buffer.is_null() || size <= 0 {
1856        return ptr::null_mut();
1857    }
1858    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1859    if ctxt.is_null() {
1860        return ptr::null_mut();
1861    }
1862    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
1863    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1864    (*ctxt).options = options;
1865    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1866        let doc = (*ctxt).myDoc;
1867        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1868        return doc;
1869    }
1870    let doc = (*ctxt).myDoc;
1871    if !doc.is_null() && !URL.is_null() {
1872        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1873    }
1874    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1875    doc
1876}
1877
1878/// Load a list of catalogs (upstream `xmlLoadCatalogs`).
1879///
1880/// # SAFETY
1881///
1882/// - `catalogs` must be a valid NUL-terminated string or NULL.
1883#[no_mangle]
1884pub unsafe extern "C" fn xmlLoadCatalogs(catalogs: *const c_char) {
1885    if !catalogs.is_null() {
1886        crate::xml::catalog::load_catalog(catalogs);
1887    }
1888}
1889
1890/// Load a single catalog (upstream `xmlLoadCatalog`).
1891///
1892/// # SAFETY
1893///
1894/// - `catalogs` must be a valid NUL-terminated string or NULL.
1895#[no_mangle]
1896pub unsafe extern "C" fn xmlLoadCatalog(catalogs: *const c_char) -> *mut c_void {
1897    crate::xml::catalog::load_catalog(catalogs)
1898}
1899
1900/// Read an XML document from a file descriptor.
1901///
1902/// # UPSTREAM-PARITY
1903///
1904/// ```c
1905/// xmlDocPtr xmlReadFd(int fd, const char *URL, const char *encoding, int options);
1906/// ```
1907#[no_mangle]
1908pub unsafe extern "C" fn xmlReadFd(
1909    fd: c_int,
1910    URL: *const c_char,
1911    encoding: *const c_char,
1912    options: c_int,
1913) -> *mut _xmlDoc {
1914    // SAFETY: fd must be a valid open file descriptor.
1915    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1916    if ctxt.is_null() {
1917        return ptr::null_mut();
1918    }
1919    // Read all data from the fd
1920    let mut buf = Vec::new();
1921    let mut tmp = [0u8; 4096];
1922    loop {
1923        let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
1924        if n <= 0 {
1925            break;
1926        }
1927        buf.extend_from_slice(&tmp[..n as usize]);
1928    }
1929    let input = crate::xml::parser::helpers::input_from_memory(
1930        buf.as_ptr() as *const c_char,
1931        buf.len() as c_int,
1932    );
1933    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1934    (*ctxt).options = options;
1935    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1936        let doc = (*ctxt).myDoc;
1937        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1938        return doc;
1939    }
1940    let doc = (*ctxt).myDoc;
1941    if !doc.is_null() && !URL.is_null() {
1942        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1943    }
1944    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1945    doc
1946}
1947
1948/// Read an XML document from I/O callbacks.
1949///
1950/// # UPSTREAM-PARITY
1951///
1952/// ```c
1953/// xmlDocPtr xmlReadIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
1954///                     void *ioctx, const char *URL, const char *encoding, int options);
1955/// ```
1956#[no_mangle]
1957pub unsafe extern "C" fn xmlReadIO(
1958    ioread: Option<xmlInputReadCallback>,
1959    ioclose: Option<xmlInputCloseCallback>,
1960    ioctx: *mut c_void,
1961    URL: *const c_char,
1962    encoding: *const c_char,
1963    options: c_int,
1964) -> *mut _xmlDoc {
1965    // SAFETY: callbacks must be valid function pointers if non-NULL.
1966    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
1967    if ctxt.is_null() {
1968        return ptr::null_mut();
1969    }
1970    let input = crate::xml::parser::helpers::input_from_io(ioread, ioclose, ioctx);
1971    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
1972    (*ctxt).options = options;
1973    if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
1974        let doc = (*ctxt).myDoc;
1975        crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1976        return doc;
1977    }
1978    let doc = (*ctxt).myDoc;
1979    if !doc.is_null() && !URL.is_null() {
1980        (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
1981    }
1982    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
1983    doc
1984}
1985
1986/// Parse an XML document (SAX1).
1987///
1988/// # UPSTREAM-PARITY
1989///
1990/// ```c
1991/// xmlDocPtr xmlSAXParseDoc(xmlSAXHandlerPtr sax, const xmlChar *cur, int recovery);
1992/// ```
1993#[no_mangle]
1994pub unsafe extern "C" fn xmlSAXParseDoc(
1995    sax: *mut _xmlSAXHandler,
1996    cur: *const xmlChar,
1997    recovery: c_int,
1998) -> *mut _xmlDoc {
1999    // SAFETY: cur must be a valid null-terminated xmlChar string.
2000    if cur.is_null() {
2001        return ptr::null_mut();
2002    }
2003    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2004    if ctxt.is_null() {
2005        return ptr::null_mut();
2006    }
2007    if !sax.is_null() {
2008        (*ctxt).sax = sax;
2009        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2010    }
2011    if recovery != 0 {
2012        (*ctxt).recovery = 1;
2013        (*ctxt).options |= 1; // XML_PARSE_RECOVER
2014    }
2015    let len = crate::xml::string::xml_strlen(cur);
2016    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
2017    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2018    crate::xml::parser::helpers::parse_document(ctxt);
2019    let doc = (*ctxt).myDoc;
2020    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2021    doc
2022}
2023
2024/// Parse an XML file (SAX1).
2025///
2026/// # UPSTREAM-PARITY
2027///
2028/// ```c
2029/// xmlDocPtr xmlSAXParseFile(xmlSAXHandlerPtr sax, const char *filename, int recovery);
2030/// ```
2031#[no_mangle]
2032pub unsafe extern "C" fn xmlSAXParseFile(
2033    sax: *mut _xmlSAXHandler,
2034    filename: *const c_char,
2035    recovery: c_int,
2036) -> *mut _xmlDoc {
2037    // SAFETY: filename must be a valid C string.
2038    if filename.is_null() {
2039        return ptr::null_mut();
2040    }
2041    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2042    if ctxt.is_null() {
2043        return ptr::null_mut();
2044    }
2045    if !sax.is_null() {
2046        (*ctxt).sax = sax;
2047        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2048    }
2049    if recovery != 0 {
2050        (*ctxt).recovery = 1;
2051        (*ctxt).options |= 1;
2052    }
2053    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2054        Ok(input) => input,
2055        Err(_) => {
2056            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2057            return ptr::null_mut();
2058        }
2059    };
2060    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2061    crate::xml::parser::helpers::parse_document(ctxt);
2062    let doc = (*ctxt).myDoc;
2063    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2064    doc
2065}
2066
2067/// Parse an XML document from memory (SAX1).
2068///
2069/// # UPSTREAM-PARITY
2070///
2071/// ```c
2072/// xmlDocPtr xmlSAXParseMemory(xmlSAXHandlerPtr sax,
2073///                             const char *buffer, int size, int recovery);
2074/// ```
2075#[no_mangle]
2076pub unsafe extern "C" fn xmlSAXParseMemory(
2077    sax: *mut _xmlSAXHandler,
2078    buffer: *const c_char,
2079    size: c_int,
2080    recovery: c_int,
2081) -> *mut _xmlDoc {
2082    // SAFETY: buffer must be valid with at least `size` bytes.
2083    if buffer.is_null() || size <= 0 {
2084        return ptr::null_mut();
2085    }
2086    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2087    if ctxt.is_null() {
2088        return ptr::null_mut();
2089    }
2090    if !sax.is_null() {
2091        (*ctxt).sax = sax;
2092        (*ctxt).userData = (*ctxt).sax as *mut c_void;
2093    }
2094    if recovery != 0 {
2095        (*ctxt).recovery = 1;
2096        (*ctxt).options |= 1;
2097    }
2098    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
2099    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2100    crate::xml::parser::helpers::parse_document(ctxt);
2101    let doc = (*ctxt).myDoc;
2102    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2103    doc
2104}
2105
2106/// SAX user parse file.
2107///
2108/// # UPSTREAM-PARITY
2109///
2110/// ```c
2111/// int xmlSAXUserParseFile(xmlSAXHandlerPtr sax, void *user_data,
2112///                         const char *filename);
2113/// ```
2114#[no_mangle]
2115pub unsafe extern "C" fn xmlSAXUserParseFile(
2116    sax: *mut _xmlSAXHandler,
2117    user_data: *mut c_void,
2118    filename: *const c_char,
2119) -> c_int {
2120    // SAFETY: filename must be a valid C string. sax and user_data may be NULL.
2121    if filename.is_null() {
2122        return -1;
2123    }
2124    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2125    if ctxt.is_null() {
2126        return -1;
2127    }
2128    if !sax.is_null() {
2129        (*ctxt).sax = sax;
2130    }
2131    (*ctxt).userData = if !user_data.is_null() {
2132        user_data
2133    } else {
2134        ctxt as *mut c_void
2135    };
2136    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2137        Ok(input) => input,
2138        Err(_) => {
2139            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2140            return -1;
2141        }
2142    };
2143    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2144    let ret = crate::xml::parser::helpers::parse_document(ctxt);
2145    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2146    ret
2147}
2148
2149/// SAX user parse memory.
2150///
2151/// # UPSTREAM-PARITY
2152///
2153/// ```c
2154/// int xmlSAXUserParseMemory(xmlSAXHandlerPtr sax, void *user_data,
2155///                           const char *buffer, int size);
2156/// ```
2157#[no_mangle]
2158pub unsafe extern "C" fn xmlSAXUserParseMemory(
2159    sax: *mut _xmlSAXHandler,
2160    user_data: *mut c_void,
2161    buffer: *const c_char,
2162    size: c_int,
2163) -> c_int {
2164    // SAFETY: buffer must be valid with at least `size` bytes.
2165    if buffer.is_null() || size <= 0 {
2166        return -1;
2167    }
2168    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2169    if ctxt.is_null() {
2170        return -1;
2171    }
2172    if !sax.is_null() {
2173        (*ctxt).sax = sax;
2174    }
2175    (*ctxt).userData = if !user_data.is_null() {
2176        user_data
2177    } else {
2178        ctxt as *mut c_void
2179    };
2180    let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
2181    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2182    let ret = crate::xml::parser::helpers::parse_document(ctxt);
2183    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2184    ret
2185}
2186
2187/// Parse an XML document from a string (DOM).
2188///
2189/// # UPSTREAM-PARITY
2190///
2191/// ```c
2192/// xmlDocPtr xmlParseDoc(const xmlChar *cur);
2193/// ```
2194#[no_mangle]
2195pub unsafe extern "C" fn xmlParseDoc(cur: *const xmlChar) -> *mut _xmlDoc {
2196    // SAFETY: cur must be a valid null-terminated xmlChar string.
2197    if cur.is_null() {
2198        return ptr::null_mut();
2199    }
2200    xmlReadDoc(cur, ptr::null(), ptr::null(), 0)
2201}
2202
2203/// Parse an XML file (DOM).
2204///
2205/// # UPSTREAM-PARITY
2206///
2207/// ```c
2208/// xmlDocPtr xmlParseFile(const char *filename);
2209/// ```
2210#[no_mangle]
2211pub unsafe extern "C" fn xmlParseFile(filename: *const c_char) -> *mut _xmlDoc {
2212    // SAFETY: filename must be a valid C string.
2213    if filename.is_null() {
2214        return ptr::null_mut();
2215    }
2216    xmlReadFile(filename, ptr::null(), 0)
2217}
2218
2219/// Parse an XML document from memory (DOM).
2220///
2221/// # UPSTREAM-PARITY
2222///
2223/// ```c
2224/// xmlDocPtr xmlParseMemory(const char *buffer, int size);
2225/// ```
2226#[no_mangle]
2227pub unsafe extern "C" fn xmlParseMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
2228    // SAFETY: buffer must be valid with at least `size` bytes.
2229    if buffer.is_null() || size <= 0 {
2230        return ptr::null_mut();
2231    }
2232    xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 0)
2233}
2234
2235/// Create a file parser context.
2236///
2237/// # UPSTREAM-PARITY
2238///
2239/// ```c
2240/// xmlParserCtxtPtr xmlCreateFileParserCtxt(const char *filename);
2241/// ```
2242#[no_mangle]
2243pub unsafe extern "C" fn xmlCreateFileParserCtxt(filename: *const c_char) -> *mut _xmlParserCtxt {
2244    // SAFETY: filename must be a valid C string.
2245    if filename.is_null() {
2246        return ptr::null_mut();
2247    }
2248    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2249    if ctxt.is_null() {
2250        return ptr::null_mut();
2251    }
2252    let input = match crate::xml::parser::helpers::input_from_file(filename) {
2253        Ok(input) => input,
2254        Err(_) => {
2255            crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2256            return ptr::null_mut();
2257        }
2258    };
2259    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2260    ctxt
2261}
2262
2263/// Create a document parser context.
2264///
2265/// # UPSTREAM-PARITY
2266///
2267/// ```c
2268/// xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar *cur);
2269/// ```
2270#[no_mangle]
2271pub unsafe extern "C" fn xmlCreateDocParserCtxt(cur: *const xmlChar) -> *mut _xmlParserCtxt {
2272    // SAFETY: cur must be a valid null-terminated xmlChar string.
2273    if cur.is_null() {
2274        return ptr::null_mut();
2275    }
2276    let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2277    if ctxt.is_null() {
2278        return ptr::null_mut();
2279    }
2280    let len = crate::xml::string::xml_strlen(cur);
2281    let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
2282    crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2283    ctxt
2284}
2285
2286/// Parse a document using an existing parser context.
2287///
2288/// # UPSTREAM-PARITY
2289///
2290/// ```c
2291/// int xmlParseDocument(xmlParserCtxtPtr ctxt);
2292/// ```
2293#[no_mangle]
2294pub unsafe extern "C" fn xmlParseDocument(ctxt: *mut _xmlParserCtxt) -> c_int {
2295    // SAFETY: ctxt must be a valid parser context.
2296    if ctxt.is_null() {
2297        return -1;
2298    }
2299    crate::xml::parser::helpers::parse_document(ctxt)
2300}
2301
2302/// Free a parser context.
2303///
2304/// # UPSTREAM-PARITY
2305///
2306/// ```c
2307/// void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt);
2308/// ```
2309#[no_mangle]
2310pub unsafe extern "C" fn xmlFreeParserCtxt(ctxt: *mut _xmlParserCtxt) {
2311    if ctxt.is_null() {
2312        return;
2313    }
2314    crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2315}
2316
2317/// Set parser options.
2318///
2319/// # UPSTREAM-PARITY
2320///
2321/// ```c
2322/// int xmlCtxtUseOptions(xmlParserCtxtPtr ctxt, int options);
2323/// ```
2324#[no_mangle]
2325pub unsafe extern "C" fn xmlCtxtUseOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
2326    if ctxt.is_null() {
2327        return -1;
2328    }
2329    // Phase 1: STUB
2330    unsafe {
2331        (*ctxt).options = options;
2332    }
2333    0
2334}
2335
2336/// Parse a well-balanced chunk (for push parsing).
2337///
2338/// # UPSTREAM-PARITY
2339///
2340/// ```c
2341/// xmlParserErrors xmlParseChunk(xmlParserCtxtPtr ctxt,
2342///                               const char *chunk, int size, int terminate);
2343/// ```
2344#[no_mangle]
2345pub unsafe extern "C" fn xmlParseChunk(
2346    ctxt: *mut _xmlParserCtxt,
2347    chunk: *const c_char,
2348    size: c_int,
2349    terminate: c_int,
2350) -> c_int {
2351    // SAFETY: ctxt must be a valid parser context.
2352    // chunk may be NULL if terminate is set (finalize without data).
2353    if ctxt.is_null() {
2354        return -1;
2355    }
2356    crate::xml::parser::helpers::parse_chunk(ctxt, chunk, size, terminate)
2357}
2358
2359/// Create a memory parser input buffer.
2360///
2361/// # UPSTREAM-PARITY
2362///
2363/// ```c
2364/// xmlParserInputBufferPtr xmlParserInputBufferCreateMem(const char *buffer, int size, int enc);
2365/// ```
2366#[no_mangle]
2367pub unsafe extern "C" fn xmlParserInputBufferCreateMem(
2368    buffer: *const c_char,
2369    size: c_int,
2370    enc: c_int,
2371) -> *mut _xmlParserInputBuffer {
2372    // SAFETY: buffer must be valid with at least `size` bytes.
2373    if buffer.is_null() || size <= 0 {
2374        return ptr::null_mut();
2375    }
2376    crate::xml::parser::helpers::alloc_parser_input_buffer()
2377}
2378
2379/// Create a file parser input buffer.
2380///
2381/// # UPSTREAM-PARITY
2382///
2383/// ```c
2384/// xmlParserInputBufferPtr xmlParserInputBufferCreateFilename(const char *URI, int enc);
2385/// ```
2386#[no_mangle]
2387pub unsafe extern "C" fn xmlParserInputBufferCreateFilename(
2388    URI: *const c_char,
2389    enc: c_int,
2390) -> *mut _xmlParserInputBuffer {
2391    // SAFETY: URI must be a valid C string or NULL.
2392    if URI.is_null() {
2393        return ptr::null_mut();
2394    }
2395    crate::xml::parser::helpers::alloc_parser_input_buffer()
2396}
2397
2398/// Create an I/O parser input buffer.
2399///
2400/// # UPSTREAM-PARITY
2401///
2402/// ```c
2403/// xmlParserInputBufferPtr xmlParserInputBufferCreateIO(
2404///     xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
2405///     void *ioctx, int enc);
2406/// ```
2407#[no_mangle]
2408pub unsafe extern "C" fn xmlParserInputBufferCreateIO(
2409    ioread: Option<xmlInputReadCallback>,
2410    ioclose: Option<xmlInputCloseCallback>,
2411    ioctx: *mut c_void,
2412    enc: c_int,
2413) -> *mut _xmlParserInputBuffer {
2414    // SAFETY: ioread must be a valid callback if Some. ioctx may be NULL.
2415    let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
2416    if !buf.is_null() {
2417        (*buf).readcallback = ioread;
2418        (*buf).closecallback = ioclose;
2419        (*buf).context = ioctx;
2420    }
2421    buf
2422}
2423
2424/// Free a parser input buffer.
2425///
2426/// # UPSTREAM-PARITY
2427///
2428/// ```c
2429/// void xmlFreeParserInputBuffer(xmlParserInputBufferPtr buf);
2430/// ```
2431#[no_mangle]
2432pub unsafe extern "C" fn xmlFreeParserInputBuffer(buf: *mut _xmlParserInputBuffer) {
2433    if buf.is_null() {
2434        return;
2435    }
2436    crate::xml::parser::helpers::free_parser_input_buffer(buf);
2437}
2438
2439/// Create a new parser input.
2440///
2441/// # UPSTREAM-PARITY
2442///
2443/// ```c
2444/// xmlParserInputPtr xmlNewInputFromFile(xmlParserCtxtPtr ctxt, const char *filename);
2445/// ```
2446#[no_mangle]
2447pub unsafe extern "C" fn xmlNewInputFromFile(
2448    ctxt: *mut _xmlParserCtxt,
2449    filename: *const c_char,
2450) -> *mut _xmlParserInput {
2451    // SAFETY: filename must be a valid C string. ctxt may be NULL.
2452    // This function allocates a _xmlParserInput. The caller owns it.
2453    // Note: The InputBuffer backing data is NOT leaked here (no ctxt._private
2454    // to store it). Use xmlCreateFileParserCtxt + xmlParseDocument instead.
2455    if filename.is_null() {
2456        return ptr::null_mut();
2457    }
2458    crate::xml::parser::helpers::alloc_parser_input_buffer() as *mut _xmlParserInput
2459}
2460
2461/// Free a parser input.
2462///
2463/// # UPSTREAM-PARITY
2464///
2465/// ```c
2466/// void xmlFreeInputStream(xmlParserInputPtr input);
2467/// ```
2468#[no_mangle]
2469pub unsafe extern "C" fn xmlFreeInputStream(input: *mut _xmlParserInput) {
2470    if input.is_null() {
2471        return;
2472    }
2473    crate::xml::parser::helpers::free_parser_input(input);
2474}
2475
2476// ═══════════════════════════════════════════════════════════════════════════════
2477// 8. I/O
2478// ═══════════════════════════════════════════════════════════════════════════════
2479
2480/// Create an output buffer for a file.
2481///
2482/// # UPSTREAM-PARITY
2483///
2484/// ```c
2485/// xmlOutputBufferPtr xmlOutputBufferCreateFilename(const char *URI,
2486///                                                  xmlCharEncodingHandlerPtr encoder,
2487///                                                  int compression);
2488/// ```
2489#[no_mangle]
2490pub unsafe extern "C" fn xmlOutputBufferCreateFilename(
2491    URI: *const c_char,
2492    encoder: *mut c_void,
2493    compression: c_int,
2494) -> *mut _xmlOutputBuffer {
2495    let _ = compression;
2496    if URI.is_null() {
2497        return ptr::null_mut();
2498    }
2499    crate::xml::io::output_buffer_create_filename(
2500        URI,
2501        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2502        0,
2503    )
2504}
2505
2506/// Create an output buffer for a file descriptor.
2507///
2508/// # UPSTREAM-PARITY
2509///
2510/// ```c
2511/// xmlOutputBufferPtr xmlOutputBufferCreateFd(int fd,
2512///                                            xmlCharEncodingHandlerPtr encoder);
2513/// ```
2514#[no_mangle]
2515pub unsafe extern "C" fn xmlOutputBufferCreateFd(
2516    fd: c_int,
2517    encoder: *mut c_void,
2518) -> *mut _xmlOutputBuffer {
2519    if fd < 0 {
2520        return ptr::null_mut();
2521    }
2522    crate::xml::io::output_buffer_create_fd(
2523        fd,
2524        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2525    )
2526}
2527
2528/// Create an output buffer from I/O callbacks.
2529///
2530/// # UPSTREAM-PARITY
2531///
2532/// ```c
2533/// xmlOutputBufferPtr xmlOutputBufferCreateIO(
2534///     xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose,
2535///     void *ioctx, xmlCharEncodingHandlerPtr encoder);
2536/// ```
2537#[no_mangle]
2538pub unsafe extern "C" fn xmlOutputBufferCreateIO(
2539    iowrite: Option<xmlOutputWriteCallback>,
2540    ioclose: Option<xmlOutputCloseCallback>,
2541    ioctx: *mut c_void,
2542    encoder: *mut c_void,
2543) -> *mut _xmlOutputBuffer {
2544    crate::xml::io::output_buffer_create_io(
2545        iowrite,
2546        ioclose,
2547        ioctx,
2548        encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
2549    )
2550}
2551
2552/// Free an output buffer.
2553///
2554/// # UPSTREAM-PARITY
2555///
2556/// ```c
2557/// void xmlOutputBufferClose(xmlOutputBufferPtr out);
2558/// ```
2559#[no_mangle]
2560pub unsafe extern "C" fn xmlOutputBufferClose(out: *mut _xmlOutputBuffer) -> c_int {
2561    if out.is_null() {
2562        return -1;
2563    }
2564    crate::xml::io::output_buffer_close(out)
2565}
2566
2567/// Flush an output buffer.
2568///
2569/// # UPSTREAM-PARITY
2570///
2571/// ```c
2572/// int xmlOutputBufferFlush(xmlOutputBufferPtr out);
2573/// ```
2574#[no_mangle]
2575pub unsafe extern "C" fn xmlOutputBufferFlush(out: *mut _xmlOutputBuffer) -> c_int {
2576    if out.is_null() {
2577        return -1;
2578    }
2579    crate::xml::io::output_buffer_flush(out)
2580}
2581
2582/// Write to an output buffer.
2583///
2584/// # UPSTREAM-PARITY
2585///
2586/// ```c
2587/// int xmlOutputBufferWrite(xmlOutputBufferPtr out, int len, const char *data);
2588/// ```
2589#[no_mangle]
2590pub unsafe extern "C" fn xmlOutputBufferWrite(
2591    out: *mut _xmlOutputBuffer,
2592    len: c_int,
2593    data: *const c_char,
2594) -> c_int {
2595    if out.is_null() || data.is_null() || len <= 0 {
2596        return -1;
2597    }
2598    crate::xml::io::output_buffer_write(out, len, data)
2599}
2600
2601/// Write a string to an output buffer.
2602///
2603/// # UPSTREAM-PARITY
2604///
2605/// ```c
2606/// int xmlOutputBufferWriteString(xmlOutputBufferPtr out, const char *str);
2607/// ```
2608#[no_mangle]
2609pub unsafe extern "C" fn xmlOutputBufferWriteString(
2610    out: *mut _xmlOutputBuffer,
2611    str: *const c_char,
2612) -> c_int {
2613    if str.is_null() {
2614        return 0;
2615    }
2616    unsafe { xmlOutputBufferWrite(out, xmlStrlen(str as *const xmlChar), str) }
2617}
2618
2619// ═══════════════════════════════════════════════════════════════════════════════
2620// 9. Dictionary
2621// ═══════════════════════════════════════════════════════════════════════════════
2622
2623/// Create a new dictionary.
2624///
2625/// # UPSTREAM-PARITY
2626///
2627/// ```c
2628/// xmlDictPtr xmlDictCreate(void);
2629/// ```
2630#[no_mangle]
2631pub extern "C" fn xmlDictCreate() -> *mut c_void {
2632    // Phase 1: STUB — will be implemented in xml/dictionary module.
2633    ptr::null_mut()
2634}
2635
2636/// Create a sub-dictionary.
2637///
2638/// # UPSTREAM-PARITY
2639///
2640/// ```c
2641/// xmlDictPtr xmlDictCreateSub(xmlDictPtr sub);
2642/// ```
2643#[no_mangle]
2644pub extern "C" fn xmlDictCreateSub(_sub: *mut c_void) -> *mut c_void {
2645    // Phase 1: STUB
2646    ptr::null_mut()
2647}
2648
2649/// Look up a string in the dictionary.
2650///
2651/// # UPSTREAM-PARITY
2652///
2653/// ```c
2654/// const xmlChar *xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len);
2655/// ```
2656///
2657/// Returns an interned string pointer (valid as long as the dictionary exists).
2658/// - If `len` < 0, `name` must be null-terminated.
2659/// - If `len` >= 0, exactly `len` bytes are used.
2660#[no_mangle]
2661pub unsafe extern "C" fn xmlDictLookup(
2662    dict: *mut c_void,
2663    name: *const xmlChar,
2664    len: c_int,
2665) -> *const xmlChar {
2666    // Phase 1: STUB
2667    name
2668}
2669
2670/// Check if a string exists in the dictionary.
2671///
2672/// # UPSTREAM-PARITY
2673///
2674/// ```c
2675/// const xmlChar *xmlDictExists(xmlDictPtr dict, const xmlChar *name, int len);
2676/// ```
2677#[no_mangle]
2678pub unsafe extern "C" fn xmlDictExists(
2679    dict: *mut c_void,
2680    name: *const xmlChar,
2681    len: c_int,
2682) -> *const xmlChar {
2683    // Phase 1: STUB
2684    ptr::null()
2685}
2686
2687/// Query dictionary size.
2688///
2689/// # UPSTREAM-PARITY
2690///
2691/// ```c
2692/// unsigned int xmlDictSize(const xmlDictPtr dict);
2693/// ```
2694#[no_mangle]
2695pub extern "C" fn xmlDictSize(dict: *const c_void) -> c_uint {
2696    // Phase 1: STUB
2697    0
2698}
2699
2700/// Free a dictionary.
2701///
2702/// # UPSTREAM-PARITY
2703///
2704/// ```c
2705/// void xmlDictFree(xmlDictPtr dict);
2706/// ```
2707#[no_mangle]
2708pub extern "C" fn xmlDictFree(_dict: *mut c_void) {
2709    // Phase 1: STUB
2710}
2711
2712/// Set the dictionary size limit.
2713///
2714/// # UPSTREAM-PARITY
2715///
2716/// ```c
2717/// unsigned int xmlDictSetLimit(xmlDictPtr dict, unsigned int limit);
2718/// ```
2719#[no_mangle]
2720pub extern "C" fn xmlDictSetLimit(_dict: *mut c_void, _limit: c_uint) -> c_uint {
2721    // Phase 1: STUB
2722    0
2723}
2724
2725/// Get current dictionary usage.
2726///
2727/// # UPSTREAM-PARITY
2728///
2729/// ```c
2730/// unsigned int xmlDictGetUsage(const xmlDictPtr dict);
2731/// ```
2732#[no_mangle]
2733pub extern "C" fn xmlDictGetUsage(_dict: *const c_void) -> c_uint {
2734    // Phase 1: STUB
2735    0
2736}
2737
2738// ═══════════════════════════════════════════════════════════════════════════════
2739// 10. Hash Table
2740// ═══════════════════════════════════════════════════════════════════════════════
2741
2742/// Create a new hash table.
2743///
2744/// # UPSTREAM-PARITY
2745///
2746/// ```c
2747/// xmlHashTablePtr xmlHashCreate(int size);
2748/// ```
2749#[no_mangle]
2750pub extern "C" fn xmlHashCreate(_size: c_int) -> *mut c_void {
2751    // Phase 1: STUB
2752    ptr::null_mut()
2753}
2754
2755/// Create a new hash table with a dictionary.
2756///
2757/// # UPSTREAM-PARITY
2758///
2759/// ```c
2760/// xmlHashTablePtr xmlHashCreateDict(int size, xmlDictPtr dict);
2761/// ```
2762#[no_mangle]
2763pub extern "C" fn xmlHashCreateDict(_size: c_int, _dict: *mut c_void) -> *mut c_void {
2764    // Phase 1: STUB
2765    ptr::null_mut()
2766}
2767
2768/// Free a hash table.
2769///
2770/// # UPSTREAM-PARITY
2771///
2772/// ```c
2773/// void xmlHashFree(xmlHashTablePtr table, xmlHashDeallocator f);
2774/// ```
2775#[no_mangle]
2776pub extern "C" fn xmlHashFree(
2777    _table: *mut c_void,
2778    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2779) {
2780    // Phase 1: STUB
2781}
2782
2783/// Add an entry to a hash table.
2784///
2785/// # UPSTREAM-PARITY
2786///
2787/// ```c
2788/// int xmlHashAddEntry(xmlHashTablePtr table, const xmlChar *name, void *userdata);
2789/// ```
2790#[no_mangle]
2791pub unsafe extern "C" fn xmlHashAddEntry(
2792    _table: *mut c_void,
2793    _name: *const xmlChar,
2794    _userdata: *mut c_void,
2795) -> c_int {
2796    // Phase 1: STUB
2797    0
2798}
2799
2800/// Add a 2-key entry.
2801///
2802/// # UPSTREAM-PARITY
2803///
2804/// ```c
2805/// int xmlHashAddEntry2(xmlHashTablePtr table, const xmlChar *name,
2806///                      const xmlChar *name2, void *userdata);
2807/// ```
2808#[no_mangle]
2809pub unsafe extern "C" fn xmlHashAddEntry2(
2810    _table: *mut c_void,
2811    _name: *const xmlChar,
2812    _name2: *const xmlChar,
2813    _userdata: *mut c_void,
2814) -> c_int {
2815    // Phase 1: STUB
2816    0
2817}
2818
2819/// Add a 3-key entry.
2820///
2821/// # UPSTREAM-PARITY
2822///
2823/// ```c
2824/// int xmlHashAddEntry3(xmlHashTablePtr table, const xmlChar *name,
2825///                      const xmlChar *name2, const xmlChar *name3, void *userdata);
2826/// ```
2827#[no_mangle]
2828pub unsafe extern "C" fn xmlHashAddEntry3(
2829    _table: *mut c_void,
2830    _name: *const xmlChar,
2831    _name2: *const xmlChar,
2832    _name3: *const xmlChar,
2833    _userdata: *mut c_void,
2834) -> c_int {
2835    // Phase 1: STUB
2836    0
2837}
2838
2839/// Update or add an entry.
2840///
2841/// # UPSTREAM-PARITY
2842///
2843/// ```c
2844/// int xmlHashUpdateEntry(xmlHashTablePtr table, const xmlChar *name,
2845///                        void *userdata, xmlHashDeallocator f);
2846/// ```
2847#[no_mangle]
2848pub unsafe extern "C" fn xmlHashUpdateEntry(
2849    _table: *mut c_void,
2850    _name: *const xmlChar,
2851    _userdata: *mut c_void,
2852    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2853) -> c_int {
2854    // Phase 1: STUB
2855    0
2856}
2857
2858/// Update or add a 2-key entry.
2859#[no_mangle]
2860pub unsafe extern "C" fn xmlHashUpdateEntry2(
2861    _table: *mut c_void,
2862    _name: *const xmlChar,
2863    _name2: *const xmlChar,
2864    _userdata: *mut c_void,
2865    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2866) -> c_int {
2867    // Phase 1: STUB
2868    0
2869}
2870
2871/// Update or add a 3-key entry.
2872#[no_mangle]
2873pub unsafe extern "C" fn xmlHashUpdateEntry3(
2874    _table: *mut c_void,
2875    _name: *const xmlChar,
2876    _name2: *const xmlChar,
2877    _name3: *const xmlChar,
2878    _userdata: *mut c_void,
2879    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2880) -> c_int {
2881    // Phase 1: STUB
2882    0
2883}
2884
2885/// Look up an entry.
2886///
2887/// # UPSTREAM-PARITY
2888///
2889/// ```c
2890/// void *xmlHashLookup(xmlHashTablePtr table, const xmlChar *name);
2891/// ```
2892#[no_mangle]
2893pub unsafe extern "C" fn xmlHashLookup(_table: *mut c_void, _name: *const xmlChar) -> *mut c_void {
2894    // Phase 1: STUB
2895    ptr::null_mut()
2896}
2897
2898/// Look up a 2-key entry.
2899#[no_mangle]
2900pub unsafe extern "C" fn xmlHashLookup2(
2901    _table: *mut c_void,
2902    _name: *const xmlChar,
2903    _name2: *const xmlChar,
2904) -> *mut c_void {
2905    // Phase 1: STUB
2906    ptr::null_mut()
2907}
2908
2909/// Look up a 3-key entry.
2910#[no_mangle]
2911pub unsafe extern "C" fn xmlHashLookup3(
2912    _table: *mut c_void,
2913    _name: *const xmlChar,
2914    _name2: *const xmlChar,
2915    _name3: *const xmlChar,
2916) -> *mut c_void {
2917    // Phase 1: STUB
2918    ptr::null_mut()
2919}
2920
2921/// Get the size of a hash table.
2922///
2923/// # UPSTREAM-PARITY
2924///
2925/// ```c
2926/// int xmlHashSize(xmlHashTablePtr table);
2927/// ```
2928#[no_mangle]
2929pub extern "C" fn xmlHashSize(_table: *mut c_void) -> c_int {
2930    // Phase 1: STUB
2931    0
2932}
2933
2934/// Remove an entry.
2935///
2936/// # UPSTREAM-PARITY
2937///
2938/// ```c
2939/// int xmlHashRemoveEntry(xmlHashTablePtr table, const xmlChar *name,
2940///                        xmlHashDeallocator f);
2941/// ```
2942#[no_mangle]
2943pub unsafe extern "C" fn xmlHashRemoveEntry(
2944    _table: *mut c_void,
2945    _name: *const xmlChar,
2946    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2947) -> c_int {
2948    // Phase 1: STUB
2949    0
2950}
2951
2952/// Remove a 2-key entry.
2953#[no_mangle]
2954pub unsafe extern "C" fn xmlHashRemoveEntry2(
2955    _table: *mut c_void,
2956    _name: *const xmlChar,
2957    _name2: *const xmlChar,
2958    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2959) -> c_int {
2960    // Phase 1: STUB
2961    0
2962}
2963
2964/// Remove a 3-key entry.
2965#[no_mangle]
2966pub unsafe extern "C" fn xmlHashRemoveEntry3(
2967    _table: *mut c_void,
2968    _name: *const xmlChar,
2969    _name2: *const xmlChar,
2970    _name3: *const xmlChar,
2971    _f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
2972) -> c_int {
2973    // Phase 1: STUB
2974    0
2975}
2976
2977/// Scan a hash table with a scanner function.
2978///
2979/// # UPSTREAM-PARITY
2980///
2981/// ```c
2982/// void xmlHashScan(xmlHashTablePtr table, xmlHashScanner f, void *data);
2983/// ```
2984#[no_mangle]
2985pub extern "C" fn xmlHashScan(
2986    _table: *mut c_void,
2987    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar, *mut c_void)>,
2988    _data: *mut c_void,
2989) {
2990    // Phase 1: STUB
2991}
2992
2993/// Scan a hash table with a full scanner function.
2994#[no_mangle]
2995pub extern "C" fn xmlHashScanFull(
2996    _table: *mut c_void,
2997    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar, *mut c_void, *mut c_void)>,
2998    _data: *mut c_void,
2999) {
3000    // Phase 1: STUB
3001}
3002
3003/// Copy a hash table.
3004///
3005/// # UPSTREAM-PARITY
3006///
3007/// ```c
3008/// xmlHashTablePtr xmlHashCopy(xmlHashTablePtr table, xmlHashCopier f);
3009/// ```
3010#[no_mangle]
3011pub extern "C" fn xmlHashCopy(
3012    _table: *mut c_void,
3013    _f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar) -> *mut c_void>,
3014) -> *mut c_void {
3015    // Phase 1: STUB
3016    ptr::null_mut()
3017}
3018
3019// ═══════════════════════════════════════════════════════════════════════════════
3020// 11. List
3021// ═══════════════════════════════════════════════════════════════════════════════
3022
3023/// Create a new list.
3024///
3025/// # UPSTREAM-PARITY
3026///
3027/// ```c
3028/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
3029///                          xmlListDataCompare compare);
3030/// ```
3031#[no_mangle]
3032pub extern "C" fn xmlListCreate(
3033    _deallocator: Option<unsafe extern "C" fn(*mut c_void)>,
3034    _compare: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
3035) -> *mut c_void {
3036    // Phase 1: STUB
3037    ptr::null_mut()
3038}
3039
3040/// Delete a list.
3041///
3042/// # UPSTREAM-PARITY
3043///
3044/// ```c
3045/// void xmlListDelete(xmlListPtr list);
3046/// ```
3047#[no_mangle]
3048pub extern "C" fn xmlListDelete(_list: *mut c_void) {
3049    // Phase 1: STUB
3050}
3051
3052/// Search a list.
3053///
3054/// # UPSTREAM-PARITY
3055///
3056/// ```c
3057/// void *xmlListSearch(xmlListPtr list, void *data);
3058/// ```
3059#[no_mangle]
3060pub extern "C" fn xmlListSearch(_list: *mut c_void, _data: *mut c_void) -> *mut c_void {
3061    // Phase 1: STUB
3062    ptr::null_mut()
3063}
3064
3065/// Walk a list.
3066///
3067/// # UPSTREAM-PARITY
3068///
3069/// ```c
3070/// void xmlListWalk(xmlListPtr list, xmlListWalker walker, void *data);
3071/// ```
3072#[no_mangle]
3073pub extern "C" fn xmlListWalk(
3074    _list: *mut c_void,
3075    _walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
3076    _data: *mut c_void,
3077) {
3078    // Phase 1: STUB
3079}
3080
3081/// Push to back.
3082///
3083/// # UPSTREAM-PARITY
3084///
3085/// ```c
3086/// int xmlListPushBack(xmlListPtr list, void *data);
3087/// ```
3088#[no_mangle]
3089pub extern "C" fn xmlListPushBack(_list: *mut c_void, _data: *mut c_void) -> c_int {
3090    // Phase 1: STUB
3091    0
3092}
3093
3094/// Push to front.
3095///
3096/// # UPSTREAM-PARITY
3097///
3098/// ```c
3099/// int xmlListPushFront(xmlListPtr list, void *data);
3100/// ```
3101#[no_mangle]
3102pub extern "C" fn xmlListPushFront(_list: *mut c_void, _data: *mut c_void) -> c_int {
3103    // Phase 1: STUB
3104    0
3105}
3106
3107/// Pop from back.
3108#[no_mangle]
3109pub extern "C" fn xmlListPopBack(_list: *mut c_void) {
3110    // Phase 1: STUB
3111}
3112
3113/// Pop from front.
3114#[no_mangle]
3115pub extern "C" fn xmlListPopFront(_list: *mut c_void) {
3116    // Phase 1: STUB
3117}
3118
3119/// Insert into sorted list.
3120///
3121/// # UPSTREAM-PARITY
3122///
3123/// ```c
3124/// int xmlListInsert(xmlListPtr list, void *data);
3125/// ```
3126#[no_mangle]
3127pub extern "C" fn xmlListInsert(_list: *mut c_void, _data: *mut c_void) -> c_int {
3128    // Phase 1: STUB
3129    0
3130}
3131
3132/// Append to list.
3133#[no_mangle]
3134pub extern "C" fn xmlListAppend(_list: *mut c_void, _data: *mut c_void) -> c_int {
3135    // Phase 1: STUB
3136    0
3137}
3138
3139/// Remove first matching element.
3140#[no_mangle]
3141pub extern "C" fn xmlListRemoveFirst(_list: *mut c_void, _data: *mut c_void) -> c_int {
3142    // Phase 1: STUB
3143    0
3144}
3145
3146/// Remove last matching element.
3147#[no_mangle]
3148pub extern "C" fn xmlListRemoveLast(_list: *mut c_void, _data: *mut c_void) -> c_int {
3149    // Phase 1: STUB
3150    0
3151}
3152
3153/// Remove all matching elements.
3154#[no_mangle]
3155pub extern "C" fn xmlListRemoveAll(_list: *mut c_void, _data: *mut c_void) -> c_int {
3156    // Phase 1: STUB
3157    0
3158}
3159
3160/// Clear a list.
3161#[no_mangle]
3162pub extern "C" fn xmlListClear(_list: *mut c_void) {
3163    // Phase 1: STUB
3164}
3165
3166/// Check if list is empty.
3167///
3168/// # UPSTREAM-PARITY
3169///
3170/// ```c
3171/// int xmlListEmpty(xmlListPtr list);
3172/// ```
3173#[no_mangle]
3174pub extern "C" fn xmlListEmpty(_list: *mut c_void) -> c_int {
3175    // Phase 1: STUB
3176    1
3177}
3178
3179/// Get front element.
3180///
3181/// # UPSTREAM-PARITY
3182///
3183/// ```c
3184/// void *xmlListFront(xmlListPtr list);
3185/// ```
3186#[no_mangle]
3187pub extern "C" fn xmlListFront(_list: *mut c_void) -> *mut c_void {
3188    // Phase 1: STUB
3189    ptr::null_mut()
3190}
3191
3192/// Get back element.
3193///
3194/// # UPSTREAM-PARITY
3195///
3196/// ```c
3197/// void *xmlListBack(xmlListPtr list);
3198/// ```
3199#[no_mangle]
3200pub extern "C" fn xmlListBack(_list: *mut c_void) -> *mut c_void {
3201    // Phase 1: STUB
3202    ptr::null_mut()
3203}
3204
3205/// Get list size.
3206///
3207/// # UPSTREAM-PARITY
3208///
3209/// ```c
3210/// int xmlListSize(xmlListPtr list);
3211/// ```
3212#[no_mangle]
3213pub extern "C" fn xmlListSize(_list: *mut c_void) -> c_int {
3214    // Phase 1: STUB
3215    0
3216}
3217
3218/// Sort a list.
3219#[no_mangle]
3220pub extern "C" fn xmlListSort(_list: *mut c_void) {
3221    // Phase 1: STUB
3222}
3223
3224/// Reverse a list.
3225#[no_mangle]
3226pub extern "C" fn xmlListReverse(_list: *mut c_void) {
3227    // Phase 1: STUB
3228}
3229
3230/// Reverse a list in-place.
3231#[no_mangle]
3232pub extern "C" fn xmlListReverseSplice(_list: *mut c_void, _list2: *mut c_void) {
3233    // Phase 1: STUB
3234}
3235
3236/// Merge two sorted lists.
3237#[no_mangle]
3238pub extern "C" fn xmlListMerge(_list: *mut c_void, _list2: *mut c_void) {
3239    // Phase 1: STUB
3240}
3241
3242// ═══════════════════════════════════════════════════════════════════════════════
3243// 12. Buffer
3244// ═══════════════════════════════════════════════════════════════════════════════
3245
3246/// Create a new buffer.
3247///
3248/// # UPSTREAM-PARITY
3249///
3250/// ```c
3251/// xmlBufferPtr xmlBufferCreate(void);
3252/// ```
3253#[no_mangle]
3254pub extern "C" fn xmlBufferCreate() -> *mut _xmlBuffer {
3255    crate::xml::io::buf_create(-1)
3256}
3257
3258/// Create a new buffer of a given size.
3259///
3260/// # UPSTREAM-PARITY
3261///
3262/// ```c
3263/// xmlBufferPtr xmlBufferCreateSize(size_t size);
3264/// ```
3265#[no_mangle]
3266pub extern "C" fn xmlBufferCreateSize(size: usize) -> *mut _xmlBuffer {
3267    crate::xml::io::buf_create(size as c_int)
3268}
3269
3270/// Create a buffer from a static string.
3271///
3272/// # UPSTREAM-PARITY
3273///
3274/// ```c
3275/// xmlBufferPtr xmlBufferCreateStatic(void *mem, size_t size);
3276/// ```
3277#[no_mangle]
3278pub extern "C" fn xmlBufferCreateStatic(mem: *mut c_void, size: usize) -> *mut _xmlBuffer {
3279    if mem.is_null() || size == 0 {
3280        return ptr::null_mut();
3281    }
3282    crate::xml::io::buf_create_static(mem as *const xmlChar, size as c_int)
3283}
3284
3285/// Free a buffer.
3286///
3287/// # UPSTREAM-PARITY
3288///
3289/// ```c
3290/// void xmlBufferFree(xmlBufferPtr buf);
3291/// ```
3292#[no_mangle]
3293pub extern "C" fn xmlBufferFree(buf: *mut _xmlBuffer) {
3294    crate::xml::io::buf_free(buf)
3295}
3296
3297/// Empty a buffer.
3298///
3299/// # UPSTREAM-PARITY
3300///
3301/// ```c
3302/// void xmlBufferEmpty(xmlBufferPtr buf);
3303/// ```
3304#[no_mangle]
3305pub extern "C" fn xmlBufferEmpty(buf: *mut _xmlBuffer) {
3306    if buf.is_null() {
3307        return;
3308    }
3309    unsafe {
3310        if !(*buf).content.is_null() {
3311            *(*buf).content = 0;
3312        }
3313        (*buf).use_ = 0;
3314    }
3315}
3316
3317/// Get buffer content.
3318///
3319/// # UPSTREAM-PARITY
3320///
3321/// ```c
3322/// xmlChar *xmlBufferContent(const xmlBuffer *buf);
3323/// ```
3324#[no_mangle]
3325pub extern "C" fn xmlBufferContent(buf: *const _xmlBuffer) -> *mut xmlChar {
3326    crate::xml::io::buf_content(buf as *mut _xmlBuffer)
3327}
3328
3329/// Get buffer length.
3330///
3331/// # UPSTREAM-PARITY
3332///
3333/// ```c
3334/// int xmlBufferLength(const xmlBuffer *buf);
3335/// ```
3336#[no_mangle]
3337pub extern "C" fn xmlBufferLength(buf: *const _xmlBuffer) -> c_int {
3338    crate::xml::io::buf_length(buf as *mut _xmlBuffer)
3339}
3340
3341/// Write to a buffer.
3342///
3343/// # UPSTREAM-PARITY
3344///
3345/// ```c
3346/// int xmlBufferAdd(xmlBufferPtr buf, const xmlChar *str, int len);
3347/// ```
3348#[no_mangle]
3349pub unsafe extern "C" fn xmlBufferAdd(
3350    buf: *mut _xmlBuffer,
3351    str: *const xmlChar,
3352    len: c_int,
3353) -> c_int {
3354    crate::xml::io::buf_add(buf, str, len)
3355}
3356
3357/// Write to a buffer at a position.
3358///
3359/// # UPSTREAM-PARITY
3360///
3361/// ```c
3362/// int xmlBufferAddHead(xmlBufferPtr buf, const xmlChar *str, int len);
3363/// ```
3364#[no_mangle]
3365pub unsafe extern "C" fn xmlBufferAddHead(
3366    buf: *mut _xmlBuffer,
3367    str: *const xmlChar,
3368    len: c_int,
3369) -> c_int {
3370    crate::xml::io::buf_add_head(buf, str, len)
3371}
3372
3373/// Write a C string to a buffer.
3374///
3375/// # UPSTREAM-PARITY
3376///
3377/// ```c
3378/// int xmlBufferCat(xmlBufferPtr buf, const xmlChar *str);
3379/// ```
3380#[no_mangle]
3381pub unsafe extern "C" fn xmlBufferCat(buf: *mut _xmlBuffer, str: *const xmlChar) -> c_int {
3382    if str.is_null() {
3383        return -1;
3384    }
3385    let len = crate::xml::string::xml_strlen(str) as c_int;
3386    crate::xml::io::buf_add(buf, str, len)
3387}
3388
3389/// Set buffer allocation scheme.
3390///
3391/// # UPSTREAM-PARITY
3392///
3393/// ```c
3394/// void xmlBufferSetAllocationScheme(xmlBufferPtr buf,
3395///                                    xmlBufferAllocationScheme scheme);
3396/// ```
3397#[no_mangle]
3398pub extern "C" fn xmlBufferSetAllocationScheme(buf: *mut _xmlBuffer, scheme: c_int) {
3399    if buf.is_null() {
3400        return;
3401    }
3402    unsafe {
3403        (*buf).alloc = scheme;
3404    }
3405}
3406
3407/// Shrink buffer.
3408///
3409/// # UPSTREAM-PARITY
3410///
3411/// ```c
3412/// int xmlBufferShrink(xmlBufferPtr buf, int len);
3413/// ```
3414#[no_mangle]
3415pub extern "C" fn xmlBufferShrink(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3416    if buf.is_null() || len <= 0 {
3417        return 0;
3418    }
3419    unsafe {
3420        let b = &mut *buf;
3421        let shrink_len = (len as c_uint).min(b.use_);
3422        if shrink_len > 0 {
3423            let remaining = b.use_ - shrink_len;
3424            if remaining > 0 {
3425                core::ptr::copy(
3426                    b.content.add(shrink_len as usize),
3427                    b.content,
3428                    remaining as usize,
3429                );
3430            }
3431            *b.content.add(remaining as usize) = 0;
3432            b.use_ = remaining;
3433        }
3434    }
3435    len
3436}
3437
3438/// Grow buffer.
3439///
3440/// # UPSTREAM-PARITY
3441///
3442/// ```c
3443/// int xmlBufferGrow(xmlBufferPtr buf, int len);
3444/// ```
3445#[no_mangle]
3446pub extern "C" fn xmlBufferGrow(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3447    if buf.is_null() || len <= 0 {
3448        return 0;
3449    }
3450    let cur_use = unsafe { (*buf).use_ };
3451    let new_size = cur_use + len as c_uint + 1;
3452    crate::xml::io::buf_grow(buf, new_size)
3453}
3454
3455/// Reserve buffer space.
3456///
3457/// # UPSTREAM-PARITY
3458///
3459/// ```c
3460/// int xmlBufferReserve(xmlBufferPtr buf, int len);
3461/// ```
3462#[no_mangle]
3463pub extern "C" fn xmlBufferReserve(buf: *mut _xmlBuffer, len: c_int) -> c_int {
3464    xmlBufferGrow(buf, len)
3465}
3466
3467/// Detach buffer content.
3468///
3469/// # UPSTREAM-PARITY
3470///
3471/// ```c
3472/// xmlChar *xmlBufferDetach(xmlBufferPtr buf);
3473/// ```
3474#[no_mangle]
3475pub extern "C" fn xmlBufferDetach(buf: *mut _xmlBuffer) -> *mut xmlChar {
3476    if buf.is_null() {
3477        return ptr::null_mut();
3478    }
3479    unsafe {
3480        let content = (*buf).content;
3481        (*buf).content = ptr::null_mut();
3482        (*buf).use_ = 0;
3483        (*buf).size = 0;
3484        content
3485    }
3486}
3487
3488// ═══════════════════════════════════════════════════════════════════════════════
3489// 13. Encoding
3490// ═══════════════════════════════════════════════════════════════════════════════
3491
3492/// Get encoding from a name string.
3493///
3494/// # UPSTREAM-PARITY
3495///
3496/// ```c
3497/// xmlCharEncoding xmlGetCharEncoding(const char *name);
3498/// ```
3499#[no_mangle]
3500pub extern "C" fn xmlGetCharEncoding(name: *const c_char) -> c_int {
3501    if name.is_null() {
3502        return 0; // XML_CHAR_ENCODING_NONE
3503    }
3504    let name_bytes = unsafe {
3505        let len = libc::strlen(name);
3506        core::slice::from_raw_parts(name as *const u8, len)
3507    };
3508    crate::xml::encoding::encoding_from_name(name_bytes) as c_int
3509}
3510
3511/// Find an encoding handler.
3512///
3513/// # UPSTREAM-PARITY
3514///
3515/// ```c
3516/// xmlCharEncodingHandlerPtr xmlFindCharEncodingHandler(const char *name);
3517/// ```
3518#[no_mangle]
3519pub extern "C" fn xmlFindCharEncodingHandler(name: *const c_char) -> *mut c_void {
3520    if name.is_null() {
3521        return ptr::null_mut();
3522    }
3523    crate::xml::encoding::find_encoding_handler(name as *const xmlChar) as *mut c_void
3524}
3525
3526/// Close an encoding handler.
3527///
3528/// # UPSTREAM-PARITY
3529///
3530/// ```c
3531/// int xmlCharEncCloseFunc(xmlCharEncodingHandlerPtr handler);
3532/// ```
3533#[no_mangle]
3534pub extern "C" fn xmlCharEncCloseFunc(handler: *mut c_void) -> c_int {
3535    if handler.is_null() {
3536        return -1;
3537    }
3538    // Free the encoding handler
3539    unsafe {
3540        let h = handler as *mut crate::abi::structs::_xmlCharEncodingHandler;
3541        if !(*h).name.is_null() {
3542            crate::abi::allocator::xmlFree((*h).name as *mut c_void);
3543        }
3544        crate::abi::allocator::xmlFree(handler);
3545    }
3546    0
3547}
3548
3549/// Convert an input buffer's encoding.
3550///
3551/// # UPSTREAM-PARITY
3552///
3553/// ```c
3554/// int xmlCharEncInput(xmlParserInputBufferPtr input, int to);
3555/// ```
3556#[no_mangle]
3557pub unsafe extern "C" fn xmlCharEncInput(input: *mut _xmlParserInputBuffer, _to: c_int) -> c_int {
3558    if input.is_null() {
3559        return -1;
3560    }
3561    let handler = (*input).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
3562    if handler.is_null() {
3563        return -1;
3564    }
3565    let raw = (*input).raw as *mut crate::abi::structs::_xmlBuffer;
3566    let buf = (*input).buffer as *mut crate::abi::structs::_xmlBuffer;
3567    if raw.is_null() || buf.is_null() {
3568        return -1;
3569    }
3570    crate::xml::encoding::char_enc_in(handler, buf, raw)
3571}
3572
3573/// Convert an output buffer's encoding.
3574///
3575/// # UPSTREAM-PARITY
3576///
3577/// ```c
3578/// int xmlCharEncOutput(xmlOutputBufferPtr output, int to);
3579/// ```
3580#[no_mangle]
3581pub unsafe extern "C" fn xmlCharEncOutput(output: *mut _xmlOutputBuffer, _to: c_int) -> c_int {
3582    if output.is_null() {
3583        return -1;
3584    }
3585    let handler = (*output).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
3586    if handler.is_null() {
3587        return -1;
3588    }
3589    let buf = (*output).buffer as *mut crate::abi::structs::_xmlBuffer;
3590    let conv = (*output).conv as *mut crate::abi::structs::_xmlBuffer;
3591    if buf.is_null() || conv.is_null() {
3592        return -1;
3593    }
3594    crate::xml::encoding::char_enc_out(handler, conv, buf)
3595}
3596
3597// ═══════════════════════════════════════════════════════════════════════════════
3598// URI
3599// ═══════════════════════════════════════════════════════════════════════════════
3600
3601/// Parse a URI string.
3602///
3603/// # UPSTREAM-PARITY
3604///
3605/// ```c
3606/// xmlURIPtr xmlParseURI(const char *str);
3607/// ```
3608#[no_mangle]
3609pub unsafe extern "C" fn xmlParseURI(str: *const c_char) -> *mut c_void {
3610    crate::xml::uri::xmlParseURI(str)
3611}
3612
3613/// Parse a URI string (raw version).
3614///
3615/// # UPSTREAM-PARITY
3616///
3617/// ```c
3618/// xmlURIPtr xmlParseURIRaw(const char *str, int raw);
3619/// ```
3620#[no_mangle]
3621pub unsafe extern "C" fn xmlParseURIRaw(str: *const c_char, raw: c_int) -> *mut c_void {
3622    let _ = raw;
3623    crate::xml::uri::xmlParseURI(str)
3624}
3625
3626/// Free a URI structure.
3627///
3628/// # UPSTREAM-PARITY
3629///
3630/// ```c
3631/// void xmlFreeURI(xmlURIPtr uri);
3632/// ```
3633#[no_mangle]
3634pub unsafe extern "C" fn xmlFreeURI(uri: *mut c_void) {
3635    crate::xml::uri::xmlFreeURI(uri)
3636}
3637
3638/// Create an empty URI.
3639///
3640/// # UPSTREAM-PARITY
3641///
3642/// ```c
3643/// xmlURIPtr xmlCreateURI(void);
3644/// ```
3645#[no_mangle]
3646pub extern "C" fn xmlCreateURI() -> *mut c_void {
3647    crate::xml::uri::xmlCreateURI()
3648}
3649
3650/// Save a URI structure to a string.
3651///
3652/// # UPSTREAM-PARITY
3653///
3654/// ```c
3655/// xmlChar *xmlSaveUri(xmlURIPtr uri);
3656/// ```
3657#[no_mangle]
3658pub unsafe extern "C" fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
3659    crate::xml::uri::xmlSaveUri(uri)
3660}
3661
3662/// Escape a URI string.
3663///
3664/// # UPSTREAM-PARITY
3665///
3666/// ```c
3667/// xmlChar *xmlURIEscapeStr(const xmlChar *str, const xmlChar *list);
3668/// ```
3669#[no_mangle]
3670pub unsafe extern "C" fn xmlURIEscapeStr(
3671    str: *const xmlChar,
3672    list: *const xmlChar,
3673) -> *mut xmlChar {
3674    crate::xml::uri::xmlURIEscapeStr(str, list)
3675}
3676
3677/// Unescape a URI string.
3678///
3679/// # UPSTREAM-PARITY
3680///
3681/// ```c
3682/// char *xmlURIUnescapeString(const char *str, int len, char *target);
3683/// ```
3684#[no_mangle]
3685pub unsafe extern "C" fn xmlURIUnescapeString(
3686    str: *const c_char,
3687    len: c_int,
3688    target: *mut c_char,
3689) -> *mut c_char {
3690    crate::xml::uri::xmlURIUnescapeString(str, len, target)
3691}
3692
3693// ═══════════════════════════════════════════════════════════════════════════════
3694// 14. XPath
3695// ═══════════════════════════════════════════════════════════════════════════════
3696
3697// ── Helper functions ────────────────────────────────────────────────────
3698
3699/// Convert an internal `XPathValue` to a C ABI `_xmlXPathObject`.
3700///
3701/// The returned pointer is heap-allocated via `xmlMallocZero` and must be
3702/// freed with `xmlXPathFreeObject`.
3703///
3704/// # Safety
3705///
3706/// Must be called from a context where `xmlMalloc` is safe to call.
3707unsafe fn xpath_to_object(val: XPathValue) -> *mut _xmlXPathObject {
3708    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
3709    if obj.is_null() {
3710        return ptr::null_mut();
3711    }
3712    match val {
3713        XPathValue::NodeSet(ns) => {
3714            (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
3715            (*obj).nodesetval = ns.to_raw() as *mut c_void;
3716        }
3717        XPathValue::Boolean(b) => {
3718            (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
3719            (*obj).boolval = if b { 1 } else { 0 };
3720        }
3721        XPathValue::Number(n) => {
3722            (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
3723            (*obj).floatval = n;
3724        }
3725        XPathValue::String(s) => {
3726            (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
3727            let bytes = s.as_bytes();
3728            let len = bytes.len();
3729            let buf = xmlMalloc(len + 1) as *mut xmlChar;
3730            if !buf.is_null() {
3731                ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
3732                *buf.add(len) = 0; // null terminator
3733            }
3734            (*obj).stringval = buf;
3735        }
3736    }
3737    obj
3738}
3739
3740/// Extract an internal `XPathValue` from a C ABI `_xmlXPathObject`.
3741///
3742/// # Safety
3743///
3744/// `obj` must be a valid, non-null pointer to a properly initialised
3745/// `_xmlXPathObject`.
3746unsafe fn object_to_xpathvalue(obj: *mut _xmlXPathObject) -> XPathValue {
3747    let typ = (*obj).type_;
3748    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
3749        let ns_ptr = (*obj).nodesetval as *mut _xmlNodeSet;
3750        if ns_ptr.is_null() {
3751            return XPathValue::NodeSet(NodeSet::new());
3752        }
3753        let node_nr = (*ns_ptr).nodeNr;
3754        let node_tab = (*ns_ptr).nodeTab;
3755        let mut ns = NodeSet::new();
3756        if !node_tab.is_null() {
3757            for i in 0..node_nr as isize {
3758                let node = *node_tab.add(i as usize);
3759                ns.push(node);
3760            }
3761        }
3762        XPathValue::NodeSet(ns)
3763    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
3764        XPathValue::Boolean((*obj).boolval != 0)
3765    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
3766        XPathValue::Number((*obj).floatval)
3767    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
3768        let s_ptr = (*obj).stringval;
3769        if s_ptr.is_null() {
3770            XPathValue::String(String::new())
3771        } else {
3772            let s = CStr::from_ptr(s_ptr as *const c_char)
3773                .to_string_lossy()
3774                .into_owned();
3775            XPathValue::String(s)
3776        }
3777    } else if typ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int {
3778        // A result tree fragment: node-set containing the fragment's
3779        // document node (matching how global RTF variables are bound), so
3780        // local RTF variables stringify to their text and remain navigable
3781        // via exsl:node-set.
3782        let frag_doc = (*obj).nodesetval as *mut _xmlDoc;
3783        if frag_doc.is_null() {
3784            XPathValue::NodeSet(NodeSet::new())
3785        } else {
3786            let mut ns = NodeSet::new();
3787            ns.push(frag_doc as *mut _xmlNode);
3788            XPathValue::NodeSet(ns)
3789        }
3790    } else {
3791        // Undefined / unknown type — return boolean false as a safe default.
3792        XPathValue::Boolean(false)
3793    }
3794}
3795
3796/// Public wrapper for `object_to_xpathvalue` (used by the XSLT engine).
3797///
3798/// # Safety
3799///
3800/// `obj` must be a valid, non-null pointer to a properly initialised
3801/// `_xmlXPathObject`.
3802pub unsafe fn object_to_xpathvalue_pub(obj: *mut _xmlXPathObject) -> XPathValue {
3803    object_to_xpathvalue(obj)
3804}
3805
3806// ── Compiled expression registry ────────────────────────────────────────
3807//
3808// Compiled XPath expressions are opaque pointers returned by xmlXPathCompile.
3809// We store them in a global registry keyed by a monotonically increasing ID.
3810
3811static COMPILED_EXPRS: Lazy<Mutex<HashMap<u64, Box<CompiledExpr>>>> =
3812    Lazy::new(|| Mutex::new(HashMap::new()));
3813static NEXT_COMPILED_KEY: Lazy<Mutex<u64>> = Lazy::new(|| Mutex::new(1));
3814
3815// ── C extension-function registry ──────────────────────────────────────
3816//
3817// C extension functions registered via xmlXPathRegisterFunc / RegisterFuncNS
3818// are stored here because the Rust XPathFunction signature is incompatible
3819// with the C xmlXPathFunction calling convention (the C function expects a
3820// parser context, not pre-evaluated argument slices). The registration is
3821// stored faithfully; invoking registered C functions from within the Rust
3822// evaluator requires a bridge that is not yet implemented.
3823
3824type CXPathFunc = unsafe extern "C" fn(*mut c_void, c_int);
3825
3826/// Wrapper around `*mut c_void` that implements `Send` + `Sync` so it can
3827/// be used as a key in a `Mutex`-protected global `HashMap`.
3828#[derive(Clone, Copy, PartialEq, Eq, Hash)]
3829struct SendSyncPtr(*mut c_void);
3830unsafe impl Send for SendSyncPtr {}
3831unsafe impl Sync for SendSyncPtr {}
3832
3833static C_FUNCTIONS: Lazy<Mutex<HashMap<(SendSyncPtr, String), CXPathFunc>>> =
3834    Lazy::new(|| Mutex::new(HashMap::new()));
3835
3836/// Rust-side wrapper that is registered in the internal XPathContext when a
3837/// C extension function is registered. It looks up the C function pointer and
3838/// attempts to call it, but the calling-convention mismatch means this is a
3839/// stub that returns an error for now.
3840fn c_func_stub(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
3841    Err(
3842        "C extension function cannot be called from Rust evaluator without a parser-context bridge"
3843            .to_string(),
3844    )
3845}
3846
3847// ── Public API ─────────────────────────────────────────────────────────
3848
3849/// Create a new XPath context.
3850///
3851/// Allocates a `_xmlXPathContext` and an internal `XPathContext`, storing
3852/// the latter's pointer in the `extra` field.
3853///
3854/// # UPSTREAM-PARITY
3855///
3856/// ```c
3857/// xmlXPathContextPtr xmlXPathNewContext(xmlDocPtr doc);
3858/// ```
3859#[no_mangle]
3860pub unsafe extern "C" fn xmlXPathNewContext(doc: *mut _xmlDoc) -> *mut _xmlXPathContext {
3861    let ctxt = xmlMallocZero(size_of::<_xmlXPathContext>()) as *mut _xmlXPathContext;
3862    if ctxt.is_null() {
3863        return ptr::null_mut();
3864    }
3865
3866    // Initialise the C ABI context fields.
3867    (*ctxt).doc = doc;
3868    (*ctxt).node = ptr::null_mut();
3869    (*ctxt).contextSize = 1;
3870    (*ctxt).proximityPosition = 1;
3871
3872    // Create the internal XPathContext and store it in `extra`.
3873    let internal = Box::new(XPathContext::new(doc));
3874    (*ctxt).extra = Box::into_raw(internal) as *mut c_void;
3875
3876    ctxt
3877}
3878
3879/// Free an XPath context.
3880///
3881/// # UPSTREAM-PARITY
3882///
3883/// ```c
3884/// void xmlXPathFreeContext(xmlXPathContextPtr ctxt);
3885/// ```
3886#[no_mangle]
3887pub unsafe extern "C" fn xmlXPathFreeContext(ctxt: *mut _xmlXPathContext) {
3888    if ctxt.is_null() {
3889        return;
3890    }
3891    // Drop the internal XPathContext.
3892    if !(*ctxt).extra.is_null() {
3893        let _ = Box::from_raw((*ctxt).extra as *mut XPathContext);
3894        (*ctxt).extra = ptr::null_mut();
3895    }
3896    // Free the C ABI context struct.
3897    xmlFree(ctxt as *mut c_void);
3898}
3899
3900/// Evaluate an XPath expression.
3901///
3902/// # UPSTREAM-PARITY
3903///
3904/// ```c
3905/// xmlXPathObjectPtr xmlXPathEvalExpression(const xmlChar *str,
3906///                                          xmlXPathContextPtr ctxt);
3907/// ```
3908#[no_mangle]
3909pub unsafe extern "C" fn xmlXPathEvalExpression(
3910    str_: *const xmlChar,
3911    ctxt: *mut _xmlXPathContext,
3912) -> *mut _xmlXPathObject {
3913    if str_.is_null() || ctxt.is_null() {
3914        return ptr::null_mut();
3915    }
3916    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
3917        Ok(s) => s,
3918        Err(_) => return ptr::null_mut(),
3919    };
3920    let internal = (*ctxt).extra as *mut XPathContext;
3921    if internal.is_null() {
3922        return ptr::null_mut();
3923    }
3924    let internal = &mut *internal;
3925
3926    match crate::xml::xpath::evaluate_str(expr_str, internal) {
3927        Some(val) => xpath_to_object(val),
3928        None => {
3929            // UPSTREAM-PARITY: libxml2 reports a failed compile/eval with
3930            // "XPath error : Invalid expression" (xmlXPathErr,
3931            // XPATH_EXPR_ERROR). The precise per-expression diagnostics are
3932            // tracked as RESIDUAL R-XPATH-ERRMSG.
3933            if internal.error.is_none() {
3934                internal.set_error("Invalid expression");
3935            }
3936            ptr::null_mut()
3937        }
3938    }
3939}
3940
3941/// Evaluate an XPath expression (simplified alias).
3942///
3943/// # UPSTREAM-PARITY
3944///
3945/// ```c
3946/// xmlXPathObjectPtr xmlXPathEval(const xmlChar *str, xmlXPathContextPtr ctxt);
3947/// ```
3948#[no_mangle]
3949pub unsafe extern "C" fn xmlXPathEval(
3950    str_: *const xmlChar,
3951    ctxt: *mut _xmlXPathContext,
3952) -> *mut _xmlXPathObject {
3953    xmlXPathEvalExpression(str_, ctxt)
3954}
3955
3956/// Free an XPath object.
3957///
3958/// Releases the internal members (string buffer or node-set) and then frees
3959/// the object struct itself.
3960///
3961/// # UPSTREAM-PARITY
3962///
3963/// ```c
3964/// void xmlXPathFreeObject(xmlXPathObjectPtr obj);
3965/// ```
3966#[no_mangle]
3967pub unsafe extern "C" fn xmlXPathFreeObject(obj: *mut _xmlXPathObject) {
3968    if obj.is_null() {
3969        return;
3970    }
3971    let typ = (*obj).type_;
3972    // Free string storage.
3973    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
3974        if !(*obj).stringval.is_null() {
3975            xmlFree((*obj).stringval as *mut c_void);
3976            (*obj).stringval = ptr::null_mut();
3977        }
3978    }
3979    // Free node-set storage.
3980    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
3981        let ns = (*obj).nodesetval as *mut _xmlNodeSet;
3982        if !ns.is_null() {
3983            if !(*ns).nodeTab.is_null() {
3984                xmlFree((*ns).nodeTab as *mut c_void);
3985            }
3986            xmlFree(ns as *mut c_void);
3987        }
3988        (*obj).nodesetval = ptr::null_mut();
3989    }
3990    xmlFree(obj as *mut c_void);
3991}
3992
3993/// Copy an XPath object (deep copy).
3994///
3995/// # UPSTREAM-PARITY
3996///
3997/// ```c
3998/// xmlXPathObjectPtr xmlXPathObjectCopy(xmlXPathObjectPtr val);
3999/// ```
4000///
4001/// Oracle behavior: returns a newly allocated object with the same type
4002/// and value. Node-sets are copied element-by-element; strings are
4003/// duplicated; numbers and booleans are copied by value.
4004#[no_mangle]
4005pub unsafe extern "C" fn xmlXPathObjectCopy(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
4006    if val.is_null() {
4007        return ptr::null_mut();
4008    }
4009    let typ = (*val).type_;
4010    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
4011    if obj.is_null() {
4012        return ptr::null_mut();
4013    }
4014    (*obj).type_ = typ;
4015    if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
4016        let src_ns = (*val).nodesetval as *mut _xmlNodeSet;
4017        if !src_ns.is_null() {
4018            let nr = (*src_ns).nodeNr;
4019            let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
4020            if ns.is_null() {
4021                xmlFree(obj as *mut c_void);
4022                return ptr::null_mut();
4023            }
4024            (*ns).nodeNr = nr;
4025            (*ns).nodeMax = nr;
4026            if nr > 0 && !(*src_ns).nodeTab.is_null() {
4027                let tab = xmlMalloc((nr as usize) * core::mem::size_of::<*mut _xmlNode>())
4028                    as *mut *mut _xmlNode;
4029                if tab.is_null() {
4030                    xmlFree(ns as *mut c_void);
4031                    xmlFree(obj as *mut c_void);
4032                    return ptr::null_mut();
4033                }
4034                ptr::copy_nonoverlapping((*src_ns).nodeTab, tab, nr as usize);
4035                (*ns).nodeTab = tab;
4036            } else {
4037                (*ns).nodeTab = ptr::null_mut();
4038            }
4039            (*obj).nodesetval = ns as *mut c_void;
4040        }
4041    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
4042        (*obj).boolval = (*val).boolval;
4043    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
4044        (*obj).floatval = (*val).floatval;
4045    } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
4046        let src = (*val).stringval;
4047        if !src.is_null() {
4048            let len = libc::strlen(src as *const libc::c_char);
4049            let buf = xmlMalloc(len + 1) as *mut xmlChar;
4050            if !buf.is_null() {
4051                ptr::copy_nonoverlapping(src, buf, len);
4052                *buf.add(len) = 0;
4053            }
4054            (*obj).stringval = buf;
4055        }
4056    }
4057    obj
4058}
4059
4060/// Cast an XPath object to its string value.
4061///
4062/// Returns a newly allocated string (caller frees with `xmlFree`).
4063///
4064/// # UPSTREAM-PARITY
4065///
4066/// ```c
4067/// xmlChar *xmlXPathCastToString(xmlXPathObjectPtr val);
4068/// ```
4069#[no_mangle]
4070pub unsafe extern "C" fn xmlXPathCastToString(val: *mut _xmlXPathObject) -> *mut xmlChar {
4071    if val.is_null() {
4072        return ptr::null_mut();
4073    }
4074    let typ = (*val).type_;
4075    let mut result: Vec<u8> = Vec::new();
4076    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
4077        if !(*val).stringval.is_null() {
4078            let len = libc::strlen((*val).stringval as *const libc::c_char);
4079            result.extend_from_slice(core::slice::from_raw_parts((*val).stringval, len));
4080        }
4081    } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
4082        // Number → string conversion per XPath 1.0 §4.2:
4083        // - NaN → "NaN"
4084        // - +0/-0 → "0"
4085        // - infinity → "Infinity" / "-Infinity"
4086        // - integer → decimal representation without exponent
4087        let n = (*val).floatval;
4088        result.extend_from_slice(xml_number_to_string(n).as_bytes());
4089    } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
4090        result.extend_from_slice(if (*val).boolval != 0 {
4091            b"true"
4092        } else {
4093            b"false"
4094        });
4095    } else if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
4096        // String value of a node-set is the string value of the first node
4097        // in document order (or empty if empty).
4098        let ns = (*val).nodesetval as *mut _xmlNodeSet;
4099        if !ns.is_null() && (*ns).nodeNr > 0 && !(*ns).nodeTab.is_null() {
4100            let node = *(*ns).nodeTab;
4101            if !node.is_null() {
4102                let content = crate::xml::tree::node_get_content(node);
4103                if !content.is_null() {
4104                    let len = libc::strlen(content as *const libc::c_char);
4105                    result.extend_from_slice(core::slice::from_raw_parts(content, len));
4106                    xmlFree(content as *mut c_void);
4107                }
4108            }
4109        }
4110    }
4111    // Allocate the C string.
4112    let buf = xmlMalloc(result.len() + 1) as *mut xmlChar;
4113    if buf.is_null() {
4114        return ptr::null_mut();
4115    }
4116    if !result.is_empty() {
4117        ptr::copy_nonoverlapping(result.as_ptr(), buf, result.len());
4118    }
4119    *buf.add(result.len()) = 0;
4120    buf
4121}
4122
4123/// Convert an XPath number to its string representation (XPath 1.0 §4.2).
4124///
4125/// Exposed as a helper; matches upstream `xmlXPathCastNumberToString`.
4126pub fn xml_number_to_string(n: f64) -> String {
4127    if n.is_nan() {
4128        return "NaN".to_string();
4129    }
4130    if n.is_infinite() {
4131        return if n > 0.0 {
4132            "Infinity".to_string()
4133        } else {
4134            "-Infinity".to_string()
4135        };
4136    }
4137    if n == 0.0 {
4138        // Both +0 and -0 serialize as "0" per XPath 1.0.
4139        return "0".to_string();
4140    }
4141    // Integer values serialize without a decimal point or exponent.
4142    if n.fract() == 0.0 && n.abs() < 1e15 {
4143        return format!("{:.0}", n);
4144    }
4145    // For other values, upstream uses %.15g-ish formatting with adjustments.
4146    // Try to find the shortest representation that round-trips (%.17g then
4147    // trim), which matches upstream's effective behavior for most inputs.
4148    let mut s = format!("{:.15}", n);
4149    // Trim trailing zeros and possible trailing dot.
4150    if s.contains('.') {
4151        while s.ends_with('0') {
4152            s.pop();
4153        }
4154        if s.ends_with('.') {
4155            s.pop();
4156        }
4157    }
4158    if s == "-0" {
4159        return "0".to_string();
4160    }
4161    s
4162}
4163
4164/// Cast a C string to a number per XPath 1.0 §4.2 conversion rules.
4165///
4166/// # UPSTREAM-PARITY
4167///
4168/// ```c
4169/// double xmlXPathCastStringToNumber(const xmlChar *val);
4170/// ```
4171#[no_mangle]
4172pub unsafe extern "C" fn xmlXPathCastStringToNumber(val: *const xmlChar) -> f64 {
4173    if val.is_null() {
4174        return f64::NAN;
4175    }
4176    let len = libc::strlen(val as *const libc::c_char);
4177    let bytes = core::slice::from_raw_parts(val, len);
4178    // Skip leading whitespace (XML whitespace per XPath 1.0).
4179    let mut i = 0;
4180    while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r') {
4181        i += 1;
4182    }
4183    let s = &bytes[i..];
4184    if s.is_empty() {
4185        return f64::NAN;
4186    }
4187    // Parse an optional sign.
4188    let (sign, rest) = match s[0] {
4189        b'+' => (1.0f64, &s[1..]),
4190        b'-' => (-1.0f64, &s[1..]),
4191        _ => (1.0f64, s),
4192    };
4193    if rest.is_empty() {
4194        return f64::NAN;
4195    }
4196    // Try full parse; if it fails (trailing junk), the value is NaN per spec.
4197    // XPath 1.0: a string that does not conform to the Number production is NaN.
4198    let num_str = core::str::from_utf8(rest);
4199    match num_str {
4200        Ok(s) => {
4201            // Accept only valid Number productions: digits with optional
4202            // fraction/exponent.
4203            let valid = is_xpath_number(s);
4204            if !valid {
4205                f64::NAN
4206            } else {
4207                s.trim()
4208                    .parse::<f64>()
4209                    .map(|v| v * sign)
4210                    .unwrap_or(f64::NAN)
4211            }
4212        }
4213        Err(_) => f64::NAN,
4214    }
4215}
4216
4217/// Check whether a string conforms to the XPath 1.0 Number production.
4218fn is_xpath_number(s: &str) -> bool {
4219    let b = s.as_bytes();
4220    if b.is_empty() {
4221        return false;
4222    }
4223    let mut i = 0;
4224    let mut saw_digit = false;
4225    while i < b.len() && b[i].is_ascii_digit() {
4226        saw_digit = true;
4227        i += 1;
4228    }
4229    if i < b.len() && b[i] == b'.' {
4230        i += 1;
4231        while i < b.len() && b[i].is_ascii_digit() {
4232            saw_digit = true;
4233            i += 1;
4234        }
4235    }
4236    if !saw_digit {
4237        return false;
4238    }
4239    if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
4240        i += 1;
4241        if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
4242            i += 1;
4243        }
4244        let mut saw_exp = false;
4245        while i < b.len() && b[i].is_ascii_digit() {
4246            saw_exp = true;
4247            i += 1;
4248        }
4249        if !saw_exp {
4250            return false;
4251        }
4252    }
4253    i == b.len()
4254}
4255
4256/// Compare two nodes in document order.
4257///
4258/// Returns negative if `node1` precedes `node2`, positive if it follows,
4259/// 0 if they are the same node.
4260///
4261/// # UPSTREAM-PARITY
4262///
4263/// ```c
4264/// int xmlXPathCmpNodes(xmlNodePtr node1, xmlNodePtr node2);
4265/// ```
4266///
4267/// Oracle behavior: uses ancestor comparison — if one node is an ancestor
4268/// of the other, the ancestor comes first; otherwise the nearest common
4269/// ancestor's child order determines the result.
4270#[no_mangle]
4271pub unsafe extern "C" fn xmlXPathCmpNodes(node1: *mut _xmlNode, node2: *mut _xmlNode) -> c_int {
4272    if node1.is_null() || node2.is_null() {
4273        return 0;
4274    }
4275    if node1 == node2 {
4276        return 0;
4277    }
4278    // Build ancestor chains.
4279    let mut chain1: Vec<*mut _xmlNode> = Vec::new();
4280    let mut chain2: Vec<*mut _xmlNode> = Vec::new();
4281    let mut n = node1;
4282    while !n.is_null() {
4283        chain1.push(n);
4284        n = (*n).parent as *mut _xmlNode;
4285    }
4286    let mut n = node2;
4287    while !n.is_null() {
4288        chain2.push(n);
4289        n = (*n).parent as *mut _xmlNode;
4290    }
4291    // Find the nearest common ancestor.
4292    let mut i = chain1.len();
4293    let mut j = chain2.len();
4294    while i > 0 && j > 0 && chain1[i - 1] == chain2[j - 1] {
4295        i -= 1;
4296        j -= 1;
4297    }
4298    if i == 0 && j == 0 {
4299        return 0; // Same node (already handled) or disjoint trees treated as equal
4300    }
4301    if i == 0 {
4302        return -1; // node1 is an ancestor of node2
4303    }
4304    if j == 0 {
4305        return 1; // node2 is an ancestor of node1
4306    }
4307    // Compare sibling order at the divergence point.
4308    let mut a = chain1[i - 1];
4309    let mut b = chain2[j - 1];
4310    // Climb to the same level.
4311    while !a.is_null() && !b.is_null() {
4312        let pa = (*a).parent as *mut _xmlNode;
4313        let pb = (*b).parent as *mut _xmlNode;
4314        if pa == pb {
4315            break;
4316        }
4317        a = pa;
4318        b = pb;
4319    }
4320    // Walk forward from the first child of the common parent.
4321    let parent = (*a).parent as *mut _xmlNode;
4322    let mut child = if parent.is_null() {
4323        ptr::null_mut()
4324    } else {
4325        (*parent).children
4326    };
4327    while !child.is_null() {
4328        if child == a {
4329            return -1;
4330        }
4331        if child == b {
4332            return 1;
4333        }
4334        child = (*child).next;
4335    }
4336    0
4337}
4338
4339/// Create a node-set from a range of an existing node-set.
4340///
4341/// # UPSTREAM-PARITY
4342///
4343/// ```c
4344/// xmlNodeSetPtr xmlXPathNodeSetCreate(xmlNodePtr val);
4345/// ```
4346///
4347/// With a null `val`, creates an empty node-set.
4348#[no_mangle]
4349pub unsafe extern "C" fn xmlXPathNodeSetCreate(val: *mut _xmlNode) -> *mut _xmlNodeSet {
4350    let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
4351    if ns.is_null() {
4352        return ptr::null_mut();
4353    }
4354    if val.is_null() {
4355        return ns;
4356    }
4357    let tab = xmlMalloc(core::mem::size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
4358    if tab.is_null() {
4359        xmlFree(ns as *mut c_void);
4360        return ptr::null_mut();
4361    }
4362    *tab = val;
4363    (*ns).nodeTab = tab;
4364    (*ns).nodeNr = 1;
4365    (*ns).nodeMax = 1;
4366    ns
4367}
4368
4369/// Free a node-set allocated by `xmlXPathNodeSetCreate` or a node-set
4370/// builder in this library.
4371///
4372/// Frees the node-set structure and its node table; the nodes themselves
4373/// are owned by their document and are not freed.
4374///
4375/// # UPSTREAM-PARITY
4376///
4377/// ```c
4378/// void xmlXPathFreeNodeSet(xmlNodeSetPtr ns);
4379/// ```
4380#[no_mangle]
4381pub unsafe extern "C" fn xmlXPathFreeNodeSet(ns: *mut _xmlNodeSet) {
4382    if ns.is_null() {
4383        return;
4384    }
4385    if !(*ns).nodeTab.is_null() {
4386        xmlFree((*ns).nodeTab as *mut c_void);
4387        (*ns).nodeTab = ptr::null_mut();
4388    }
4389    (*ns).nodeNr = 0;
4390    (*ns).nodeMax = 0;
4391    xmlFree(ns as *mut c_void);
4392}
4393
4394/// Compile an XPath expression.
4395///
4396/// Returns an opaque pointer that can be passed to `xmlXPathEvalExpression`
4397/// (via the compiled-expr infrastructure) or freed with `xmlXPathFreeCompExpr`.
4398///
4399/// # UPSTREAM-PARITY
4400///
4401/// ```c
4402/// xmlXPathCompExprPtr xmlXPathCompile(const xmlChar *str);
4403/// ```
4404#[no_mangle]
4405pub unsafe extern "C" fn xmlXPathCompile(str_: *const xmlChar) -> *mut c_void {
4406    if str_.is_null() {
4407        return ptr::null_mut();
4408    }
4409    let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
4410        Ok(s) => s,
4411        Err(_) => return ptr::null_mut(),
4412    };
4413
4414    match crate::xml::xpath::compile(expr_str) {
4415        Some(compiled) => {
4416            let mut map = COMPILED_EXPRS.lock();
4417            let mut counter = NEXT_COMPILED_KEY.lock();
4418            let key = *counter;
4419            *counter += 1;
4420            map.insert(key, Box::new(compiled));
4421            key as *mut c_void
4422        }
4423        None => ptr::null_mut(),
4424    }
4425}
4426
4427/// Free a compiled XPath expression.
4428///
4429/// # UPSTREAM-PARITY
4430///
4431/// ```c
4432/// void xmlXPathFreeCompExpr(xmlXPathCompExprPtr comp);
4433/// ```
4434#[no_mangle]
4435pub unsafe extern "C" fn xmlXPathFreeCompExpr(comp: *mut c_void) {
4436    if comp.is_null() {
4437        return;
4438    }
4439    let mut map = COMPILED_EXPRS.lock();
4440    map.remove(&(comp as u64));
4441}
4442
4443/// Register an XPath namespace.
4444///
4445/// # UPSTREAM-PARITY
4446///
4447/// ```c
4448/// int xmlXPathRegisterNs(xmlXPathContextPtr ctxt,
4449///                        const xmlChar *prefix, const xmlChar *ns_uri);
4450/// ```
4451#[no_mangle]
4452pub unsafe extern "C" fn xmlXPathRegisterNs(
4453    ctxt: *mut _xmlXPathContext,
4454    prefix: *const xmlChar,
4455    ns_uri: *const xmlChar,
4456) -> c_int {
4457    if ctxt.is_null() || prefix.is_null() || ns_uri.is_null() {
4458        return -1;
4459    }
4460    let internal = (*ctxt).extra as *mut XPathContext;
4461    if internal.is_null() {
4462        return -1;
4463    }
4464    let internal = &mut *internal;
4465
4466    let prefix_str = match CStr::from_ptr(prefix as *const c_char).to_str() {
4467        Ok(s) => s,
4468        Err(_) => return -1,
4469    };
4470    let uri_str = match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4471        Ok(s) => s,
4472        Err(_) => return -1,
4473    };
4474
4475    internal.register_namespace(prefix_str, uri_str);
4476    0
4477}
4478
4479/// Register an XPath function.
4480///
4481/// The C function pointer is stored in a side table keyed by the context.
4482/// A Rust-side stub is registered in the internal context so that the Rust
4483/// evaluator is aware of the function; however, calling the C function
4484/// directly from the Rust evaluator is not yet supported.
4485///
4486/// # UPSTREAM-PARITY
4487///
4488/// ```c
4489/// int xmlXPathRegisterFunc(xmlXPathContextPtr ctxt,
4490///                          const xmlChar *name, xmlXPathFunction f);
4491/// ```
4492#[no_mangle]
4493pub unsafe extern "C" fn xmlXPathRegisterFunc(
4494    ctxt: *mut _xmlXPathContext,
4495    name: *const xmlChar,
4496    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
4497) -> c_int {
4498    if ctxt.is_null() || name.is_null() {
4499        return -1;
4500    }
4501    let internal = (*ctxt).extra as *mut XPathContext;
4502    if internal.is_null() {
4503        return -1;
4504    }
4505    let internal = &mut *internal;
4506
4507    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4508        Ok(s) => s,
4509        Err(_) => return -1,
4510    };
4511
4512    if let Some(func) = f {
4513        // Store the C function pointer in the side table.
4514        let key = (SendSyncPtr((*ctxt).extra), name_str.to_string());
4515        C_FUNCTIONS.lock().insert(key, func);
4516        // Register a Rust stub so the evaluator knows the function exists.
4517        internal.register_function(name_str, c_func_stub);
4518    }
4519    0
4520}
4521
4522/// Register an XPath function with namespace.
4523///
4524/// # UPSTREAM-PARITY
4525///
4526/// ```c
4527/// int xmlXPathRegisterFuncNS(xmlXPathContextPtr ctxt,
4528///                            const xmlChar *name, const xmlChar *ns_uri,
4529///                            xmlXPathFunction f);
4530/// ```
4531#[no_mangle]
4532pub unsafe extern "C" fn xmlXPathRegisterFuncNS(
4533    ctxt: *mut _xmlXPathContext,
4534    name: *const xmlChar,
4535    ns_uri: *const xmlChar,
4536    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
4537) -> c_int {
4538    if ctxt.is_null() || name.is_null() {
4539        return -1;
4540    }
4541    let internal = (*ctxt).extra as *mut XPathContext;
4542    if internal.is_null() {
4543        return -1;
4544    }
4545    let internal = &mut *internal;
4546
4547    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4548        Ok(s) => s,
4549        Err(_) => return -1,
4550    };
4551    let ns_str = if ns_uri.is_null() {
4552        String::new()
4553    } else {
4554        match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4555            Ok(s) => s.to_string(),
4556            Err(_) => return -1,
4557        }
4558    };
4559
4560    // Use "{ns}:" prefix as part of the key to keep functions unique.
4561    let qualified = if ns_str.is_empty() {
4562        name_str.to_string()
4563    } else {
4564        format!("{{{}}}{}", ns_str, name_str)
4565    };
4566
4567    if let Some(func) = f {
4568        let key = (SendSyncPtr((*ctxt).extra), qualified.clone());
4569        C_FUNCTIONS.lock().insert(key, func);
4570        internal.register_function(&qualified, c_func_stub);
4571    }
4572    0
4573}
4574
4575/// Register an XPath variable.
4576///
4577/// # UPSTREAM-PARITY
4578///
4579/// ```c
4580/// int xmlXPathRegisterVariable(xmlXPathContextPtr ctxt,
4581///                              const xmlChar *name, xmlXPathObjectPtr value);
4582/// ```
4583#[no_mangle]
4584pub unsafe extern "C" fn xmlXPathRegisterVariable(
4585    ctxt: *mut _xmlXPathContext,
4586    name: *const xmlChar,
4587    value: *mut _xmlXPathObject,
4588) -> c_int {
4589    if ctxt.is_null() || name.is_null() || value.is_null() {
4590        return -1;
4591    }
4592    let internal = (*ctxt).extra as *mut XPathContext;
4593    if internal.is_null() {
4594        return -1;
4595    }
4596    let internal = &mut *internal;
4597
4598    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4599        Ok(s) => s,
4600        Err(_) => return -1,
4601    };
4602
4603    let xpath_val = object_to_xpathvalue(value);
4604    internal.register_variable(name_str, xpath_val);
4605    0
4606}
4607
4608/// Create an XPath object wrapping a single node in a node-set.
4609///
4610/// # UPSTREAM-PARITY
4611///
4612/// ```c
4613/// xmlXPathObjectPtr xmlXPathNewNodeSet(xmlNodePtr val);
4614/// ```
4615#[no_mangle]
4616pub unsafe extern "C" fn xmlXPathNewNodeSet(val: *mut _xmlNode) -> *mut _xmlXPathObject {
4617    let ns = if val.is_null() {
4618        NodeSet::new()
4619    } else {
4620        NodeSet::singleton(val)
4621    };
4622    xpath_to_object(XPathValue::NodeSet(ns))
4623}
4624
4625/// Create an XPath object from a C string value.
4626///
4627/// # UPSTREAM-PARITY
4628///
4629/// ```c
4630/// xmlXPathObjectPtr xmlXPathNewCString(const xmlChar *val);
4631/// ```
4632#[no_mangle]
4633pub unsafe extern "C" fn xmlXPathNewCString(val: *const xmlChar) -> *mut _xmlXPathObject {
4634    if val.is_null() {
4635        return xpath_to_object(XPathValue::String(String::new()));
4636    }
4637    let s = match CStr::from_ptr(val as *const c_char).to_str() {
4638        Ok(s) => s.to_string(),
4639        Err(_) => return ptr::null_mut(),
4640    };
4641    xpath_to_object(XPathValue::String(s))
4642}
4643
4644/// Create an XPath number object.
4645///
4646/// # UPSTREAM-PARITY
4647///
4648/// ```c
4649/// xmlXPathObjectPtr xmlXPathNewFloat(double val);
4650/// ```
4651#[no_mangle]
4652pub extern "C" fn xmlXPathNewFloat(val: f64) -> *mut _xmlXPathObject {
4653    unsafe { xpath_to_object(XPathValue::Number(val)) }
4654}
4655
4656/// Create an XPath boolean object.
4657///
4658/// # UPSTREAM-PARITY
4659///
4660/// ```c
4661/// xmlXPathObjectPtr xmlXPathNewBoolean(int val);
4662/// ```
4663#[no_mangle]
4664pub extern "C" fn xmlXPathNewBoolean(val: c_int) -> *mut _xmlXPathObject {
4665    unsafe { xpath_to_object(XPathValue::Boolean(val != 0)) }
4666}
4667
4668// ═══════════════════════════════════════════════════════════════════════════════
4669// 14.5. XPointer
4670// ═══════════════════════════════════════════════════════════════════════════════
4671
4672/// Evaluate an XPointer expression.
4673///
4674/// Delegates to the xpointer module.
4675///
4676/// # UPSTREAM-PARITY
4677///
4678/// ```c
4679/// xmlNodePtr xmlXPtrEval(const xmlChar *expr, xmlDocPtr doc);
4680/// ```
4681#[no_mangle]
4682pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
4683    crate::xml::xpointer::xmlXPtrEval(expr, doc)
4684}
4685
4686// ═══════════════════════════════════════════════════════════════════════════════
4687// 15. XInclude
4688// ═══════════════════════════════════════════════════════════════════════════════
4689
4690/// Process XInclude nodes in a document.
4691///
4692/// # UPSTREAM-PARITY
4693///
4694/// ```c
4695/// int xmlXIncludeProcess(xmlDocPtr doc);
4696/// ```
4697#[no_mangle]
4698pub unsafe extern "C" fn xmlXIncludeProcess(doc: *mut _xmlDoc) -> c_int {
4699    crate::xml::xinclude::xinclude_process(doc)
4700}
4701
4702/// Process XInclude nodes with flags.
4703///
4704/// # UPSTREAM-PARITY
4705///
4706/// ```c
4707/// int xmlXIncludeProcessFlags(xmlDocPtr doc, int flags);
4708/// ```
4709#[no_mangle]
4710pub unsafe extern "C" fn xmlXIncludeProcessFlags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
4711    crate::xml::xinclude::xinclude_process_flags(doc, flags)
4712}
4713
4714// ═══════════════════════════════════════════════════════════════════════════════
4715// 16. Catalog
4716// ═══════════════════════════════════════════════════════════════════════════════
4717
4718/// Load a catalog.
4719///
4720/// # UPSTREAM-PARITY
4721///
4722/// ```c
4723/// xmlCatalogPtr xmlCatalogLoad(const char *catalogs);
4724/// ```
4725#[no_mangle]
4726pub extern "C" fn xmlCatalogLoad(catalogs: *const c_char) -> *mut c_void {
4727    if catalogs.is_null() {
4728        return ptr::null_mut();
4729    }
4730    crate::xml::catalog::load_catalog(catalogs)
4731}
4732
4733/// Resolve a public ID.
4734///
4735/// # UPSTREAM-PARITY
4736///
4737/// ```c
4738/// xmlCharPtr xmlCatalogResolvePublic(const xmlChar *pubID);
4739/// ```
4740#[no_mangle]
4741pub unsafe extern "C" fn xmlCatalogResolvePublic(pubID: *const xmlChar) -> *mut xmlChar {
4742    if pubID.is_null() {
4743        return ptr::null_mut();
4744    }
4745    crate::xml::catalog::resolve_public(pubID)
4746}
4747
4748/// Resolve a system ID.
4749///
4750/// # UPSTREAM-PARITY
4751///
4752/// ```c
4753/// xmlCharPtr xmlCatalogResolveSystem(const xmlChar *sysID);
4754/// ```
4755#[no_mangle]
4756pub unsafe extern "C" fn xmlCatalogResolveSystem(sysID: *const xmlChar) -> *mut xmlChar {
4757    if sysID.is_null() {
4758        return ptr::null_mut();
4759    }
4760    crate::xml::catalog::resolve_system(sysID)
4761}
4762
4763/// Resolve a URI.
4764///
4765/// # UPSTREAM-PARITY
4766///
4767/// ```c
4768/// xmlCharPtr xmlCatalogResolveURI(const xmlChar *URI);
4769/// ```
4770#[no_mangle]
4771pub unsafe extern "C" fn xmlCatalogResolveURI(URI: *const xmlChar) -> *mut xmlChar {
4772    if URI.is_null() {
4773        return ptr::null_mut();
4774    }
4775    crate::xml::catalog::resolve_uri(URI)
4776}
4777
4778/// Set catalog defaults.
4779///
4780/// # UPSTREAM-PARITY
4781///
4782/// ```c
4783/// void xmlCatalogSetDefaults(xmlCatalogAllowValue allow);
4784/// ```
4785#[no_mangle]
4786pub extern "C" fn xmlCatalogSetDefaults(allow: c_int) {
4787    crate::xml::catalog::set_defaults(allow)
4788}
4789
4790/// Get catalog defaults.
4791///
4792/// # UPSTREAM-PARITY
4793///
4794/// ```c
4795/// xmlCatalogAllowValue xmlCatalogGetDefaults(void);
4796/// ```
4797#[no_mangle]
4798pub extern "C" fn xmlCatalogGetDefaults() -> c_int {
4799    crate::xml::catalog::get_defaults()
4800}
4801
4802/// Add a catalog.
4803///
4804/// # UPSTREAM-PARITY
4805///
4806/// ```c
4807/// int xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace);
4808/// ```
4809#[no_mangle]
4810pub unsafe extern "C" fn xmlCatalogAdd(
4811    type_: *const xmlChar,
4812    orig: *const xmlChar,
4813    replace: *const xmlChar,
4814) -> c_int {
4815    if type_.is_null() || orig.is_null() || replace.is_null() {
4816        return -1;
4817    }
4818    crate::xml::catalog::add(type_, orig, replace)
4819}
4820
4821/// Remove a catalog entry.
4822///
4823/// # UPSTREAM-PARITY
4824///
4825/// ```c
4826/// int xmlCatalogRemove(const xmlChar *value);
4827/// ```
4828#[no_mangle]
4829pub unsafe extern "C" fn xmlCatalogRemove(value: *const xmlChar) -> c_int {
4830    if value.is_null() {
4831        return 0;
4832    }
4833    crate::xml::catalog::remove(value)
4834}
4835
4836/// Clean up the catalog subsystem.
4837///
4838/// # UPSTREAM-PARITY
4839///
4840/// ```c
4841/// void xmlCatalogCleanup(void);
4842/// ```
4843#[no_mangle]
4844pub extern "C" fn xmlCatalogCleanup() {
4845    crate::xml::catalog::cleanup();
4846}
4847
4848/// Convert an SGML catalog to XML.
4849///
4850/// # UPSTREAM-PARITY
4851///
4852/// ```c
4853/// xmlDocPtr xmlCatalogConvert(void);
4854/// ```
4855#[no_mangle]
4856pub extern "C" fn xmlCatalogConvert() -> *mut _xmlDoc {
4857    // SAFETY: catalog::convert() allocates and builds an XML document tree.
4858    unsafe { crate::xml::catalog::convert() }
4859}
4860
4861// ═══════════════════════════════════════════════════════════════════════════════
4862// 17. HTML
4863// ═══════════════════════════════════════════════════════════════════════════════
4864
4865/// Parse an HTML document from a file.
4866///
4867/// # UPSTREAM-PARITY
4868///
4869/// ```c
4870/// htmlDocPtr htmlParseFile(const char *filename, const char *encoding);
4871/// ```
4872#[no_mangle]
4873pub unsafe extern "C" fn htmlParseFile(
4874    _filename: *const c_char,
4875    _encoding: *const c_char,
4876) -> *mut _xmlDoc {
4877    // Phase 1: STUB
4878    ptr::null_mut()
4879}
4880
4881/// Parse an HTML document from memory.
4882///
4883/// # UPSTREAM-PARITY
4884///
4885/// ```c
4886/// htmlDocPtr htmlParseMemory(const char *buffer, int size);
4887/// ```
4888#[no_mangle]
4889pub unsafe extern "C" fn htmlParseMemory(_buffer: *const c_char, _size: c_int) -> *mut _xmlDoc {
4890    // Phase 1: STUB
4891    ptr::null_mut()
4892}
4893
4894/// Parse an HTML document from a document string.
4895///
4896/// # UPSTREAM-PARITY
4897///
4898/// ```c
4899/// htmlDocPtr htmlParseDoc(const xmlChar *cur, const char *encoding);
4900/// ```
4901#[no_mangle]
4902pub unsafe extern "C" fn htmlParseDoc(
4903    _cur: *const xmlChar,
4904    _encoding: *const c_char,
4905) -> *mut _xmlDoc {
4906    // Phase 1: STUB
4907    ptr::null_mut()
4908}
4909
4910/// Create an HTML parser context.
4911///
4912/// # UPSTREAM-PARITY
4913///
4914/// ```c
4915/// htmlParserCtxtPtr htmlCreateFileParserCtxt(const char *filename,
4916///                                            const char *encoding);
4917/// ```
4918#[no_mangle]
4919pub unsafe extern "C" fn htmlCreateFileParserCtxt(
4920    _filename: *const c_char,
4921    _encoding: *const c_char,
4922) -> *mut c_void {
4923    // Phase 1: STUB
4924    ptr::null_mut()
4925}
4926
4927/// Free an HTML parser context.
4928///
4929/// # UPSTREAM-PARITY
4930///
4931/// ```c
4932/// void htmlFreeParserCtxt(htmlParserCtxtPtr ctxt);
4933/// ```
4934#[no_mangle]
4935pub extern "C" fn htmlFreeParserCtxt(_ctxt: *mut c_void) {
4936    // Phase 1: STUB
4937}
4938
4939/// Initialize the HTML parser.
4940///
4941/// # UPSTREAM-PARITY
4942///
4943/// ```c
4944/// void htmlInitParser(void);
4945/// ```
4946#[no_mangle]
4947pub extern "C" fn htmlInitParser() {
4948    // Phase 1: STUB
4949}
4950
4951/// Clean up the HTML parser.
4952///
4953/// # UPSTREAM-PARITY
4954///
4955/// ```c
4956/// void htmlCleanupParser(void);
4957/// ```
4958#[no_mangle]
4959pub extern "C" fn htmlCleanupParser() {
4960    // Phase 1: STUB
4961}
4962
4963// ═══════════════════════════════════════════════════════════════════════════════
4964// 17.5. Validation (DTD)
4965// ═══════════════════════════════════════════════════════════════════════════════
4966
4967/// Create a new validation context.
4968///
4969/// # UPSTREAM-PARITY
4970///
4971/// ```c
4972/// xmlValidCtxtPtr xmlNewValidCtxt(void);
4973/// ```
4974#[no_mangle]
4975pub unsafe extern "C" fn xmlNewValidCtxt() -> *mut _xmlValidCtxt {
4976    crate::xml::validation::new_valid_ctxt()
4977}
4978
4979/// Free a validation context.
4980///
4981/// # UPSTREAM-PARITY
4982///
4983/// ```c
4984/// void xmlFreeValidCtxt(xmlValidCtxtPtr ctxt);
4985/// ```
4986#[no_mangle]
4987pub unsafe extern "C" fn xmlFreeValidCtxt(ctxt: *mut _xmlValidCtxt) {
4988    crate::xml::validation::free_valid_ctxt(ctxt);
4989}
4990
4991/// Set error and warning callbacks on a validation context.
4992///
4993/// # UPSTREAM-PARITY
4994///
4995/// ```c
4996/// void xmlSetValidErrors(xmlValidCtxtPtr ctxt,
4997///                        xmlGenericErrorFunc err,
4998///                        xmlGenericErrorFunc warn,
4999///                        void *data);
5000/// ```
5001#[no_mangle]
5002pub unsafe extern "C" fn xmlSetValidErrors(
5003    ctxt: *mut _xmlValidCtxt,
5004    err: Option<xmlGenericErrorFunc>,
5005    warn: Option<xmlGenericErrorFunc>,
5006    data: *mut c_void,
5007) {
5008    crate::xml::validation::set_valid_errors(ctxt, err, warn, data);
5009}
5010
5011/// Validate a document against its DTD.
5012///
5013/// # UPSTREAM-PARITY
5014///
5015/// ```c
5016/// int xmlValidateDocument(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5017/// ```
5018#[no_mangle]
5019pub unsafe extern "C" fn xmlValidateDocument(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5020    crate::xml::validation::validate_document(ctxt, doc)
5021}
5022
5023/// Final validation pass (check ID/IDREF consistency).
5024///
5025/// # UPSTREAM-PARITY
5026///
5027/// ```c
5028/// int xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5029/// ```
5030#[no_mangle]
5031pub unsafe extern "C" fn xmlValidateDocumentFinal(
5032    ctxt: *mut _xmlValidCtxt,
5033    doc: *mut _xmlDoc,
5034) -> c_int {
5035    crate::xml::validation::validate_document_final(ctxt, doc)
5036}
5037
5038/// Validate an element node against its DTD declarations.
5039///
5040/// # UPSTREAM-PARITY
5041///
5042/// ```c
5043/// int xmlValidateElement(xmlValidCtxtPtr ctxt,
5044///                        xmlDocPtr doc,
5045///                        xmlNodePtr elem);
5046/// ```
5047#[no_mangle]
5048pub unsafe extern "C" fn xmlValidateElement(
5049    ctxt: *mut _xmlValidCtxt,
5050    doc: *mut _xmlDoc,
5051    elem: *mut _xmlNode,
5052) -> c_int {
5053    crate::xml::validation::validate_element(ctxt, doc, elem)
5054}
5055
5056/// Validate an attribute declaration.
5057///
5058/// # UPSTREAM-PARITY
5059///
5060/// ```c
5061/// int xmlValidateAttributeDecl(xmlValidCtxtPtr ctxt,
5062///                              xmlDocPtr doc,
5063///                              xmlNodePtr elem,
5064///                              xmlAttributePtr attr);
5065/// ```
5066#[no_mangle]
5067pub unsafe extern "C" fn xmlValidateAttributeDecl(
5068    ctxt: *mut _xmlValidCtxt,
5069    doc: *mut _xmlDoc,
5070    elem: *mut _xmlNode,
5071    attr: *mut _xmlAttribute,
5072) -> c_int {
5073    crate::xml::validation::validate_attribute_decl(ctxt, doc, elem, attr)
5074}
5075
5076/// Validate an attribute value against its declared type.
5077///
5078/// # UPSTREAM-PARITY
5079///
5080/// ```c
5081/// int xmlValidateAttributeValue(int type, const xmlChar *value);
5082/// ```
5083#[no_mangle]
5084pub unsafe extern "C" fn xmlValidateAttributeValue(atype: c_int, value: *const xmlChar) -> c_int {
5085    crate::xml::validation::validate_attribute_value(atype, value)
5086}
5087
5088/// Validate a NOTATION reference.
5089///
5090/// # UPSTREAM-PARITY
5091///
5092/// ```c
5093/// int xmlValidateNotationUse(xmlValidCtxtPtr ctxt,
5094///                            xmlDocPtr doc,
5095///                            const xmlChar *notationName);
5096/// ```
5097#[no_mangle]
5098pub unsafe extern "C" fn xmlValidateNotationUse(
5099    ctxt: *mut _xmlValidCtxt,
5100    doc: *mut _xmlDoc,
5101    notation_name: *const xmlChar,
5102) -> c_int {
5103    crate::xml::validation::validate_notation_use(ctxt, doc, notation_name)
5104}
5105
5106/// Validate an ID value (check uniqueness).
5107///
5108/// # UPSTREAM-PARITY
5109///
5110/// ```c
5111/// int xmlValidateID(xmlValidCtxtPtr ctxt,
5112///                   xmlDocPtr doc,
5113///                   xmlNodePtr node,
5114///                   const xmlChar *value);
5115/// ```
5116#[no_mangle]
5117pub unsafe extern "C" fn xmlValidateID(
5118    ctxt: *mut _xmlValidCtxt,
5119    doc: *mut _xmlDoc,
5120    node: *mut _xmlNode,
5121    value: *const xmlChar,
5122) -> c_int {
5123    crate::xml::validation::validate_id(ctxt, doc, node, value)
5124}
5125
5126/// Validate an IDREF value (check it references a known ID).
5127///
5128/// # UPSTREAM-PARITY
5129///
5130/// ```c
5131/// int xmlValidateIDRef(xmlValidCtxtPtr ctxt,
5132///                      xmlDocPtr doc,
5133///                      xmlNodePtr node,
5134///                      const xmlChar *value);
5135/// ```
5136#[no_mangle]
5137pub unsafe extern "C" fn xmlValidateIDRef(
5138    ctxt: *mut _xmlValidCtxt,
5139    doc: *mut _xmlDoc,
5140    node: *mut _xmlNode,
5141    value: *const xmlChar,
5142) -> c_int {
5143    crate::xml::validation::validate_id_ref(ctxt, doc, node, value)
5144}
5145
5146/// Validate IDREFS (whitespace-separated list of IDREFs).
5147///
5148/// # UPSTREAM-PARITY
5149///
5150/// ```c
5151/// int xmlValidateIDRefs(xmlValidCtxtPtr ctxt,
5152///                       xmlDocPtr doc,
5153///                       xmlNodePtr node,
5154///                       const xmlChar *value);
5155/// ```
5156#[no_mangle]
5157pub unsafe extern "C" fn xmlValidateIDRefs(
5158    ctxt: *mut _xmlValidCtxt,
5159    doc: *mut _xmlDoc,
5160    node: *mut _xmlNode,
5161    value: *const xmlChar,
5162) -> c_int {
5163    crate::xml::validation::validate_id_refs(ctxt, doc, node, value)
5164}
5165
5166/// Validate an NMTOKEN value.
5167///
5168/// # UPSTREAM-PARITY
5169///
5170/// ```c
5171/// int xmlValidateNmtoken(const xmlChar *value);
5172/// ```
5173#[no_mangle]
5174pub unsafe extern "C" fn xmlValidateNmtoken(value: *const xmlChar) -> c_int {
5175    crate::xml::validation::validate_nmtoken(value)
5176}
5177
5178/// Validate a whitespace-separated list of NMTOKENs.
5179///
5180/// # UPSTREAM-PARITY
5181///
5182/// ```c
5183/// int xmlValidateNmtokens(const xmlChar *value);
5184/// ```
5185#[no_mangle]
5186pub unsafe extern "C" fn xmlValidateNmtokens(value: *const xmlChar) -> c_int {
5187    crate::xml::validation::validate_nmtokens(value)
5188}
5189
5190/// Validate an XML Name value.
5191///
5192/// # UPSTREAM-PARITY
5193///
5194/// ```c
5195/// int xmlValidateName(const xmlChar *value);
5196/// ```
5197#[no_mangle]
5198pub unsafe extern "C" fn xmlValidateName(value: *const xmlChar) -> c_int {
5199    crate::xml::validation::validate_name(value)
5200}
5201
5202/// Validate a whitespace-separated list of XML Names.
5203///
5204/// # UPSTREAM-PARITY
5205///
5206/// ```c
5207/// int xmlValidateNames(const xmlChar *value);
5208/// ```
5209#[no_mangle]
5210pub unsafe extern "C" fn xmlValidateNames(value: *const xmlChar) -> c_int {
5211    crate::xml::validation::validate_names(value)
5212}
5213
5214/// Validate the root element of a document.
5215///
5216/// # UPSTREAM-PARITY
5217///
5218/// ```c
5219/// int xmlValidateRoot(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5220/// ```
5221#[no_mangle]
5222pub unsafe extern "C" fn xmlValidateRoot(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5223    crate::xml::validation::validate_root(ctxt, doc)
5224}
5225
5226/// Validate element content against its content model.
5227///
5228/// # UPSTREAM-PARITY
5229///
5230/// ```c
5231/// int xmlValidateContent(xmlValidCtxtPtr ctxt,
5232///                        xmlNodePtr node,
5233///                        xmlDocPtr doc);
5234/// ```
5235#[no_mangle]
5236pub unsafe extern "C" fn xmlValidateContent(
5237    ctxt: *mut _xmlValidCtxt,
5238    node: *mut _xmlNode,
5239    doc: *mut _xmlDoc,
5240) -> c_int {
5241    crate::xml::validation::validate_content(ctxt, node, doc)
5242}
5243
5244/// Check if an element is declared as mixed content.
5245///
5246/// # UPSTREAM-PARITY
5247///
5248/// ```c
5249/// int xmlIsMixedElement(xmlDocPtr doc, const xmlChar *name);
5250/// ```
5251#[no_mangle]
5252pub unsafe extern "C" fn xmlIsMixedElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
5253    crate::xml::validation::is_mixed_element(doc, name)
5254}
5255
5256/// Check if an element is declared as EMPTY.
5257///
5258/// # UPSTREAM-PARITY
5259///
5260/// ```c
5261/// int xmlIsEmptyElement(xmlDocPtr doc, const xmlChar *name);
5262/// ```
5263#[no_mangle]
5264pub unsafe extern "C" fn xmlIsEmptyElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
5265    crate::xml::validation::is_empty_element(doc, name)
5266}
5267
5268/// Validate a DTD's declarations.
5269///
5270/// # UPSTREAM-PARITY
5271///
5272/// ```c
5273/// int xmlValidateDtd(xmlValidCtxtPtr ctxt,
5274///                    xmlDocPtr doc,
5275///                    xmlDtdPtr dtd);
5276/// ```
5277#[no_mangle]
5278pub unsafe extern "C" fn xmlValidateDtd(
5279    ctxt: *mut _xmlValidCtxt,
5280    doc: *mut _xmlDoc,
5281    dtd: *mut _xmlDtd,
5282) -> c_int {
5283    crate::xml::validation::validate_dtd(ctxt, doc, dtd)
5284}
5285
5286/// Final DTD validation (ID/IDREF consistency).
5287///
5288/// # UPSTREAM-PARITY
5289///
5290/// ```c
5291/// int xmlValidateDtdFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
5292/// ```
5293#[no_mangle]
5294pub unsafe extern "C" fn xmlValidateDtdFinal(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
5295    crate::xml::validation::validate_dtd_final(ctxt, doc)
5296}
5297
5298/// Validate that a value is in an enumeration.
5299///
5300/// # UPSTREAM-PARITY
5301///
5302/// ```c
5303/// int xmlValidateEnumeration(xmlValidCtxtPtr ctxt,
5304///                            const xmlChar *value,
5305///                            xmlEnumerationPtr tree);
5306/// ```
5307#[no_mangle]
5308pub unsafe extern "C" fn xmlValidateEnumeration(
5309    ctxt: *mut _xmlValidCtxt,
5310    value: *const xmlChar,
5311    tree: *mut _xmlEnumeration,
5312) -> c_int {
5313    crate::xml::validation::validate_enumeration(ctxt, value, tree)
5314}
5315
5316// ═══════════════════════════════════════════════════════════════════════════════
5317// 18. Debug / Miscellaneous
5318// ═══════════════════════════════════════════════════════════════════════════════
5319
5320/// Dump a document to a file for debugging.
5321
5322/// Get the path to the current executable.
5323///
5324/// # UPSTREAM-PARITY
5325///
5326/// ```c
5327/// char *xmlGetBinaryPath(void);
5328/// ```
5329#[no_mangle]
5330pub extern "C" fn xmlGetBinaryPath() -> *mut c_char {
5331    // Phase 1: STUB
5332    ptr::null_mut()
5333}
5334
5335/// Get the path to the current executable's home directory.
5336///
5337/// # UPSTREAM-PARITY
5338///
5339/// ```c
5340/// char *xmlGetHomeOfBinary(void);
5341/// ```
5342#[no_mangle]
5343pub extern "C" fn xmlGetHomeOfBinary() -> *mut c_char {
5344    // Phase 1: STUB
5345    ptr::null_mut()
5346}