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