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 // §16.5.2: xmlReadDoc creates, parses and frees its own context inside
2770 // this call, so the input BORROWS the caller's string (zero copy); the
2771 // caller's buffer is only required to stay valid for the call.
2772 let input =
2773 crate::xml::parser::helpers::input_from_memory_borrowed(cur as *const c_char, len as c_int);
2774 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2775 (*ctxt).options = options;
2776 if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
2777 let doc = (*ctxt).myDoc;
2778 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2779 return doc;
2780 }
2781 let doc = (*ctxt).myDoc;
2782 if !doc.is_null() {
2783 (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
2784 }
2785 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2786 doc
2787}
2788
2789/// Read an XML document from a file.
2790///
2791/// # UPSTREAM-PARITY
2792///
2793/// ```c
2794/// xmlDocPtr xmlReadFile(const char *URL, const char *encoding, int options);
2795/// ```
2796#[no_mangle]
2797pub unsafe extern "C" fn xmlReadFile(
2798 URL: *const c_char,
2799 encoding: *const c_char,
2800 options: c_int,
2801) -> *mut _xmlDoc {
2802 // SAFETY: URL must be a valid C string or NULL.
2803 if URL.is_null() {
2804 return ptr::null_mut();
2805 }
2806 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2807 if ctxt.is_null() {
2808 return ptr::null_mut();
2809 }
2810 // UPSTREAM-PARITY (parser.c xmlReadFile -> xmlCtxtReadFile): options are
2811 // applied before the input open (xmlCtxtUseOptions -> xmlCtxtNewInputFromUrl
2812 // -> xmlLoadResource), so a registered external entity loader observes
2813 // them; below it the xmlParserInputBufferCreateFilenameDefault (php
2814 // streams loader) is consulted — a NULL result raises xmlCtxtErrIO — "I/O
2815 // warning : failed to load \"%s\": %s\n" — and the load fails.
2816 (*ctxt).options = options;
2817 // UPSTREAM-PARITY (xmlCtxtUseOptions): replaceEntities is derived from
2818 // the options argument — a deprecated-global seed (create_parser_ctxt
2819 // snapshots xmlSubstituteEntitiesDefault, which PHP's ext/xsl sets at
2820 // request init) must not leak into a read whose options lack NOENT.
2821 (*ctxt).replaceEntities = (options & crate::abi::types::XML_PARSE_NOENT != 0) as c_int;
2822 let input = match crate::abi::exports_parser::open_filename_routed(URL, ctxt) {
2823 crate::abi::exports_parser::RoutedFileOpen::Loaded(i) => i,
2824 crate::abi::exports_parser::RoutedFileOpen::Failed => {
2825 crate::abi::exports_parser::emit_io_warning(
2826 ctxt,
2827 crate::abi::exports_parser::io_load_failure_message(URL),
2828 );
2829 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2830 return ptr::null_mut();
2831 }
2832 crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
2833 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2834 return ptr::null_mut();
2835 }
2836 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
2837 match crate::xml::parser::helpers::input_from_file(URL) {
2838 Ok(input) => input,
2839 Err(_) => {
2840 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2841 return ptr::null_mut();
2842 }
2843 }
2844 }
2845 };
2846 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2847 if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
2848 let doc = (*ctxt).myDoc;
2849 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2850 // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
2851 // partially built document is discarded and NULL is returned; with
2852 // XML_PARSE_RECOVER the partial tree is kept.
2853 if options & 1 << 0 != 0 {
2854 return doc;
2855 }
2856 if !doc.is_null() {
2857 crate::xml::tree::free_doc(doc);
2858 }
2859 return ptr::null_mut();
2860 }
2861 let doc = (*ctxt).myDoc;
2862 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
2863 doc
2864}
2865
2866/// Recover-parse a document from a string (upstream parser.h): same as
2867/// `xmlReadDoc` with XML_PARSE_RECOVER forced.
2868///
2869/// # UPSTREAM-PARITY
2870///
2871/// ```c
2872/// xmlDocPtr xmlRecoverDoc(const xmlChar *cur);
2873/// ```
2874#[no_mangle]
2875pub unsafe extern "C" fn xmlRecoverDoc(cur: *const xmlChar) -> *mut _xmlDoc {
2876 unsafe { xmlReadDoc(cur, ptr::null(), ptr::null(), 1 << 0) }
2877}
2878
2879/// Recover-parse a document from a file (upstream parser.h).
2880///
2881/// # UPSTREAM-PARITY
2882///
2883/// ```c
2884/// xmlDocPtr xmlRecoverFile(const char *filename);
2885/// ```
2886#[no_mangle]
2887pub unsafe extern "C" fn xmlRecoverFile(filename: *const c_char) -> *mut _xmlDoc {
2888 unsafe { xmlReadFile(filename, ptr::null(), 1 << 0) }
2889}
2890
2891/// Recover-parse a document from memory (upstream parser.h).
2892///
2893/// # UPSTREAM-PARITY
2894///
2895/// ```c
2896/// xmlDocPtr xmlRecoverMemory(const char *buffer, int size);
2897/// ```
2898#[no_mangle]
2899pub unsafe extern "C" fn xmlRecoverMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
2900 unsafe { xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 1 << 0) }
2901}
2902
2903/// Read an XML document from a file (upstream parser.h).
2904/// Canonically record an explicit input `encoding` on `doc->encoding` when
2905/// the caller supplied one and the document has no encoding of its own.
2906///
2907/// Mirrors upstream: the encoding name is canonicalized via the encoding
2908/// handler table (so `utf-8`/`UTF8` become `UTF-8`, `latin1` becomes
2909/// `ISO-8859-1`, …) rather than stored verbatim.
2910///
2911/// # SAFETY
2912///
2913/// - `doc` must be a valid `_xmlDoc` with a NULL `encoding` field.
2914/// - `encoding` must be a valid NUL-terminated C string.
2915unsafe fn canonical_doc_encoding(doc: *mut _xmlDoc, encoding: *const c_char) {
2916 let handler = crate::xml::encoding::xmlFindCharEncodingHandler(encoding);
2917 if handler.is_null() {
2918 return;
2919 }
2920 // SAFETY: handler is non-NULL; name is a NUL-terminated owned buffer.
2921 let name = unsafe { (*handler).name }; // *mut c_char
2922 if name.is_null() {
2923 return;
2924 }
2925 // SAFETY: name is a valid NUL-terminated string; caller frees via xmlFree.
2926 unsafe {
2927 (*doc).encoding = crate::xml::string::xml_strdup(name as *const xmlChar);
2928 }
2929}
2930
2931/// Parse an XML document from a C memory buffer.
2932///
2933/// # UPSTREAM-PARITY
2934///
2935/// ```c
2936/// xmlDocPtr xmlReadMemory(const char *buffer, int size,
2937/// const char *URL, const char *encoding, int options);
2938/// ```
2939#[no_mangle]
2940pub unsafe extern "C" fn xmlReadMemory(
2941 buffer: *const c_char,
2942 size: c_int,
2943 URL: *const c_char,
2944 encoding: *const c_char,
2945 options: c_int,
2946) -> *mut _xmlDoc {
2947 // SAFETY: buffer must be a valid pointer with at least `size` readable
2948 // bytes. An empty input (size 0) is still parsed — upstream reports
2949 // "Document is empty".
2950 if buffer.is_null() || size < 0 {
2951 return ptr::null_mut();
2952 }
2953 // UPSTREAM-PARITY + HOSTILE-ABI hardening (parser.c 2.15 xmlReadMemory):
2954 // upstream only rejects `size < 0` and then streams from the caller's
2955 // buffer, so a size at or beyond INT_MAX turns into an unsized wild read
2956 // (the oracle's own outcome for xmlReadMemory("<a/>", INT_MAX, ...) is a
2957 // NULL document — or a crash of the oracle itself depending on the heap
2958 // layout). The candidate rejects such sizes up front instead of copying
2959 // ~2 GiB from the caller's buffer; the observable result matches the
2960 // oracle's deterministic probe outcome (HOSTILE-ABI D1).
2961 if size == c_int::MAX {
2962 return ptr::null_mut();
2963 }
2964 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
2965 if ctxt.is_null() {
2966 return ptr::null_mut();
2967 }
2968 // UPSTREAM-PARITY: the URL becomes the input's filename (feeds the
2969 // `file:line:` error prefix and doc->URL). §16.5.2: xmlReadMemory owns
2970 // its context for the duration of this call, so the input BORROWS the
2971 // caller's buffer — zero copies on the ordinary UTF-8 path (the caller's
2972 // memory only has to stay valid for the call, exactly like upstream's
2973 // xmlReadMemory streaming contract). A caller-supplied `encoding` below
2974 // converts to an owned transcoded buffer when it applies.
2975 let mut input =
2976 crate::xml::parser::helpers::input_from_memory_named_borrowed(buffer, size, URL);
2977 // UPSTREAM-PARITY (xmlReadMemory -> xmlCtxtReadMemory): an explicit
2978 // `encoding` argument switches the input before parsing (lxml/KIND-2/4
2979 // python strings arrive as raw UTF-16/UCS-4). Best-effort: unknown
2980 // names leave the raw bytes for BOM/declaration detection.
2981 if !encoding.is_null() {
2982 let name = core::ffi::CStr::from_ptr(encoding).to_bytes();
2983 input.apply_explicit_input_encoding(name);
2984 }
2985 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
2986 // UPSTREAM-PARITY (xmlReadMemory -> xmlCtxtReadMemory): the parse
2987 // options are mirrored into the context members (dictNames, keepBlanks,
2988 // recovery, ...) before parsing starts.
2989 crate::abi::exports_parser::apply_options(ctxt, options);
2990 let parsed = crate::xml::parser::helpers::parse_document(ctxt);
2991 let doc = (*ctxt).myDoc;
2992 // UPSTREAM-PARITY: the URL is attached to the document on success AND on
2993 // the recovery path (the partial tree keeps the document identity).
2994 if !doc.is_null() && !URL.is_null() && (*doc).URL.is_null() {
2995 (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
2996 }
2997 // UPSTREAM-PARITY (xmlCtxtReadMemory): an explicit `encoding` argument
2998 // is recorded on the document when the document carries no encoding
2999 // declaration of its own, so `doc->encoding` (nokogiri Document#encoding)
3000 // reflects what the caller requested.
3001 if !doc.is_null() && (*doc).encoding.is_null() && !encoding.is_null() {
3002 canonical_doc_encoding(doc, encoding);
3003 }
3004 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3005 if parsed != 0 {
3006 // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
3007 // partially built document is discarded and NULL is returned; with
3008 // XML_PARSE_RECOVER the partial tree is kept.
3009 if options & 1 << 0 != 0 {
3010 return doc;
3011 }
3012 if !doc.is_null() {
3013 crate::xml::tree::free_doc(doc);
3014 }
3015 return ptr::null_mut();
3016 }
3017 doc
3018}
3019
3020/// Load a list of catalogs (upstream `xmlLoadCatalogs`).
3021///
3022/// # SAFETY
3023///
3024/// - `catalogs` must be a valid NUL-terminated string or NULL.
3025#[no_mangle]
3026pub unsafe extern "C" fn xmlLoadCatalogs(catalogs: *const c_char) {
3027 if !catalogs.is_null() {
3028 crate::xml::catalog::load_catalog(catalogs);
3029 }
3030}
3031
3032/// Load a single catalog (upstream `xmlLoadCatalog`).
3033///
3034/// # SAFETY
3035///
3036/// - `catalogs` must be a valid NUL-terminated string or NULL.
3037#[no_mangle]
3038pub unsafe extern "C" fn xmlLoadCatalog(catalogs: *const c_char) -> c_int {
3039 // UPSTREAM-PARITY (catalog.c xmlLoadCatalog): returns 0 on success,
3040 // 1 on error (unlike xmlCatalogLoad which returns the catalog handle).
3041 let handle = crate::xml::catalog::load_catalog(catalogs);
3042 if handle.is_null() {
3043 1
3044 } else {
3045 0
3046 }
3047}
3048
3049/// Read an XML document from a file descriptor.
3050///
3051/// # UPSTREAM-PARITY
3052///
3053/// ```c
3054/// xmlDocPtr xmlReadFd(int fd, const char *URL, const char *encoding, int options);
3055/// ```
3056#[no_mangle]
3057pub unsafe extern "C" fn xmlReadFd(
3058 fd: c_int,
3059 URL: *const c_char,
3060 encoding: *const c_char,
3061 options: c_int,
3062) -> *mut _xmlDoc {
3063 // SAFETY: fd must be a valid open file descriptor.
3064 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3065 if ctxt.is_null() {
3066 return ptr::null_mut();
3067 }
3068 // Read all data from the fd
3069 let mut buf = Vec::new();
3070 let mut tmp = [0u8; 4096];
3071 loop {
3072 let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
3073 if n <= 0 {
3074 break;
3075 }
3076 buf.extend_from_slice(&tmp[..n as usize]);
3077 }
3078 let input = crate::xml::parser::helpers::input_from_memory(
3079 buf.as_ptr() as *const c_char,
3080 buf.len() as c_int,
3081 );
3082 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3083 (*ctxt).options = options;
3084 if crate::xml::parser::helpers::parse_document(ctxt) != 0 {
3085 let doc = (*ctxt).myDoc;
3086 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3087 return doc;
3088 }
3089 let doc = (*ctxt).myDoc;
3090 if !doc.is_null() && !URL.is_null() {
3091 (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
3092 }
3093 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3094 doc
3095}
3096
3097/// Read an XML document from I/O callbacks.
3098///
3099/// # UPSTREAM-PARITY
3100///
3101/// ```c
3102/// xmlDocPtr xmlReadIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
3103/// void *ioctx, const char *URL, const char *encoding, int options);
3104/// ```
3105#[no_mangle]
3106pub unsafe extern "C" fn xmlReadIO(
3107 ioread: Option<xmlInputReadCallback>,
3108 ioclose: Option<xmlInputCloseCallback>,
3109 ioctx: *mut c_void,
3110 URL: *const c_char,
3111 encoding: *const c_char,
3112 options: c_int,
3113) -> *mut _xmlDoc {
3114 // SAFETY: callbacks must be valid function pointers if non-NULL.
3115 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3116 if ctxt.is_null() {
3117 return ptr::null_mut();
3118 }
3119 let input = crate::xml::parser::helpers::input_from_io(ioread, ioclose, ioctx);
3120 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3121 (*ctxt).options = options;
3122 let parsed = crate::xml::parser::helpers::parse_document(ctxt);
3123 let doc = (*ctxt).myDoc;
3124 // UPSTREAM-PARITY (xmlReadIO -> xmlCtxtReadIO): the URL is attached to the
3125 // document on success AND on the recovery path (the partial tree keeps the
3126 // document identity).
3127 if !doc.is_null() && !URL.is_null() && (*doc).URL.is_null() {
3128 (*doc).URL = crate::xml::string::xml_strdup(URL as *const xmlChar);
3129 }
3130 if !doc.is_null() && (*doc).encoding.is_null() && !encoding.is_null() {
3131 canonical_doc_encoding(doc, encoding);
3132 }
3133 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3134 if parsed != 0 {
3135 // UPSTREAM-PARITY: on a hard (non-recoverable) parse error the
3136 // partially built document is discarded and NULL is returned; only with
3137 // XML_PARSE_RECOVER is the partial tree kept. nokogiri's read_io/strict
3138 // path relies on NULL here to raise a SyntaxError.
3139 if options & 1 << 0 != 0 {
3140 return doc;
3141 }
3142 if !doc.is_null() {
3143 crate::xml::tree::free_doc(doc);
3144 }
3145 return ptr::null_mut();
3146 }
3147 doc
3148}
3149
3150/// Parse an XML document (SAX1).
3151///
3152/// # UPSTREAM-PARITY
3153///
3154/// ```c
3155/// xmlDocPtr xmlSAXParseDoc(xmlSAXHandlerPtr sax, const xmlChar *cur, int recovery);
3156/// ```
3157#[no_mangle]
3158pub unsafe extern "C" fn xmlSAXParseDoc(
3159 sax: *mut _xmlSAXHandler,
3160 cur: *const xmlChar,
3161 recovery: c_int,
3162) -> *mut _xmlDoc {
3163 // SAFETY: cur must be a valid null-terminated xmlChar string.
3164 if cur.is_null() {
3165 return ptr::null_mut();
3166 }
3167 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3168 if ctxt.is_null() {
3169 return ptr::null_mut();
3170 }
3171 if !sax.is_null() {
3172 (*ctxt).sax = sax;
3173 (*ctxt).userData = (*ctxt).sax as *mut c_void;
3174 }
3175 if recovery != 0 {
3176 (*ctxt).recovery = 1;
3177 (*ctxt).options |= 1; // XML_PARSE_RECOVER
3178 }
3179 let len = crate::xml::string::xml_strlen(cur);
3180 // §16.5.2 zero-copy: xmlSAXParseDoc creates, parses and frees its own
3181 // context inside this call — the input borrows the caller's string.
3182 let input =
3183 crate::xml::parser::helpers::input_from_memory_borrowed(cur as *const c_char, len as c_int);
3184 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3185 crate::xml::parser::helpers::parse_document(ctxt);
3186 let doc = (*ctxt).myDoc;
3187 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3188 doc
3189}
3190
3191/// Parse an XML document (SAX1) with user data (upstream parser.h
3192/// `xmlSAXParseDocWithData`): `user_data` is passed to the SAX callbacks.
3193///
3194/// # UPSTREAM-PARITY
3195///
3196/// ```c
3197/// xmlDocPtr xmlSAXParseDocWithData(xmlSAXHandlerPtr sax, const xmlChar *cur,
3198/// int recovery, void *data);
3199/// ```
3200#[no_mangle]
3201pub unsafe extern "C" fn xmlSAXParseDocWithData(
3202 sax: *mut _xmlSAXHandler,
3203 cur: *const xmlChar,
3204 recovery: c_int,
3205 data: *mut c_void,
3206) -> *mut _xmlDoc {
3207 // SAFETY: cur must be a valid null-terminated xmlChar string.
3208 if cur.is_null() {
3209 return ptr::null_mut();
3210 }
3211 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3212 if ctxt.is_null() {
3213 return ptr::null_mut();
3214 }
3215 if !sax.is_null() {
3216 (*ctxt).sax = sax;
3217 }
3218 (*ctxt).userData = data;
3219 if recovery != 0 {
3220 (*ctxt).recovery = 1;
3221 (*ctxt).options |= 1; // XML_PARSE_RECOVER
3222 }
3223 let len = crate::xml::string::xml_strlen(cur);
3224 // §16.5.2 zero-copy: xmlSAXParseDocWithData owns its context for this
3225 // call — the input borrows the caller's string.
3226 let input =
3227 crate::xml::parser::helpers::input_from_memory_borrowed(cur as *const c_char, len as c_int);
3228 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3229 crate::xml::parser::helpers::parse_document(ctxt);
3230 let doc = (*ctxt).myDoc;
3231 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3232 doc
3233}
3234///
3235/// # UPSTREAM-PARITY
3236///
3237/// ```c
3238/// xmlDocPtr xmlSAXParseFileWithData(xmlSAXHandlerPtr sax, const char *filename,
3239/// int recovery, void *data);
3240/// ```
3241#[no_mangle]
3242pub unsafe extern "C" fn xmlSAXParseFileWithData(
3243 sax: *mut _xmlSAXHandler,
3244 filename: *const c_char,
3245 recovery: c_int,
3246 data: *mut c_void,
3247) -> *mut _xmlDoc {
3248 // SAFETY: filename must be a valid C string or NULL.
3249 if filename.is_null() {
3250 return ptr::null_mut();
3251 }
3252 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3253 if ctxt.is_null() {
3254 return ptr::null_mut();
3255 }
3256 if !sax.is_null() {
3257 (*ctxt).sax = sax;
3258 }
3259 (*ctxt).userData = data;
3260 if recovery != 0 {
3261 (*ctxt).recovery = 1;
3262 (*ctxt).options |= 1; // XML_PARSE_RECOVER
3263 }
3264 let input = match crate::abi::exports_parser::open_filename_routed(filename, ctxt) {
3265 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
3266 crate::abi::exports_parser::RoutedFileOpen::Failed => {
3267 crate::abi::exports_parser::emit_io_warning(
3268 ctxt,
3269 crate::abi::exports_parser::io_load_failure_message(filename),
3270 );
3271 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3272 return ptr::null_mut();
3273 }
3274 crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
3275 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3276 return ptr::null_mut();
3277 }
3278 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
3279 match crate::xml::parser::helpers::input_from_file(filename) {
3280 Ok(input) => input,
3281 Err(_) => {
3282 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3283 return ptr::null_mut();
3284 }
3285 }
3286 }
3287 };
3288 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3289 crate::xml::parser::helpers::parse_document(ctxt);
3290 let doc = (*ctxt).myDoc;
3291 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3292 doc
3293}
3294
3295/// Parse an XML document (SAX1) with user data from memory (upstream
3296/// parser.h `xmlSAXParseMemoryWithData`).
3297///
3298/// # UPSTREAM-PARITY
3299///
3300/// ```c
3301/// xmlDocPtr xmlSAXParseMemoryWithData(xmlSAXHandlerPtr sax, const char *buffer,
3302/// int size, int recovery, void *data);
3303/// ```
3304#[no_mangle]
3305pub unsafe extern "C" fn xmlSAXParseMemoryWithData(
3306 sax: *mut _xmlSAXHandler,
3307 buffer: *const c_char,
3308 size: c_int,
3309 recovery: c_int,
3310 data: *mut c_void,
3311) -> *mut _xmlDoc {
3312 // SAFETY: buffer must be a valid pointer with `size` readable bytes.
3313 if buffer.is_null() || size <= 0 {
3314 return ptr::null_mut();
3315 }
3316 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3317 if ctxt.is_null() {
3318 return ptr::null_mut();
3319 }
3320 if !sax.is_null() {
3321 (*ctxt).sax = sax;
3322 }
3323 (*ctxt).userData = data;
3324 if recovery != 0 {
3325 (*ctxt).recovery = 1;
3326 (*ctxt).options |= 1; // XML_PARSE_RECOVER
3327 }
3328 // §16.5.2 zero-copy: xmlSAXParseMemoryWithData owns its context for this
3329 // call — the input borrows the caller's buffer.
3330 let input = crate::xml::parser::helpers::input_from_memory_borrowed(buffer, size);
3331 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3332 crate::xml::parser::helpers::parse_document(ctxt);
3333 let doc = (*ctxt).myDoc;
3334 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3335 doc
3336}
3337
3338/// Parse an XML file (SAX1).
3339///
3340/// # UPSTREAM-PARITY
3341///
3342/// ```c
3343/// xmlDocPtr xmlSAXParseFile(xmlSAXHandlerPtr sax, const char *filename, int recovery);
3344/// ```
3345#[no_mangle]
3346pub unsafe extern "C" fn xmlSAXParseFile(
3347 sax: *mut _xmlSAXHandler,
3348 filename: *const c_char,
3349 recovery: c_int,
3350) -> *mut _xmlDoc {
3351 // SAFETY: filename must be a valid C string.
3352 if filename.is_null() {
3353 return ptr::null_mut();
3354 }
3355 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3356 if ctxt.is_null() {
3357 return ptr::null_mut();
3358 }
3359 if !sax.is_null() {
3360 (*ctxt).sax = sax;
3361 (*ctxt).userData = (*ctxt).sax as *mut c_void;
3362 }
3363 if recovery != 0 {
3364 (*ctxt).recovery = 1;
3365 (*ctxt).options |= 1;
3366 }
3367 let input = match crate::abi::exports_parser::open_filename_routed(filename, ctxt) {
3368 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
3369 crate::abi::exports_parser::RoutedFileOpen::Failed => {
3370 crate::abi::exports_parser::emit_io_warning(
3371 ctxt,
3372 crate::abi::exports_parser::io_load_failure_message(filename),
3373 );
3374 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3375 return ptr::null_mut();
3376 }
3377 crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
3378 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3379 return ptr::null_mut();
3380 }
3381 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
3382 match crate::xml::parser::helpers::input_from_file(filename) {
3383 Ok(input) => input,
3384 Err(_) => {
3385 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3386 return ptr::null_mut();
3387 }
3388 }
3389 }
3390 };
3391 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3392 crate::xml::parser::helpers::parse_document(ctxt);
3393 let doc = (*ctxt).myDoc;
3394 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3395 doc
3396}
3397
3398/// Parse an XML document from memory (SAX1).
3399///
3400/// # UPSTREAM-PARITY
3401///
3402/// ```c
3403/// xmlDocPtr xmlSAXParseMemory(xmlSAXHandlerPtr sax,
3404/// const char *buffer, int size, int recovery);
3405/// ```
3406#[no_mangle]
3407pub unsafe extern "C" fn xmlSAXParseMemory(
3408 sax: *mut _xmlSAXHandler,
3409 buffer: *const c_char,
3410 size: c_int,
3411 recovery: c_int,
3412) -> *mut _xmlDoc {
3413 // SAFETY: buffer must be valid with at least `size` bytes.
3414 if buffer.is_null() || size <= 0 {
3415 return ptr::null_mut();
3416 }
3417 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3418 if ctxt.is_null() {
3419 return ptr::null_mut();
3420 }
3421 if !sax.is_null() {
3422 (*ctxt).sax = sax;
3423 (*ctxt).userData = (*ctxt).sax as *mut c_void;
3424 }
3425 if recovery != 0 {
3426 (*ctxt).recovery = 1;
3427 (*ctxt).options |= 1;
3428 }
3429 // §16.5.2 zero-copy: xmlSAXParseMemory owns its context for this call —
3430 // the input borrows the caller's buffer.
3431 let input = crate::xml::parser::helpers::input_from_memory_borrowed(buffer, size);
3432 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3433 crate::xml::parser::helpers::parse_document(ctxt);
3434 let doc = (*ctxt).myDoc;
3435 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3436 doc
3437}
3438
3439/// SAX user parse file.
3440///
3441/// # UPSTREAM-PARITY
3442///
3443/// ```c
3444/// int xmlSAXUserParseFile(xmlSAXHandlerPtr sax, void *user_data,
3445/// const char *filename);
3446/// ```
3447#[no_mangle]
3448pub unsafe extern "C" fn xmlSAXUserParseFile(
3449 sax: *mut _xmlSAXHandler,
3450 user_data: *mut c_void,
3451 filename: *const c_char,
3452) -> c_int {
3453 // SAFETY: filename must be a valid C string. sax and user_data may be NULL.
3454 if filename.is_null() {
3455 return -1;
3456 }
3457 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3458 if ctxt.is_null() {
3459 return -1;
3460 }
3461 if !sax.is_null() {
3462 // UPSTREAM-PARITY (parser.c xmlSAXUserParseFile): same copy-into-
3463 //-own-storage contract as xmlSAXUserParseMemory (HOSTILE-CALLBACKS
3464 // C10 class).
3465 if unsafe { (*sax).initialized } == XML_SAX2_MAGIC as c_uint {
3466 unsafe {
3467 ptr::copy_nonoverlapping(sax as *const _xmlSAXHandler, (*ctxt).sax, 1);
3468 }
3469 } else {
3470 unsafe {
3471 ptr::copy_nonoverlapping(
3472 sax as *const u8,
3473 (*ctxt).sax as *mut u8,
3474 size_of::<crate::abi::structs::_xmlSAXHandlerV1>(),
3475 );
3476 }
3477 }
3478 }
3479 (*ctxt).userData = if !user_data.is_null() {
3480 user_data
3481 } else {
3482 ctxt as *mut c_void
3483 };
3484 let input = match crate::abi::exports_parser::open_filename_routed(filename, ctxt) {
3485 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
3486 crate::abi::exports_parser::RoutedFileOpen::Failed => {
3487 crate::abi::exports_parser::emit_io_warning(
3488 ctxt,
3489 crate::abi::exports_parser::io_load_failure_message(filename),
3490 );
3491 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3492 return -1;
3493 }
3494 crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
3495 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3496 return -1;
3497 }
3498 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
3499 match crate::xml::parser::helpers::input_from_file(filename) {
3500 Ok(input) => input,
3501 Err(_) => {
3502 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3503 return -1;
3504 }
3505 }
3506 }
3507 };
3508 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3509 let ret = crate::xml::parser::helpers::parse_document(ctxt);
3510 // UPSTREAM-PARITY (parser.c xmlSAXUserParseFile): 0 when well-formed,
3511 // otherwise the recorded errNo (or -1).
3512 let ret = if ret == 0 {
3513 0
3514 } else {
3515 let err = unsafe { (*ctxt).errNo };
3516 if err != crate::abi::types::XML_ERR_OK {
3517 err
3518 } else {
3519 -1
3520 }
3521 };
3522 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3523 ret
3524}
3525
3526/// SAX user parse memory.
3527///
3528/// # UPSTREAM-PARITY
3529///
3530/// ```c
3531/// int xmlSAXUserParseMemory(xmlSAXHandlerPtr sax, void *user_data,
3532/// const char *buffer, int size);
3533/// ```
3534#[no_mangle]
3535pub unsafe extern "C" fn xmlSAXUserParseMemory(
3536 sax: *mut _xmlSAXHandler,
3537 user_data: *mut c_void,
3538 buffer: *const c_char,
3539 size: c_int,
3540) -> c_int {
3541 // SAFETY: buffer must be valid with at least `size` bytes.
3542 if buffer.is_null() || size <= 0 {
3543 return -1;
3544 }
3545 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3546 if ctxt.is_null() {
3547 return -1;
3548 }
3549 if !sax.is_null() {
3550 // UPSTREAM-PARITY (parser.c xmlSAXUserParseMemory): the caller's
3551 // handler is COPIED into the context's own SAX struct — never
3552 // borrowed — so xmlFreeParserCtxt frees only the copy. SAX2-magic
3553 // handlers are copied in full; legacy handlers expose only the V1
3554 // prefix (HOSTILE-CALLBACKS C10: borrowing the caller's struct made
3555 // xmlFreeParserCtxt free a stack object).
3556 if unsafe { (*sax).initialized } == XML_SAX2_MAGIC as c_uint {
3557 unsafe {
3558 ptr::copy_nonoverlapping(sax as *const _xmlSAXHandler, (*ctxt).sax, 1);
3559 }
3560 } else {
3561 unsafe {
3562 ptr::copy_nonoverlapping(
3563 sax as *const u8,
3564 (*ctxt).sax as *mut u8,
3565 size_of::<crate::abi::structs::_xmlSAXHandlerV1>(),
3566 );
3567 }
3568 }
3569 }
3570 (*ctxt).userData = if !user_data.is_null() {
3571 user_data
3572 } else {
3573 ctxt as *mut c_void
3574 };
3575 let input = crate::xml::parser::helpers::input_from_memory(buffer, size);
3576 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3577 let ret = crate::xml::parser::helpers::parse_document(ctxt);
3578 // UPSTREAM-PARITY (parser.c xmlSAXUserParseMemory): the return value is
3579 // 0 when well-formed, otherwise the recorded errNo (or -1).
3580 let ret = if ret == 0 {
3581 0
3582 } else {
3583 let err = unsafe { (*ctxt).errNo };
3584 if err != crate::abi::types::XML_ERR_OK {
3585 err
3586 } else {
3587 -1
3588 }
3589 };
3590 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3591 ret
3592}
3593
3594/// Parse an XML document from a string (DOM).
3595///
3596/// # UPSTREAM-PARITY
3597///
3598/// ```c
3599/// xmlDocPtr xmlParseDoc(const xmlChar *cur);
3600/// ```
3601#[no_mangle]
3602pub unsafe extern "C" fn xmlParseDoc(cur: *const xmlChar) -> *mut _xmlDoc {
3603 // SAFETY: cur must be a valid null-terminated xmlChar string.
3604 if cur.is_null() {
3605 return ptr::null_mut();
3606 }
3607 xmlReadDoc(cur, ptr::null(), ptr::null(), 0)
3608}
3609
3610/// Parse an XML file (DOM).
3611///
3612/// # UPSTREAM-PARITY
3613///
3614/// ```c
3615/// xmlDocPtr xmlParseFile(const char *filename);
3616/// ```
3617#[no_mangle]
3618pub unsafe extern "C" fn xmlParseFile(filename: *const c_char) -> *mut _xmlDoc {
3619 // SAFETY: filename must be a valid C string.
3620 if filename.is_null() {
3621 return ptr::null_mut();
3622 }
3623 xmlReadFile(filename, ptr::null(), 0)
3624}
3625
3626/// Parse an XML document from memory (DOM).
3627///
3628/// # UPSTREAM-PARITY
3629///
3630/// ```c
3631/// xmlDocPtr xmlParseMemory(const char *buffer, int size);
3632/// ```
3633#[no_mangle]
3634pub unsafe extern "C" fn xmlParseMemory(buffer: *const c_char, size: c_int) -> *mut _xmlDoc {
3635 // SAFETY: buffer must be valid with at least `size` bytes.
3636 if buffer.is_null() || size <= 0 {
3637 return ptr::null_mut();
3638 }
3639 xmlReadMemory(buffer, size, ptr::null(), ptr::null(), 0)
3640}
3641
3642/// Create a file parser context.
3643///
3644/// # UPSTREAM-PARITY
3645///
3646/// ```c
3647/// xmlParserCtxtPtr xmlCreateFileParserCtxt(const char *filename);
3648/// ```
3649#[no_mangle]
3650pub unsafe extern "C" fn xmlCreateFileParserCtxt(filename: *const c_char) -> *mut _xmlParserCtxt {
3651 // SAFETY: filename must be a valid C string.
3652 if filename.is_null() {
3653 return ptr::null_mut();
3654 }
3655 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3656 if ctxt.is_null() {
3657 return ptr::null_mut();
3658 }
3659 let input = match crate::abi::exports_parser::open_filename_routed(filename, ctxt) {
3660 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
3661 crate::abi::exports_parser::RoutedFileOpen::Failed => {
3662 // UPSTREAM-PARITY (parser.c xmlCreateFileParserCtxt ->
3663 // xmlNewInputFromFile): a failed open raises the xmlCtxtErrIO
3664 // warning through the context channel.
3665 crate::abi::exports_parser::emit_io_warning(
3666 ctxt,
3667 crate::abi::exports_parser::io_load_failure_message(filename),
3668 );
3669 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3670 return ptr::null_mut();
3671 }
3672 crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
3673 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3674 return ptr::null_mut();
3675 }
3676 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
3677 match crate::xml::parser::helpers::input_from_file(filename) {
3678 Ok(input) => input,
3679 Err(_) => {
3680 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3681 return ptr::null_mut();
3682 }
3683 }
3684 }
3685 };
3686 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3687 ctxt
3688}
3689
3690/// Create a document parser context.
3691///
3692/// # UPSTREAM-PARITY
3693///
3694/// ```c
3695/// xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar *cur);
3696/// ```
3697#[no_mangle]
3698pub unsafe extern "C" fn xmlCreateDocParserCtxt(cur: *const xmlChar) -> *mut _xmlParserCtxt {
3699 // SAFETY: cur must be a valid null-terminated xmlChar string.
3700 if cur.is_null() {
3701 return ptr::null_mut();
3702 }
3703 let ctxt = crate::xml::parser::helpers::create_parser_ctxt();
3704 if ctxt.is_null() {
3705 return ptr::null_mut();
3706 }
3707 let len = crate::xml::string::xml_strlen(cur);
3708 let input = crate::xml::parser::helpers::input_from_memory(cur as *const c_char, len as c_int);
3709 crate::xml::parser::helpers::setup_parser_input(ctxt, input);
3710 ctxt
3711}
3712
3713/// Parse a document using an existing parser context.
3714///
3715/// # UPSTREAM-PARITY
3716///
3717/// ```c
3718/// int xmlParseDocument(xmlParserCtxtPtr ctxt);
3719/// ```
3720#[no_mangle]
3721pub unsafe extern "C" fn xmlParseDocument(ctxt: *mut _xmlParserCtxt) -> c_int {
3722 // SAFETY: ctxt must be a valid parser context.
3723 if ctxt.is_null() {
3724 return -1;
3725 }
3726 crate::xml::parser::helpers::parse_document(ctxt)
3727}
3728
3729/// Free a parser context.
3730///
3731/// # UPSTREAM-PARITY
3732///
3733/// ```c
3734/// void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt);
3735/// ```
3736#[no_mangle]
3737pub unsafe extern "C" fn xmlFreeParserCtxt(ctxt: *mut _xmlParserCtxt) {
3738 if ctxt.is_null() {
3739 return;
3740 }
3741 crate::xml::parser::helpers::free_parser_ctxt(ctxt);
3742}
3743
3744/// Set parser options.
3745///
3746/// # UPSTREAM-PARITY
3747///
3748/// ```c
3749/// int xmlCtxtUseOptions(xmlParserCtxtPtr ctxt, int options);
3750/// ```
3751#[no_mangle]
3752pub unsafe extern "C" fn xmlCtxtUseOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
3753 if ctxt.is_null() {
3754 return -1;
3755 }
3756 // UPSTREAM-PARITY (parser.c 2.15 xmlCtxtUseOptions ->
3757 // xmlCtxtSetOptionsInternal): options in the keep mask can only ever be
3758 // enabled (historic never-clear bits); the remaining handled bits are
3759 // taken from the caller's `options`. The historical struct members
3760 // (recovery / replaceEntities / loadsubset / validate / pedantic /
3761 // keepBlanks / dictNames) are derived from the option bits exactly like
3762 // upstream, because deprecated APIs and consumers (e.g. PHP's expat
3763 // compat layer, which sanitizes then calls xmlCtxtUseOptions with
3764 // XML_PARSE_OLDSAX | XML_PARSE_NOENT) read those members directly.
3765 const KEEP_MASK: c_int = XML_PARSE_NOERROR
3766 | XML_PARSE_NOWARNING
3767 | XML_PARSE_NONET
3768 | XML_PARSE_NSCLEAN
3769 | XML_PARSE_NOCDATA
3770 | XML_PARSE_COMPACT
3771 | XML_PARSE_OLD10
3772 | XML_PARSE_HUGE
3773 | XML_PARSE_OLDSAX
3774 | XML_PARSE_IGNORE_ENC
3775 | XML_PARSE_BIG_LINES;
3776 const ALL_MASK: c_int = XML_PARSE_RECOVER
3777 | XML_PARSE_NOENT
3778 | XML_PARSE_DTDLOAD
3779 | XML_PARSE_DTDATTR
3780 | XML_PARSE_DTDVALID
3781 | XML_PARSE_NOERROR
3782 | XML_PARSE_NOWARNING
3783 | XML_PARSE_PEDANTIC
3784 | XML_PARSE_NOBLANKS
3785 | XML_PARSE_SAX1
3786 | XML_PARSE_NONET
3787 | XML_PARSE_NODICT
3788 | XML_PARSE_NSCLEAN
3789 | XML_PARSE_NOCDATA
3790 | XML_PARSE_COMPACT
3791 | XML_PARSE_OLD10
3792 | XML_PARSE_HUGE
3793 | XML_PARSE_OLDSAX
3794 | XML_PARSE_IGNORE_ENC
3795 | XML_PARSE_BIG_LINES
3796 | XML_PARSE_NO_XXE;
3797 unsafe {
3798 let merged = ((*ctxt).options & KEEP_MASK) | (options & ALL_MASK);
3799 crate::abi::exports_parser::apply_options(ctxt, merged);
3800 }
3801 options & !ALL_MASK
3802}
3803
3804/// Parse a well-balanced chunk (for push parsing).
3805///
3806/// # UPSTREAM-PARITY
3807///
3808/// ```c
3809/// xmlParserErrors xmlParseChunk(xmlParserCtxtPtr ctxt,
3810/// const char *chunk, int size, int terminate);
3811/// ```
3812#[no_mangle]
3813pub unsafe extern "C" fn xmlParseChunk(
3814 ctxt: *mut _xmlParserCtxt,
3815 chunk: *const c_char,
3816 size: c_int,
3817 terminate: c_int,
3818) -> c_int {
3819 // SAFETY: ctxt must be a valid parser context.
3820 // chunk may be NULL if terminate is set (finalize without data).
3821 //
3822 // UPSTREAM-PARITY (parser.c 2.15 xmlParseChunk): NULL context, negative
3823 // sizes and NULL chunk with positive size all return XML_ERR_ARGUMENT
3824 // (115), never -1 — HOSTILE-ABI B10/B13/D2.
3825 if ctxt.is_null() || size < 0 || (chunk.is_null() && size > 0) {
3826 return XML_ERR_ARGUMENT;
3827 }
3828 crate::xml::parser::helpers::parse_chunk(ctxt, chunk, size, terminate)
3829}
3830
3831/// Create a memory parser input buffer.
3832///
3833/// # UPSTREAM-PARITY
3834///
3835/// ```c
3836/// xmlParserInputBufferPtr xmlParserInputBufferCreateMem(const char *buffer, int size, int enc);
3837/// ```
3838#[no_mangle]
3839pub unsafe extern "C" fn xmlParserInputBufferCreateMem(
3840 buffer: *const c_char,
3841 size: c_int,
3842 enc: c_int,
3843) -> *mut _xmlParserInputBuffer {
3844 // SAFETY: buffer must be valid with at least `size` bytes.
3845 if buffer.is_null() || size <= 0 {
3846 return ptr::null_mut();
3847 }
3848 // UPSTREAM-PARITY (xmlIO.c xmlParserInputBufferCreateMem): the content
3849 // is copied into the buffer (readcallback stays NULL — PHP's
3850 // XMLReader::XML()/fromString() parse from the buffer's own bytes).
3851 // The enc argument selects an input converter upstream; the candidate
3852 // parser handles declared encodings itself, so it is not stored.
3853 let _ = enc;
3854 crate::xml::parser::helpers::alloc_parser_input_buffer_with_mem(buffer, size)
3855}
3856
3857/// Create a file parser input buffer.
3858///
3859/// # UPSTREAM-PARITY
3860///
3861/// ```c
3862/// xmlParserInputBufferPtr xmlParserInputBufferCreateFilename(const char *URI, int enc);
3863/// ```
3864#[no_mangle]
3865pub unsafe extern "C" fn xmlParserInputBufferCreateFilename(
3866 URI: *const c_char,
3867 enc: c_int,
3868) -> *mut _xmlParserInputBuffer {
3869 // SAFETY: URI must be a valid C string or NULL.
3870 if URI.is_null() {
3871 return ptr::null_mut();
3872 }
3873 crate::xml::parser::helpers::alloc_parser_input_buffer()
3874}
3875
3876/// Create an I/O parser input buffer.
3877///
3878/// # UPSTREAM-PARITY
3879///
3880/// ```c
3881/// xmlParserInputBufferPtr xmlParserInputBufferCreateIO(
3882/// xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
3883/// void *ioctx, int enc);
3884/// ```
3885#[no_mangle]
3886pub unsafe extern "C" fn xmlParserInputBufferCreateIO(
3887 ioread: Option<xmlInputReadCallback>,
3888 ioclose: Option<xmlInputCloseCallback>,
3889 ioctx: *mut c_void,
3890 enc: c_int,
3891) -> *mut _xmlParserInputBuffer {
3892 // SAFETY: ioread must be a valid callback if Some. ioctx may be NULL.
3893 let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
3894 if !buf.is_null() {
3895 (*buf).readcallback = ioread;
3896 (*buf).closecallback = ioclose;
3897 (*buf).context = ioctx;
3898 }
3899 buf
3900}
3901
3902/// Free a parser input buffer.
3903///
3904/// # UPSTREAM-PARITY
3905///
3906/// ```c
3907/// void xmlFreeParserInputBuffer(xmlParserInputBufferPtr buf);
3908/// ```
3909#[no_mangle]
3910pub unsafe extern "C" fn xmlFreeParserInputBuffer(buf: *mut _xmlParserInputBuffer) {
3911 if buf.is_null() {
3912 return;
3913 }
3914 crate::xml::parser::helpers::free_parser_input_buffer(buf);
3915}
3916
3917/// Create a new parser input.
3918///
3919/// # UPSTREAM-PARITY
3920///
3921/// ```c
3922/// xmlParserInputPtr xmlNewInputFromFile(xmlParserCtxtPtr ctxt, const char *filename);
3923/// ```
3924#[no_mangle]
3925pub unsafe extern "C" fn xmlNewInputFromFile(
3926 ctxt: *mut _xmlParserCtxt,
3927 filename: *const c_char,
3928) -> *mut _xmlParserInput {
3929 // SAFETY: filename must be a valid C string. ctxt may be NULL.
3930 // This function allocates a _xmlParserInput. The caller owns it.
3931 // Note: The InputBuffer backing data is NOT leaked here (no ctxt._private
3932 // to store it). Use xmlCreateFileParserCtxt + xmlParseDocument instead.
3933 if filename.is_null() {
3934 return ptr::null_mut();
3935 }
3936 crate::xml::parser::helpers::alloc_parser_input_buffer() as *mut _xmlParserInput
3937}
3938
3939/// Free a parser input.
3940///
3941/// # UPSTREAM-PARITY
3942///
3943/// ```c
3944/// void xmlFreeInputStream(xmlParserInputPtr input);
3945/// ```
3946#[no_mangle]
3947pub unsafe extern "C" fn xmlFreeInputStream(input: *mut _xmlParserInput) {
3948 if input.is_null() {
3949 return;
3950 }
3951 crate::xml::parser::helpers::free_parser_input(input);
3952}
3953
3954// ═══════════════════════════════════════════════════════════════════════════════
3955// 8. I/O
3956// ═══════════════════════════════════════════════════════════════════════════════
3957
3958/// Create an output buffer for a file.
3959///
3960/// # UPSTREAM-PARITY
3961///
3962/// ```c
3963/// xmlOutputBufferPtr xmlOutputBufferCreateFilename(const char *URI,
3964/// xmlCharEncodingHandlerPtr encoder,
3965/// int compression);
3966/// ```
3967#[no_mangle]
3968pub unsafe extern "C" fn xmlOutputBufferCreateFilename(
3969 URI: *const c_char,
3970 encoder: *mut c_void,
3971 compression: c_int,
3972) -> *mut _xmlOutputBuffer {
3973 if URI.is_null() {
3974 return ptr::null_mut();
3975 }
3976 // UPSTREAM-PARITY (xmlIO.c xmlOutputBufferCreateFilename): a default
3977 // create-filename callback registered via xmlOutputBufferCreateFilenameDefault
3978 // is consulted first (PHP installs php_libxml_output_buffer_create_filename at
3979 // request init, so every filename open routes through the PHP streams layer);
3980 // otherwise the builtin file open runs.
3981 crate::xml::io::output_buffer_create_filename_routed(
3982 URI,
3983 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
3984 compression,
3985 )
3986}
3987
3988/// Create an output buffer for a file descriptor.
3989///
3990/// # UPSTREAM-PARITY
3991///
3992/// ```c
3993/// xmlOutputBufferPtr xmlOutputBufferCreateFd(int fd,
3994/// xmlCharEncodingHandlerPtr encoder);
3995/// ```
3996#[no_mangle]
3997pub unsafe extern "C" fn xmlOutputBufferCreateFd(
3998 fd: c_int,
3999 encoder: *mut c_void,
4000) -> *mut _xmlOutputBuffer {
4001 if fd < 0 {
4002 return ptr::null_mut();
4003 }
4004 crate::xml::io::output_buffer_create_fd(
4005 fd,
4006 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
4007 )
4008}
4009
4010/// Create an output buffer from I/O callbacks.
4011///
4012/// # UPSTREAM-PARITY
4013///
4014/// ```c
4015/// xmlOutputBufferPtr xmlOutputBufferCreateIO(
4016/// xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose,
4017/// void *ioctx, xmlCharEncodingHandlerPtr encoder);
4018/// ```
4019#[no_mangle]
4020pub unsafe extern "C" fn xmlOutputBufferCreateIO(
4021 iowrite: Option<xmlOutputWriteCallback>,
4022 ioclose: Option<xmlOutputCloseCallback>,
4023 ioctx: *mut c_void,
4024 encoder: *mut c_void,
4025) -> *mut _xmlOutputBuffer {
4026 crate::xml::io::output_buffer_create_io(
4027 iowrite,
4028 ioclose,
4029 ioctx,
4030 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
4031 )
4032}
4033
4034/// Free an output buffer.
4035///
4036/// # UPSTREAM-PARITY
4037///
4038/// ```c
4039/// void xmlOutputBufferClose(xmlOutputBufferPtr out);
4040/// ```
4041#[no_mangle]
4042pub unsafe extern "C" fn xmlOutputBufferClose(out: *mut _xmlOutputBuffer) -> c_int {
4043 if out.is_null() {
4044 return -1;
4045 }
4046 crate::xml::io::output_buffer_close(out)
4047}
4048
4049/// Flush an output buffer.
4050///
4051/// # UPSTREAM-PARITY
4052///
4053/// ```c
4054/// int xmlOutputBufferFlush(xmlOutputBufferPtr out);
4055/// ```
4056#[no_mangle]
4057pub unsafe extern "C" fn xmlOutputBufferFlush(out: *mut _xmlOutputBuffer) -> c_int {
4058 if out.is_null() {
4059 return -1;
4060 }
4061 crate::xml::io::output_buffer_flush(out)
4062}
4063
4064/// Write to an output buffer.
4065///
4066/// # UPSTREAM-PARITY
4067///
4068/// ```c
4069/// int xmlOutputBufferWrite(xmlOutputBufferPtr out, int len, const char *data);
4070/// ```
4071#[no_mangle]
4072pub unsafe extern "C" fn xmlOutputBufferWrite(
4073 out: *mut _xmlOutputBuffer,
4074 len: c_int,
4075 data: *const c_char,
4076) -> c_int {
4077 // UPSTREAM-PARITY (xmlIO.c xmlOutputBufferWrite): a zero-length write
4078 // is a legal no-op returning 0 — PHP's W3C DOM-Parsing serializer
4079 // (ext/dom xml_serializer.c dom_xml_common_text_serialization) issues
4080 // xmlOutputBufferWrite(out, 0, p) when a text/attribute run starts with
4081 // a character that needs escaping; -1 there aborts the whole save with
4082 // "Could not save document".
4083 if out.is_null() || data.is_null() || len < 0 {
4084 return -1;
4085 }
4086 if len == 0 {
4087 return 0;
4088 }
4089 crate::xml::io::output_buffer_write(out, len, data)
4090}
4091
4092/// Write a string to an output buffer.
4093///
4094/// # UPSTREAM-PARITY
4095///
4096/// ```c
4097/// int xmlOutputBufferWriteString(xmlOutputBufferPtr out, const char *str);
4098/// ```
4099#[no_mangle]
4100pub unsafe extern "C" fn xmlOutputBufferWriteString(
4101 out: *mut _xmlOutputBuffer,
4102 str: *const c_char,
4103) -> c_int {
4104 if str.is_null() {
4105 return 0;
4106 }
4107 unsafe { xmlOutputBufferWrite(out, xmlStrlen(str as *const xmlChar), str) }
4108}
4109
4110/// Allocate an output buffer with no I/O target (upstream xmlAllocOutputBuffer).
4111///
4112/// # UPSTREAM-PARITY
4113///
4114/// ```c
4115/// xmlOutputBufferPtr xmlAllocOutputBuffer(xmlCharEncodingHandlerPtr encoder);
4116/// ```
4117#[no_mangle]
4118pub unsafe extern "C" fn xmlAllocOutputBuffer(encoder: *mut c_void) -> *mut _xmlOutputBuffer {
4119 crate::xml::io::output_buffer_create(
4120 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
4121 )
4122}
4123
4124/// Create an output buffer that writes into a `_xmlBuffer` (upstream
4125/// xmlOutputBufferCreateBuffer).
4126///
4127/// # UPSTREAM-PARITY
4128///
4129/// ```c
4130/// xmlOutputBufferPtr xmlOutputBufferCreateBuffer(xmlBufferPtr buffer,
4131/// xmlCharEncodingHandlerPtr encoder);
4132/// ```
4133///
4134/// # SAFETY
4135///
4136/// - `buffer` must be a valid `_xmlBuffer`.
4137#[no_mangle]
4138pub unsafe extern "C" fn xmlOutputBufferCreateBuffer(
4139 buffer: *mut crate::abi::structs::_xmlBuffer,
4140 encoder: *mut c_void,
4141) -> *mut _xmlOutputBuffer {
4142 crate::xml::io::output_buffer_create_buffer(
4143 buffer,
4144 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
4145 )
4146}
4147
4148/// Create an output buffer writing to a `FILE *` (upstream
4149/// xmlOutputBufferCreateFile): the FILE is the I/O context with a
4150/// write callback wrapping `fwrite` and a close callback wrapping `fflush`.
4151///
4152/// # SAFETY
4153///
4154/// - `file` must be a valid `FILE *` or NULL.
4155#[no_mangle]
4156pub unsafe extern "C" fn xmlOutputBufferCreateFile(
4157 file: *mut libc::FILE,
4158 encoder: *mut c_void,
4159) -> *mut _xmlOutputBuffer {
4160 crate::xml::io::output_buffer_create_file(
4161 file,
4162 encoder as *mut crate::abi::structs::_xmlCharEncodingHandler,
4163 )
4164}
4165
4166/// Get the current content of an output buffer (upstream xmlOutputBufferGetContent).
4167///
4168/// # SAFETY
4169///
4170/// - `out` must be a valid output buffer.
4171#[no_mangle]
4172pub unsafe extern "C" fn xmlOutputBufferGetContent(out: *mut _xmlOutputBuffer) -> *const c_char {
4173 crate::xml::io::output_buffer_get_content(out) as *const c_char
4174}
4175
4176/// Get the number of bytes currently in the output buffer (upstream
4177/// xmlOutputBufferGetSize: `size_t`, 0 on NULL/error — 11.1-Z.3 signature
4178/// court: the pre-Z.3 candidate returned `int` with -1 on error).
4179///
4180/// # SAFETY
4181///
4182/// - `out` must be a valid output buffer.
4183#[no_mangle]
4184pub unsafe extern "C" fn xmlOutputBufferGetSize(out: *mut _xmlOutputBuffer) -> usize {
4185 crate::xml::io::output_buffer_get_size(out)
4186}
4187
4188/// Write to an output buffer, escaping special characters with the given
4189/// escape function (upstream xmlOutputBufferWriteEscape).
4190///
4191/// # SAFETY
4192///
4193/// - `out` must be a valid output buffer; `str` a NUL-terminated string;
4194/// `escaping` a valid escape callback or NULL.
4195#[no_mangle]
4196pub unsafe extern "C" fn xmlOutputBufferWriteEscape(
4197 out: *mut _xmlOutputBuffer,
4198 str: *const xmlChar,
4199 escaping: Option<xmlCharEncodingOutputFunc>,
4200) -> c_int {
4201 if out.is_null() || str.is_null() {
4202 return -1;
4203 }
4204 crate::xml::io::output_buffer_write_escape(out, str, escaping)
4205}
4206
4207/// Set/query the default output-buffer filename callback
4208/// (upstream xmlOutputBufferCreateFilenameDefault).
4209///
4210/// Stored per-thread in the same cell the I/O layer consults
4211/// (`xml::globals::OUTPUT_CREATE_FILENAME`, upstream's
4212/// `xmlOutputBufferCreateFilenameValue`); the deprecated plain-global
4213/// accessor `__xmlOutputBufferCreateFilename` returns a pointer to it.
4214///
4215/// # SAFETY
4216///
4217/// - `func` must be a valid function pointer or NULL.
4218/// - The previous value (NULL when none was registered) is returned.
4219#[no_mangle]
4220pub unsafe extern "C" fn xmlOutputBufferCreateFilenameDefault(
4221 func: Option<
4222 unsafe extern "C" fn(
4223 *const c_char,
4224 *mut crate::abi::structs::_xmlCharEncodingHandler,
4225 c_int,
4226 ) -> *mut _xmlOutputBuffer,
4227 >,
4228) -> Option<
4229 unsafe extern "C" fn(
4230 *const c_char,
4231 *mut crate::abi::structs::_xmlCharEncodingHandler,
4232 c_int,
4233 ) -> *mut _xmlOutputBuffer,
4234> {
4235 // UPSTREAM-PARITY (xmlIO.c): set only when func is non-NULL, return the
4236 // previously registered value (NULL when none). Same per-thread slot the
4237 // thrDef variant and the I/O routing consult.
4238 let old = crate::xml::globals::get_output_buffer_create_filename_value();
4239 if func.is_some() {
4240 crate::xml::globals::set_output_buffer_create_filename_value(func);
4241 }
4242 old
4243}
4244
4245/// `__xmlOutputBufferCreateFilename` — accessor returning a pointer to the
4246/// default callback (upstream xmlIO.c).
4247#[no_mangle]
4248pub unsafe extern "C" fn __xmlOutputBufferCreateFilename() -> *mut Option<
4249 unsafe extern "C" fn(
4250 *const c_char,
4251 *mut crate::abi::structs::_xmlCharEncodingHandler,
4252 c_int,
4253 ) -> *mut _xmlOutputBuffer,
4254> {
4255 crate::xml::globals::output_create_filename_ptr()
4256}
4257
4258// ═══════════════════════════════════════════════════════════════════════════════
4259// 9. Dictionary
4260// ═══════════════════════════════════════════════════════════════════════════════
4261
4262/// Create a new dictionary.
4263///
4264/// # UPSTREAM-PARITY
4265///
4266/// ```c
4267/// xmlDictPtr xmlDictCreate(void);
4268/// ```
4269#[no_mangle]
4270pub extern "C" fn xmlDictCreate() -> *mut c_void {
4271 // The creator holds the base reference (Dict.ref_count = 1, upstream
4272 // ref_counter); xmlDictReference adds to it and xmlDictFree decrements,
4273 // freeing the dictionary when it reaches zero. The count lives in the
4274 // shared dict memory (R-000177: cross-DSO coherent).
4275 crate::xml::dictionary::dict_create() as *mut c_void
4276}
4277
4278/// Create a sub-dictionary.
4279///
4280/// # UPSTREAM-PARITY
4281///
4282/// ```c
4283/// xmlDictPtr xmlDictCreateSub(xmlDictPtr sub);
4284/// ```
4285#[no_mangle]
4286pub extern "C" fn xmlDictCreateSub(sub: *mut c_void) -> *mut c_void {
4287 // Base reference lives in the shared dict memory (Dict.ref_count).
4288 unsafe {
4289 crate::xml::dictionary::dict_create_sub(sub as *mut crate::xml::dictionary::Dict)
4290 as *mut c_void
4291 }
4292}
4293
4294/// Look up a string in the dictionary.
4295///
4296/// # UPSTREAM-PARITY
4297///
4298/// ```c
4299/// const xmlChar *xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len);
4300/// ```
4301///
4302/// Returns an interned string pointer (valid as long as the dictionary exists).
4303/// - If `len` < 0, `name` must be null-terminated.
4304/// - If `len` >= 0, exactly `len` bytes are used.
4305#[no_mangle]
4306pub unsafe extern "C" fn xmlDictLookup(
4307 dict: *mut c_void,
4308 name: *const xmlChar,
4309 len: c_int,
4310) -> *const xmlChar {
4311 unsafe {
4312 crate::xml::dictionary::dict_lookup(dict as *mut crate::xml::dictionary::Dict, name, len)
4313 }
4314}
4315
4316/// Check if a string exists in the dictionary.
4317///
4318/// # UPSTREAM-PARITY
4319///
4320/// ```c
4321/// const xmlChar *xmlDictExists(xmlDictPtr dict, const xmlChar *name, int len);
4322/// ```
4323#[no_mangle]
4324pub unsafe extern "C" fn xmlDictExists(
4325 dict: *mut c_void,
4326 name: *const xmlChar,
4327 len: c_int,
4328) -> *const xmlChar {
4329 unsafe {
4330 crate::xml::dictionary::dict_exists(dict as *mut crate::xml::dictionary::Dict, name, len)
4331 }
4332}
4333
4334/// Query dictionary size.
4335///
4336/// # UPSTREAM-PARITY
4337///
4338/// ```c
4339/// int xmlDictSize(const xmlDictPtr dict);
4340/// ```
4341#[no_mangle]
4342pub const extern "C" fn xmlDictSize(dict: *const c_void) -> c_int {
4343 {
4344 crate::xml::dictionary::dict_size(dict as *const crate::xml::dictionary::Dict)
4345 }
4346}
4347
4348/// Free a dictionary.
4349///
4350/// # UPSTREAM-PARITY
4351///
4352/// ```c
4353/// void xmlDictFree(xmlDictPtr dict);
4354/// ```
4355///
4356/// The reference counter added by `xmlDictReference` is honored: the
4357/// underlying dictionary is destroyed only when the last reference is
4358/// released (the base owner counts as one implicit reference).
4359#[no_mangle]
4360pub extern "C" fn xmlDictFree(dict: *mut c_void) {
4361 if dict.is_null() {
4362 return;
4363 }
4364 // The reference count lives IN the shared dict memory (R-000177): a
4365 // decrement through one DSO observes references made through every
4366 // other DSO, so a dict created by libxml2.so.16 is never freed early by
4367 // libxslt.so.1's teardown. The pre-fix per-DSO side table partitioned
4368 // the count and unconditionally freed unknown dicts.
4369 let d = dict as *mut crate::xml::dictionary::Dict;
4370 let prev = unsafe {
4371 (*d).ref_count
4372 .fetch_sub(1, core::sync::atomic::Ordering::Relaxed)
4373 };
4374 if prev == 1 {
4375 unsafe { crate::xml::dictionary::dict_free(dict as *mut crate::xml::dictionary::Dict) };
4376 }
4377}
4378
4379/// Set the dictionary size limit.
4380///
4381/// # UPSTREAM-PARITY
4382///
4383/// ```c
4384/// size_t xmlDictSetLimit(xmlDictPtr dict, size_t limit);
4385/// ```
4386#[no_mangle]
4387pub extern "C" fn xmlDictSetLimit(dict: *mut c_void, limit: usize) -> usize {
4388 {
4389 crate::xml::dictionary::dict_set_limit(dict as *mut crate::xml::dictionary::Dict, limit)
4390 }
4391}
4392
4393/// Get current dictionary usage.
4394///
4395/// # UPSTREAM-PARITY
4396///
4397/// ```c
4398/// size_t xmlDictGetUsage(const xmlDictPtr dict);
4399/// ```
4400#[no_mangle]
4401pub extern "C" fn xmlDictGetUsage(dict: *const c_void) -> usize {
4402 {
4403 crate::xml::dictionary::dict_get_usage(dict as *mut crate::xml::dictionary::Dict)
4404 }
4405}
4406
4407// ═══════════════════════════════════════════════════════════════════════════════
4408// 10. Hash Table
4409// ═══════════════════════════════════════════════════════════════════════════════
4410
4411/// Create a new hash table.
4412///
4413/// # UPSTREAM-PARITY
4414///
4415/// ```c
4416/// xmlHashTablePtr xmlHashCreate(int size);
4417/// ```
4418#[no_mangle]
4419pub extern "C" fn xmlHashCreate(size: c_int) -> *mut c_void {
4420 crate::xml::hash::hash_create(size) as *mut c_void
4421}
4422
4423/// Create a new hash table with a dictionary.
4424///
4425/// # UPSTREAM-PARITY
4426///
4427/// ```c
4428/// xmlHashTablePtr xmlHashCreateDict(int size, xmlDictPtr dict);
4429/// ```
4430#[no_mangle]
4431pub extern "C" fn xmlHashCreateDict(size: c_int, dict: *mut c_void) -> *mut c_void {
4432 crate::xml::hash::hash_create_dict(size, dict) as *mut c_void
4433}
4434
4435/// Free a hash table.
4436///
4437/// # UPSTREAM-PARITY
4438///
4439/// ```c
4440/// void xmlHashFree(xmlHashTablePtr table, xmlHashDeallocator f);
4441/// ```
4442#[no_mangle]
4443pub extern "C" fn xmlHashFree(
4444 table: *mut c_void,
4445 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4446) {
4447 unsafe { crate::xml::hash::hash_free(table as *mut crate::xml::hash::HashTable, f) }
4448}
4449
4450/// Add an entry to a hash table.
4451///
4452/// # UPSTREAM-PARITY
4453///
4454/// ```c
4455/// int xmlHashAddEntry(xmlHashTablePtr table, const xmlChar *name, void *userdata);
4456/// ```
4457#[no_mangle]
4458pub unsafe extern "C" fn xmlHashAddEntry(
4459 table: *mut c_void,
4460 name: *const xmlChar,
4461 userdata: *mut c_void,
4462) -> c_int {
4463 unsafe {
4464 crate::xml::hash::hash_add_entry(table as *mut crate::xml::hash::HashTable, name, userdata)
4465 }
4466}
4467
4468/// Add a 2-key entry.
4469///
4470/// # UPSTREAM-PARITY
4471///
4472/// ```c
4473/// int xmlHashAddEntry2(xmlHashTablePtr table, const xmlChar *name,
4474/// const xmlChar *name2, void *userdata);
4475/// ```
4476#[no_mangle]
4477pub unsafe extern "C" fn xmlHashAddEntry2(
4478 table: *mut c_void,
4479 name: *const xmlChar,
4480 name2: *const xmlChar,
4481 userdata: *mut c_void,
4482) -> c_int {
4483 unsafe {
4484 crate::xml::hash::hash_add_entry2(
4485 table as *mut crate::xml::hash::HashTable,
4486 name,
4487 name2,
4488 userdata,
4489 )
4490 }
4491}
4492
4493/// Add a 3-key entry.
4494///
4495/// # UPSTREAM-PARITY
4496///
4497/// ```c
4498/// int xmlHashAddEntry3(xmlHashTablePtr table, const xmlChar *name,
4499/// const xmlChar *name2, const xmlChar *name3, void *userdata);
4500/// ```
4501#[no_mangle]
4502pub unsafe extern "C" fn xmlHashAddEntry3(
4503 table: *mut c_void,
4504 name: *const xmlChar,
4505 name2: *const xmlChar,
4506 name3: *const xmlChar,
4507 userdata: *mut c_void,
4508) -> c_int {
4509 unsafe {
4510 crate::xml::hash::hash_add_entry3(
4511 table as *mut crate::xml::hash::HashTable,
4512 name,
4513 name2,
4514 name3,
4515 userdata,
4516 )
4517 }
4518}
4519
4520/// Update or add an entry.
4521///
4522/// # UPSTREAM-PARITY
4523///
4524/// ```c
4525/// int xmlHashUpdateEntry(xmlHashTablePtr table, const xmlChar *name,
4526/// void *userdata, xmlHashDeallocator f);
4527/// ```
4528#[no_mangle]
4529pub unsafe extern "C" fn xmlHashUpdateEntry(
4530 table: *mut c_void,
4531 name: *const xmlChar,
4532 userdata: *mut c_void,
4533 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4534) -> c_int {
4535 unsafe {
4536 crate::xml::hash::hash_update_entry(
4537 table as *mut crate::xml::hash::HashTable,
4538 name,
4539 userdata,
4540 f,
4541 )
4542 }
4543}
4544
4545/// Update or add a 2-key entry.
4546#[no_mangle]
4547pub unsafe extern "C" fn xmlHashUpdateEntry2(
4548 table: *mut c_void,
4549 name: *const xmlChar,
4550 name2: *const xmlChar,
4551 userdata: *mut c_void,
4552 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4553) -> c_int {
4554 unsafe {
4555 crate::xml::hash::hash_update_entry2(
4556 table as *mut crate::xml::hash::HashTable,
4557 name,
4558 name2,
4559 userdata,
4560 f,
4561 )
4562 }
4563}
4564
4565/// Update or add a 3-key entry.
4566#[no_mangle]
4567pub unsafe extern "C" fn xmlHashUpdateEntry3(
4568 table: *mut c_void,
4569 name: *const xmlChar,
4570 name2: *const xmlChar,
4571 name3: *const xmlChar,
4572 userdata: *mut c_void,
4573 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4574) -> c_int {
4575 unsafe {
4576 crate::xml::hash::hash_update_entry3(
4577 table as *mut crate::xml::hash::HashTable,
4578 name,
4579 name2,
4580 name3,
4581 userdata,
4582 f,
4583 )
4584 }
4585}
4586
4587/// Look up an entry.
4588///
4589/// # UPSTREAM-PARITY
4590///
4591/// ```c
4592/// void *xmlHashLookup(xmlHashTablePtr table, const xmlChar *name);
4593/// ```
4594#[no_mangle]
4595pub unsafe extern "C" fn xmlHashLookup(table: *mut c_void, name: *const xmlChar) -> *mut c_void {
4596 unsafe { crate::xml::hash::hash_lookup(table as *mut crate::xml::hash::HashTable, name) }
4597}
4598
4599/// Look up a 2-key entry.
4600#[no_mangle]
4601pub unsafe extern "C" fn xmlHashLookup2(
4602 table: *mut c_void,
4603 name: *const xmlChar,
4604 name2: *const xmlChar,
4605) -> *mut c_void {
4606 unsafe {
4607 crate::xml::hash::hash_lookup2(table as *mut crate::xml::hash::HashTable, name, name2)
4608 }
4609}
4610
4611/// Look up a 3-key entry.
4612#[no_mangle]
4613pub unsafe extern "C" fn xmlHashLookup3(
4614 table: *mut c_void,
4615 name: *const xmlChar,
4616 name2: *const xmlChar,
4617 name3: *const xmlChar,
4618) -> *mut c_void {
4619 unsafe {
4620 crate::xml::hash::hash_lookup3(
4621 table as *mut crate::xml::hash::HashTable,
4622 name,
4623 name2,
4624 name3,
4625 )
4626 }
4627}
4628
4629/// Get the size of a hash table.
4630///
4631/// # UPSTREAM-PARITY
4632///
4633/// ```c
4634/// int xmlHashSize(xmlHashTablePtr table);
4635/// ```
4636#[no_mangle]
4637pub extern "C" fn xmlHashSize(table: *mut c_void) -> c_int {
4638 crate::xml::hash::hash_size(table as *mut crate::xml::hash::HashTable)
4639}
4640
4641/// Remove an entry.
4642///
4643/// # UPSTREAM-PARITY
4644///
4645/// ```c
4646/// int xmlHashRemoveEntry(xmlHashTablePtr table, const xmlChar *name,
4647/// xmlHashDeallocator f);
4648/// ```
4649#[no_mangle]
4650pub unsafe extern "C" fn xmlHashRemoveEntry(
4651 table: *mut c_void,
4652 name: *const xmlChar,
4653 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4654) -> c_int {
4655 unsafe {
4656 crate::xml::hash::hash_remove_entry(table as *mut crate::xml::hash::HashTable, name, f)
4657 }
4658}
4659
4660/// Remove a 2-key entry.
4661#[no_mangle]
4662pub unsafe extern "C" fn xmlHashRemoveEntry2(
4663 table: *mut c_void,
4664 name: *const xmlChar,
4665 name2: *const xmlChar,
4666 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4667) -> c_int {
4668 unsafe {
4669 crate::xml::hash::hash_remove_entry2(
4670 table as *mut crate::xml::hash::HashTable,
4671 name,
4672 name2,
4673 f,
4674 )
4675 }
4676}
4677
4678/// Remove a 3-key entry.
4679#[no_mangle]
4680pub unsafe extern "C" fn xmlHashRemoveEntry3(
4681 table: *mut c_void,
4682 name: *const xmlChar,
4683 name2: *const xmlChar,
4684 name3: *const xmlChar,
4685 f: Option<unsafe extern "C" fn(*mut c_void, *mut xmlChar)>,
4686) -> c_int {
4687 unsafe {
4688 crate::xml::hash::hash_remove_entry3(
4689 table as *mut crate::xml::hash::HashTable,
4690 name,
4691 name2,
4692 name3,
4693 f,
4694 )
4695 }
4696}
4697
4698/// Scan a hash table with a scanner function.
4699///
4700/// # UPSTREAM-PARITY
4701///
4702/// ```c
4703/// void xmlHashScan(xmlHashTablePtr table, xmlHashScanner f, void *data);
4704/// ```
4705#[no_mangle]
4706pub extern "C" fn xmlHashScan(table: *mut c_void, f: Option<xmlHashScanner>, data: *mut c_void) {
4707 unsafe { crate::xml::hash::hash_scan(table as *mut crate::xml::hash::HashTable, f, data) }
4708}
4709
4710/// Scan a hash table with a full scanner function.
4711#[no_mangle]
4712pub extern "C" fn xmlHashScanFull(
4713 table: *mut c_void,
4714 f: Option<xmlHashScannerFull>,
4715 data: *mut c_void,
4716) {
4717 unsafe { crate::xml::hash::hash_scan_full(table as *mut crate::xml::hash::HashTable, f, data) }
4718}
4719
4720/// Copy a hash table.
4721///
4722/// # UPSTREAM-PARITY
4723///
4724/// ```c
4725/// xmlHashTablePtr xmlHashCopy(xmlHashTablePtr table, xmlHashCopier f);
4726/// ```
4727#[no_mangle]
4728pub extern "C" fn xmlHashCopy(
4729 table: *mut c_void,
4730 f: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar) -> *mut c_void>,
4731) -> *mut c_void {
4732 unsafe {
4733 crate::xml::hash::hash_copy(table as *mut crate::xml::hash::HashTable, f) as *mut c_void
4734 }
4735}
4736
4737// ═══════════════════════════════════════════════════════════════════════════════
4738// 11. List
4739// ═══════════════════════════════════════════════════════════════════════════════
4740
4741/// Create a new list.
4742///
4743/// # UPSTREAM-PARITY
4744///
4745/// ```c
4746/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
4747/// xmlListDataCompare compare);
4748/// ```
4749#[no_mangle]
4750pub extern "C" fn xmlListCreate(
4751 deallocator: Option<unsafe extern "C" fn(*mut c_void)>,
4752 compare: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
4753) -> *mut c_void {
4754 crate::xml::list::list_create(deallocator, compare) as *mut c_void
4755}
4756
4757/// Delete a list.
4758///
4759/// # UPSTREAM-PARITY
4760///
4761/// ```c
4762/// void xmlListDelete(xmlListPtr list);
4763/// ```
4764#[no_mangle]
4765pub extern "C" fn xmlListDelete(list: *mut c_void) {
4766 unsafe { crate::xml::list::list_delete(list as *mut crate::xml::list::List) }
4767}
4768
4769/// Search a list.
4770///
4771/// # UPSTREAM-PARITY
4772///
4773/// ```c
4774/// void *xmlListSearch(xmlListPtr list, void *data);
4775/// ```
4776#[no_mangle]
4777pub extern "C" fn xmlListSearch(list: *mut c_void, data: *mut c_void) -> *mut c_void {
4778 unsafe { crate::xml::list::list_search(list as *mut crate::xml::list::List, data) }
4779}
4780
4781/// Walk a list.
4782///
4783/// # UPSTREAM-PARITY
4784///
4785/// ```c
4786/// void xmlListWalk(xmlListPtr list, xmlListWalker walker, void *data);
4787/// ```
4788#[no_mangle]
4789pub extern "C" fn xmlListWalk(
4790 list: *mut c_void,
4791 walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
4792 data: *mut c_void,
4793) {
4794 unsafe { crate::xml::list::list_walk(list as *mut crate::xml::list::List, walker, data) }
4795}
4796
4797/// Push to back.
4798///
4799/// # UPSTREAM-PARITY
4800///
4801/// ```c
4802/// int xmlListPushBack(xmlListPtr list, void *data);
4803/// ```
4804#[no_mangle]
4805pub extern "C" fn xmlListPushBack(list: *mut c_void, data: *mut c_void) -> c_int {
4806 unsafe { crate::xml::list::list_push_back(list as *mut crate::xml::list::List, data) }
4807}
4808
4809/// Push to front.
4810///
4811/// # UPSTREAM-PARITY
4812///
4813/// ```c
4814/// int xmlListPushFront(xmlListPtr list, void *data);
4815/// ```
4816#[no_mangle]
4817pub extern "C" fn xmlListPushFront(list: *mut c_void, data: *mut c_void) -> c_int {
4818 unsafe { crate::xml::list::list_push_front(list as *mut crate::xml::list::List, data) }
4819}
4820
4821/// Pop from back.
4822#[no_mangle]
4823pub extern "C" fn xmlListPopBack(list: *mut c_void) {
4824 unsafe { crate::xml::list::list_pop_back(list as *mut crate::xml::list::List) }
4825}
4826
4827/// Pop from front.
4828#[no_mangle]
4829pub extern "C" fn xmlListPopFront(list: *mut c_void) {
4830 unsafe { crate::xml::list::list_pop_front(list as *mut crate::xml::list::List) }
4831}
4832
4833/// Insert into sorted list.
4834///
4835/// # UPSTREAM-PARITY
4836///
4837/// ```c
4838/// int xmlListInsert(xmlListPtr list, void *data);
4839/// ```
4840#[no_mangle]
4841pub extern "C" fn xmlListInsert(list: *mut c_void, data: *mut c_void) -> c_int {
4842 unsafe { crate::xml::list::list_insert(list as *mut crate::xml::list::List, data) }
4843}
4844
4845/// Append to list.
4846#[no_mangle]
4847pub extern "C" fn xmlListAppend(list: *mut c_void, data: *mut c_void) -> c_int {
4848 unsafe { crate::xml::list::list_append(list as *mut crate::xml::list::List, data) }
4849}
4850
4851/// Remove first matching element.
4852#[no_mangle]
4853pub extern "C" fn xmlListRemoveFirst(list: *mut c_void, data: *mut c_void) -> c_int {
4854 unsafe { crate::xml::list::list_remove_first(list as *mut crate::xml::list::List, data) }
4855}
4856
4857/// Remove last matching element.
4858#[no_mangle]
4859pub extern "C" fn xmlListRemoveLast(list: *mut c_void, data: *mut c_void) -> c_int {
4860 unsafe { crate::xml::list::list_remove_last(list as *mut crate::xml::list::List, data) }
4861}
4862
4863/// Remove all matching elements.
4864#[no_mangle]
4865pub extern "C" fn xmlListRemoveAll(list: *mut c_void, data: *mut c_void) -> c_int {
4866 unsafe { crate::xml::list::list_remove_all(list as *mut crate::xml::list::List, data) }
4867}
4868
4869/// Clear a list.
4870#[no_mangle]
4871pub extern "C" fn xmlListClear(list: *mut c_void) {
4872 unsafe { crate::xml::list::list_clear(list as *mut crate::xml::list::List) }
4873}
4874
4875/// Check if list is empty.
4876///
4877/// # UPSTREAM-PARITY
4878///
4879/// ```c
4880/// int xmlListEmpty(xmlListPtr list);
4881/// ```
4882#[no_mangle]
4883pub extern "C" fn xmlListEmpty(list: *mut c_void) -> c_int {
4884 crate::xml::list::list_empty(list as *mut crate::xml::list::List)
4885}
4886
4887/// Get front element.
4888///
4889/// # UPSTREAM-PARITY
4890///
4891/// ```c
4892/// void *xmlListFront(xmlListPtr list);
4893/// ```
4894#[no_mangle]
4895pub extern "C" fn xmlListFront(list: *mut c_void) -> *mut c_void {
4896 crate::xml::list::list_front(list as *mut crate::xml::list::List)
4897}
4898
4899/// Get back element.
4900///
4901/// # UPSTREAM-PARITY
4902///
4903/// ```c
4904/// void *xmlListBack(xmlListPtr list);
4905/// ```
4906#[no_mangle]
4907pub extern "C" fn xmlListBack(list: *mut c_void) -> *mut c_void {
4908 crate::xml::list::list_back(list as *mut crate::xml::list::List)
4909}
4910
4911/// Get list size.
4912///
4913/// # UPSTREAM-PARITY
4914///
4915/// ```c
4916/// int xmlListSize(xmlListPtr list);
4917/// ```
4918#[no_mangle]
4919pub extern "C" fn xmlListSize(list: *mut c_void) -> c_int {
4920 crate::xml::list::list_size(list as *mut crate::xml::list::List)
4921}
4922
4923/// Sort a list.
4924#[no_mangle]
4925pub extern "C" fn xmlListSort(list: *mut c_void) {
4926 unsafe { crate::xml::list::list_sort(list as *mut crate::xml::list::List) }
4927}
4928
4929/// Reverse a list.
4930#[no_mangle]
4931pub extern "C" fn xmlListReverse(list: *mut c_void) {
4932 unsafe { crate::xml::list::list_reverse(list as *mut crate::xml::list::List) }
4933}
4934
4935/// Reverse a list in-place.
4936#[no_mangle]
4937pub extern "C" fn xmlListReverseSplice(list: *mut c_void, list2: *mut c_void) {
4938 unsafe {
4939 crate::xml::list::list_reverse_splice(
4940 list as *mut crate::xml::list::List,
4941 list2 as *mut crate::xml::list::List,
4942 )
4943 }
4944}
4945
4946/// Merge two sorted lists.
4947#[no_mangle]
4948pub extern "C" fn xmlListMerge(list: *mut c_void, list2: *mut c_void) {
4949 unsafe {
4950 crate::xml::list::list_merge(
4951 list as *mut crate::xml::list::List,
4952 list2 as *mut crate::xml::list::List,
4953 )
4954 }
4955}
4956/// Return the last element of a list (upstream list.h).
4957///
4958/// # UPSTREAM-PARITY
4959///
4960/// ```c
4961/// void *xmlListEnd(xmlListPtr l);
4962/// ```
4963#[no_mangle]
4964pub unsafe extern "C" fn xmlListEnd(l: *mut c_void) -> *mut c_void {
4965 crate::xml::list::list_end(l as *mut crate::xml::list::List)
4966}
4967
4968/// Reverse-search a list (upstream list.h).
4969///
4970/// # UPSTREAM-PARITY
4971///
4972/// ```c
4973/// void *xmlListReverseSearch(xmlListPtr l, void *data);
4974/// ```
4975#[no_mangle]
4976pub unsafe extern "C" fn xmlListReverseSearch(l: *mut c_void, data: *mut c_void) -> *mut c_void {
4977 crate::xml::list::list_reverse_search(l as *mut crate::xml::list::List, data)
4978}
4979
4980/// Walk a list in reverse (upstream list.h).
4981///
4982/// # UPSTREAM-PARITY
4983///
4984/// ```c
4985/// void xmlListReverseWalk(xmlListPtr l, xmlListWalker walker, void *data);
4986/// ```
4987#[no_mangle]
4988pub unsafe extern "C" fn xmlListReverseWalk(
4989 l: *mut c_void,
4990 walker: Option<unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int>,
4991 data: *mut c_void,
4992) {
4993 crate::xml::list::list_reverse_walk(l as *mut crate::xml::list::List, walker, data)
4994}
4995
4996/// Duplicate a list (upstream list.h).
4997///
4998/// # UPSTREAM-PARITY
4999///
5000/// ```c
5001/// xmlListPtr xmlListDup(xmlListPtr l);
5002/// ```
5003#[no_mangle]
5004pub unsafe extern "C" fn xmlListDup(l: *mut c_void) -> *mut c_void {
5005 crate::xml::list::list_dup(l as *mut crate::xml::list::List) as *mut c_void
5006}
5007
5008/// Copy the contents of `old` into the existing list `cur` (upstream
5009/// list.h — R-000176, the candidate previously passed a copier callback).
5010///
5011/// # UPSTREAM-PARITY
5012///
5013/// ```c
5014/// int xmlListCopy(xmlListPtr cur, const xmlListPtr old);
5015/// ```
5016///
5017/// Returns 0 on success, 1 on error (upstream list.c).
5018#[no_mangle]
5019pub unsafe extern "C" fn xmlListCopy(cur: *mut c_void, old: *mut c_void) -> c_int {
5020 crate::xml::list::list_copy(
5021 cur as *mut crate::xml::list::List,
5022 old as *mut crate::xml::list::List,
5023 )
5024}
5025
5026/// Return the data of a link (upstream list.h).
5027///
5028/// # UPSTREAM-PARITY
5029///
5030/// ```c
5031/// void *xmlLinkGetData(xmlLinkPtr lk);
5032/// ```
5033#[no_mangle]
5034pub unsafe extern "C" fn xmlLinkGetData(lk: *mut c_void) -> *mut c_void {
5035 crate::xml::list::link_get_data(lk)
5036}
5037
5038// ═══════════════════════════════════════════════════════════════════════════════
5039// 12. Buffer
5040// ═══════════════════════════════════════════════════════════════════════════════
5041
5042/// Create a new buffer.
5043///
5044/// # UPSTREAM-PARITY
5045///
5046/// ```c
5047/// xmlBufferPtr xmlBufferCreate(void);
5048/// ```
5049#[no_mangle]
5050pub extern "C" fn xmlBufferCreate() -> *mut _xmlBuffer {
5051 // UPSTREAM-PARITY (buf.c 2.15): xmlBufferCreate = size 256, alloc scheme
5052 // XML_BUFFER_ALLOC_IO, content[0] = 0. (The pre-Phase-13 path used the
5053 // internal default-size DOUBLEIT helper, which diverged from the oracle
5054 // for negative/huge CreateSize arguments — HOSTILE-ABI finding.)
5055 unsafe { xml_buffer_create_upstream(256) }
5056}
5057
5058/// Create a new buffer of a given size.
5059///
5060/// # UPSTREAM-PARITY
5061///
5062/// ```c
5063/// xmlBufferPtr xmlBufferCreateSize(size_t size);
5064/// ```
5065///
5066/// Upstream buf.c 2.15: `size >= INT_MAX` returns NULL; `size == 0` returns
5067/// a buffer with a NULL content; otherwise the content is `size + 1` bytes
5068/// (the extra byte is the NUL terminator). The alloc scheme is
5069/// XML_BUFFER_ALLOC_IO.
5070#[no_mangle]
5071pub extern "C" fn xmlBufferCreateSize(size: usize) -> *mut _xmlBuffer {
5072 // UPSTREAM-PARITY (buf.c 2.15 xmlBufferCreateSize): sizes at or beyond
5073 // INT_MAX are rejected up front — a C caller passing -1 (huge size_t)
5074 // gets NULL exactly like the oracle (HOSTILE-ABI finding).
5075 if size >= c_int::MAX as usize {
5076 return ptr::null_mut();
5077 }
5078 unsafe { xml_buffer_create_upstream(size) }
5079}
5080
5081/// Upstream `xmlBufferCreateSize` body (buf.c 2.15): allocate the struct,
5082/// then `size + 1` content bytes when size != 0 (zero-size buffers have a
5083/// NULL content), alloc scheme XML_BUFFER_ALLOC_IO, content[0] = 0.
5084///
5085/// # Safety
5086///
5087/// - No caller-provided pointers; every allocation is checked for NULL and
5088/// the struct is freed on content-allocation failure.
5089unsafe fn xml_buffer_create_upstream(size: usize) -> *mut _xmlBuffer {
5090 let ret = unsafe { xmlMallocImpl(size_of::<_xmlBuffer>()) as *mut _xmlBuffer };
5091 if ret.is_null() {
5092 return ptr::null_mut();
5093 }
5094 let sz = if size != 0 { size + 1 } else { 0 };
5095 unsafe {
5096 if sz != 0 {
5097 let content = xmlMallocImpl(sz) as *mut xmlChar;
5098 if content.is_null() {
5099 xmlFreeImpl(ret as *mut c_void);
5100 return ptr::null_mut();
5101 }
5102 *content = 0;
5103 (*ret).content = content;
5104 (*ret).contentIO = content;
5105 } else {
5106 (*ret).content = ptr::null_mut();
5107 (*ret).contentIO = ptr::null_mut();
5108 }
5109 (*ret).use_ = 0;
5110 (*ret).size = sz as c_uint;
5111 (*ret).alloc = crate::abi::types::xmlBufferAllocationScheme::XML_BUFFER_ALLOC_IO as c_int;
5112 }
5113 ret
5114}
5115
5116/// Create a buffer from a static string.
5117///
5118/// # UPSTREAM-PARITY
5119///
5120/// ```c
5121/// xmlBufferPtr xmlBufferCreateStatic(void *mem, size_t size);
5122/// ```
5123#[no_mangle]
5124pub extern "C" fn xmlBufferCreateStatic(mem: *mut c_void, size: usize) -> *mut _xmlBuffer {
5125 if mem.is_null() || size == 0 {
5126 return ptr::null_mut();
5127 }
5128 crate::xml::io::buf_create_static(mem as *const xmlChar, size as c_int)
5129}
5130
5131/// Free a buffer.
5132///
5133/// # UPSTREAM-PARITY
5134///
5135/// ```c
5136/// void xmlBufferFree(xmlBufferPtr buf);
5137/// ```
5138#[no_mangle]
5139pub extern "C" fn xmlBufferFree(buf: *mut _xmlBuffer) {
5140 crate::xml::io::buf_free(buf)
5141}
5142
5143/// Empty a buffer.
5144///
5145/// # UPSTREAM-PARITY
5146///
5147/// ```c
5148/// void xmlBufferEmpty(xmlBufferPtr buf);
5149/// ```
5150#[no_mangle]
5151pub extern "C" fn xmlBufferEmpty(buf: *mut _xmlBuffer) {
5152 if buf.is_null() {
5153 return;
5154 }
5155 unsafe {
5156 if !(*buf).content.is_null() {
5157 *(*buf).content = 0;
5158 }
5159 (*buf).use_ = 0;
5160 }
5161}
5162
5163/// Get buffer content.
5164///
5165/// # UPSTREAM-PARITY
5166///
5167/// ```c
5168/// xmlChar *xmlBufferContent(const xmlBuffer *buf);
5169/// ```
5170#[no_mangle]
5171pub extern "C" fn xmlBufferContent(buf: *const _xmlBuffer) -> *mut xmlChar {
5172 crate::xml::io::buf_content(buf as *mut _xmlBuffer)
5173}
5174
5175/// Get buffer length.
5176///
5177/// # UPSTREAM-PARITY
5178///
5179/// ```c
5180/// int xmlBufferLength(const xmlBuffer *buf);
5181/// ```
5182#[no_mangle]
5183pub extern "C" fn xmlBufferLength(buf: *const _xmlBuffer) -> c_int {
5184 crate::xml::io::buf_length(buf as *mut _xmlBuffer)
5185}
5186
5187/// Write to a buffer.
5188///
5189/// # UPSTREAM-PARITY
5190///
5191/// ```c
5192/// int xmlBufferAdd(xmlBufferPtr buf, const xmlChar *str, int len);
5193/// ```
5194#[no_mangle]
5195pub unsafe extern "C" fn xmlBufferAdd(
5196 buf: *mut _xmlBuffer,
5197 str: *const xmlChar,
5198 len: c_int,
5199) -> c_int {
5200 crate::xml::io::buf_add(buf, str, len)
5201}
5202
5203/// Write to a buffer at a position.
5204///
5205/// # UPSTREAM-PARITY
5206///
5207/// ```c
5208/// int xmlBufferAddHead(xmlBufferPtr buf, const xmlChar *str, int len);
5209/// ```
5210#[no_mangle]
5211pub unsafe extern "C" fn xmlBufferAddHead(
5212 buf: *mut _xmlBuffer,
5213 str: *const xmlChar,
5214 len: c_int,
5215) -> c_int {
5216 crate::xml::io::buf_add_head(buf, str, len)
5217}
5218
5219/// Write a C string to a buffer.
5220///
5221/// # UPSTREAM-PARITY
5222///
5223/// ```c
5224/// int xmlBufferCat(xmlBufferPtr buf, const xmlChar *str);
5225/// ```
5226#[no_mangle]
5227pub unsafe extern "C" fn xmlBufferCat(buf: *mut _xmlBuffer, str: *const xmlChar) -> c_int {
5228 if str.is_null() {
5229 return -1;
5230 }
5231 let len = crate::xml::string::xml_strlen(str) as c_int;
5232 crate::xml::io::buf_add(buf, str, len)
5233}
5234
5235/// Set buffer allocation scheme.
5236///
5237/// # UPSTREAM-PARITY
5238///
5239/// ```c
5240/// void xmlBufferSetAllocationScheme(xmlBufferPtr buf,
5241/// xmlBufferAllocationScheme scheme);
5242/// ```
5243#[no_mangle]
5244pub extern "C" fn xmlBufferSetAllocationScheme(buf: *mut _xmlBuffer, scheme: c_int) {
5245 if buf.is_null() {
5246 return;
5247 }
5248 unsafe {
5249 (*buf).alloc = scheme;
5250 }
5251}
5252
5253/// Shrink buffer.
5254///
5255/// # UPSTREAM-PARITY
5256///
5257/// ```c
5258/// int xmlBufferShrink(xmlBufferPtr buf, unsigned int len);
5259/// ```
5260///
5261/// Oracle buf.c semantics (11.1-Z.3 alignment): -1 on NULL or `len` larger
5262/// than the buffer content, 0 for `len == 0`, else the removed byte count.
5263#[no_mangle]
5264pub extern "C" fn xmlBufferShrink(buf: *mut _xmlBuffer, len: c_uint) -> c_int {
5265 if buf.is_null() {
5266 return -1;
5267 }
5268 if len == 0 {
5269 return 0;
5270 }
5271 unsafe {
5272 let b = &mut *buf;
5273 if len > b.use_ {
5274 return -1;
5275 }
5276 let remaining = b.use_ - len;
5277 if remaining > 0 {
5278 core::ptr::copy(b.content.add(len as usize), b.content, remaining as usize);
5279 }
5280 *b.content.add(remaining as usize) = 0;
5281 b.use_ = remaining;
5282 }
5283 len as c_int
5284}
5285
5286/// Grow buffer.
5287///
5288/// # UPSTREAM-PARITY
5289///
5290/// ```c
5291/// int xmlBufferGrow(xmlBufferPtr buf, unsigned int len);
5292/// ```
5293#[no_mangle]
5294pub extern "C" fn xmlBufferGrow(buf: *mut _xmlBuffer, len: c_uint) -> c_int {
5295 if buf.is_null() || len == 0 {
5296 return 0;
5297 }
5298 let cur_use = unsafe { (*buf).use_ };
5299 let new_size = cur_use + len + 1;
5300 crate::xml::io::buf_grow(buf, new_size)
5301}
5302
5303/// Reserve buffer space.
5304///
5305/// # UPSTREAM-PARITY
5306///
5307/// ```c
5308/// int xmlBufferReserve(xmlBufferPtr buf, int len);
5309/// ```
5310#[no_mangle]
5311pub extern "C" fn xmlBufferReserve(buf: *mut _xmlBuffer, len: c_int) -> c_int {
5312 xmlBufferGrow(buf, len as c_uint)
5313}
5314
5315/// Detach buffer content.
5316///
5317/// # UPSTREAM-PARITY
5318///
5319/// ```c
5320/// xmlChar *xmlBufferDetach(xmlBufferPtr buf);
5321/// ```
5322#[no_mangle]
5323pub extern "C" fn xmlBufferDetach(buf: *mut _xmlBuffer) -> *mut xmlChar {
5324 if buf.is_null() {
5325 return ptr::null_mut();
5326 }
5327 unsafe {
5328 let content = (*buf).content;
5329 (*buf).content = ptr::null_mut();
5330 (*buf).use_ = 0;
5331 (*buf).size = 0;
5332 content
5333 }
5334}
5335
5336// ═══════════════════════════════════════════════════════════════════════════════
5337// 13. Encoding
5338// ═══════════════════════════════════════════════════════════════════════════════
5339
5340/// Get encoding from a name string.
5341///
5342/// # UPSTREAM-PARITY
5343///
5344/// ```c
5345/// xmlCharEncoding xmlGetCharEncoding(const char *name);
5346/// ```
5347#[no_mangle]
5348pub extern "C" fn xmlGetCharEncoding(name: *const c_char) -> c_int {
5349 if name.is_null() {
5350 return 0; // XML_CHAR_ENCODING_NONE
5351 }
5352 let name_bytes = unsafe {
5353 let len = libc::strlen(name);
5354 core::slice::from_raw_parts(name as *const u8, len)
5355 };
5356 crate::xml::encoding::encoding_from_name(name_bytes) as c_int
5357}
5358
5359/// Find an encoding handler.
5360///
5361/// # UPSTREAM-PARITY
5362///
5363/// ```c
5364/// xmlCharEncodingHandlerPtr xmlFindCharEncodingHandler(const char *name);
5365/// ```
5366#[no_mangle]
5367pub extern "C" fn xmlFindCharEncodingHandler(name: *const c_char) -> *mut c_void {
5368 if name.is_null() {
5369 return ptr::null_mut();
5370 }
5371 // Upstream hands the caller an OWNED handler it must release with
5372 // xmlCharEncCloseFunc (PHP's dom_document_encoding_write closes it after
5373 // every $dom->encoding= write) — except UTF-8, where the handler is static
5374 // and close is a no-op. Returning the persistent registry pointer directly
5375 // would let a non-UTF-8 caller's close free a still-registered handler (the
5376 // use-after-free behind DOMDocument::$encoding='UTF-16' crashing the next
5377 // registry lookup). See xmlFindCharEncodingHandler_owned.
5378 crate::xml::encoding::xmlFindCharEncodingHandler_owned(
5379 name as *const crate::abi::types::xmlChar,
5380 ) as *mut c_void
5381}
5382
5383/// Close an encoding handler.
5384///
5385/// # UPSTREAM-PARITY
5386///
5387/// ```c
5388/// int xmlCharEncCloseFunc(xmlCharEncodingHandlerPtr handler);
5389/// ```
5390#[no_mangle]
5391pub extern "C" fn xmlCharEncCloseFunc(handler: *mut c_void) -> c_int {
5392 if handler.is_null() {
5393 return -1;
5394 }
5395 // Upstream xmlCharEncCloseFunc (encoding.c): a handler flagged
5396 // XML_HANDLER_STATIC (UTF-8's default handler, or any static table
5397 // handler) is not owned by the caller and must not be released.
5398 unsafe {
5399 let h = handler as *mut crate::abi::structs::_xmlCharEncodingHandler;
5400 if (*h).flags & crate::xml::encoding::XML_HANDLER_STATIC != 0 {
5401 return 0;
5402 }
5403 if !(*h).name.is_null() {
5404 crate::abi::allocator::xmlFreeImpl((*h).name as *mut c_void);
5405 }
5406 // Run any conversion-context destructor before freeing the struct,
5407 // matching upstream's ordering (name, then ctxtDtor, then struct).
5408 if let Some(dtor) = (*h).ctxtDtor {
5409 if !(*h).inputCtxt.is_null() {
5410 dtor((*h).inputCtxt);
5411 }
5412 if !(*h).outputCtxt.is_null() {
5413 dtor((*h).outputCtxt);
5414 }
5415 }
5416 crate::abi::allocator::xmlFreeImpl(handler);
5417 }
5418 0
5419}
5420
5421/// Convert a block of ISO-8859-1 bytes to UTF-8 (upstream encoding.c
5422/// `xmlIsolat1ToUTF8`; R-000165 closure).
5423///
5424/// `*outlen`/`*inlen` are updated with the bytes produced/consumed; returns
5425/// the number of bytes written or an xmlCharEncError code.
5426///
5427/// # SAFETY
5428///
5429/// `out`/`in` must be valid buffers for `*outlen`/`*inlen` bytes.
5430#[no_mangle]
5431pub unsafe extern "C" fn xmlIsolat1ToUTF8(
5432 out: *mut u8,
5433 outlen: *mut c_int,
5434 input: *const u8,
5435 inlen: *mut c_int,
5436) -> c_int {
5437 // xmlCharEncError (encoding.h): SUCCESS 0, INTERNAL -1, SPACE -2.
5438 const XML_ENC_ERR_SPACE: c_int = -2;
5439 const XML_ENC_ERR_INTERNAL: c_int = -1;
5440 unsafe {
5441 if out.is_null() || input.is_null() || outlen.is_null() || inlen.is_null() {
5442 return XML_ENC_ERR_INTERNAL;
5443 }
5444 let outstart = out;
5445 let instart = input;
5446 let outend = out.add(*outlen as usize);
5447 let inend = input.add(*inlen as usize);
5448 let mut cur = input;
5449 let mut o = out;
5450 while cur < inend {
5451 let c = *cur;
5452 if c < 0x80 {
5453 if o >= outend {
5454 break;
5455 }
5456 *o = c;
5457 o = o.add(1);
5458 } else {
5459 if (outend as usize) - (o as usize) < 2 {
5460 break;
5461 }
5462 *o = (c >> 6) | 0xC0;
5463 *o.add(1) = (c & 0x3F) | 0x80;
5464 o = o.add(2);
5465 }
5466 cur = cur.add(1);
5467 }
5468 let mut ret = XML_ENC_ERR_SPACE;
5469 if cur == inend {
5470 ret = (o as usize - outstart as usize) as c_int;
5471 }
5472 *outlen = (o as usize - outstart as usize) as c_int;
5473 *inlen = (cur as usize - instart as usize) as c_int;
5474 ret
5475 }
5476}
5477
5478/// Convert a block of UTF-8 to ISO-8859-1 (upstream encoding.c
5479/// `xmlUTF8ToIsolat1`; R-000165 closure).
5480///
5481/// # SAFETY
5482///
5483/// `out`/`in` must be valid buffers for `*outlen`/`*inlen` bytes.
5484#[no_mangle]
5485pub unsafe extern "C" fn xmlUTF8ToIsolat1(
5486 out: *mut u8,
5487 outlen: *mut c_int,
5488 input: *const u8,
5489 inlen: *mut c_int,
5490) -> c_int {
5491 const XML_ENC_ERR_SPACE: c_int = -2;
5492 const XML_ENC_ERR_INTERNAL: c_int = -1;
5493 const XML_ENC_ERR_INPUT: c_int = -3;
5494 const XML_ENC_ERR_SUCCESS: c_int = 0;
5495 unsafe {
5496 if out.is_null() || outlen.is_null() || inlen.is_null() {
5497 return XML_ENC_ERR_INTERNAL;
5498 }
5499 if input.is_null() {
5500 *inlen = 0;
5501 *outlen = 0;
5502 return XML_ENC_ERR_SUCCESS;
5503 }
5504 let outstart = out;
5505 let instart = input;
5506 let outend = out.add(*outlen as usize);
5507 let inend = input.add(*inlen as usize);
5508 let mut cur = input;
5509 let mut o = out;
5510 let mut ret = XML_ENC_ERR_SPACE;
5511 while cur < inend {
5512 if o >= outend {
5513 break;
5514 }
5515 let c = *cur;
5516 if c < 0x80 {
5517 *o = c;
5518 o = o.add(1);
5519 } else if (0xC2..=0xC3).contains(&c) {
5520 if (inend as usize) - (cur as usize) < 2 {
5521 break;
5522 }
5523 cur = cur.add(1);
5524 *o = (c << 6) | (*cur & 0x3F);
5525 o = o.add(1);
5526 } else {
5527 ret = XML_ENC_ERR_INPUT;
5528 break;
5529 }
5530 cur = cur.add(1);
5531 }
5532 if ret != XML_ENC_ERR_INPUT {
5533 ret = (o as usize - outstart as usize) as c_int;
5534 }
5535 *outlen = (o as usize - outstart as usize) as c_int;
5536 *inlen = (cur as usize - instart as usize) as c_int;
5537 ret
5538 }
5539}
5540
5541/// Return the name of a character encoding (upstream encoding.h).
5542///
5543/// # UPSTREAM-PARITY
5544///
5545/// ```c
5546/// const char *xmlGetCharEncodingName(xmlCharEncoding enc);
5547/// ```
5548#[no_mangle]
5549pub extern "C" fn xmlGetCharEncodingName(enc: c_int) -> *const c_char {
5550 /* Values outside the local enum resolve against the upstream
5551 * defaultHandlers table (XML_CHAR_ENCODING_UTF16=23, HTML=24,
5552 * WINDOWS_1252=31); anything else is unknown (NULL). */
5553 if !(-1..=22).contains(&enc) {
5554 return match enc {
5555 23 => c"UTF-16".as_ptr(),
5556 24 => c"HTML".as_ptr(),
5557 31 => c"windows-1252".as_ptr(),
5558 _ => ptr::null(),
5559 };
5560 }
5561 let e: crate::abi::types::xmlCharEncoding = unsafe { core::mem::transmute(enc) };
5562 crate::xml::encoding::xmlGetCharEncodingName(e)
5563}
5564
5565/// Parse an encoding name into an xmlCharEncoding value (upstream encoding.h).
5566///
5567/// # UPSTREAM-PARITY
5568///
5569/// ```c
5570/// xmlCharEncoding xmlParseCharEncoding(const char *name);
5571/// ```
5572///
5573/// Returns the encoding value or XML_CHAR_ENCODING_ERROR (-1).
5574#[no_mangle]
5575pub extern "C" fn xmlParseCharEncoding(name: *const c_char) -> c_int {
5576 crate::xml::encoding::xmlParseCharEncoding(name)
5577}
5578
5579/// Add an encoding alias (upstream encoding.h).
5580///
5581/// # UPSTREAM-PARITY
5582///
5583/// ```c
5584/// int xmlAddEncodingAlias(const char *name, const char *alias);
5585/// ```
5586#[no_mangle]
5587pub extern "C" fn xmlAddEncodingAlias(name: *const c_char, alias: *const c_char) -> c_int {
5588 crate::xml::encoding::add_encoding_alias(name, alias)
5589}
5590
5591/// Delete an encoding alias (upstream encoding.h).
5592///
5593/// # UPSTREAM-PARITY
5594///
5595/// ```c
5596/// int xmlDelEncodingAlias(const char *alias);
5597/// ```
5598#[no_mangle]
5599pub extern "C" fn xmlDelEncodingAlias(alias: *const c_char) -> c_int {
5600 crate::xml::encoding::del_encoding_alias(alias)
5601}
5602
5603/// Look up an encoding alias (upstream encoding.h).
5604///
5605/// # UPSTREAM-PARITY
5606///
5607/// ```c
5608/// const char *xmlGetEncodingAlias(const char *alias);
5609/// ```
5610#[no_mangle]
5611pub extern "C" fn xmlGetEncodingAlias(alias: *const c_char) -> *const c_char {
5612 crate::xml::encoding::get_encoding_alias(alias)
5613}
5614
5615/// Clean up the encoding alias table (upstream encoding.h).
5616///
5617/// # UPSTREAM-PARITY
5618///
5619/// ```c
5620/// void xmlCleanupEncodingAliases(void);
5621/// ```
5622#[no_mangle]
5623pub extern "C" fn xmlCleanupEncodingAliases() {
5624 crate::xml::encoding::cleanup_encoding_aliases();
5625}
5626
5627/// Convert the input buffer using an encoding handler (upstream encoding.h).
5628///
5629/// # UPSTREAM-PARITY
5630///
5631/// ```c
5632/// int xmlCharEncInFunc(xmlCharEncodingHandler *handler,
5633/// xmlBufferPtr out, xmlBufferPtr in);
5634/// ```
5635#[no_mangle]
5636pub extern "C" fn xmlCharEncInFunc(
5637 handler: *mut c_void,
5638 out: *mut c_void,
5639 in_: *mut c_void,
5640) -> c_int {
5641 crate::xml::encoding::xmlCharEncInFunc(
5642 handler as *mut crate::abi::structs::_xmlCharEncodingHandler,
5643 out as *mut crate::abi::structs::_xmlBuffer,
5644 in_ as *mut crate::abi::structs::_xmlBuffer,
5645 )
5646}
5647
5648/// Convert the output buffer using an encoding handler (upstream encoding.h).
5649///
5650/// # UPSTREAM-PARITY
5651///
5652/// ```c
5653/// int xmlCharEncOutFunc(xmlCharEncodingHandler *handler,
5654/// xmlBufferPtr out, xmlBufferPtr in);
5655/// ```
5656#[no_mangle]
5657pub extern "C" fn xmlCharEncOutFunc(
5658 handler: *mut c_void,
5659 out: *mut c_void,
5660 in_: *mut c_void,
5661) -> c_int {
5662 crate::xml::encoding::xmlCharEncOutFunc(
5663 handler as *mut crate::abi::structs::_xmlCharEncodingHandler,
5664 out as *mut crate::abi::structs::_xmlBuffer,
5665 in_ as *mut crate::abi::structs::_xmlBuffer,
5666 )
5667}
5668
5669/// Create a new encoding handler (upstream encoding.h).
5670///
5671/// # UPSTREAM-PARITY
5672///
5673/// ```c
5674/// xmlCharEncodingHandlerPtr xmlNewCharEncodingHandler(
5675/// const char *name, xmlCharEncodingInputFunc input,
5676/// xmlCharEncodingOutputFunc output);
5677/// ```
5678#[no_mangle]
5679pub extern "C" fn xmlNewCharEncodingHandler(
5680 name: *const c_char,
5681 input: crate::abi::callbacks::xmlCharEncodingInputFunc,
5682 output: crate::abi::callbacks::xmlCharEncodingOutputFunc,
5683) -> *mut c_void {
5684 crate::xml::encoding::xmlNewCharEncodingHandler(name, input, output) as *mut c_void
5685}
5686
5687/// Initialize the built-in encoding handlers (upstream encoding.h).
5688///
5689/// # UPSTREAM-PARITY
5690///
5691/// ```c
5692/// void xmlInitCharEncodingHandlers(void);
5693/// ```
5694#[no_mangle]
5695pub extern "C" fn xmlInitCharEncodingHandlers() {
5696 crate::xml::encoding::xmlInitCharEncodingHandlers();
5697}
5698
5699/// Clean up the encoding handlers (upstream encoding.h).
5700///
5701/// # UPSTREAM-PARITY
5702///
5703/// ```c
5704/// void xmlCleanupCharEncodingHandlers(void);
5705/// ```
5706#[no_mangle]
5707pub extern "C" fn xmlCleanupCharEncodingHandlers() {
5708 crate::xml::encoding::xmlCleanupCharEncodingHandlers();
5709}
5710
5711/// Look up a built-in encoding handler by `xmlCharEncoding` value.
5712///
5713/// Returns an `xmlParserErrors` code; on success `*out` receives the static
5714/// handler (NULL for UTF-8, which needs no conversion).
5715///
5716/// # UPSTREAM-PARITY
5717///
5718/// ```c
5719/// xmlParserErrors xmlLookupCharEncodingHandler(xmlCharEncoding enc,
5720/// xmlCharEncodingHandler **out);
5721/// ```
5722#[no_mangle]
5723pub extern "C" fn xmlLookupCharEncodingHandler(enc: c_int, out: *mut *mut c_void) -> c_int {
5724 crate::xml::encoding::xmlLookupCharEncodingHandler(enc, out)
5725}
5726
5727/// Get the encoding handler for an `xmlCharEncoding` value (deprecated).
5728///
5729/// # UPSTREAM-PARITY
5730///
5731/// ```c
5732/// xmlCharEncodingHandler *xmlGetCharEncodingHandler(xmlCharEncoding enc);
5733/// ```
5734#[no_mangle]
5735pub extern "C" fn xmlGetCharEncodingHandler(enc: c_int) -> *mut c_void {
5736 crate::xml::encoding::xmlGetCharEncodingHandler(enc)
5737}
5738
5739/// Find or create an encoding handler by name for one conversion direction.
5740///
5741/// # UPSTREAM-PARITY
5742///
5743/// ```c
5744/// xmlParserErrors xmlOpenCharEncodingHandler(const char *name, int output,
5745/// xmlCharEncodingHandler **out);
5746/// ```
5747#[no_mangle]
5748pub extern "C" fn xmlOpenCharEncodingHandler(
5749 name: *const c_char,
5750 output: c_int,
5751 out: *mut *mut c_void,
5752) -> c_int {
5753 crate::xml::encoding::xmlOpenCharEncodingHandler(name, output, out)
5754}
5755
5756/// Find or create an encoding handler by name with flags and an optional
5757/// custom conversion implementation.
5758///
5759/// # UPSTREAM-PARITY
5760///
5761/// ```c
5762/// xmlParserErrors xmlCreateCharEncodingHandler(
5763/// const char *name, xmlCharEncFlags flags, xmlCharEncConvImpl impl,
5764/// void *implCtxt, xmlCharEncodingHandler **out);
5765/// ```
5766#[no_mangle]
5767pub extern "C" fn xmlCreateCharEncodingHandler(
5768 name: *const c_char,
5769 flags: c_int,
5770 impl_: Option<crate::abi::callbacks::xmlCharEncConvImpl>,
5771 implCtxt: *mut c_void,
5772 out: *mut *mut c_void,
5773) -> c_int {
5774 crate::xml::encoding::xmlCreateCharEncodingHandler(name, flags, impl_, implCtxt, out)
5775}
5776
5777/// Create an encoding handler backed by modern conversion callbacks.
5778///
5779/// # UPSTREAM-PARITY
5780///
5781/// ```c
5782/// xmlParserErrors xmlCharEncNewCustomHandler(
5783/// const char *name, xmlCharEncConvFunc input, xmlCharEncConvFunc output,
5784/// xmlCharEncConvCtxtDtor ctxtDtor, void *inputCtxt, void *outputCtxt,
5785/// xmlCharEncodingHandler **out);
5786/// ```
5787#[no_mangle]
5788pub extern "C" fn xmlCharEncNewCustomHandler(
5789 name: *const c_char,
5790 input: crate::abi::callbacks::xmlCharEncConvFunc,
5791 output: crate::abi::callbacks::xmlCharEncConvFunc,
5792 ctxtDtor: Option<crate::abi::callbacks::xmlCharEncConvCtxtDtor>,
5793 inputCtxt: *mut c_void,
5794 outputCtxt: *mut c_void,
5795 out: *mut *mut c_void,
5796) -> c_int {
5797 crate::xml::encoding::xmlCharEncNewCustomHandler(
5798 name, input, output, ctxtDtor, inputCtxt, outputCtxt, out,
5799 )
5800}
5801
5802/// Convert an input buffer's encoding.
5803///
5804/// # UPSTREAM-PARITY
5805///
5806/// ```c
5807/// int xmlCharEncInput(xmlParserInputBufferPtr input, int to);
5808/// ```
5809#[no_mangle]
5810pub unsafe extern "C" fn xmlCharEncInput(input: *mut _xmlParserInputBuffer, _to: c_int) -> c_int {
5811 if input.is_null() {
5812 return -1;
5813 }
5814 let handler = (*input).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
5815 if handler.is_null() {
5816 return -1;
5817 }
5818 let raw = (*input).raw as *mut crate::abi::structs::_xmlBuffer;
5819 let buf = (*input).buffer as *mut crate::abi::structs::_xmlBuffer;
5820 if raw.is_null() || buf.is_null() {
5821 return -1;
5822 }
5823 crate::xml::encoding::char_enc_in(handler, buf, raw)
5824}
5825
5826/// Convert an output buffer's encoding.
5827///
5828/// # UPSTREAM-PARITY
5829///
5830/// ```c
5831/// int xmlCharEncOutput(xmlOutputBufferPtr output, int to);
5832/// ```
5833#[no_mangle]
5834pub unsafe extern "C" fn xmlCharEncOutput(output: *mut _xmlOutputBuffer, _to: c_int) -> c_int {
5835 if output.is_null() {
5836 return -1;
5837 }
5838 let handler = (*output).encoder as *mut crate::abi::structs::_xmlCharEncodingHandler;
5839 if handler.is_null() {
5840 return -1;
5841 }
5842 let buf = (*output).buffer as *mut crate::abi::structs::_xmlBuffer;
5843 let conv = (*output).conv as *mut crate::abi::structs::_xmlBuffer;
5844 if buf.is_null() || conv.is_null() {
5845 return -1;
5846 }
5847 crate::xml::encoding::char_enc_out(handler, conv, buf)
5848}
5849
5850// ═══════════════════════════════════════════════════════════════════════════════
5851// URI
5852// ═══════════════════════════════════════════════════════════════════════════════
5853
5854/// Parse a URI string.
5855///
5856/// # UPSTREAM-PARITY
5857///
5858/// ```c
5859/// xmlURIPtr xmlParseURI(const char *str);
5860/// ```
5861#[no_mangle]
5862pub unsafe extern "C" fn xmlParseURI(str: *const c_char) -> *mut c_void {
5863 crate::xml::uri::xmlParseURI(str)
5864}
5865
5866/// Parse a URI string (raw version).
5867///
5868/// # UPSTREAM-PARITY
5869///
5870/// ```c
5871/// xmlURIPtr xmlParseURIRaw(const char *str, int raw);
5872/// ```
5873#[no_mangle]
5874pub unsafe extern "C" fn xmlParseURIRaw(str: *const c_char, raw: c_int) -> *mut c_void {
5875 let _ = raw;
5876 crate::xml::uri::xmlParseURI(str)
5877}
5878
5879/// Free a URI structure.
5880///
5881/// # UPSTREAM-PARITY
5882///
5883/// ```c
5884/// void xmlFreeURI(xmlURIPtr uri);
5885/// ```
5886#[no_mangle]
5887pub unsafe extern "C" fn xmlFreeURI(uri: *mut c_void) {
5888 crate::xml::uri::xmlFreeURI(uri)
5889}
5890
5891/// Create an empty URI.
5892///
5893/// # UPSTREAM-PARITY
5894///
5895/// ```c
5896/// xmlURIPtr xmlCreateURI(void);
5897/// ```
5898#[no_mangle]
5899pub extern "C" fn xmlCreateURI() -> *mut c_void {
5900 crate::xml::uri::xmlCreateURI()
5901}
5902
5903/// Save a URI structure to a string.
5904///
5905/// # UPSTREAM-PARITY
5906///
5907/// ```c
5908/// xmlChar *xmlSaveUri(xmlURIPtr uri);
5909/// ```
5910#[no_mangle]
5911pub unsafe extern "C" fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
5912 crate::xml::uri::xmlSaveUri(uri)
5913}
5914
5915/// Parse a URI string into an existing URI structure (upstream uri.h).
5916///
5917/// # UPSTREAM-PARITY
5918///
5919/// ```c
5920/// int xmlParseURIReference(xmlURIPtr uri, const char *str);
5921/// ```
5922///
5923/// Returns 0 on success, -1 on failure (the URI structure is left
5924/// untouched on failure).
5925///
5926/// # Safety
5927///
5928/// - `uri` must be a valid pointer from `xmlParseURI`/`xmlCreateURI`.
5929/// - `str` must be a valid null-terminated C string.
5930#[no_mangle]
5931pub unsafe extern "C" fn xmlParseURIReference(uri: *mut c_void, str: *const c_char) -> c_int {
5932 crate::xml::uri::xmlParseURIReference(uri, str)
5933}
5934
5935/// Normalize a URI path in place (upstream uri.h).
5936///
5937/// # UPSTREAM-PARITY
5938///
5939/// ```c
5940/// int xmlNormalizeURIPath(char *path);
5941/// ```
5942///
5943/// Returns 0 on success, -1 if the path is NULL, not absolute, or contains
5944/// `..` segments that climb above the root.
5945///
5946/// # Safety
5947///
5948/// `path` must be a valid writable null-terminated C string buffer.
5949#[no_mangle]
5950pub unsafe extern "C" fn xmlNormalizeURIPath(path: *mut c_char) -> c_int {
5951 crate::xml::uri::xmlNormalizeURIPath(path)
5952}
5953
5954/// Escape a URI string.
5955///
5956/// # UPSTREAM-PARITY
5957///
5958/// ```c
5959/// xmlChar *xmlURIEscapeStr(const xmlChar *str, const xmlChar *list);
5960/// ```
5961#[no_mangle]
5962pub unsafe extern "C" fn xmlURIEscapeStr(
5963 str: *const xmlChar,
5964 list: *const xmlChar,
5965) -> *mut xmlChar {
5966 crate::xml::uri::xmlURIEscapeStr(str, list)
5967}
5968
5969/// Unescape a URI string.
5970///
5971/// # UPSTREAM-PARITY
5972///
5973/// ```c
5974/// char *xmlURIUnescapeString(const char *str, int len, char *target);
5975/// ```
5976#[no_mangle]
5977pub unsafe extern "C" fn xmlURIUnescapeString(
5978 str: *const c_char,
5979 len: c_int,
5980 target: *mut c_char,
5981) -> *mut c_char {
5982 crate::xml::uri::xmlURIUnescapeString(str, len, target)
5983}
5984
5985// ═══════════════════════════════════════════════════════════════════════════════
5986// 14. XPath
5987// ═══════════════════════════════════════════════════════════════════════════════
5988
5989// ── Helper functions ────────────────────────────────────────────────────
5990
5991/// Convert an internal `XPathValue` to a C ABI `_xmlXPathObject`.
5992///
5993/// The returned pointer is heap-allocated via `xmlMallocZero` and must be
5994/// freed with `xmlXPathFreeObject`.
5995///
5996/// # Safety
5997///
5998/// Must be called from a context where `xmlMalloc` is safe to call.
5999unsafe fn xpath_to_object(val: XPathValue) -> *mut _xmlXPathObject {
6000 let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
6001 if obj.is_null() {
6002 return ptr::null_mut();
6003 }
6004 match val {
6005 XPathValue::NodeSet(ns) => {
6006 (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
6007 (*obj).nodesetval = ns.to_raw() as *mut c_void;
6008 }
6009 XPathValue::Boolean(b) => {
6010 (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
6011 (*obj).boolval = if b { 1 } else { 0 };
6012 }
6013 XPathValue::Number(n) => {
6014 (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
6015 (*obj).floatval = n;
6016 }
6017 XPathValue::String(s) => {
6018 (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
6019 let bytes = s.as_bytes();
6020 let len = bytes.len();
6021 let buf = xmlMallocImpl(len + 1) as *mut xmlChar;
6022 if !buf.is_null() {
6023 ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
6024 *buf.add(len) = 0; // null terminator
6025 }
6026 (*obj).stringval = buf;
6027 }
6028 }
6029 obj
6030}
6031
6032/// Extract an internal `XPathValue` from a C ABI `_xmlXPathObject`.
6033///
6034/// # Safety
6035///
6036/// `obj` must be a valid, non-null pointer to a properly initialised
6037/// `_xmlXPathObject`.
6038unsafe fn object_to_xpathvalue(obj: *mut _xmlXPathObject) -> XPathValue {
6039 let typ = (*obj).type_;
6040 if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6041 let ns_ptr = (*obj).nodesetval as *mut _xmlNodeSet;
6042 if ns_ptr.is_null() {
6043 return XPathValue::NodeSet(NodeSet::new());
6044 }
6045 let node_nr = (*ns_ptr).nodeNr;
6046 let node_tab = (*ns_ptr).nodeTab;
6047 let mut ns = NodeSet::new();
6048 if !node_tab.is_null() {
6049 for i in 0..node_nr as isize {
6050 let node = *node_tab.add(i as usize);
6051 ns.push(node);
6052 }
6053 }
6054 XPathValue::NodeSet(ns)
6055 } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
6056 XPathValue::Boolean((*obj).boolval != 0)
6057 } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
6058 XPathValue::Number((*obj).floatval)
6059 } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
6060 let s_ptr = (*obj).stringval;
6061 if s_ptr.is_null() {
6062 XPathValue::String(String::new())
6063 } else {
6064 let s = CStr::from_ptr(s_ptr as *const c_char)
6065 .to_string_lossy()
6066 .into_owned();
6067 XPathValue::String(s)
6068 }
6069 } else if typ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int {
6070 // A result tree fragment: node-set containing the fragment's
6071 // document node (matching how global RTF variables are bound), so
6072 // local RTF variables stringify to their text and remain navigable
6073 // via exsl:node-set.
6074 let frag_doc = (*obj).nodesetval as *mut _xmlDoc;
6075 if frag_doc.is_null() {
6076 XPathValue::NodeSet(NodeSet::new())
6077 } else {
6078 let mut ns = NodeSet::new();
6079 ns.push(frag_doc as *mut _xmlNode);
6080 XPathValue::NodeSet(ns)
6081 }
6082 } else {
6083 // Undefined / unknown type — return boolean false as a safe default.
6084 XPathValue::Boolean(false)
6085 }
6086}
6087
6088/// Public wrapper for `xpath_to_object` (used by the XPath export bridge).
6089///
6090/// # Safety
6091///
6092/// - `val` is consumed and converted into a heap-allocated `_xmlXPathObject`.
6093pub unsafe fn xpath_to_object_pub(val: XPathValue) -> *mut _xmlXPathObject {
6094 xpath_to_object(val)
6095}
6096
6097/// Public wrapper for `object_to_xpathvalue` (used by the XSLT engine).
6098///
6099/// # Safety
6100///
6101/// `obj` must be a valid, non-null pointer to a properly initialised
6102/// `_xmlXPathObject`.
6103pub unsafe fn object_to_xpathvalue_pub(obj: *mut _xmlXPathObject) -> XPathValue {
6104 object_to_xpathvalue(obj)
6105}
6106
6107// ── Compiled expression registry ────────────────────────────────────────
6108//
6109// Compiled XPath expressions are opaque pointers returned by xmlXPathCompile.
6110// We store them in a global registry keyed by a monotonically increasing ID.
6111
6112static COMPILED_EXPRS: Lazy<Mutex<HashMap<u64, Box<CompiledExpr>>>> =
6113 Lazy::new(|| Mutex::new(HashMap::new()));
6114static NEXT_COMPILED_KEY: Lazy<Mutex<u64>> = Lazy::new(|| Mutex::new(1));
6115
6116/// Accessor for the compiled-expression registry (used by the XPath export
6117/// bridge for `xmlXPathCompiledEval` / `xmlXPathCompiledEvalToBoolean`).
6118pub(crate) fn xpath_compiled_registry() -> &'static Mutex<HashMap<u64, Box<CompiledExpr>>> {
6119 &COMPILED_EXPRS
6120}
6121
6122// ── C extension-function registry ──────────────────────────────────────
6123//
6124// C extension functions registered via xmlXPathRegisterFunc / RegisterFuncNS
6125// are stored here because the Rust XPathFunction signature is incompatible
6126// with the C xmlXPathFunction calling convention (the C function expects a
6127// parser context, not pre-evaluated argument slices). The registration is
6128// stored faithfully; invoking registered C functions from within the Rust
6129// evaluator requires a bridge that is not yet implemented.
6130
6131type CXPathFunc = unsafe extern "C" fn(*mut c_void, c_int);
6132
6133/// Wrapper around `*mut c_void` that implements `Send` + `Sync` so it can
6134/// be used as a key in a `Mutex`-protected global `HashMap`.
6135#[derive(Clone, Copy, PartialEq, Eq, Hash)]
6136struct SendSyncPtr(*mut c_void);
6137unsafe impl Send for SendSyncPtr {}
6138unsafe impl Sync for SendSyncPtr {}
6139
6140static C_FUNCTIONS: Lazy<Mutex<HashMap<(SendSyncPtr, String), CXPathFunc>>> =
6141 Lazy::new(|| Mutex::new(HashMap::new()));
6142
6143/// Look up a C-registered extension function for the context identified by
6144/// `extra` (the internal XPathContext pointer). Used by
6145/// `xmlXPathFunctionLookupNS`.
6146pub(crate) fn xpath_cfunc_lookup(extra: *mut c_void, qualified: &str) -> Option<CXPathFunc> {
6147 C_FUNCTIONS
6148 .lock()
6149 .get(&(SendSyncPtr(extra), qualified.to_string()))
6150 .copied()
6151}
6152
6153/// Drop every C extension-function registration belonging to the context
6154/// identified by `extra` (upstream `xmlXPathRegisteredFuncsCleanup`).
6155pub(crate) fn xpath_cfunc_cleanup(extra: *mut c_void) {
6156 C_FUNCTIONS.lock().retain(|(k, _), _| k.0 != extra);
6157}
6158
6159/// Build the Rust-side closure that bridges a C-registered XPath function
6160/// into the Rust evaluator (see `c_func_call_bridge`). Returns a
6161/// `BoxedXPathFunction` so the closure is coerced with the higher-ranked
6162/// signature the evaluator requires.
6163fn c_func_bridge_closure(c_ctxt: SendSyncPtr, qualified: String) -> BoxedXPathFunction {
6164 let (local_name, ns_uri) = split_qualified(&qualified);
6165 Box::new(move |_ctx: &mut XPathContext, args: &[XPathValue]| {
6166 let cc = c_ctxt;
6167 unsafe {
6168 c_func_call_bridge(
6169 cc.0 as *mut _xmlXPathContext,
6170 &qualified,
6171 &local_name,
6172 ns_uri.as_deref(),
6173 args,
6174 )
6175 }
6176 })
6177}
6178
6179/// Split a registered function key (`{uri}name` — the Clark notation used by
6180/// `xmlXPathRegisterFuncNS` — or a bare `name`) into the LOCAL function name
6181/// and the optional namespace URI. Upstream sets exactly these two on
6182/// `ctxt->context->function` / `functionURI` before invoking a registered C
6183/// function (xpath.c xmlXPathCompOpEval), and PHP's dom/xsl trampolines read
6184/// them back to dispatch to the registered PHP closure.
6185fn split_qualified(qualified: &str) -> (String, Option<String>) {
6186 if let Some(rest) = qualified.strip_prefix('{') {
6187 if let Some(end) = rest.find('}') {
6188 let uri = rest[..end].to_string();
6189 let local = rest[end + 1..].to_string();
6190 return (local, Some(uri));
6191 }
6192 }
6193 (qualified.to_string(), None)
6194}
6195
6196/// Call a C-ABI `xmlXPathFunction` through a synthesized
6197/// `xmlXPathParserContext`: push the evaluated arguments as XPath objects,
6198/// invoke the function, pop and convert the result — the upstream
6199/// `xmlXPathCompOpEval` function-call sequence (xpath.c).
6200///
6201/// # UPSTREAM-PARITY
6202///
6203/// xpath.c xmlXPathCompOpEval (XPATH_OP_FUNCTION) sets the in-context
6204/// function identity before the call and restores it after:
6205///
6206/// ```c
6207/// oldFunc = ctxt->context->function;
6208/// oldFuncURI = ctxt->context->functionURI;
6209/// ctxt->context->function = op->value4; /* local name */
6210/// ctxt->context->functionURI = op->cacheURI; /* resolved ns URI or NULL */
6211/// func(ctxt, op->value);
6212/// ctxt->context->function = oldFunc;
6213/// ctxt->context->functionURI = oldFuncURI;
6214/// ```
6215///
6216/// PHP registers ONE trampoline for every custom-namespace XPath function
6217/// and dispatches to the PHP closure by reading `ctxt->context->function` /
6218/// `functionURI`, so without these fields it dereferences garbage
6219/// (SP-14.3.6-dom O1: return_dom_node_from_xpath / registerPhpFunctionNS
6220/// segv).
6221///
6222/// # SAFETY
6223///
6224/// - `fnptr` must be a valid C callback (or None).
6225/// - `c_ctxt` must be the live C XPath context the callback belongs to.
6226/// - `name`/`ns_uri` must be the function's local name / namespace being
6227/// invoked, valid for the duration of the call.
6228pub(crate) unsafe fn call_c_xpath_function(
6229 fnptr: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
6230 c_ctxt: *mut _xmlXPathContext,
6231 name: &str,
6232 ns_uri: Option<&str>,
6233 args: &[XPathValue],
6234) -> Result<XPathValue, String> {
6235 let func = match fnptr {
6236 Some(f) => f,
6237 None => return Err("XPath: missing C function pointer".to_string()),
6238 };
6239 let pc = crate::xml::xpath::parser_context::new_parser_context(ptr::null(), c_ctxt);
6240 if pc.is_null() {
6241 return Err("XPath: parser-context allocation failure".to_string());
6242 }
6243 let mut push_ok = true;
6244 for v in args {
6245 let obj = xpath_to_object(v.clone());
6246 if obj.is_null() || crate::xml::xpath::parser_context::value_push(pc, obj).is_null() {
6247 push_ok = false;
6248 break;
6249 }
6250 }
6251 let result = if push_ok {
6252 // NUL-terminated buffers naming the invoked function, live for the
6253 // callback duration (upstream op->value4 / op->cacheURI).
6254 let mut name_nul: Vec<xmlChar> = name.as_bytes().to_vec();
6255 name_nul.push(0);
6256 let uri_nul: Option<Vec<xmlChar>> = ns_uri.map(|u| {
6257 let mut v = u.as_bytes().to_vec();
6258 v.push(0);
6259 v
6260 });
6261 let saved_function = (*c_ctxt).function;
6262 let saved_function_uri = (*c_ctxt).functionURI;
6263 (*c_ctxt).function = name_nul.as_ptr() as *const xmlChar;
6264 (*c_ctxt).functionURI = uri_nul
6265 .as_ref()
6266 .map_or(ptr::null(), |v| v.as_ptr() as *const xmlChar);
6267 // SAFETY: `func` is a valid C callback; the arguments are on the
6268 // parser-context value stack exactly as upstream would leave them
6269 // and the context names the invoked function.
6270 unsafe { func(pc as *mut c_void, args.len() as c_int) };
6271 (*c_ctxt).function = saved_function;
6272 (*c_ctxt).functionURI = saved_function_uri;
6273 let ret = crate::xml::xpath::parser_context::value_pop(pc);
6274 if ret.is_null() {
6275 Err("XPath: C function returned no value".to_string())
6276 } else {
6277 let v = object_to_xpathvalue(ret);
6278 // The popped object is heap-allocated; free it after converting.
6279 unsafe { xmlXPathFreeObject(ret) };
6280 Ok(v)
6281 }
6282 } else {
6283 Err("XPath: failed to push arguments to C function".to_string())
6284 };
6285 // Free any objects the C function left on the stack, then the context.
6286 unsafe {
6287 loop {
6288 let leftover = crate::xml::xpath::parser_context::value_pop(pc);
6289 if leftover.is_null() {
6290 break;
6291 }
6292 xmlXPathFreeObject(leftover);
6293 }
6294 crate::xml::xpath::parser_context::free_parser_context(pc);
6295 }
6296 result
6297}
6298
6299/// Invoke an XSLT extension / module function through the upstream
6300/// parser-context protocol, with an upstream-layout context.
6301///
6302/// Upstream libxslt stores the transform context in `xmlXPathContext.extra`
6303/// (XSLT_REGISTER_VARIABLE_LOOKUP, variables.h), and PHP's xsl callbacks
6304/// read `parser_ctxt->context->extra` DIRECTLY as the
6305/// `xsltTransformContextPtr` (xsltprocessor.c xsl_proxy_factory). The
6306/// candidate reserves `extra` for the internal Rust `XPathContext`, so this
6307/// bridge synthesises a shallow MIRROR of the C context (same doc/node/…,
6308/// `extra` = transform context) that lives only for the callback duration.
6309/// The real transform XPath context is left untouched; the function name
6310/// is set on the mirror (php's call_custom_ns reads `context->function` /
6311/// `functionURI`), and arguments are pushed in evaluation order (last arg
6312/// on top) exactly as upstream `xmlXPathCompOpEval` leaves them.
6313///
6314/// Returns `Err` when the callback produced no value.
6315///
6316/// # Safety
6317///
6318/// - `fnptr` must be a C `xmlXPathFunction`-compatible callback.
6319/// - `tctxt` / `xpath_ctxt` must be the live transform context pair.
6320/// - `xpath_ctxt` must be the transform context's own `xpathCtxt`.
6321pub(crate) unsafe fn call_xslt_ext_function(
6322 fnptr: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
6323 tctxt: *mut crate::abi::structs::_xsltTransformContext,
6324 xpath_ctxt: *mut _xmlXPathContext,
6325 name: &str,
6326 ns_uri: Option<&str>,
6327 args: &[XPathValue],
6328) -> Result<XPathValue, String> {
6329 let func = match fnptr {
6330 Some(f) => f,
6331 None => return Err("XPath: missing C function pointer".to_string()),
6332 };
6333 if tctxt.is_null() || xpath_ctxt.is_null() {
6334 return Err("XPath: null XSLT context in extension-function bridge".to_string());
6335 }
6336 // Mirror: shallow copy of the C-visible context with `extra` = tctxt
6337 // (upstream layout that php's proxy code dereferences).
6338 let mirror = libc::calloc(1, core::mem::size_of::<_xmlXPathContext>()) as *mut _xmlXPathContext;
6339 if mirror.is_null() {
6340 return Err("XPath: mirror-context allocation failure".to_string());
6341 }
6342 unsafe {
6343 libc::memcpy(
6344 mirror as *mut libc::c_void,
6345 xpath_ctxt as *const libc::c_void,
6346 core::mem::size_of::<_xmlXPathContext>(),
6347 );
6348 (*mirror).extra = tctxt as *mut c_void;
6349 let pc = crate::xml::xpath::parser_context::new_parser_context(ptr::null(), mirror);
6350 if pc.is_null() {
6351 libc::free(mirror as *mut libc::c_void);
6352 return Err("XPath: parser-context allocation failure".to_string());
6353 }
6354 let mut push_ok = true;
6355 for v in args {
6356 let obj = xpath_to_object(v.clone());
6357 if obj.is_null() || crate::xml::xpath::parser_context::value_push(pc, obj).is_null() {
6358 push_ok = false;
6359 break;
6360 }
6361 }
6362 let result = if push_ok {
6363 // NUL-terminated buffers naming the invoked function, live for
6364 // the callback duration (upstream op->value4 / op->cacheURI on
6365 // the eval context).
6366 let mut name_nul: Vec<xmlChar> = name.as_bytes().to_vec();
6367 name_nul.push(0);
6368 let uri_nul: Option<Vec<xmlChar>> = ns_uri.map(|u| {
6369 let mut v = u.as_bytes().to_vec();
6370 v.push(0);
6371 v
6372 });
6373 (*mirror).function = name_nul.as_ptr() as *const xmlChar;
6374 (*mirror).functionURI = uri_nul
6375 .as_ref()
6376 .map_or(ptr::null(), |v| v.as_ptr() as *const xmlChar);
6377 // SAFETY: `func` is a valid C callback; the arguments are on the
6378 // parser-context value stack exactly as upstream would leave them
6379 // and the context names the invoked function.
6380 func(pc as *mut c_void, args.len() as c_int);
6381 let ret = crate::xml::xpath::parser_context::value_pop(pc);
6382 if ret.is_null() {
6383 Err("XPath: C function returned no value".to_string())
6384 } else {
6385 let v = object_to_xpathvalue(ret);
6386 // The popped object is heap-allocated; free it after converting.
6387 xmlXPathFreeObject(ret);
6388 Ok(v)
6389 }
6390 } else {
6391 Err("XPath: failed to push arguments to C function".to_string())
6392 };
6393 // Free any objects the C function left on the stack, then the
6394 // parser context and the mirror.
6395 loop {
6396 let leftover = crate::xml::xpath::parser_context::value_pop(pc);
6397 if leftover.is_null() {
6398 break;
6399 }
6400 xmlXPathFreeObject(leftover);
6401 }
6402 crate::xml::xpath::parser_context::free_parser_context(pc);
6403 libc::free(mirror as *mut libc::c_void);
6404 result
6405 }
6406}
6407
6408/// Rust-side wrapper registered in the internal XPathContext when a C
6409/// extension function is registered (`xmlXPathRegisterFunc[NS]`). This is the
6410/// parser-context bridge: it synthesises the upstream `xmlXPathParserContext`
6411/// (value stack + context pointer), pushes the evaluated arguments as XPath
6412/// objects, invokes the C function, then pops and converts the result — the
6413/// upstream `xmlXPathCompOpEval` function-call sequence (xpath.c).
6414unsafe fn c_func_call_bridge(
6415 c_ctxt: *mut _xmlXPathContext,
6416 qualified: &str,
6417 name: &str,
6418 ns_uri: Option<&str>,
6419 args: &[XPathValue],
6420) -> Result<XPathValue, String> {
6421 if c_ctxt.is_null() {
6422 return Err("XPath: null context in C function bridge".to_string());
6423 }
6424 let func = xpath_cfunc_lookup((*c_ctxt).extra, qualified);
6425 if func.is_none() {
6426 return Err(format!("XPath: unknown C function '{}'", qualified));
6427 }
6428 unsafe { call_c_xpath_function(func, c_ctxt, name, ns_uri, args) }
6429}
6430
6431// ── Public API ─────────────────────────────────────────────────────────
6432
6433/// Create a new XPath context.
6434///
6435/// Allocates a `_xmlXPathContext` and an internal `XPathContext`, storing
6436/// the latter's pointer in the `extra` field.
6437///
6438/// # UPSTREAM-PARITY
6439///
6440/// ```c
6441/// xmlXPathContextPtr xmlXPathNewContext(xmlDocPtr doc);
6442/// ```
6443#[no_mangle]
6444pub unsafe extern "C" fn xmlXPathNewContext(doc: *mut _xmlDoc) -> *mut _xmlXPathContext {
6445 let ctxt = xmlMallocZero(size_of::<_xmlXPathContext>()) as *mut _xmlXPathContext;
6446 if ctxt.is_null() {
6447 return ptr::null_mut();
6448 }
6449
6450 // Initialise the C ABI context fields.
6451 (*ctxt).doc = doc;
6452 (*ctxt).node = ptr::null_mut();
6453 (*ctxt).contextSize = 1;
6454 (*ctxt).proximityPosition = 1;
6455
6456 // Create the internal XPathContext and store it in `extra`.
6457 let mut internal = Box::new(XPathContext::new(doc));
6458 // UPSTREAM-PARITY: the standard function library is implicitly available
6459 // in every context (upstream compiles it in; xmlXPathRegisterAllFunctions
6460 // is a no-op since 2.14.0). Core built-ins are served by the static
6461 // lookup_core_function table (Phase 16.5.9) — no per-context copy.
6462 internal.c_context = ctxt;
6463 (*ctxt).extra = Box::into_raw(internal) as *mut c_void;
6464
6465 ctxt
6466}
6467
6468/// Deallocator for `xmlXPathContext.nsHash` payloads (strdup'd namespace
6469/// URIs; upstream `xmlXPathFreeContext` / `xmlXPathRegisteredNsCleanup` pass
6470/// `xmlFree`).
6471pub(crate) unsafe extern "C" fn free_ns_uri_payload(payload: *mut c_void, _key: *mut xmlChar) {
6472 if !payload.is_null() {
6473 crate::abi::allocator::xmlFreeImpl(payload);
6474 }
6475}
6476
6477/// Free an XPath context.
6478///
6479/// # UPSTREAM-PARITY
6480///
6481/// ```c
6482/// void xmlXPathFreeContext(xmlXPathContextPtr ctxt);
6483/// ```
6484#[no_mangle]
6485pub unsafe extern "C" fn xmlXPathFreeContext(ctxt: *mut _xmlXPathContext) {
6486 if ctxt.is_null() {
6487 return;
6488 }
6489 // Drop the internal XPathContext.
6490 if !(*ctxt).extra.is_null() {
6491 let _ = Box::from_raw((*ctxt).extra as *mut XPathContext);
6492 (*ctxt).extra = ptr::null_mut();
6493 }
6494 // Free the registered-namespace hash (upstream xmlXPathFreeContext:
6495 // xmlHashFree(ctxt->nsHash, xmlFree) — the payloads are strdup'd URIs).
6496 if !(*ctxt).nsHash.is_null() {
6497 xmlHashFree((*ctxt).nsHash, Some(free_ns_uri_payload));
6498 (*ctxt).nsHash = ptr::null_mut();
6499 }
6500 // UPSTREAM-PARITY (xpath.c xmlXPathFreeContext): the lastError fields
6501 // are heap-owned by the context (raise_xpath_error strdups the
6502 // expression into str1 and later raises free the previous strings
6503 // before overwriting). Release them here or every failed evaluation
6504 // leaks (Phase 16 ASan fuzz finding: 1 B per error raise).
6505 crate::xml::globals::free_error_strings(&(*ctxt).lastError);
6506 // Free the C ABI context struct.
6507 xmlFreeImpl(ctxt as *mut c_void);
6508}
6509
6510/// Evaluate an XPath expression.
6511///
6512/// # UPSTREAM-PARITY
6513///
6514/// ```c
6515/// xmlXPathObjectPtr xmlXPathEvalExpression(const xmlChar *str,
6516/// xmlXPathContextPtr ctxt);
6517/// ```
6518#[no_mangle]
6519pub unsafe extern "C" fn xmlXPathEvalExpression(
6520 str_: *const xmlChar,
6521 ctxt: *mut _xmlXPathContext,
6522) -> *mut _xmlXPathObject {
6523 if str_.is_null() || ctxt.is_null() {
6524 return ptr::null_mut();
6525 }
6526 let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
6527 Ok(s) => s,
6528 Err(_) => return ptr::null_mut(),
6529 };
6530 let internal = (*ctxt).extra as *mut XPathContext;
6531 if internal.is_null() {
6532 return ptr::null_mut();
6533 }
6534 let internal = &mut *internal;
6535 // UPSTREAM-PARITY (xpath.c xmlXPathEvalExpression): the evaluation
6536 // context node and position come from the C context fields — consumers
6537 // (lxml XPathElementEvaluator) set xpathCtxt->node per evaluation — so
6538 // mirror them into the internal context before evaluating. The pre-fix
6539 // code never did, so relative paths ("a", "./a", "a/@i") evaluated
6540 // against a NULL context node and returned an empty node-set (Phase 14
6541 // lxml XPath court).
6542 internal.set_context_node((*ctxt).node);
6543 internal.context_position = (*ctxt).proximityPosition;
6544 internal.context_size = (*ctxt).contextSize;
6545 internal.proximity_position = (*ctxt).proximityPosition;
6546 // Mirror the C context's namespace array (upstream xmlXPathNsLookup
6547 // consults ctxt->namespaces/nsNr first). PHP's DOMXPath fills the array
6548 // with the CONTEXT NODE's in-scope namespaces before evaluating
6549 // (ext/dom xpath.c php_dom_get_in_scope_ns*), so prefixed tests resolve
6550 // without an explicit registerNamespace when the document declares them.
6551 crate::abi::exports_xml2::sync_xpath_context_namespaces(ctxt, internal);
6552 // Clear any stale error so a fresh evaluation either succeeds or records
6553 // its own failure message (the XSLT layer surfaces it verbatim).
6554 internal.clear_error();
6555
6556 match crate::xml::xpath::evaluate_str(expr_str, internal) {
6557 Some(val) => xpath_to_object(val),
6558 None => {
6559 // UPSTREAM-PARITY (xpath.c xmlXPathErrFmt): report the failure
6560 // through the C context (ctxt->lastError pre-fill + ctxt->error
6561 // handler with ctxt->userData) so consumers like lxml's
6562 // _receiveXPathError receive the specific message.
6563 raise_internal_xpath_error(ctxt, internal, expr_str);
6564 ptr::null_mut()
6565 }
6566 }
6567}
6568
6569/// Map an internal XPath error message to its upstream `xmlXPathError` code
6570/// (0-based, xpath.h) and raise it through the C context.
6571pub(crate) unsafe fn raise_internal_xpath_error(
6572 ctxt: *mut _xmlXPathContext,
6573 internal: &mut XPathContext,
6574 expr: &str,
6575) {
6576 let (xpath_code, message) = match internal.error.as_deref() {
6577 Some(m) if m.starts_with("Undefined namespace prefix") => (XPATH_UNDEF_PREFIX_ERROR, m),
6578 Some(m) if m.starts_with("Unregistered function") => (XPATH_UNKNOWN_FUNC_ERROR, m),
6579 Some(m) if m.starts_with("Undefined variable") => (XPATH_UNDEF_VARIABLE_ERROR, m),
6580 // UPSTREAM-PARITY (xpath.c XPATH_RECURSION_LIMIT_EXCEEDED — the
6581 // compile recursion budget, e.g. a 500th nested '(' group): the
6582 // oracle reports "XPath error : Recursion limit exceeded" (verified
6583 // against xmllint 2.15.3).
6584 Some(m) if m == "Recursion limit exceeded" => (XPATH_RECURSION_LIMIT_EXCEEDED, m),
6585 // UPSTREAM-PARITY (xpath.c XPATH_INVALID_TYPE — FilterExpr
6586 // '/'-paths and unions over non-node-sets, e.g. `1/b`): the oracle
6587 // reports "XPath error : Invalid type" (verified against xmllint
6588 // 2.15.3).
6589 Some(m) if m == "Invalid type" => (XPATH_INVALID_TYPE, m),
6590 Some(m) => (XPATH_EXPR_ERROR, m),
6591 None => {
6592 internal.set_error("Invalid expression");
6593 (XPATH_EXPR_ERROR, "Invalid expression")
6594 }
6595 };
6596 unsafe { raise_xpath_error(ctxt, xpath_code, message, expr) }
6597}
6598
6599/// Mirror the C-visible context's `namespaces[0..nsNr]` array into the
6600/// internal XPath context's prefix map (upstream xmlXPathNsLookup consults
6601/// the array before nsHash). PHP's DOMXPath fills the array with the context
6602/// node's in-scope namespaces (ext/dom xpath.c), which is how prefixed
6603/// location steps resolve against the document's declarations.
6604///
6605/// # Safety
6606///
6607/// - `ctxt` must be a valid `_xmlXPathContext`; `internal` its internal
6608/// Rust context (may be shared).
6609pub(crate) unsafe fn sync_xpath_context_namespaces(
6610 ctxt: *mut _xmlXPathContext,
6611 internal: &mut crate::xml::xpath::context::XPathContext,
6612) {
6613 if ctxt.is_null() {
6614 return;
6615 }
6616 unsafe {
6617 let tab = (*ctxt).namespaces;
6618 if tab.is_null() {
6619 return;
6620 }
6621 let count = (*ctxt).nsNr;
6622 if count <= 0 {
6623 return;
6624 }
6625 let mut i = 0;
6626 while i < count {
6627 let ns = *tab.add(i as usize);
6628 if !ns.is_null() && !(*ns).prefix.is_null() && !(*ns).href.is_null() {
6629 let prefix_len = libc::strlen((*ns).prefix as *const libc::c_char) as usize;
6630 let href_len = libc::strlen((*ns).href as *const libc::c_char) as usize;
6631 let prefix =
6632 String::from_utf8_lossy(core::slice::from_raw_parts((*ns).prefix, prefix_len))
6633 .into_owned();
6634 let href =
6635 String::from_utf8_lossy(core::slice::from_raw_parts((*ns).href, href_len))
6636 .into_owned();
6637 internal.register_namespace(&prefix, &href);
6638 }
6639 i += 1;
6640 }
6641 }
6642}
6643
6644/// UPSTREAM-PARITY (xpath.c `xmlXPathErrFmt` -> error.c `xmlVRaiseError`):
6645/// deliver an XPath compile/eval failure to the C context. Pre-fills
6646/// `ctxt->lastError` (domain/code/level, the expression as `str1`, `int1` =
6647/// 0) exactly like upstream's message-less pre-fill, then raises through the
6648/// shared streamed path: TLS-global storage + dispatch to `ctxt->error`
6649/// (with `ctxt->userData`), the global structured handler, or the generic
6650/// channel (`xmlFormatError` "XPath error : ..." fragments).
6651///
6652/// `code` is the 0-based `xmlXPathError` value (candidate `XPATH_*_ERROR`
6653/// constants); the delivered structured code is offset by
6654/// `XML_XPATH_EXPRESSION_OK` (1200) like upstream.
6655///
6656/// # SAFETY
6657///
6658/// - `ctxt` must be a valid `_xmlXPathContext`.
6659pub(crate) unsafe fn raise_xpath_error(
6660 ctxt: *mut _xmlXPathContext,
6661 code: c_int,
6662 message: &str,
6663 expr: &str,
6664) {
6665 unsafe {
6666 use crate::xml::errors::{raise_error_streamed, GenericDelivery};
6667 use crate::xml::globals;
6668
6669 // Upstream xmlXPathErr / xmlXPathErrFmt always format with a
6670 // trailing newline ("%s\n"); lxml's _LogEntry.message strips one.
6671 let msg_c = CString::new(format!("{}\n", message)).unwrap_or_default();
6672 let expr_c = CString::new(expr).unwrap_or_default();
6673 let xerr_code = code + XML_XPATH_EXPRESSION_OK;
6674
6675 // Upstream pre-fill of ctxt->lastError (xmlXPathErrFmt): domain,
6676 // code, level, str1 = strdup(base), int1 = cur - base. The message
6677 // stays NULL here; xmlVRaiseError writes it into the TLS global.
6678 globals::free_error_strings(&(*ctxt).lastError);
6679 let pre = _xmlError {
6680 domain: XML_FROM_XPATH,
6681 code: xerr_code,
6682 message: ptr::null_mut(),
6683 level: xmlErrorLevel::XML_ERR_ERROR as c_int,
6684 file: ptr::null_mut(),
6685 line: 0,
6686 str1: xmlMemStrdupImpl(expr_c.as_ptr()) as *mut c_char,
6687 str2: ptr::null_mut(),
6688 str3: ptr::null_mut(),
6689 int1: 0,
6690 int2: 0,
6691 ctxt: ctxt as *mut c_void,
6692 node: (*ctxt).debugNode as *mut c_void,
6693 };
6694 ptr::write(&mut (*ctxt).lastError, pre);
6695
6696 // Cross-DSO channel routing: the whole-archive facade layout embeds a
6697 // per-DSO copy of libxml2's TLS error slots, so PHP's
6698 // xmlSetGenericErrorFunc (registered in the core) is invisible to the
6699 // XSLT engine running inside the libxslt facade. Upstream consumers
6700 // register on the xsltGenericError channel for transform-time
6701 // messages (php's ext/xsl MINIT), and that static IS shared with the
6702 // engine — when a real (non-default) handler is installed there,
6703 // deliver the XPath diagnostic through it exactly like the XSLT error
6704 // channel does (xsltproc leaves the default installed and is
6705 // unaffected).
6706 let xslt_bound = {
6707 let extra = (*ctxt).extra;
6708 if extra.is_null() || !crate::xml::xpath::context::has_signature(extra) {
6709 false
6710 } else {
6711 let internal: *mut crate::xml::xpath::context::XPathContext =
6712 extra as *mut crate::xml::xpath::context::XPathContext;
6713 !(*internal).func_lookup_data.is_null()
6714 }
6715 };
6716 let xslt_global = crate::abi::data_globals::xsltGenericError;
6717 let xslt_default = crate::abi::data_globals::xslt_default_generic_error_func()
6718 .map_or(ptr::null(), |d| d as *const ());
6719 if xslt_bound {
6720 if let Some(g) = xslt_global {
6721 if g as *const () != xslt_default {
6722 // SAFETY: the channel is a variadic C callback
6723 // (upstream xmlGenericErrorFunc ABI); the xslt handler
6724 // special-cases the "%s" format (php xsl_libxslt_error
6725 // _handler).
6726 let hv: unsafe extern "C" fn(*mut c_void, *const c_char, ...) =
6727 core::mem::transmute(g);
6728 hv(
6729 crate::abi::data_globals::xsltGenericErrorContext,
6730 c"%s".as_ptr() as *const c_char,
6731 msg_c.as_ptr() as *const c_char,
6732 );
6733 return;
6734 }
6735 }
6736 }
6737
6738 // UPSTREAM-PARITY (error.c xmlVRaiseError channel selection): the
6739 // XPath raise passes the message VERBATIM to the generic channel —
6740 // `channel(data, "%s", to->message)` — EXCEPT when that channel IS
6741 // the default handler (xmlGenericErrorDefaultFunc / xmlParserError &co),
6742 // which routes through xmlFormatError's fragment stream and prefixes
6743 // "XPath error : " (verified against the 2.15.3 oracle: no-handler
6744 // C caller sees "XPath error : Invalid type"). PHP installs a custom
6745 // generic handler and must keep receiving the text alone
6746 // (ext/simplexml 008: "Invalid expression", not "XPath error :
6747 // Invalid expression"). The pre-fix delivery classified the INSTALLED
6748 // default func as a custom channel, so handler-less callers (xmllint,
6749 // plain C) saw the bare message instead of the formatted stream.
6750 let delivery = match globals::get_generic_error_func() {
6751 Some(f)
6752 if crate::abi::data_globals::default_generic_error_func()
6753 .map_or(false, |d| d as usize == f as usize) =>
6754 {
6755 GenericDelivery::Stream
6756 }
6757 Some(f) => GenericDelivery::Custom(f, globals::get_generic_error_ctx()),
6758 None => GenericDelivery::Stream,
6759 };
6760
6761 raise_error_streamed(
6762 ctxt as *mut c_void,
6763 XML_FROM_XPATH,
6764 xerr_code,
6765 xmlErrorLevel::XML_ERR_ERROR as c_int,
6766 ptr::null_mut(),
6767 0,
6768 0,
6769 expr_c.as_ptr(),
6770 ptr::null_mut(),
6771 ptr::null_mut(),
6772 0,
6773 msg_c.as_ptr(),
6774 None,
6775 None,
6776 delivery,
6777 None,
6778 );
6779 }
6780}
6781
6782/// Evaluate an XPath expression (simplified alias).
6783///
6784/// # UPSTREAM-PARITY
6785///
6786/// ```c
6787/// xmlXPathObjectPtr xmlXPathEval(const xmlChar *str, xmlXPathContextPtr ctxt);
6788/// ```
6789#[no_mangle]
6790pub unsafe extern "C" fn xmlXPathEval(
6791 str_: *const xmlChar,
6792 ctxt: *mut _xmlXPathContext,
6793) -> *mut _xmlXPathObject {
6794 xmlXPathEvalExpression(str_, ctxt)
6795}
6796
6797/// Free an XPath object.
6798///
6799/// Releases the internal members (string buffer or node-set) and then frees
6800/// the object struct itself.
6801///
6802/// # UPSTREAM-PARITY
6803///
6804/// ```c
6805/// void xmlXPathFreeObject(xmlXPathObjectPtr obj);
6806/// ```
6807#[no_mangle]
6808pub unsafe extern "C" fn xmlXPathFreeObject(obj: *mut _xmlXPathObject) {
6809 if obj.is_null() {
6810 return;
6811 }
6812 let typ = (*obj).type_;
6813 // Free string storage.
6814 if typ == xmlXPathObjectType::XPATH_STRING as c_int && !(*obj).stringval.is_null() {
6815 xmlFreeImpl((*obj).stringval as *mut c_void);
6816 (*obj).stringval = ptr::null_mut();
6817 }
6818 // Free node-set storage.
6819 if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6820 let ns = (*obj).nodesetval as *mut _xmlNodeSet;
6821 if !ns.is_null() {
6822 if !(*ns).nodeTab.is_null() {
6823 xmlFreeImpl((*ns).nodeTab as *mut c_void);
6824 }
6825 xmlFreeImpl(ns as *mut c_void);
6826 }
6827 (*obj).nodesetval = ptr::null_mut();
6828 }
6829 xmlFreeImpl(obj as *mut c_void);
6830}
6831
6832/// Copy an XPath object (deep copy).
6833///
6834/// # UPSTREAM-PARITY
6835///
6836/// ```c
6837/// xmlXPathObjectPtr xmlXPathObjectCopy(xmlXPathObjectPtr val);
6838/// ```
6839///
6840/// Oracle behavior: returns a newly allocated object with the same type
6841/// and value. Node-sets are copied element-by-element; strings are
6842/// duplicated; numbers and booleans are copied by value.
6843#[no_mangle]
6844pub unsafe extern "C" fn xmlXPathObjectCopy(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
6845 if val.is_null() {
6846 return ptr::null_mut();
6847 }
6848 let typ = (*val).type_;
6849 let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
6850 if obj.is_null() {
6851 return ptr::null_mut();
6852 }
6853 (*obj).type_ = typ;
6854 if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6855 let src_ns = (*val).nodesetval as *mut _xmlNodeSet;
6856 if !src_ns.is_null() {
6857 let nr = (*src_ns).nodeNr;
6858 let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
6859 if ns.is_null() {
6860 xmlFreeImpl(obj as *mut c_void);
6861 return ptr::null_mut();
6862 }
6863 (*ns).nodeNr = nr;
6864 (*ns).nodeMax = nr;
6865 if nr > 0 && !(*src_ns).nodeTab.is_null() {
6866 let tab = xmlMallocImpl((nr as usize) * core::mem::size_of::<*mut _xmlNode>())
6867 as *mut *mut _xmlNode;
6868 if tab.is_null() {
6869 xmlFreeImpl(ns as *mut c_void);
6870 xmlFreeImpl(obj as *mut c_void);
6871 return ptr::null_mut();
6872 }
6873 ptr::copy_nonoverlapping((*src_ns).nodeTab, tab, nr as usize);
6874 (*ns).nodeTab = tab;
6875 } else {
6876 (*ns).nodeTab = ptr::null_mut();
6877 }
6878 (*obj).nodesetval = ns as *mut c_void;
6879 }
6880 } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
6881 (*obj).boolval = (*val).boolval;
6882 } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
6883 (*obj).floatval = (*val).floatval;
6884 } else if typ == xmlXPathObjectType::XPATH_STRING as c_int {
6885 let src = (*val).stringval;
6886 if !src.is_null() {
6887 let len = libc::strlen(src as *const libc::c_char);
6888 let buf = xmlMallocImpl(len + 1) as *mut xmlChar;
6889 if !buf.is_null() {
6890 ptr::copy_nonoverlapping(src, buf, len);
6891 *buf.add(len) = 0;
6892 }
6893 (*obj).stringval = buf;
6894 }
6895 }
6896 obj
6897}
6898
6899/// Cast an XPath object to its string value.
6900///
6901/// Returns a newly allocated string (caller frees with `xmlFree`).
6902///
6903/// # UPSTREAM-PARITY
6904///
6905/// ```c
6906/// xmlChar *xmlXPathCastToString(xmlXPathObjectPtr val);
6907/// ```
6908#[no_mangle]
6909pub unsafe extern "C" fn xmlXPathCastToString(val: *mut _xmlXPathObject) -> *mut xmlChar {
6910 if val.is_null() {
6911 return ptr::null_mut();
6912 }
6913 let typ = (*val).type_;
6914 let mut result: Vec<u8> = Vec::new();
6915 if typ == xmlXPathObjectType::XPATH_STRING as c_int {
6916 if !(*val).stringval.is_null() {
6917 let len = libc::strlen((*val).stringval as *const libc::c_char);
6918 result.extend_from_slice(core::slice::from_raw_parts((*val).stringval, len));
6919 }
6920 } else if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
6921 // Number → string conversion per XPath 1.0 §4.2:
6922 // - NaN → "NaN"
6923 // - +0/-0 → "0"
6924 // - infinity → "Infinity" / "-Infinity"
6925 // - integer → decimal representation without exponent
6926 let n = (*val).floatval;
6927 result.extend_from_slice(xml_number_to_string(n).as_bytes());
6928 } else if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
6929 result.extend_from_slice(if (*val).boolval != 0 {
6930 b"true"
6931 } else {
6932 b"false"
6933 });
6934 } else if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
6935 // String value of a node-set is the string value of the first node
6936 // in document order (or empty if empty).
6937 let ns = (*val).nodesetval as *mut _xmlNodeSet;
6938 if !ns.is_null() && (*ns).nodeNr > 0 && !(*ns).nodeTab.is_null() {
6939 let node = *(*ns).nodeTab;
6940 if !node.is_null() {
6941 let content = crate::xml::tree::node_get_content(node);
6942 if !content.is_null() {
6943 let len = libc::strlen(content as *const libc::c_char);
6944 result.extend_from_slice(core::slice::from_raw_parts(content, len));
6945 xmlFreeImpl(content as *mut c_void);
6946 }
6947 }
6948 }
6949 }
6950 // Allocate the C string.
6951 let buf = xmlMallocImpl(result.len() + 1) as *mut xmlChar;
6952 if buf.is_null() {
6953 return ptr::null_mut();
6954 }
6955 if !result.is_empty() {
6956 ptr::copy_nonoverlapping(result.as_ptr(), buf, result.len());
6957 }
6958 *buf.add(result.len()) = 0;
6959 buf
6960}
6961
6962/// Convert an XPath number to its string representation (XPath 1.0 §4.2).
6963///
6964/// Canonical implementation lives in `crate::xml::xpath::types::number_to_string`
6965/// (a port of upstream `xmlXPathCastNumberToString` / `xmlXPathFormatNumber`,
6966/// R-000166); this ABI helper delegates so every number→string conversion
6967/// shares exactly one oracle-verified code path.
6968pub fn xml_number_to_string(n: f64) -> String {
6969 crate::xml::xpath::types::number_to_string(n)
6970}
6971
6972/// Port of upstream xpath.c `xmlXPathStringEvalNumber` (R-000166): see
6973/// `crate::xml::xpath::types::string_bytes_to_number` — the oracle
6974/// accumulates digits directly, caps the fraction at MAX_FRAC=20 digits
6975/// after any leading zeros, applies the exponent with `pow(10.0, exp)`
6976/// (underflowing to 0 below the smallest subnormal), accepts XML whitespace
6977/// around the number, and returns NaN for anything else — including a
6978/// leading '+'.
6979fn xpath_string_eval_number(bytes: &[u8]) -> f64 {
6980 crate::xml::xpath::types::string_bytes_to_number(bytes)
6981}
6982
6983/// Cast a C string to a number per XPath 1.0 §4.2 conversion rules.
6984///
6985/// # UPSTREAM-PARITY
6986///
6987/// ```c
6988/// double xmlXPathCastStringToNumber(const xmlChar *val);
6989/// ```
6990#[no_mangle]
6991pub unsafe extern "C" fn xmlXPathCastStringToNumber(val: *const xmlChar) -> f64 {
6992 if val.is_null() {
6993 return f64::NAN;
6994 }
6995 let len = libc::strlen(val as *const libc::c_char);
6996 let bytes = core::slice::from_raw_parts(val, len);
6997 xpath_string_eval_number(bytes)
6998}
6999
7000/// Compare two nodes in document order.
7001///
7002/// UPSTREAM-PARITY (xpath.c `xmlXPathCmpNodes`): returns **1** when
7003/// `node1` precedes `node2` in document order, **-1** when `node1` follows
7004/// `node2`, 0 for the same node, and -2 for NULL or cross-document
7005/// comparisons. The sign convention was verified against the system oracle
7006/// (libxml2 2.15.3): `xmlXPathCmpNodes(book1, book2)` returns 1.
7007///
7008/// # UPSTREAM-PARITY
7009///
7010/// ```c
7011/// int xmlXPathCmpNodes(xmlNodePtr node1, xmlNodePtr node2);
7012/// ```
7013#[no_mangle]
7014pub unsafe extern "C" fn xmlXPathCmpNodes(node1: *mut _xmlNode, node2: *mut _xmlNode) -> c_int {
7015 if node1.is_null() || node2.is_null() {
7016 return -2;
7017 }
7018 if node1 == node2 {
7019 return 0;
7020 }
7021 // Build ancestor chains.
7022 let mut chain1: Vec<*mut _xmlNode> = Vec::new();
7023 let mut chain2: Vec<*mut _xmlNode> = Vec::new();
7024 let mut n = node1;
7025 while !n.is_null() {
7026 chain1.push(n);
7027 n = (*n).parent;
7028 }
7029 let mut n = node2;
7030 while !n.is_null() {
7031 chain2.push(n);
7032 n = (*n).parent;
7033 }
7034 // Distinct documents (or entities) case.
7035 if chain1[chain1.len() - 1] != chain2[chain2.len() - 1] {
7036 return -2;
7037 }
7038 // Find the nearest common ancestor.
7039 let mut i = chain1.len();
7040 let mut j = chain2.len();
7041 while i > 0 && j > 0 && chain1[i - 1] == chain2[j - 1] {
7042 i -= 1;
7043 j -= 1;
7044 }
7045 // node1 is an ancestor of node2 -> node1 precedes it -> 1.
7046 if i == 0 {
7047 return 1;
7048 }
7049 // node2 is an ancestor of node1 -> node1 follows it -> -1.
7050 if j == 0 {
7051 return -1;
7052 }
7053 // Compare sibling order at the divergence point.
7054 let mut a = chain1[i - 1];
7055 let mut b = chain2[j - 1];
7056 // Climb to the same level.
7057 while !a.is_null() && !b.is_null() {
7058 let pa = (*a).parent;
7059 let pb = (*b).parent;
7060 if pa == pb {
7061 break;
7062 }
7063 a = pa;
7064 b = pb;
7065 }
7066 // Walk forward from the first child of the common parent.
7067 let parent = (*a).parent;
7068 let mut child = if parent.is_null() {
7069 ptr::null_mut()
7070 } else {
7071 (*parent).children
7072 };
7073 while !child.is_null() {
7074 if child == a {
7075 return 1; // a precedes b
7076 }
7077 if child == b {
7078 return -1; // b precedes a
7079 }
7080 child = (*child).next;
7081 }
7082 0
7083}
7084
7085/// Create a node-set from a range of an existing node-set.
7086///
7087/// # UPSTREAM-PARITY
7088///
7089/// ```c
7090/// xmlNodeSetPtr xmlXPathNodeSetCreate(xmlNodePtr val);
7091/// ```
7092///
7093/// With a null `val`, creates an empty node-set.
7094#[no_mangle]
7095pub unsafe extern "C" fn xmlXPathNodeSetCreate(val: *mut _xmlNode) -> *mut _xmlNodeSet {
7096 let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
7097 if ns.is_null() {
7098 return ptr::null_mut();
7099 }
7100 if val.is_null() {
7101 return ns;
7102 }
7103 let tab = xmlMallocImpl(core::mem::size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
7104 if tab.is_null() {
7105 xmlFreeImpl(ns as *mut c_void);
7106 return ptr::null_mut();
7107 }
7108 *tab = val;
7109 (*ns).nodeTab = tab;
7110 (*ns).nodeNr = 1;
7111 (*ns).nodeMax = 1;
7112 ns
7113}
7114
7115/// Free a node-set allocated by `xmlXPathNodeSetCreate` or a node-set
7116/// builder in this library.
7117///
7118/// Frees the node-set structure and its node table; the nodes themselves
7119/// are owned by their document and are not freed.
7120///
7121/// # UPSTREAM-PARITY
7122///
7123/// ```c
7124/// void xmlXPathFreeNodeSet(xmlNodeSetPtr ns);
7125/// ```
7126#[no_mangle]
7127pub unsafe extern "C" fn xmlXPathFreeNodeSet(ns: *mut _xmlNodeSet) {
7128 if ns.is_null() {
7129 return;
7130 }
7131 if !(*ns).nodeTab.is_null() {
7132 xmlFreeImpl((*ns).nodeTab as *mut c_void);
7133 (*ns).nodeTab = ptr::null_mut();
7134 }
7135 (*ns).nodeNr = 0;
7136 (*ns).nodeMax = 0;
7137 xmlFreeImpl(ns as *mut c_void);
7138}
7139
7140/// Compile `expr_str` and store it in the compiled-expression registry,
7141/// returning the opaque key (or the parse error without raising — the caller
7142/// decides delivery).
7143pub(crate) fn xpath_compile_store(
7144 expr_str: &str,
7145) -> Result<*mut c_void, crate::xml::xpath::parser::ParseError> {
7146 match crate::xml::xpath::compile_result(expr_str) {
7147 Ok(compiled) => {
7148 let mut map = COMPILED_EXPRS.lock();
7149 let mut counter = NEXT_COMPILED_KEY.lock();
7150 let key = *counter;
7151 *counter += 1;
7152 map.insert(key, Box::new(compiled));
7153 Ok(key as *mut c_void)
7154 }
7155 Err(e) => Err(e),
7156 }
7157}
7158
7159/// Compile an XPath expression.
7160///
7161/// # UPSTREAM-PARITY
7162///
7163/// ```c
7164/// xmlXPathCompExprPtr xmlXPathCompile(const xmlChar *str);
7165/// ```
7166#[no_mangle]
7167pub unsafe extern "C" fn xmlXPathCompile(str_: *const xmlChar) -> *mut c_void {
7168 if str_.is_null() {
7169 return ptr::null_mut();
7170 }
7171 let expr_str = match CStr::from_ptr(str_ as *const c_char).to_str() {
7172 Ok(s) => s,
7173 Err(_) => return ptr::null_mut(),
7174 };
7175
7176 match xpath_compile_store(expr_str) {
7177 Ok(key) => key,
7178 Err(e) => {
7179 // UPSTREAM-PARITY (xpath.c xmlXPathErrFmt): a failed compile
7180 // reports "XPath error : Invalid expression\n" plus the
7181 // expression and a caret at the error offset through the generic
7182 // channel (HOSTILE-FAILURE F3). The structured code is
7183 // 1200-based like upstream `code + XML_XPATH_EXPRESSION_OK`.
7184 let msg_cstr = std::ffi::CString::new("Invalid expression\n").unwrap_or_default();
7185 let expr_cstr = std::ffi::CString::new(expr_str).unwrap_or_default();
7186 let off = e.pos;
7187 let window = if off < 100 && off < expr_str.len() {
7188 Some((expr_str.as_bytes(), off))
7189 } else {
7190 None
7191 };
7192 unsafe {
7193 crate::xml::errors::raise_error_streamed(
7194 ptr::null_mut(),
7195 crate::abi::types::XML_FROM_XPATH,
7196 crate::abi::types::XPATH_EXPR_ERROR
7197 + crate::abi::types::XML_XPATH_EXPRESSION_OK,
7198 crate::abi::types::xmlErrorLevel::XML_ERR_ERROR as c_int,
7199 ptr::null(),
7200 0,
7201 0,
7202 expr_cstr.as_ptr(),
7203 ptr::null(),
7204 ptr::null(),
7205 off as c_int,
7206 msg_cstr.as_ptr(),
7207 window,
7208 None,
7209 crate::xml::errors::GenericDelivery::Stream,
7210 None,
7211 );
7212 }
7213 ptr::null_mut()
7214 }
7215 }
7216}
7217
7218/// Free a compiled XPath expression.
7219///
7220/// # UPSTREAM-PARITY
7221///
7222/// ```c
7223/// void xmlXPathFreeCompExpr(xmlXPathCompExprPtr comp);
7224/// ```
7225#[no_mangle]
7226pub unsafe extern "C" fn xmlXPathFreeCompExpr(comp: *mut c_void) {
7227 if comp.is_null() {
7228 return;
7229 }
7230 let mut map = COMPILED_EXPRS.lock();
7231 map.remove(&(comp as u64));
7232}
7233
7234/// Register an XPath namespace.
7235///
7236/// # UPSTREAM-PARITY
7237///
7238/// ```c
7239/// int xmlXPathRegisterNs(xmlXPathContextPtr ctxt,
7240/// const xmlChar *prefix, const xmlChar *ns_uri);
7241/// ```
7242#[no_mangle]
7243pub unsafe extern "C" fn xmlXPathRegisterNs(
7244 ctxt: *mut _xmlXPathContext,
7245 prefix: *const xmlChar,
7246 ns_uri: *const xmlChar,
7247) -> c_int {
7248 if ctxt.is_null() || prefix.is_null() || ns_uri.is_null() {
7249 return -1;
7250 }
7251 let internal = (*ctxt).extra as *mut XPathContext;
7252 if internal.is_null() {
7253 return -1;
7254 }
7255 let internal = &mut *internal;
7256
7257 let prefix_str = match CStr::from_ptr(prefix as *const c_char).to_str() {
7258 Ok(s) => s,
7259 Err(_) => return -1,
7260 };
7261 let uri_str = match CStr::from_ptr(ns_uri as *const c_char).to_str() {
7262 Ok(s) => s,
7263 Err(_) => return -1,
7264 };
7265
7266 internal.register_namespace(prefix_str, uri_str);
7267
7268 // UPSTREAM-PARITY (xpath.c xmlXPathRegisterNs): the C context's nsHash
7269 // is a REAL xmlHashTable keyed by prefix with a strdup'd URI payload —
7270 // C consumers (lxml registerExsltFunctions) call xmlHashScan and
7271 // xmlHashLookup on it, so it must be an xmlHashTable, not a Rust map.
7272 // (R-00019x: the pre-fix nsHash held a Box<HashMap<..>>; lxml's
7273 // xmlHashScan interpreted the HashMap as an xmlHashTable and crashed on
7274 // its internal layout.)
7275 if (*ctxt).nsHash.is_null() {
7276 (*ctxt).nsHash = xmlHashCreate(10);
7277 }
7278 if !(*ctxt).nsHash.is_null() {
7279 let uri_dup = crate::xml::string::xml_strdup(ns_uri);
7280 let rc = xmlHashAddEntry((*ctxt).nsHash, prefix, uri_dup as *mut c_void);
7281 if rc != 0 {
7282 // Duplicate prefix: upstream keeps the first mapping (and leaks
7283 // the new strdup'd URI); the candidate frees the unused copy.
7284 crate::abi::allocator::xmlFreeImpl(uri_dup as *mut c_void);
7285 }
7286 }
7287 0
7288}
7289
7290/// Register an XPath function.
7291///
7292/// The C function pointer is stored in a side table keyed by the context.
7293/// A Rust-side stub is registered in the internal context so that the Rust
7294/// evaluator is aware of the function; however, calling the C function
7295/// directly from the Rust evaluator is not yet supported.
7296///
7297/// # UPSTREAM-PARITY
7298///
7299/// ```c
7300/// int xmlXPathRegisterFunc(xmlXPathContextPtr ctxt,
7301/// const xmlChar *name, xmlXPathFunction f);
7302/// ```
7303#[no_mangle]
7304pub unsafe extern "C" fn xmlXPathRegisterFunc(
7305 ctxt: *mut _xmlXPathContext,
7306 name: *const xmlChar,
7307 f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
7308) -> c_int {
7309 if ctxt.is_null() || name.is_null() {
7310 return -1;
7311 }
7312 let internal = (*ctxt).extra as *mut XPathContext;
7313 if internal.is_null() {
7314 return -1;
7315 }
7316 let internal = &mut *internal;
7317
7318 let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
7319 Ok(s) => s,
7320 Err(_) => return -1,
7321 };
7322
7323 if let Some(func) = f {
7324 // Store the C function pointer in the side table.
7325 let key = (SendSyncPtr((*ctxt).extra), name_str.to_string());
7326 C_FUNCTIONS.lock().insert(key, func);
7327 // Register a Rust closure that bridges to the C function through a
7328 // synthesized xmlXPathParserContext (upstream function-call ABI).
7329 let c_ctxt = SendSyncPtr(ctxt as *mut c_void);
7330 let name_owned = name_str.to_string();
7331 internal.register_function(name_str, c_func_bridge_closure(c_ctxt, name_owned));
7332 }
7333 0
7334}
7335
7336/// Register an XPath function with namespace.
7337///
7338/// # UPSTREAM-PARITY
7339///
7340/// ```c
7341/// int xmlXPathRegisterFuncNS(xmlXPathContextPtr ctxt,
7342/// const xmlChar *name, const xmlChar *ns_uri,
7343/// xmlXPathFunction f);
7344/// ```
7345#[no_mangle]
7346pub unsafe extern "C" fn xmlXPathRegisterFuncNS(
7347 ctxt: *mut _xmlXPathContext,
7348 name: *const xmlChar,
7349 ns_uri: *const xmlChar,
7350 f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
7351) -> c_int {
7352 if ctxt.is_null() || name.is_null() {
7353 return -1;
7354 }
7355 let internal = (*ctxt).extra as *mut XPathContext;
7356 if internal.is_null() {
7357 return -1;
7358 }
7359 let internal = &mut *internal;
7360
7361 let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
7362 Ok(s) => s,
7363 Err(_) => return -1,
7364 };
7365 let ns_str = if ns_uri.is_null() {
7366 String::new()
7367 } else {
7368 match CStr::from_ptr(ns_uri as *const c_char).to_str() {
7369 Ok(s) => s.to_string(),
7370 Err(_) => return -1,
7371 }
7372 };
7373
7374 // Use "{ns}:" prefix as part of the key to keep functions unique.
7375 let qualified = if ns_str.is_empty() {
7376 name_str.to_string()
7377 } else {
7378 format!("{{{}}}{}", ns_str, name_str)
7379 };
7380
7381 if let Some(func) = f {
7382 let key = (SendSyncPtr((*ctxt).extra), qualified.clone());
7383 C_FUNCTIONS.lock().insert(key, func);
7384 let c_ctxt = SendSyncPtr(ctxt as *mut c_void);
7385 let qualified_owned = qualified.clone();
7386 internal.register_function(&qualified, c_func_bridge_closure(c_ctxt, qualified_owned));
7387 }
7388 0
7389}
7390
7391/// Register an XPath variable.
7392///
7393/// # UPSTREAM-PARITY
7394///
7395/// ```c
7396/// int xmlXPathRegisterVariable(xmlXPathContextPtr ctxt,
7397/// const xmlChar *name, xmlXPathObjectPtr value);
7398/// ```
7399#[no_mangle]
7400pub unsafe extern "C" fn xmlXPathRegisterVariable(
7401 ctxt: *mut _xmlXPathContext,
7402 name: *const xmlChar,
7403 value: *mut _xmlXPathObject,
7404) -> c_int {
7405 if ctxt.is_null() || name.is_null() || value.is_null() {
7406 return -1;
7407 }
7408 let internal = (*ctxt).extra as *mut XPathContext;
7409 if internal.is_null() {
7410 return -1;
7411 }
7412 let internal = &mut *internal;
7413
7414 let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
7415 Ok(s) => s,
7416 Err(_) => return -1,
7417 };
7418
7419 let xpath_val = object_to_xpathvalue(value);
7420 internal.register_variable(name_str, xpath_val);
7421 0
7422}
7423
7424/// Create an XPath object wrapping a single node in a node-set.
7425///
7426/// # UPSTREAM-PARITY
7427///
7428/// ```c
7429/// xmlXPathObjectPtr xmlXPathNewNodeSet(xmlNodePtr val);
7430/// ```
7431#[no_mangle]
7432pub unsafe extern "C" fn xmlXPathNewNodeSet(val: *mut _xmlNode) -> *mut _xmlXPathObject {
7433 let ns = if val.is_null() {
7434 NodeSet::new()
7435 } else {
7436 NodeSet::singleton(val)
7437 };
7438 xpath_to_object(XPathValue::NodeSet(ns))
7439}
7440
7441/// Create an XPath object from a C string value.
7442///
7443/// # UPSTREAM-PARITY
7444///
7445/// ```c
7446/// xmlXPathObjectPtr xmlXPathNewCString(const xmlChar *val);
7447/// ```
7448#[no_mangle]
7449pub unsafe extern "C" fn xmlXPathNewCString(val: *const xmlChar) -> *mut _xmlXPathObject {
7450 if val.is_null() {
7451 return xpath_to_object(XPathValue::String(String::new()));
7452 }
7453 let s = match CStr::from_ptr(val as *const c_char).to_str() {
7454 Ok(s) => s.to_string(),
7455 Err(_) => return ptr::null_mut(),
7456 };
7457 xpath_to_object(XPathValue::String(s))
7458}
7459
7460/// Create an XPath number object.
7461///
7462/// # UPSTREAM-PARITY
7463///
7464/// ```c
7465/// xmlXPathObjectPtr xmlXPathNewFloat(double val);
7466/// ```
7467#[no_mangle]
7468pub extern "C" fn xmlXPathNewFloat(val: f64) -> *mut _xmlXPathObject {
7469 unsafe { xpath_to_object(XPathValue::Number(val)) }
7470}
7471
7472/// Create an XPath boolean object.
7473///
7474/// # UPSTREAM-PARITY
7475///
7476/// ```c
7477/// xmlXPathObjectPtr xmlXPathNewBoolean(int val);
7478/// ```
7479#[no_mangle]
7480pub extern "C" fn xmlXPathNewBoolean(val: c_int) -> *mut _xmlXPathObject {
7481 unsafe { xpath_to_object(XPathValue::Boolean(val != 0)) }
7482}
7483
7484// ═══════════════════════════════════════════════════════════════════════════════
7485// 14.5. XPointer
7486// ═══════════════════════════════════════════════════════════════════════════════
7487
7488/// Evaluate an XPointer expression.
7489///
7490/// Delegates to the xpointer module.
7491///
7492/// # UPSTREAM-PARITY
7493///
7494/// ```c
7495/// xmlNodePtr xmlXPtrEval(const xmlChar *expr, xmlDocPtr doc);
7496/// ```
7497#[no_mangle]
7498pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
7499 crate::xml::xpointer::xmlXPtrEval(expr, doc)
7500}
7501
7502// ═══════════════════════════════════════════════════════════════════════════════
7503// 15. XInclude
7504// ═══════════════════════════════════════════════════════════════════════════════
7505
7506/// Process XInclude nodes in a document.
7507///
7508/// # UPSTREAM-PARITY
7509///
7510/// ```c
7511/// int xmlXIncludeProcess(xmlDocPtr doc);
7512/// ```
7513#[no_mangle]
7514pub unsafe extern "C" fn xmlXIncludeProcess(doc: *mut _xmlDoc) -> c_int {
7515 crate::xml::xinclude::xinclude_process(doc)
7516}
7517
7518/// Process XInclude nodes with flags.
7519///
7520/// # UPSTREAM-PARITY
7521///
7522/// ```c
7523/// int xmlXIncludeProcessFlags(xmlDocPtr doc, int flags);
7524/// ```
7525#[no_mangle]
7526pub unsafe extern "C" fn xmlXIncludeProcessFlags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
7527 crate::xml::xinclude::xinclude_process_flags(doc, flags)
7528}
7529
7530// ═══════════════════════════════════════════════════════════════════════════════
7531// 16. Catalog
7532// ═══════════════════════════════════════════════════════════════════════════════
7533
7534/// Load a catalog.
7535///
7536/// # UPSTREAM-PARITY
7537///
7538/// ```c
7539/// xmlCatalogPtr xmlCatalogLoad(const char *catalogs);
7540/// ```
7541#[no_mangle]
7542pub extern "C" fn xmlCatalogLoad(catalogs: *const c_char) -> *mut c_void {
7543 if catalogs.is_null() {
7544 return ptr::null_mut();
7545 }
7546 crate::xml::catalog::load_catalog(catalogs)
7547}
7548
7549/// Resolve a public ID.
7550///
7551/// # UPSTREAM-PARITY
7552///
7553/// ```c
7554/// xmlCharPtr xmlCatalogResolvePublic(const xmlChar *pubID);
7555/// ```
7556#[no_mangle]
7557pub unsafe extern "C" fn xmlCatalogResolvePublic(pubID: *const xmlChar) -> *mut xmlChar {
7558 if pubID.is_null() {
7559 return ptr::null_mut();
7560 }
7561 crate::xml::catalog::resolve_public(pubID)
7562}
7563
7564/// Resolve a system ID.
7565///
7566/// # UPSTREAM-PARITY
7567///
7568/// ```c
7569/// xmlCharPtr xmlCatalogResolveSystem(const xmlChar *sysID);
7570/// ```
7571#[no_mangle]
7572pub unsafe extern "C" fn xmlCatalogResolveSystem(sysID: *const xmlChar) -> *mut xmlChar {
7573 if sysID.is_null() {
7574 return ptr::null_mut();
7575 }
7576 crate::xml::catalog::resolve_system(sysID)
7577}
7578
7579/// Resolve a URI.
7580///
7581/// # UPSTREAM-PARITY
7582///
7583/// ```c
7584/// xmlCharPtr xmlCatalogResolveURI(const xmlChar *URI);
7585/// ```
7586#[no_mangle]
7587pub unsafe extern "C" fn xmlCatalogResolveURI(URI: *const xmlChar) -> *mut xmlChar {
7588 if URI.is_null() {
7589 return ptr::null_mut();
7590 }
7591 crate::xml::catalog::resolve_uri(URI)
7592}
7593
7594/// Set catalog defaults.
7595///
7596/// # UPSTREAM-PARITY
7597///
7598/// ```c
7599/// void xmlCatalogSetDefaults(xmlCatalogAllowValue allow);
7600/// ```
7601#[no_mangle]
7602pub extern "C" fn xmlCatalogSetDefaults(allow: c_int) {
7603 crate::xml::catalog::set_defaults(allow)
7604}
7605
7606/// Get catalog defaults.
7607///
7608/// # UPSTREAM-PARITY
7609///
7610/// ```c
7611/// xmlCatalogAllowValue xmlCatalogGetDefaults(void);
7612/// ```
7613#[no_mangle]
7614pub extern "C" fn xmlCatalogGetDefaults() -> c_int {
7615 crate::xml::catalog::get_defaults()
7616}
7617
7618/// Add a catalog.
7619///
7620/// # UPSTREAM-PARITY
7621///
7622/// ```c
7623/// int xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace);
7624/// ```
7625#[no_mangle]
7626pub unsafe extern "C" fn xmlCatalogAdd(
7627 type_: *const xmlChar,
7628 orig: *const xmlChar,
7629 replace: *const xmlChar,
7630) -> c_int {
7631 if type_.is_null() || orig.is_null() || replace.is_null() {
7632 return -1;
7633 }
7634 crate::xml::catalog::add(type_, orig, replace)
7635}
7636
7637/// Remove a catalog entry.
7638///
7639/// # UPSTREAM-PARITY
7640///
7641/// ```c
7642/// int xmlCatalogRemove(const xmlChar *value);
7643/// ```
7644#[no_mangle]
7645pub unsafe extern "C" fn xmlCatalogRemove(value: *const xmlChar) -> c_int {
7646 if value.is_null() {
7647 return 0;
7648 }
7649 crate::xml::catalog::remove(value)
7650}
7651
7652/// Dump the catalog in XML format to a FILE* (upstream catalog.h:
7653/// 1-argument form — R-000176, the candidate previously took a spurious
7654/// second `xmlCatalogPtr` argument).
7655///
7656/// # UPSTREAM-PARITY
7657///
7658/// ```c
7659/// void xmlCatalogDump(FILE *out);
7660/// ```
7661#[no_mangle]
7662pub unsafe extern "C" fn xmlCatalogDump(output: *mut c_void) {
7663 if output.is_null() {
7664 return;
7665 }
7666 let doc = crate::xml::catalog::dump_doc();
7667 if doc.is_null() {
7668 return;
7669 }
7670 let mut mem: *mut xmlChar = ptr::null_mut();
7671 let mut size: c_int = 0;
7672 crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
7673 if !mem.is_null() {
7674 libc::fwrite(
7675 mem as *const c_void,
7676 1,
7677 size as usize,
7678 output as *mut libc::FILE,
7679 );
7680 xmlFreeImpl(mem as *mut c_void);
7681 }
7682 crate::xml::tree::free_doc(doc);
7683}
7684
7685/// Save the catalog to a file (upstream `xmlCatalogSave`).
7686///
7687/// Returns 0 on success, -1 on failure.
7688///
7689/// # UPSTREAM-PARITY
7690///
7691/// ```c
7692/// int xmlCatalogSave(const char *filename);
7693/// ```
7694#[no_mangle]
7695pub unsafe extern "C" fn xmlCatalogSave(filename: *const c_char) -> c_int {
7696 if filename.is_null() {
7697 return -1;
7698 }
7699 let doc = crate::xml::catalog::dump_doc();
7700 if doc.is_null() {
7701 return -1;
7702 }
7703 let mut mem: *mut xmlChar = ptr::null_mut();
7704 let mut size: c_int = 0;
7705 crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 1);
7706 let mut ret: c_int = -1;
7707 if !mem.is_null() {
7708 let fp = libc::fopen(filename, c"w".as_ptr() as *const c_char);
7709 if !fp.is_null() {
7710 let written = libc::fwrite(mem as *const c_void, 1, size as usize, fp);
7711 ret = if written == size as usize { 0 } else { -1 };
7712 libc::fclose(fp);
7713 }
7714 xmlFreeImpl(mem as *mut c_void);
7715 }
7716 crate::xml::tree::free_doc(doc);
7717 ret
7718}
7719
7720/// Clean up the catalog subsystem.
7721///
7722/// # UPSTREAM-PARITY
7723///
7724/// ```c
7725/// void xmlCatalogCleanup(void);
7726/// ```
7727#[no_mangle]
7728pub extern "C" fn xmlCatalogCleanup() {
7729 crate::xml::catalog::cleanup();
7730}
7731
7732/// Convert all the SGML catalog entries as XML ones (upstream catalog.c:
7733/// returns 0 on success, -1 otherwise — R-000176, the candidate previously
7734/// returned a dump document).
7735///
7736/// # UPSTREAM-PARITY
7737///
7738/// ```c
7739/// int xmlCatalogConvert(void);
7740/// ```
7741///
7742/// The candidate's catalog stores XML-flavored entries natively (there is no
7743/// separate SGML table to convert), so once the catalog is initialized the
7744/// conversion succeeds as a no-op, matching upstream's successful-return
7745/// contract.
7746#[no_mangle]
7747pub extern "C" fn xmlCatalogConvert() -> c_int {
7748 if !crate::xml::catalog::is_initialized() {
7749 return -1;
7750 }
7751 0
7752}
7753
7754// ═══════════════════════════════════════════════════════════════════════════════
7755// 17. HTML
7756// ═══════════════════════════════════════════════════════════════════════════════
7757
7758/// Parse an HTML document from a file.
7759///
7760/// # UPSTREAM-PARITY
7761///
7762/// ```c
7763/// htmlDocPtr htmlParseFile(const char *filename, const char *encoding);
7764/// ```
7765#[no_mangle]
7766pub const unsafe extern "C" fn htmlParseFile(
7767 _filename: *const c_char,
7768 _encoding: *const c_char,
7769) -> *mut _xmlDoc {
7770 // Phase 1: STUB
7771 ptr::null_mut()
7772}
7773
7774/// Parse an HTML document from memory.
7775///
7776/// # UPSTREAM-PARITY
7777///
7778/// ```c
7779/// htmlDocPtr htmlParseMemory(const char *buffer, int size);
7780/// ```
7781#[no_mangle]
7782pub const unsafe extern "C" fn htmlParseMemory(
7783 _buffer: *const c_char,
7784 _size: c_int,
7785) -> *mut _xmlDoc {
7786 // Phase 1: STUB
7787 ptr::null_mut()
7788}
7789
7790/// Parse an HTML document from a document string.
7791///
7792/// # UPSTREAM-PARITY
7793///
7794/// ```c
7795/// htmlDocPtr htmlParseDoc(const xmlChar *cur, const char *encoding);
7796/// ```
7797#[no_mangle]
7798pub const unsafe extern "C" fn htmlParseDoc(
7799 _cur: *const xmlChar,
7800 _encoding: *const c_char,
7801) -> *mut _xmlDoc {
7802 // Phase 1: STUB
7803 ptr::null_mut()
7804}
7805
7806/// Create an HTML parser context.
7807///
7808/// # UPSTREAM-PARITY
7809///
7810/// ```c
7811/// Free an HTML parser context.
7812///
7813/// # UPSTREAM-PARITY
7814///
7815/// ```c
7816/// void htmlFreeParserCtxt(htmlParserCtxtPtr ctxt);
7817/// ```
7818#[no_mangle]
7819pub extern "C" fn htmlFreeParserCtxt(ctxt: *mut c_void) {
7820 unsafe { crate::xml::html::free_parser_ctxt(ctxt) }
7821}
7822
7823/// Initialize the HTML parser.
7824///
7825/// # UPSTREAM-PARITY
7826///
7827/// ```c
7828/// void htmlInitParser(void);
7829/// ```
7830#[no_mangle]
7831pub const extern "C" fn htmlInitParser() {
7832 // Phase 1: STUB
7833}
7834
7835/// Clean up the HTML parser.
7836///
7837/// # UPSTREAM-PARITY
7838///
7839/// ```c
7840/// void htmlCleanupParser(void);
7841/// ```
7842#[no_mangle]
7843pub const extern "C" fn htmlCleanupParser() {
7844 // Phase 1: STUB
7845}
7846
7847// ═══════════════════════════════════════════════════════════════════════════════
7848// 17.5. Validation (DTD)
7849// ═══════════════════════════════════════════════════════════════════════════════
7850
7851/// Create a new validation context.
7852///
7853/// # UPSTREAM-PARITY
7854///
7855/// ```c
7856/// xmlValidCtxtPtr xmlNewValidCtxt(void);
7857/// ```
7858#[no_mangle]
7859pub unsafe extern "C" fn xmlNewValidCtxt() -> *mut _xmlValidCtxt {
7860 crate::xml::validation::new_valid_ctxt()
7861}
7862
7863/// Free a validation context.
7864///
7865/// # UPSTREAM-PARITY
7866///
7867/// ```c
7868/// void xmlFreeValidCtxt(xmlValidCtxtPtr ctxt);
7869/// ```
7870#[no_mangle]
7871pub unsafe extern "C" fn xmlFreeValidCtxt(ctxt: *mut _xmlValidCtxt) {
7872 crate::xml::validation::free_valid_ctxt(ctxt);
7873}
7874
7875/// Set error and warning callbacks on a validation context.
7876///
7877/// # UPSTREAM-PARITY
7878///
7879/// ```c
7880/// void xmlSetValidErrors(xmlValidCtxtPtr ctxt,
7881/// xmlGenericErrorFunc err,
7882/// xmlGenericErrorFunc warn,
7883/// void *data);
7884/// ```
7885#[no_mangle]
7886pub unsafe extern "C" fn xmlSetValidErrors(
7887 ctxt: *mut _xmlValidCtxt,
7888 err: Option<xmlGenericErrorFunc>,
7889 warn: Option<xmlGenericErrorFunc>,
7890 data: *mut c_void,
7891) {
7892 crate::xml::validation::set_valid_errors(ctxt, err, warn, data);
7893}
7894
7895/// Validate a document against its DTD.
7896///
7897/// # UPSTREAM-PARITY
7898///
7899/// ```c
7900/// int xmlValidateDocument(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7901/// ```
7902#[no_mangle]
7903pub unsafe extern "C" fn xmlValidateDocument(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
7904 crate::xml::validation::validate_document(ctxt, doc)
7905}
7906
7907/// Final validation pass (check ID/IDREF consistency).
7908///
7909/// # UPSTREAM-PARITY
7910///
7911/// ```c
7912/// int xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
7913/// ```
7914#[no_mangle]
7915pub unsafe extern "C" fn xmlValidateDocumentFinal(
7916 ctxt: *mut _xmlValidCtxt,
7917 doc: *mut _xmlDoc,
7918) -> c_int {
7919 crate::xml::validation::validate_document_final(ctxt, doc)
7920}
7921
7922/// Validate an element node against its DTD declarations.
7923///
7924/// # UPSTREAM-PARITY
7925///
7926/// ```c
7927/// int xmlValidateElement(xmlValidCtxtPtr ctxt,
7928/// xmlDocPtr doc,
7929/// xmlNodePtr elem);
7930/// ```
7931#[no_mangle]
7932pub unsafe extern "C" fn xmlValidateElement(
7933 ctxt: *mut _xmlValidCtxt,
7934 doc: *mut _xmlDoc,
7935 elem: *mut _xmlNode,
7936) -> c_int {
7937 crate::xml::validation::validate_element(ctxt, doc, elem)
7938}
7939
7940/// Validate an attribute declaration.
7941///
7942/// # UPSTREAM-PARITY
7943///
7944/// ```c
7945/// int xmlValidateAttributeDecl(xmlValidCtxt *ctxt,
7946/// xmlDoc *doc,
7947/// xmlAttribute *attr);
7948/// ```
7949///
7950/// 11.1-Z.3 signature court: the pre-Z.3 candidate declared a fourth
7951/// `xmlNodePtr elem` argument that does not exist upstream (valid.h 2.15.3).
7952#[no_mangle]
7953pub unsafe extern "C" fn xmlValidateAttributeDecl(
7954 ctxt: *mut _xmlValidCtxt,
7955 doc: *mut _xmlDoc,
7956 attr: *mut _xmlAttribute,
7957) -> c_int {
7958 crate::xml::validation::validate_attribute_decl(ctxt, doc, attr)
7959}
7960
7961/// Validate an attribute value against its declared type.
7962///
7963/// # UPSTREAM-PARITY
7964///
7965/// ```c
7966/// int xmlValidateAttributeValue(int type, const xmlChar *value);
7967/// ```
7968#[no_mangle]
7969pub unsafe extern "C" fn xmlValidateAttributeValue(atype: c_int, value: *const xmlChar) -> c_int {
7970 crate::xml::validation::validate_attribute_value(atype, value)
7971}
7972
7973/// Validate a NOTATION reference.
7974///
7975/// # UPSTREAM-PARITY
7976///
7977/// ```c
7978/// int xmlValidateNotationUse(xmlValidCtxtPtr ctxt,
7979/// xmlDocPtr doc,
7980/// const xmlChar *notationName);
7981/// ```
7982#[no_mangle]
7983pub unsafe extern "C" fn xmlValidateNotationUse(
7984 ctxt: *mut _xmlValidCtxt,
7985 doc: *mut _xmlDoc,
7986 notation_name: *const xmlChar,
7987) -> c_int {
7988 crate::xml::validation::validate_notation_use(ctxt, doc, notation_name)
7989}
7990
7991/// Validate an ID value (check uniqueness).
7992///
7993/// # UPSTREAM-PARITY
7994///
7995/// ```c
7996/// int xmlValidateID(xmlValidCtxtPtr ctxt,
7997/// xmlDocPtr doc,
7998/// xmlNodePtr node,
7999/// const xmlChar *value);
8000/// ```
8001#[no_mangle]
8002pub unsafe extern "C" fn xmlValidateID(
8003 ctxt: *mut _xmlValidCtxt,
8004 doc: *mut _xmlDoc,
8005 node: *mut _xmlNode,
8006 value: *const xmlChar,
8007) -> c_int {
8008 crate::xml::validation::validate_id(ctxt, doc, node, value)
8009}
8010
8011/// Validate an IDREF value (check it references a known ID; upstream
8012/// valid.h 2.15 has no `node` argument — R-000176).
8013///
8014/// # UPSTREAM-PARITY
8015///
8016/// ```c
8017/// int xmlValidateIDRef(xmlValidCtxt *ctxt, xmlDoc *doc,
8018/// const xmlChar *value);
8019/// ```
8020#[no_mangle]
8021pub unsafe extern "C" fn xmlValidateIDRef(
8022 ctxt: *mut _xmlValidCtxt,
8023 doc: *mut _xmlDoc,
8024 value: *const xmlChar,
8025) -> c_int {
8026 crate::xml::validation::validate_id_ref(ctxt, doc, ptr::null_mut(), value)
8027}
8028
8029/// Validate IDREFS (whitespace-separated list of IDREFs; upstream valid.h
8030/// 2.15 has no `node` argument — R-000176).
8031///
8032/// # UPSTREAM-PARITY
8033///
8034/// ```c
8035/// int xmlValidateIDRefs(xmlValidCtxt *ctxt, xmlDoc *doc,
8036/// const xmlChar *value);
8037/// ```
8038#[no_mangle]
8039pub unsafe extern "C" fn xmlValidateIDRefs(
8040 ctxt: *mut _xmlValidCtxt,
8041 doc: *mut _xmlDoc,
8042 value: *const xmlChar,
8043) -> c_int {
8044 crate::xml::validation::validate_id_refs(ctxt, doc, ptr::null_mut(), value)
8045}
8046
8047/// Validate an NCName value (modern 2-arg form, upstream tree.c).
8048///
8049/// # UPSTREAM-PARITY
8050///
8051/// ```c
8052/// int xmlValidateNCName(const xmlChar *value, int space);
8053/// ```
8054///
8055/// Returns -1 on NULL, 0 if valid, 1 if invalid.
8056#[no_mangle]
8057pub unsafe extern "C" fn xmlValidateNCName(value: *const xmlChar, space: c_int) -> c_int {
8058 crate::xml::validation::validate_ncname(value, space)
8059}
8060
8061/// Validate a QName value (modern 2-arg form, upstream tree.c).
8062///
8063/// # UPSTREAM-PARITY
8064///
8065/// ```c
8066/// int xmlValidateQName(const xmlChar *value, int space);
8067/// ```
8068#[no_mangle]
8069pub unsafe extern "C" fn xmlValidateQName(value: *const xmlChar, space: c_int) -> c_int {
8070 crate::xml::validation::validate_qname(value, space)
8071}
8072
8073/// Validate an XML Name value (modern 2-arg form, upstream tree.c).
8074///
8075/// # UPSTREAM-PARITY / HISTORICAL
8076///
8077/// Since libxml2 2.12 the DSO symbol carries a second `int space` parameter
8078/// with inverted return semantics (0 valid / 1 invalid / -1 NULL); the
8079/// pre-2.12 1-arg form no longer exists in the DSO. The candidate matches
8080/// the current oracle. (The 1-arg semantics live on as xmlValidateNameValue.)
8081///
8082/// ```c
8083/// int xmlValidateName(const xmlChar *value, int space);
8084/// ```
8085#[no_mangle]
8086pub unsafe extern "C" fn xmlValidateName(value: *const xmlChar, space: c_int) -> c_int {
8087 crate::xml::validation::validate_name_space(value, space)
8088}
8089
8090/// Validate an NMToken value (modern 2-arg form, upstream tree.c).
8091///
8092/// # UPSTREAM-PARITY
8093///
8094/// ```c
8095/// int xmlValidateNMToken(const xmlChar *value, int space);
8096/// ```
8097#[no_mangle]
8098pub unsafe extern "C" fn xmlValidateNMToken(value: *const xmlChar, space: c_int) -> c_int {
8099 crate::xml::validation::validate_nmtoken_space(value, space)
8100}
8101
8102/// Validate a Name value (1-arg form, upstream valid.c).
8103///
8104/// # UPSTREAM-PARITY
8105///
8106/// ```c
8107/// int xmlValidateNameValue(const xmlChar *value);
8108/// ```
8109///
8110/// Returns 1 if valid, 0 if not (NULL included).
8111#[no_mangle]
8112pub unsafe extern "C" fn xmlValidateNameValue(value: *const xmlChar) -> c_int {
8113 crate::xml::validation::validate_name_value(value)
8114}
8115
8116/// Validate a whitespace-separated list of Names (separator is exactly
8117/// 0x20, upstream erratum E20).
8118///
8119/// # UPSTREAM-PARITY
8120///
8121/// ```c
8122/// int xmlValidateNamesValue(const xmlChar *value);
8123/// ```
8124#[no_mangle]
8125pub unsafe extern "C" fn xmlValidateNamesValue(value: *const xmlChar) -> c_int {
8126 crate::xml::validation::validate_names_value(value)
8127}
8128
8129/// Validate an Nmtoken value (1-arg form, upstream valid.c).
8130///
8131/// # UPSTREAM-PARITY
8132///
8133/// ```c
8134/// int xmlValidateNmtokenValue(const xmlChar *value);
8135/// ```
8136#[no_mangle]
8137pub unsafe extern "C" fn xmlValidateNmtokenValue(value: *const xmlChar) -> c_int {
8138 crate::xml::validation::validate_nmtoken_value(value)
8139}
8140
8141/// Validate a whitespace-separated list of Nmtokens.
8142///
8143/// # UPSTREAM-PARITY
8144///
8145/// ```c
8146/// int xmlValidateNmtokensValue(const xmlChar *value);
8147/// ```
8148#[no_mangle]
8149pub unsafe extern "C" fn xmlValidateNmtokensValue(value: *const xmlChar) -> c_int {
8150 crate::xml::validation::validate_nmtokens_value(value)
8151}
8152
8153/// Validate a single element declaration (VC: Unique Element Type
8154/// Declaration, VC: No Duplicate Types).
8155///
8156/// # UPSTREAM-PARITY
8157///
8158/// ```c
8159/// int xmlValidateElementDecl(xmlValidCtxtPtr ctxt,
8160/// xmlDocPtr doc,
8161/// xmlElementPtr elem);
8162/// ```
8163#[no_mangle]
8164pub unsafe extern "C" fn xmlValidateElementDecl(
8165 ctxt: *mut _xmlValidCtxt,
8166 doc: *mut _xmlDoc,
8167 elem: *mut _xmlElement,
8168) -> c_int {
8169 crate::xml::validation::validate_element_decl(ctxt, doc, elem)
8170}
8171
8172/// Validate a notation declaration.
8173///
8174/// # UPSTREAM-PARITY
8175///
8176/// Modern libxml2 has no validity constraint on notation declarations; the
8177/// oracle returns 1 unconditionally (verified by DSO disassembly).
8178///
8179/// ```c
8180/// int xmlValidateNotationDecl(xmlValidCtxtPtr ctxt,
8181/// xmlDocPtr doc,
8182/// xmlNotationPtr nota);
8183/// ```
8184#[no_mangle]
8185pub const unsafe extern "C" fn xmlValidateNotationDecl(
8186 ctxt: *mut _xmlValidCtxt,
8187 doc: *mut _xmlDoc,
8188 nota: *mut _xmlNotation,
8189) -> c_int {
8190 crate::xml::validation::validate_notation_decl(ctxt, doc, nota)
8191}
8192
8193/// Validate a single attribute against its declaration.
8194///
8195/// # UPSTREAM-PARITY
8196///
8197/// ```c
8198/// int xmlValidateOneAttribute(xmlValidCtxtPtr ctxt,
8199/// xmlDocPtr doc,
8200/// xmlNodePtr elem,
8201/// xmlAttrPtr attr,
8202/// const xmlChar *value);
8203/// ```
8204#[no_mangle]
8205pub unsafe extern "C" fn xmlValidateOneAttribute(
8206 ctxt: *mut _xmlValidCtxt,
8207 doc: *mut _xmlDoc,
8208 elem: *mut _xmlNode,
8209 attr: *mut _xmlAttr,
8210 value: *const xmlChar,
8211) -> c_int {
8212 crate::xml::validation::validate_one_attribute(ctxt, doc, elem, attr, value)
8213}
8214
8215/// Validate a single element against its declaration (without recursing).
8216///
8217/// # UPSTREAM-PARITY
8218///
8219/// ```c
8220/// int xmlValidateOneElement(xmlValidCtxtPtr ctxt,
8221/// xmlDocPtr doc,
8222/// xmlNodePtr elem);
8223/// ```
8224#[no_mangle]
8225pub unsafe extern "C" fn xmlValidateOneElement(
8226 ctxt: *mut _xmlValidCtxt,
8227 doc: *mut _xmlDoc,
8228 elem: *mut _xmlNode,
8229) -> c_int {
8230 crate::xml::validation::validate_one_element(ctxt, doc, elem)
8231}
8232
8233/// Validate a namespace declaration attribute.
8234///
8235/// # UPSTREAM-PARITY
8236///
8237/// ```c
8238/// int xmlValidateOneNamespace(xmlValidCtxtPtr ctxt,
8239/// xmlDocPtr doc,
8240/// xmlNodePtr elem,
8241/// const xmlChar *prefix,
8242/// xmlNsPtr ns,
8243/// const xmlChar *value);
8244/// ```
8245#[no_mangle]
8246pub unsafe extern "C" fn xmlValidateOneNamespace(
8247 ctxt: *mut _xmlValidCtxt,
8248 doc: *mut _xmlDoc,
8249 elem: *mut _xmlNode,
8250 prefix: *const xmlChar,
8251 ns: *mut _xmlNs,
8252 value: *const xmlChar,
8253) -> c_int {
8254 crate::xml::validation::validate_one_namespace(ctxt, doc, elem, prefix, ns, value)
8255}
8256
8257/// Push a new element start onto the validation stack (streaming DTD
8258/// validation).
8259///
8260/// # UPSTREAM-PARITY
8261///
8262/// ```c
8263/// int xmlValidatePushElement(xmlValidCtxtPtr ctxt,
8264/// xmlDocPtr doc,
8265/// xmlNodePtr elem,
8266/// const xmlChar *qname);
8267/// ```
8268#[no_mangle]
8269pub unsafe extern "C" fn xmlValidatePushElement(
8270 ctxt: *mut _xmlValidCtxt,
8271 doc: *mut _xmlDoc,
8272 elem: *mut _xmlNode,
8273 qname: *const xmlChar,
8274) -> c_int {
8275 crate::xml::validation::validate_push_element(ctxt, doc, elem, qname)
8276}
8277
8278/// Push character data onto the validation stack.
8279///
8280/// # UPSTREAM-PARITY
8281///
8282/// ```c
8283/// int xmlValidatePushCData(xmlValidCtxtPtr ctxt,
8284/// const xmlChar *data,
8285/// int len);
8286/// ```
8287#[no_mangle]
8288pub unsafe extern "C" fn xmlValidatePushCData(
8289 ctxt: *mut _xmlValidCtxt,
8290 data: *const xmlChar,
8291 len: c_int,
8292) -> c_int {
8293 crate::xml::validation::validate_push_cdata(ctxt, data, len)
8294}
8295
8296/// Pop an element end from the validation stack.
8297///
8298/// # UPSTREAM-PARITY
8299///
8300/// ```c
8301/// int xmlValidatePopElement(xmlValidCtxtPtr ctxt,
8302/// xmlDocPtr doc,
8303/// xmlNodePtr elem,
8304/// const xmlChar *qname);
8305/// ```
8306#[no_mangle]
8307pub unsafe extern "C" fn xmlValidatePopElement(
8308 ctxt: *mut _xmlValidCtxt,
8309 doc: *mut _xmlDoc,
8310 elem: *mut _xmlNode,
8311 qname: *const xmlChar,
8312) -> c_int {
8313 crate::xml::validation::validate_pop_element(ctxt, doc, elem, qname)
8314}
8315
8316/// Build the content-model automaton for an element declaration.
8317///
8318/// # UPSTREAM-PARITY
8319///
8320/// ```c
8321/// int xmlValidBuildContentModel(xmlValidCtxtPtr ctxt,
8322/// xmlElementPtr elem);
8323/// ```
8324#[no_mangle]
8325pub unsafe extern "C" fn xmlValidBuildContentModel(
8326 ctxt: *mut _xmlValidCtxt,
8327 elem: *mut _xmlElement,
8328) -> c_int {
8329 crate::xml::validation::validate_build_content_model(ctxt, elem)
8330}
8331
8332/// Add an attribute to the document's ID table.
8333///
8334/// # UPSTREAM-PARITY
8335///
8336/// ```c
8337/// xmlIDPtr xmlAddID(xmlValidCtxtPtr ctxt,
8338/// xmlDocPtr doc,
8339/// const xmlChar *value,
8340/// xmlAttrPtr attr);
8341/// ```
8342#[no_mangle]
8343pub unsafe extern "C" fn xmlAddID(
8344 ctxt: *mut _xmlValidCtxt,
8345 doc: *mut _xmlDoc,
8346 value: *const xmlChar,
8347 attr: *mut _xmlAttr,
8348) -> *mut _xmlID {
8349 crate::xml::validation::add_id(ctxt, doc, value, attr)
8350}
8351
8352/// Remove an attribute from the document's ID table.
8353///
8354/// # UPSTREAM-PARITY
8355///
8356/// ```c
8357/// int xmlRemoveID(xmlDocPtr doc, xmlAttrPtr attr);
8358/// ```
8359#[no_mangle]
8360pub unsafe extern "C" fn xmlRemoveID(doc: *mut _xmlDoc, attr: *mut _xmlAttr) -> c_int {
8361 crate::xml::validation::remove_id(doc, attr)
8362}
8363
8364/// Register an IDREF in the document's ref table.
8365///
8366/// # UPSTREAM-PARITY
8367///
8368/// ```c
8369/// xmlRefPtr xmlAddRef(xmlValidCtxtPtr ctxt,
8370/// xmlDocPtr doc,
8371/// const xmlChar *value,
8372/// xmlAttrPtr attr);
8373/// ```
8374#[no_mangle]
8375pub unsafe extern "C" fn xmlAddRef(
8376 ctxt: *mut _xmlValidCtxt,
8377 doc: *mut _xmlDoc,
8378 value: *const xmlChar,
8379 attr: *mut _xmlAttr,
8380) -> *mut _xmlRef {
8381 crate::xml::validation::add_ref(ctxt, doc, value, attr)
8382}
8383
8384/// Remove an attribute's IDREF entries.
8385///
8386/// # UPSTREAM-PARITY
8387///
8388/// ```c
8389/// int xmlRemoveRef(xmlDocPtr doc, xmlAttrPtr attr);
8390/// ```
8391#[no_mangle]
8392pub unsafe extern "C" fn xmlRemoveRef(doc: *mut _xmlDoc, attr: *mut _xmlAttr) -> c_int {
8393 crate::xml::validation::remove_ref(doc, attr)
8394}
8395
8396/// Add an ID without a validation context (2.13+).
8397///
8398/// # UPSTREAM-PARITY
8399///
8400/// ```c
8401/// int xmlAddIDSafe(xmlAttrPtr attr, const xmlChar *value);
8402/// ```
8403#[no_mangle]
8404pub unsafe extern "C" fn xmlAddIDSafe(attr: *mut _xmlAttr, value: *const xmlChar) -> c_int {
8405 crate::xml::validation::add_id_safe(attr, value)
8406}
8407
8408/// Free an ID hash table.
8409///
8410/// # UPSTREAM-PARITY
8411///
8412/// ```c
8413/// void xmlFreeIDTable(xmlIDTablePtr table);
8414/// ```
8415#[no_mangle]
8416pub unsafe extern "C" fn xmlFreeIDTable(table: *mut c_void) {
8417 crate::xml::validation::free_id_table(table as *mut crate::xml::hash::HashTable);
8418}
8419
8420/// Free an IDREF hash table.
8421///
8422/// # UPSTREAM-PARITY
8423///
8424/// ```c
8425/// void xmlFreeRefTable(xmlRefTablePtr table);
8426/// ```
8427#[no_mangle]
8428pub unsafe extern "C" fn xmlFreeRefTable(table: *mut c_void) {
8429 crate::xml::validation::free_ref_table(table as *mut crate::xml::hash::HashTable);
8430}
8431
8432/// Look up the attribute holding an ID.
8433///
8434/// # UPSTREAM-PARITY
8435///
8436/// ```c
8437/// xmlAttrPtr xmlGetID(xmlDocPtr doc, const xmlChar *ID);
8438/// ```
8439#[no_mangle]
8440pub unsafe extern "C" fn xmlGetID(doc: *mut _xmlDoc, id: *const xmlChar) -> *mut _xmlAttr {
8441 crate::xml::validation::get_id(doc, id)
8442}
8443
8444/// Look up the list of references for an ID.
8445///
8446/// # UPSTREAM-PARITY
8447///
8448/// ```c
8449/// xmlListPtr xmlGetRefs(xmlDocPtr doc, const xmlChar *ID);
8450/// ```
8451#[no_mangle]
8452pub unsafe extern "C" fn xmlGetRefs(doc: *mut _xmlDoc, id: *const xmlChar) -> *mut c_void {
8453 crate::xml::validation::get_refs(doc, id) as *mut c_void
8454}
8455
8456/// Is this attribute an ID?
8457///
8458/// # UPSTREAM-PARITY
8459///
8460/// ```c
8461/// int xmlIsID(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr);
8462/// ```
8463#[no_mangle]
8464pub unsafe extern "C" fn xmlIsID(
8465 doc: *mut _xmlDoc,
8466 elem: *mut _xmlNode,
8467 attr: *mut _xmlAttr,
8468) -> c_int {
8469 crate::xml::validation::is_id(doc, elem, attr)
8470}
8471
8472/// Is this attribute an IDREF?
8473///
8474/// # UPSTREAM-PARITY
8475///
8476/// ```c
8477/// int xmlIsRef(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr);
8478/// ```
8479#[no_mangle]
8480pub unsafe extern "C" fn xmlIsRef(
8481 doc: *mut _xmlDoc,
8482 elem: *mut _xmlNode,
8483 attr: *mut _xmlAttr,
8484) -> c_int {
8485 crate::xml::validation::is_ref(doc, elem, attr)
8486}
8487
8488/// Search a DTD for an element declaration (with QName splitting).
8489///
8490/// # UPSTREAM-PARITY
8491///
8492/// ```c
8493/// xmlElementPtr xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name);
8494/// ```
8495#[no_mangle]
8496pub unsafe extern "C" fn xmlGetDtdElementDesc(
8497 dtd: *mut _xmlDtd,
8498 name: *const xmlChar,
8499) -> *mut _xmlElement {
8500 crate::xml::validation::get_dtd_element_desc(dtd, name)
8501}
8502
8503/// Search a DTD for an attribute declaration (with QName splitting).
8504///
8505/// # UPSTREAM-PARITY
8506///
8507/// ```c
8508/// xmlAttributePtr xmlGetDtdAttrDesc(xmlDtdPtr dtd,
8509/// const xmlChar *elem,
8510/// const xmlChar *name);
8511/// ```
8512#[no_mangle]
8513pub unsafe extern "C" fn xmlGetDtdAttrDesc(
8514 dtd: *mut _xmlDtd,
8515 elem: *const xmlChar,
8516 name: *const xmlChar,
8517) -> *mut _xmlAttribute {
8518 crate::xml::validation::get_dtd_attr_desc(dtd, elem, name)
8519}
8520
8521/// Search a DTD for a qualified element declaration.
8522///
8523/// # UPSTREAM-PARITY
8524///
8525/// ```c
8526/// xmlElementPtr xmlGetDtdQElementDesc(xmlDtdPtr dtd,
8527/// const xmlChar *name,
8528/// const xmlChar *prefix);
8529/// ```
8530#[no_mangle]
8531pub unsafe extern "C" fn xmlGetDtdQElementDesc(
8532 dtd: *mut _xmlDtd,
8533 name: *const xmlChar,
8534 prefix: *const xmlChar,
8535) -> *mut _xmlElement {
8536 crate::xml::validation::get_dtd_qelement_desc(dtd, name, prefix)
8537}
8538
8539/// Search a DTD for a qualified attribute declaration.
8540///
8541/// # UPSTREAM-PARITY
8542///
8543/// ```c
8544/// xmlAttributePtr xmlGetDtdQAttrDesc(xmlDtdPtr dtd,
8545/// const xmlChar *elem,
8546/// const xmlChar *name,
8547/// const xmlChar *prefix);
8548/// ```
8549#[no_mangle]
8550pub unsafe extern "C" fn xmlGetDtdQAttrDesc(
8551 dtd: *mut _xmlDtd,
8552 elem: *const xmlChar,
8553 name: *const xmlChar,
8554 prefix: *const xmlChar,
8555) -> *mut _xmlAttribute {
8556 crate::xml::validation::get_dtd_qattr_desc(dtd, elem, name, prefix)
8557}
8558
8559/// Search a DTD for a notation declaration.
8560///
8561/// # UPSTREAM-PARITY
8562///
8563/// ```c
8564/// xmlNotationPtr xmlGetDtdNotationDesc(xmlDtdPtr dtd, const xmlChar *name);
8565/// ```
8566#[no_mangle]
8567pub unsafe extern "C" fn xmlGetDtdNotationDesc(
8568 dtd: *mut _xmlDtd,
8569 name: *const xmlChar,
8570) -> *mut _xmlNotation {
8571 crate::xml::validation::get_dtd_notation_desc(dtd, name)
8572}
8573
8574/// Validate the root element of a document.
8575///
8576/// # UPSTREAM-PARITY
8577///
8578/// ```c
8579/// int xmlValidateRoot(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
8580/// ```
8581#[no_mangle]
8582pub unsafe extern "C" fn xmlValidateRoot(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
8583 crate::xml::validation::validate_root(ctxt, doc)
8584}
8585
8586/// Validate element content against its content model.
8587///
8588/// # UPSTREAM-PARITY
8589///
8590/// ```c
8591/// int xmlValidateContent(xmlValidCtxtPtr ctxt,
8592/// xmlNodePtr node,
8593/// xmlDocPtr doc);
8594/// ```
8595#[no_mangle]
8596pub unsafe extern "C" fn xmlValidateContent(
8597 ctxt: *mut _xmlValidCtxt,
8598 node: *mut _xmlNode,
8599 doc: *mut _xmlDoc,
8600) -> c_int {
8601 crate::xml::validation::validate_content(ctxt, node, doc)
8602}
8603
8604/// Check if an element is declared as mixed content.
8605///
8606/// # UPSTREAM-PARITY
8607///
8608/// ```c
8609/// int xmlIsMixedElement(xmlDocPtr doc, const xmlChar *name);
8610/// ```
8611#[no_mangle]
8612pub unsafe extern "C" fn xmlIsMixedElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
8613 crate::xml::validation::is_mixed_element(doc, name)
8614}
8615
8616/// Check if an element is declared as EMPTY.
8617///
8618/// # UPSTREAM-PARITY
8619///
8620/// ```c
8621/// int xmlIsEmptyElement(xmlDocPtr doc, const xmlChar *name);
8622/// ```
8623#[no_mangle]
8624pub unsafe extern "C" fn xmlIsEmptyElement(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
8625 crate::xml::validation::is_empty_element(doc, name)
8626}
8627
8628/// Validate a DTD's declarations.
8629///
8630/// # UPSTREAM-PARITY
8631///
8632/// ```c
8633/// int xmlValidateDtd(xmlValidCtxtPtr ctxt,
8634/// xmlDocPtr doc,
8635/// xmlDtdPtr dtd);
8636/// ```
8637#[no_mangle]
8638pub unsafe extern "C" fn xmlValidateDtd(
8639 ctxt: *mut _xmlValidCtxt,
8640 doc: *mut _xmlDoc,
8641 dtd: *mut _xmlDtd,
8642) -> c_int {
8643 crate::xml::validation::validate_dtd(ctxt, doc, dtd)
8644}
8645
8646/// Final DTD validation (ID/IDREF consistency).
8647///
8648/// # UPSTREAM-PARITY
8649///
8650/// ```c
8651/// int xmlValidateDtdFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc);
8652/// ```
8653#[no_mangle]
8654pub unsafe extern "C" fn xmlValidateDtdFinal(ctxt: *mut _xmlValidCtxt, doc: *mut _xmlDoc) -> c_int {
8655 crate::xml::validation::validate_dtd_final(ctxt, doc)
8656}
8657
8658/// Validate that a value is in an enumeration.
8659///
8660/// # UPSTREAM-PARITY
8661///
8662/// ```c
8663/// int xmlValidateEnumeration(xmlValidCtxtPtr ctxt,
8664/// const xmlChar *value,
8665/// xmlEnumerationPtr tree);
8666/// ```
8667#[no_mangle]
8668pub unsafe extern "C" fn xmlValidateEnumeration(
8669 ctxt: *mut _xmlValidCtxt,
8670 value: *const xmlChar,
8671 tree: *mut _xmlEnumeration,
8672) -> c_int {
8673 crate::xml::validation::validate_enumeration(ctxt, value, tree)
8674}
8675
8676// ═══════════════════════════════════════════════════════════════════════════════
8677// 18. Debug / Miscellaneous
8678// ═══════════════════════════════════════════════════════════════════════════════
8679
8680/// Dump a document to a file for debugging.
8681/// Get the path to the current executable.
8682///
8683/// # UPSTREAM-PARITY
8684///
8685/// ```c
8686/// char *xmlGetBinaryPath(void);
8687/// ```
8688#[no_mangle]
8689pub const extern "C" fn xmlGetBinaryPath() -> *mut c_char {
8690 // Phase 1: STUB
8691 ptr::null_mut()
8692}
8693
8694/// Get the path to the current executable's home directory.
8695///
8696/// # UPSTREAM-PARITY
8697///
8698/// ```c
8699/// char *xmlGetHomeOfBinary(void);
8700/// ```
8701#[no_mangle]
8702pub const extern "C" fn xmlGetHomeOfBinary() -> *mut c_char {
8703 // Phase 1: STUB
8704 ptr::null_mut()
8705}
8706
8707// ═══════════════════════════════════════════════════════════════════════════════
8708// SAX2 default callback entry points (upstream SAX2.c)
8709// ═══════════════════════════════════════════════════════════════════════════════
8710//
8711// These are the public `xmlSAX2*` callback functions that downstream code
8712// installs into `xmlSAXHandler` structures. They are the same implementations
8713// the candidate's default SAX handler uses; exporting them under the
8714// upstream names is required for ABI parity (R-000136 closure).
8715
8716/// Upstream SAX2.c `xmlSAX2StartDocument` — public entry point of the default handler.
8717#[no_mangle]
8718pub unsafe extern "C" fn xmlSAX2StartDocument(ctx: *mut c_void) {
8719 crate::xml::sax::default::default_sax_handler::startDocument(ctx)
8720}
8721
8722/// Upstream SAX2.c `xmlSAX2EndDocument` — public entry point of the default handler.
8723#[no_mangle]
8724pub unsafe extern "C" fn xmlSAX2EndDocument(ctx: *mut c_void) {
8725 crate::xml::sax::default::default_sax_handler::endDocument(ctx)
8726}
8727
8728/// Upstream SAX2.c `xmlSAX2StartElementNs` — public entry point of the default handler.
8729#[no_mangle]
8730pub unsafe extern "C" fn xmlSAX2StartElementNs(
8731 ctx: *mut c_void,
8732 localname: *const xmlChar,
8733 prefix: *const xmlChar,
8734 URI: *const xmlChar,
8735 nb_namespaces: c_int,
8736 namespaces: *mut *const xmlChar,
8737 nb_attributes: c_int,
8738 nb_defaulted: c_int,
8739 attributes: *mut *const xmlChar,
8740) {
8741 crate::xml::sax::default::default_sax_handler::startElementNs(
8742 ctx,
8743 localname,
8744 prefix,
8745 URI,
8746 nb_namespaces,
8747 namespaces,
8748 nb_attributes,
8749 nb_defaulted,
8750 attributes,
8751 )
8752}
8753
8754/// Upstream SAX2.c `xmlSAX2EndElementNs` — public entry point of the default handler.
8755#[no_mangle]
8756pub unsafe extern "C" fn xmlSAX2EndElementNs(
8757 ctx: *mut c_void,
8758 localname: *const xmlChar,
8759 prefix: *const xmlChar,
8760 URI: *const xmlChar,
8761) {
8762 crate::xml::sax::default::default_sax_handler::endElementNs(ctx, localname, prefix, URI)
8763}
8764
8765/// Upstream SAX2.c `xmlSAX2Characters` — public entry point of the default handler.
8766#[no_mangle]
8767pub unsafe extern "C" fn xmlSAX2Characters(ctx: *mut c_void, ch: *const xmlChar, len: c_int) {
8768 crate::xml::sax::default::default_sax_handler::characters(ctx, ch, len)
8769}
8770
8771/// Upstream SAX2.c `xmlSAX2IgnorableWhitespace` — public entry point of the default handler.
8772#[no_mangle]
8773pub unsafe extern "C" fn xmlSAX2IgnorableWhitespace(
8774 ctx: *mut c_void,
8775 ch: *const xmlChar,
8776 len: c_int,
8777) {
8778 crate::xml::sax::default::default_sax_handler::ignorableWhitespace(ctx, ch, len)
8779}
8780
8781/// Upstream SAX2.c `xmlSAX2Comment` — public entry point of the default handler.
8782#[no_mangle]
8783pub unsafe extern "C" fn xmlSAX2Comment(ctx: *mut c_void, value: *const xmlChar) {
8784 crate::xml::sax::default::default_sax_handler::comment(ctx, value)
8785}
8786
8787/// Upstream SAX2.c `xmlSAX2ProcessingInstruction` — public entry point of the default handler.
8788#[no_mangle]
8789pub unsafe extern "C" fn xmlSAX2ProcessingInstruction(
8790 ctx: *mut c_void,
8791 target: *const xmlChar,
8792 data: *const xmlChar,
8793) {
8794 crate::xml::sax::default::default_sax_handler::processingInstruction(ctx, target, data)
8795}
8796
8797/// Upstream SAX2.c `xmlSAX2CDataBlock` — public entry point of the default handler.
8798#[no_mangle]
8799pub unsafe extern "C" fn xmlSAX2CDataBlock(ctx: *mut c_void, value: *const xmlChar, len: c_int) {
8800 crate::xml::sax::default::default_sax_handler::cdataBlock(ctx, value, len)
8801}
8802
8803/// Upstream SAX2.c `xmlSAX2InternalSubset` — public entry point of the default handler.
8804#[no_mangle]
8805pub unsafe extern "C" fn xmlSAX2InternalSubset(
8806 ctx: *mut c_void,
8807 name: *const xmlChar,
8808 ExternalID: *const xmlChar,
8809 SystemID: *const xmlChar,
8810) {
8811 crate::xml::sax::default::default_sax_handler::internalSubset(ctx, name, ExternalID, SystemID)
8812}
8813
8814/// Upstream SAX2.c `xmlSAX2ExternalSubset` — public entry point of the default handler.
8815#[no_mangle]
8816pub unsafe extern "C" fn xmlSAX2ExternalSubset(
8817 ctx: *mut c_void,
8818 name: *const xmlChar,
8819 ExternalID: *const xmlChar,
8820 SystemID: *const xmlChar,
8821) {
8822 crate::xml::sax::default::default_sax_handler::externalSubset(ctx, name, ExternalID, SystemID)
8823}
8824
8825/// Upstream SAX2.c `xmlSAX2EntityDecl` — public entry point of the default handler.
8826#[no_mangle]
8827pub unsafe extern "C" fn xmlSAX2EntityDecl(
8828 ctx: *mut c_void,
8829 name: *const xmlChar,
8830 type_: c_int,
8831 publicId: *const xmlChar,
8832 systemId: *const xmlChar,
8833 content: *mut xmlChar,
8834) {
8835 crate::xml::sax::default::default_sax_handler::entityDecl(
8836 ctx, name, type_, publicId, systemId, content,
8837 )
8838}
8839
8840/// Upstream SAX2.c `xmlSAX2AttributeDecl` — public entry point of the default handler.
8841#[no_mangle]
8842pub const unsafe extern "C" fn xmlSAX2AttributeDecl(
8843 ctx: *mut c_void,
8844 elem: *const xmlChar,
8845 fullname: *const xmlChar,
8846 type_: c_int,
8847 def: c_int,
8848 defaultValue: *const xmlChar,
8849 tree: *mut crate::abi::structs::_xmlEnumeration,
8850) {
8851 crate::xml::sax::default::default_sax_handler::attributeDecl(
8852 ctx,
8853 elem,
8854 fullname,
8855 type_,
8856 def,
8857 defaultValue,
8858 tree,
8859 )
8860}
8861
8862/// Upstream SAX2.c `xmlSAX2ElementDecl` — public entry point of the default handler.
8863#[no_mangle]
8864pub const unsafe extern "C" fn xmlSAX2ElementDecl(
8865 ctx: *mut c_void,
8866 name: *const xmlChar,
8867 type_: c_int,
8868 content: *mut crate::abi::structs::_xmlElementContent,
8869) {
8870 crate::xml::sax::default::default_sax_handler::elementDecl(ctx, name, type_, content)
8871}
8872
8873/// Upstream SAX2.c `xmlSAX2NotationDecl` — public entry point of the default handler.
8874#[no_mangle]
8875pub const unsafe extern "C" fn xmlSAX2NotationDecl(
8876 ctx: *mut c_void,
8877 name: *const xmlChar,
8878 publicId: *const xmlChar,
8879 systemId: *const xmlChar,
8880) {
8881 crate::xml::sax::default::default_sax_handler::notationDecl(ctx, name, publicId, systemId)
8882}
8883
8884/// Upstream SAX2.c `xmlSAX2UnparsedEntityDecl` — public entry point of the default handler.
8885#[no_mangle]
8886pub const unsafe extern "C" fn xmlSAX2UnparsedEntityDecl(
8887 ctx: *mut c_void,
8888 name: *const xmlChar,
8889 publicId: *const xmlChar,
8890 systemId: *const xmlChar,
8891 notationName: *const xmlChar,
8892) {
8893 crate::xml::sax::default::default_sax_handler::unparsedEntityDecl(
8894 ctx,
8895 name,
8896 publicId,
8897 systemId,
8898 notationName,
8899 )
8900}
8901
8902/// Upstream SAX2.c `xmlSAX2ResolveEntity` — public entry point of the default handler.
8903#[no_mangle]
8904pub const unsafe extern "C" fn xmlSAX2ResolveEntity(
8905 ctx: *mut c_void,
8906 publicId: *const xmlChar,
8907 systemId: *const xmlChar,
8908) -> *mut crate::abi::structs::_xmlParserInput {
8909 crate::xml::sax::default::default_sax_handler::resolveEntity(ctx, publicId, systemId)
8910}
8911
8912/// Upstream SAX2.c `xmlSAX2IsStandalone` — public entry point of the default handler.
8913#[no_mangle]
8914pub const unsafe extern "C" fn xmlSAX2IsStandalone(ctx: *mut c_void) -> c_int {
8915 crate::xml::sax::default::default_sax_handler::isStandalone(ctx)
8916}
8917
8918/// Upstream SAX2.c `xmlSAX2HasInternalSubset` — public entry point of the default handler.
8919#[no_mangle]
8920pub unsafe extern "C" fn xmlSAX2HasInternalSubset(ctx: *mut c_void) -> c_int {
8921 crate::xml::sax::default::default_sax_handler::hasInternalSubset(ctx)
8922}
8923
8924/// Upstream SAX2.c `xmlSAX2HasExternalSubset` — public entry point of the default handler.
8925#[no_mangle]
8926pub unsafe extern "C" fn xmlSAX2HasExternalSubset(ctx: *mut c_void) -> c_int {
8927 crate::xml::sax::default::default_sax_handler::hasExternalSubset(ctx)
8928}
8929
8930/// Upstream SAX2.c `xmlSAX2GetEntity` — public entry point of the default handler.
8931#[no_mangle]
8932pub unsafe extern "C" fn xmlSAX2GetEntity(
8933 ctx: *mut c_void,
8934 name: *const xmlChar,
8935) -> *mut crate::abi::structs::_xmlEntity {
8936 crate::xml::sax::default::default_sax_handler::getEntity(ctx, name)
8937}
8938
8939/// Upstream SAX2.c `xmlSAX2GetParameterEntity` — public entry point of the default handler.
8940#[no_mangle]
8941pub unsafe extern "C" fn xmlSAX2GetParameterEntity(
8942 ctx: *mut c_void,
8943 name: *const xmlChar,
8944) -> *mut crate::abi::structs::_xmlEntity {
8945 crate::xml::sax::default::default_sax_handler::getParameterEntity(ctx, name)
8946}
8947
8948/// Upstream SAX2.c `xmlSAX2GetLineNumber` — public entry point of the
8949/// default handler (SAX locator callback).
8950#[no_mangle]
8951pub unsafe extern "C" fn xmlSAX2GetLineNumber(ctx: *mut c_void) -> c_int {
8952 crate::xml::sax::default::default_sax_handler::getLineNumber(ctx)
8953}
8954
8955/// Upstream SAX2.c `xmlSAX2GetColumnNumber`.
8956#[no_mangle]
8957pub unsafe extern "C" fn xmlSAX2GetColumnNumber(ctx: *mut c_void) -> c_int {
8958 crate::xml::sax::default::default_sax_handler::getColumnNumber(ctx)
8959}
8960
8961/// Upstream SAX2.c `xmlSAX2GetPublicId`.
8962#[no_mangle]
8963pub const unsafe extern "C" fn xmlSAX2GetPublicId(ctx: *mut c_void) -> *const xmlChar {
8964 crate::xml::sax::default::default_sax_handler::getPublicId(ctx)
8965}
8966
8967/// Upstream SAX2.c `xmlSAX2GetSystemId`.
8968#[no_mangle]
8969pub unsafe extern "C" fn xmlSAX2GetSystemId(ctx: *mut c_void) -> *const xmlChar {
8970 crate::xml::sax::default::default_sax_handler::getSystemId(ctx)
8971}
8972
8973/// Upstream SAX2.c `xmlSAX2StartElement` — SAX1 start-element entry point.
8974/// The candidate parser dispatches through the SAX2 (namespaced) callbacks;
8975/// this wrapper maps to the SAX1 handler when installed.
8976#[no_mangle]
8977pub unsafe extern "C" fn xmlSAX2StartElement(
8978 ctx: *mut c_void,
8979 name: *const xmlChar,
8980 atts: *mut *const xmlChar,
8981) {
8982 // The parser core invokes startElementNs; the SAX1 shim is provided by
8983 // the dispatch layer. When this entry point is installed directly on a
8984 // handler, route through the internal SAX1 path.
8985 crate::xml::sax::dispatch::SaxDispatcher::sax1_start_element(ctx, name, atts);
8986}
8987
8988/// Upstream SAX2.c `xmlSAX2EndElement` — SAX1 end-element entry point.
8989#[no_mangle]
8990pub unsafe extern "C" fn xmlSAX2EndElement(ctx: *mut c_void, name: *const xmlChar) {
8991 crate::xml::sax::dispatch::SaxDispatcher::sax1_end_element(ctx, name);
8992}
8993
8994/// Upstream SAX2.c `xmlSAX2SetDocumentLocator` — public entry point of the default handler.
8995#[no_mangle]
8996pub const unsafe extern "C" fn xmlSAX2SetDocumentLocator(
8997 ctx: *mut c_void,
8998 loc: *mut crate::abi::callbacks::_xmlSAXLocator,
8999) {
9000 crate::xml::sax::default::default_sax_handler::setDocumentLocator(ctx, loc)
9001}
9002
9003/// Upstream SAX2.c `xmlSAX2Reference` — public entry point of the default handler.
9004#[no_mangle]
9005pub unsafe extern "C" fn xmlSAX2Reference(ctx: *mut c_void, name: *const xmlChar) {
9006 crate::xml::sax::default::default_sax_handler::reference(ctx, name)
9007}
9008
9009#[cfg(test)]
9010mod tests {
9011 use super::xml_number_to_string;
9012
9013 /// R-000166: number-to-string follows upstream xmlXPathFormatNumber —
9014 /// verified byte-identical against the oracle (xsltproc) on the t4/n3
9015 /// differential corpora. Cases here are exact doubles or
9016 /// rounding-robust formats (parser-dependent literals are covered by the
9017 /// differential corpora, not unit tests).
9018 #[allow(clippy::approx_constant)]
9019 #[test]
9020 fn test_xml_number_to_string_parity_cases() {
9021 let cases: &[(f64, &str)] = &[
9022 (1234567.891, "1234567.891"),
9023 (0.1 + 0.2, "0.3"),
9024 (1.0 / 3.0, "0.333333333333333"),
9025 (1e20, "1e+20"),
9026 (1e-5, "0.00001"),
9027 (123456789012345678901234567890.0, "1.23456789012346e+29"),
9028 (1e100, "1e+100"),
9029 (-1e100, "-1e+100"),
9030 (1.5e-100, "1.5e-100"),
9031 (1e9, "1000000000"),
9032 (0.00001, "0.00001"),
9033 (9.99e-6, "9.99e-06"),
9034 (2147483646.0, "2147483646"),
9035 (2147483648.0, "2.147483648e+09"),
9036 (-2147483647.0, "-2147483647"),
9037 (-2147483649.0, "-2.147483649e+09"),
9038 (0.5, "0.5"),
9039 (1.0 / 7.0, "0.142857142857143"),
9040 (2.675, "2.675"),
9041 (3.141592653589793, "3.141592653589793"),
9042 (-0.0, "0"),
9043 (0.0, "0"),
9044 (f64::INFINITY, "Infinity"),
9045 (f64::NEG_INFINITY, "-Infinity"),
9046 (f64::NAN, "NaN"),
9047 (0.30000000000000004, "0.3"),
9048 (2.2250738585072014e-308, "2.2250738585072e-308"),
9049 (5e-324, "4.94065645841247e-324"),
9050 ];
9051 for (n, expected) in cases {
9052 assert_eq!(&xml_number_to_string(*n), expected, "value: {}", n);
9053 }
9054 }
9055
9056 /// xmlNewChild with a non-NULL content creates the element and appends a
9057 /// text child (upstream tree.c xmlNewChild -> xmlNewDocNode ->
9058 /// xmlNewDocText + xmlAddChild; tree2.c relies on it — Phase-12
9059 /// EXTERNAL-CONSUMERS court).
9060 ///
9061 /// # Safety
9062 ///
9063 /// - The doc and nodes are created and freed exactly once within the
9064 /// test; pointers are asserted non-NULL before dereference.
9065 #[test]
9066 fn test_xml_new_child_with_content() {
9067 unsafe {
9068 let doc =
9069 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9070 assert!(!doc.is_null());
9071 let root = crate::xml::tree::new_node(
9072 core::ptr::null_mut(),
9073 c"root".as_ptr() as *const crate::abi::types::xmlChar,
9074 );
9075 assert!(!root.is_null());
9076 crate::xml::tree::doc_set_root_element(doc, root);
9077
9078 let child = super::xmlNewChild(
9079 root,
9080 core::ptr::null_mut(),
9081 c"node1".as_ptr() as *const crate::abi::types::xmlChar,
9082 c"content of node 1".as_ptr() as *const crate::abi::types::xmlChar,
9083 );
9084 assert!(!child.is_null());
9085 assert!(
9086 !(*child).children.is_null(),
9087 "content must become a text child"
9088 );
9089 assert_eq!(
9090 crate::abi::types::xmlElementType::XML_TEXT_NODE as i32,
9091 (*(*child).children).type_
9092 );
9093 let text = crate::xml::string::xmlstr_to_bytes((*(*child).children).content);
9094 assert_eq!(text, b"content of node 1");
9095
9096 // NULL content stays childless
9097 let empty = super::xmlNewChild(
9098 root,
9099 core::ptr::null_mut(),
9100 c"node2".as_ptr() as *const crate::abi::types::xmlChar,
9101 core::ptr::null(),
9102 );
9103 assert!(!empty.is_null());
9104 assert!((*empty).children.is_null());
9105
9106 crate::xml::tree::free_doc(doc);
9107 }
9108 }
9109
9110 /// xmlNewChild content is parsed as an ATTRIBUTE VALUE (upstream tree.c
9111 /// xmlNewChild -> xmlNewDocNode -> xmlNewElem -> xmlNodeParseAttValue):
9112 /// an EMPTY content adds NO text child (`<bar/>`, SimpleXML bug76712),
9113 /// character references are decoded (`a & b` -> `a & b`, SimpleXML
9114 /// bug44478) and a declared general entity becomes an entity-ref child.
9115 /// The old raw text storage appended an empty text node for "" and kept
9116 /// the reference text verbatim.
9117 ///
9118 /// # Safety
9119 ///
9120 /// - doc/root/child are created and freed exactly once; the text child
9121 /// content string is read while live.
9122 #[test]
9123 fn test_xml_new_child_parses_content_as_att_value() {
9124 unsafe {
9125 let doc =
9126 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9127 assert!(!doc.is_null());
9128 let root = crate::xml::tree::new_node(
9129 core::ptr::null_mut(),
9130 c"root".as_ptr() as *const crate::abi::types::xmlChar,
9131 );
9132 assert!(!root.is_null());
9133 crate::xml::tree::doc_set_root_element(doc, root);
9134
9135 // Empty content -> NO text child (upstream value[0] == 0 early
9136 // out); serializes as `<empty/>`.
9137 let e = super::xmlNewChild(
9138 root,
9139 core::ptr::null_mut(),
9140 c"empty".as_ptr() as *const crate::abi::types::xmlChar,
9141 c"".as_ptr() as *const crate::abi::types::xmlChar,
9142 );
9143 assert!(!e.is_null());
9144 assert!(
9145 (*e).children.is_null(),
9146 "empty content must not add a text child"
9147 );
9148
9149 // Character reference content is DECODED into the text child.
9150 let r = super::xmlNewChild(
9151 root,
9152 core::ptr::null_mut(),
9153 c"ref".as_ptr() as *const crate::abi::types::xmlChar,
9154 c"a & b".as_ptr() as *const crate::abi::types::xmlChar,
9155 );
9156 assert!(!r.is_null());
9157 assert!(!(*r).children.is_null());
9158 let txt = crate::xml::string::xmlstr_to_bytes((*(*r).children).content);
9159 assert_eq!(txt, b"a & b");
9160
9161 // A bare '&' (no terminating ';') consumes the '&' and keeps the
9162 // rest as text (upstream xmlNodeParseAttValue, "x & y" -> "x y").
9163 let b = super::xmlNewChild(
9164 root,
9165 core::ptr::null_mut(),
9166 c"bare".as_ptr() as *const crate::abi::types::xmlChar,
9167 c"x & y".as_ptr() as *const crate::abi::types::xmlChar,
9168 );
9169 assert!(!b.is_null());
9170 assert!(!(*b).children.is_null());
9171 let txt = crate::xml::string::xmlstr_to_bytes((*(*b).children).content);
9172 assert_eq!(txt, b"x y");
9173
9174 crate::xml::tree::free_doc(doc);
9175 }
9176 }
9177
9178 /// Phase 14.3 Bug-2 regression: `xmlFreeProp` must not free a
9179 /// dict-interned attribute name. PHP's SimpleXML unset path
9180 /// (`sxe_unlink_node` -> `php_libxml_node_free` -> `xmlFreeProp`) frees an
9181 /// attribute whose name the parser interned in the document dictionary;
9182 /// the pre-fix `free_prop_impl` freed the interned string, and
9183 /// `xmlDictFree` at doc teardown freed it again (double free). Mirrors the
9184 /// PHP sequence: unlink the attribute, free it via `xmlFreeProp`, then
9185 /// free the document.
9186 ///
9187 /// # Safety
9188 ///
9189 /// - doc/dict/root/attr are built and freed exactly once; a double free
9190 /// (the bug) aborts the test process under glibc tcache detection.
9191 #[test]
9192 fn test_xml_free_prop_preserves_dict_interned_attr_name() {
9193 unsafe {
9194 use crate::abi::allocator::xmlMallocZero;
9195 use crate::abi::structs::{_xmlAttr, _xmlNode};
9196 use core::mem::size_of;
9197
9198 let dict = super::xmlDictCreate();
9199 assert!(!dict.is_null());
9200 let doc =
9201 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9202 assert!(!doc.is_null());
9203 (*doc).dict = dict;
9204 let root = crate::xml::tree::new_node(
9205 core::ptr::null_mut(),
9206 c"root".as_ptr() as *const crate::abi::types::xmlChar,
9207 );
9208 assert!(!root.is_null());
9209 crate::xml::tree::doc_set_root_element(doc, root);
9210
9211 // Intern the attribute name exactly as the dictNames parser does.
9212 let iname = super::xmlDictLookup(
9213 dict,
9214 c"id".as_ptr() as *const crate::abi::types::xmlChar,
9215 -1,
9216 );
9217 assert!(!iname.is_null());
9218
9219 // Build the attribute node with the borrowed dict-interned name
9220 // (mirrors parser_set_prop attribute creation).
9221 let attr = xmlMallocZero(size_of::<_xmlAttr>()) as *mut _xmlAttr;
9222 assert!(!attr.is_null());
9223 (*attr).type_ = crate::abi::types::xmlElementType::XML_ATTRIBUTE_NODE as i32;
9224 (*attr).name = iname as *mut crate::abi::types::xmlChar;
9225 (*attr).parent = root;
9226 (*attr).doc = doc;
9227 (*root).properties = attr;
9228
9229 // PHP SimpleXML unset: xmlUnlinkNode(attr) then xmlFreeProp(attr).
9230 crate::xml::tree::unlink_node(attr as *mut _xmlNode);
9231 crate::abi::exports_tree::xmlFreeProp(attr);
9232
9233 // Teardown: xmlDictFree reaches refcount 0 and frees the interned
9234 // string once. A pre-fix double free aborts here.
9235 crate::xml::tree::free_doc(doc);
9236 }
9237 }
9238
9239 /// Phase 14.3 regression: `xmlNodeListGetString` over a non-text node
9240 /// list must return a NUL-terminated EMPTY string. The pre-fix
9241 /// `xml_strdup(b"")` idiom handed xml_strdup a dangling 0x1 pointer
9242 /// (Rust zero-length byte-string literal), crashing in xml_strlen when
9243 /// SimpleXML string-casts an element whose children produce no text
9244 /// (ext/simplexml 027/028).
9245 ///
9246 /// # Safety
9247 ///
9248 /// - doc/root/person are built and freed exactly once; returned strings
9249 /// are freed with `xmlFreeImpl`.
9250 #[test]
9251 fn test_nodelist_getstring_empty_from_element_chain() {
9252 unsafe {
9253 let doc =
9254 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9255 assert!(!doc.is_null());
9256 let root = crate::xml::tree::new_node(
9257 core::ptr::null_mut(),
9258 c"people".as_ptr() as *const crate::abi::types::xmlChar,
9259 );
9260 assert!(!root.is_null());
9261 crate::xml::tree::doc_set_root_element(doc, root);
9262 let person = crate::abi::exports_tree::xmlNewTextChild(
9263 root,
9264 core::ptr::null_mut(),
9265 c"person".as_ptr() as *const crate::abi::types::xmlChar,
9266 c"Joe".as_ptr() as *const crate::abi::types::xmlChar,
9267 );
9268 assert!(!person.is_null());
9269 super::xmlSetProp(
9270 person,
9271 c"gender".as_ptr() as *const crate::abi::types::xmlChar,
9272 c"male".as_ptr() as *const crate::abi::types::xmlChar,
9273 );
9274
9275 // Element list head yields an empty string (pre-fix: crash).
9276 let s1 = crate::abi::exports_treedump::xmlNodeListGetString(doc, (*root).children, 1);
9277 assert!(!s1.is_null());
9278 assert_eq!(*s1 as u8, 0);
9279 crate::abi::allocator::xmlFreeImpl(s1 as *mut core::ffi::c_void);
9280
9281 // Text child list yields the element text.
9282 let s2 = crate::abi::exports_treedump::xmlNodeListGetString(doc, (*person).children, 1);
9283 assert!(!s2.is_null());
9284 let b = crate::xml::string::xmlstr_to_bytes(s2);
9285 assert_eq!(b, b"Joe");
9286 crate::abi::allocator::xmlFreeImpl(s2 as *mut core::ffi::c_void);
9287
9288 crate::xml::tree::free_doc(doc);
9289 }
9290 }
9291
9292 /// Phase 14.3 (simplexml S3 / ext/simplexml 008): with no structured
9293 /// handler, a failed XPath compile delivers the message VERBATIM to the
9294 /// generic channel — upstream xpath.c xmlXPathErrFmt sets
9295 /// `channel = xmlGenericError; data = xmlGenericErrorContext`, and
9296 /// xmlVRaiseError calls `channel(data, "%s", to->message)` because the
9297 /// generic channel is NOT one of the parser channels that trigger
9298 /// xmlFormatError's fragment stream (which would prefix "XPath error :").
9299 /// PHP installs a generic handler at request start (php_libxml_issue_
9300 /// warning), so the raw text "Invalid expression\n" must arrive alone;
9301 /// a pre-fix `GenericDelivery::Stream` reached PHP's handler with the
9302 /// fragment prefix and ext/simplexml 008 warned "XPath error : Invalid
9303 /// expression".
9304 ///
9305 /// # Safety
9306 ///
9307 /// - doc/ctxt are created and freed exactly once; `captured` lives on
9308 /// the stack for the duration of the call; the generic handler slot is
9309 /// restored to the default printer before the test ends.
9310 #[test]
9311 fn test_xpath_compile_error_verbatim_to_generic_channel() {
9312 use crate::abi::callbacks::xmlGenericErrorFunc;
9313 use core::ffi::{c_char, c_void};
9314
9315 // Serialized against the handler-slot tests in xml::globals (11.1-X)
9316 // and xml::errors: the generic handler slot is shared global state.
9317 let _guard = crate::xml::globals::ERROR_HANDLER_TEST_LOCK.lock();
9318 unsafe {
9319 let mut captured: Vec<u8> = Vec::new();
9320 let captured_ptr = &mut captured as *mut Vec<u8> as *mut c_void;
9321
9322 unsafe extern "C" fn record(ctx: *mut c_void, msg: *const c_char) {
9323 if msg.is_null() {
9324 return;
9325 }
9326 let out = &mut *(ctx as *mut Vec<u8>);
9327 let bytes = std::ffi::CStr::from_ptr(msg).to_bytes();
9328 out.extend_from_slice(bytes);
9329 }
9330
9331 super::xmlSetGenericErrorFunc(captured_ptr, Some(record as xmlGenericErrorFunc));
9332
9333 let doc =
9334 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9335 assert!(!doc.is_null());
9336 let ctxt = super::xmlXPathNewContext(doc);
9337 assert!(!ctxt.is_null());
9338 // No structured handler on the context (ctxt->error stays NULL)
9339 // so the generic channel is the delivery target.
9340 (*ctxt).error = None;
9341
9342 let comp = crate::xml::xpath::exports::xmlXPathCtxtCompile(
9343 ctxt,
9344 c"**".as_ptr() as *const crate::abi::types::xmlChar,
9345 );
9346 assert!(comp.is_null(), "`**` must fail to compile");
9347
9348 assert_eq!(
9349 captured, b"Invalid expression\n",
9350 "generic channel must receive the raw message, no \"XPath error : \" prefix"
9351 );
9352
9353 // Restore the generic handler slot to the default printer.
9354 super::xmlSetGenericErrorFunc(core::ptr::null_mut(), None);
9355 super::xmlXPathFreeContext(ctxt);
9356 crate::xml::tree::free_doc(doc);
9357 }
9358 }
9359
9360 /// Phase 14.3 (simplexml S7 / bug63575): node/document copies must NOT
9361 /// carry the source `_private` — upstream xmlStaticCopyNode zeroes the
9362 /// new node and xmlCopyNamespaceList never copies _private. PHP keys its
9363 /// wrapper registrations on `_private` (php_libxml_node_ptr), so a
9364 /// copied subtree that inherits the original's registrations binds the
9365 /// clone to the ORIGINAL document: SimpleXML root-element clone
9366 /// (xmlCopyDoc) then resolved XPath and mutations into the original's
9367 /// tree. The copy must look UNREGISTERED.
9368 ///
9369 /// # Safety
9370 ///
9371 /// - doc/root/marker are created and freed exactly once within the test;
9372 /// pointers are asserted non-NULL before dereference.
9373 #[test]
9374 fn test_copies_do_not_carry_private() {
9375 unsafe {
9376 let doc =
9377 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9378 assert!(!doc.is_null());
9379 let root = crate::xml::tree::new_node(
9380 core::ptr::null_mut(),
9381 c"a".as_ptr() as *const crate::abi::types::xmlChar,
9382 );
9383 assert!(!root.is_null());
9384 crate::xml::tree::doc_set_root_element(doc, root);
9385
9386 // PHP-style registration marker on the SOURCE node.
9387 let marker: usize = 0x5A5A;
9388 (*root)._private = marker as *mut core::ffi::c_void;
9389
9390 // xmlCopyDoc: the new root must be unregistered.
9391 let cdoc = super::xmlCopyDoc(doc, 1);
9392 assert!(!cdoc.is_null());
9393 let croot = (*cdoc).children;
9394 assert!(!croot.is_null());
9395 assert!(
9396 (*croot)._private.is_null(),
9397 "xmlCopyDoc must not carry the source node _private"
9398 );
9399 assert_eq!(
9400 (*root)._private as usize,
9401 marker,
9402 "source _private is untouched"
9403 );
9404 crate::xml::tree::free_doc(cdoc);
9405
9406 // xmlDocCopyNode into the same doc: the copy must be unregistered.
9407 let copy = crate::abi::exports_treedump::xmlDocCopyNode(root, doc, 1);
9408 assert!(!copy.is_null());
9409 assert!(
9410 (*copy)._private.is_null(),
9411 "xmlDocCopyNode must not carry the source node's _private"
9412 );
9413 // The detached copy owns a duplicated name string: free it
9414 // directly, then the document.
9415 super::xmlFreeNode(copy);
9416 crate::xml::tree::free_doc(doc);
9417 }
9418 }
9419
9420 /// The C-extension-function bridge must expose the invoked function's
9421 /// LOCAL name and namespace URI on `ctxt->context->function` /
9422 /// `functionURI` for the duration of the call, and restore the previous
9423 /// values afterwards (upstream xpath.c xmlXPathCompOpEval, XPATH_OP_
9424 /// FUNCTION). PHP registers ONE trampoline for every custom-namespace
9425 /// XPath function and dispatches to the PHP closure from those two
9426 /// fields — without them the dom/xsl php:function callbacks dereference
9427 /// garbage (SP-14.3.6-dom O1: return_dom_node_from_xpath /
9428 /// registerPhpFunctionNS / gh22077 segv).
9429 ///
9430 /// # Safety
9431 ///
9432 /// - doc/ctxt/result are created and freed exactly once; the callback
9433 /// reads only the two fields the bridge must set.
9434 #[test]
9435 fn test_c_xpath_function_bridge_exposes_function_and_uri() {
9436 unsafe {
9437 use crate::xml::xpath::exports::{xmlXPathNewString, xmlXPathValuePush};
9438 use crate::xml::xpath::parser_context::XmlXPathParserContext;
9439 use std::os::raw::{c_char, c_int};
9440
9441 /// Mirrors PHP's dom_xpath_ext_fetch_intern: read the function
9442 /// identity the invoker set on the context.
9443 unsafe extern "C" fn capture_identity(ctxt: *mut core::ffi::c_void, _nargs: c_int) {
9444 let pc = ctxt as *mut XmlXPathParserContext;
9445 let ctx = (*pc).context;
9446 assert!(!ctx.is_null());
9447 let name = (*ctx).function;
9448 let uri = (*ctx).functionURI;
9449 assert!(!name.is_null());
9450 let name_s = std::ffi::CStr::from_ptr(name as *const c_char)
9451 .to_string_lossy()
9452 .into_owned();
9453 let uri_s = if uri.is_null() {
9454 "(null)".to_string()
9455 } else {
9456 std::ffi::CStr::from_ptr(uri as *const c_char)
9457 .to_string_lossy()
9458 .into_owned()
9459 };
9460 let out = std::ffi::CString::new(format!("{}@{}", name_s, uri_s)).unwrap();
9461 let obj = xmlXPathNewString(out.as_ptr() as *const crate::abi::types::xmlChar);
9462 assert!(!obj.is_null());
9463 assert_eq!(xmlXPathValuePush(ctxt, obj), 0);
9464 }
9465
9466 let doc =
9467 crate::xml::tree::new_doc(c"1.0".as_ptr() as *const crate::abi::types::xmlChar);
9468 assert!(!doc.is_null());
9469 let ctxt = super::xmlXPathNewContext(doc);
9470 assert!(!ctxt.is_null());
9471
9472 super::xmlXPathRegisterNs(
9473 ctxt,
9474 c"t".as_ptr() as *const crate::abi::types::xmlChar,
9475 c"urn:t".as_ptr() as *const crate::abi::types::xmlChar,
9476 );
9477 super::xmlXPathRegisterFuncNS(
9478 ctxt,
9479 c"capture".as_ptr() as *const crate::abi::types::xmlChar,
9480 c"urn:t".as_ptr() as *const crate::abi::types::xmlChar,
9481 Some(capture_identity),
9482 );
9483
9484 let res = super::xmlXPathEvalExpression(
9485 c"t:capture()".as_ptr() as *const crate::abi::types::xmlChar,
9486 ctxt,
9487 );
9488 assert!(!res.is_null());
9489 let s = crate::xml::string::xmlstr_to_bytes((*res).stringval);
9490 assert_eq!(
9491 s, b"capture@urn:t",
9492 "bridge must set context->function (local name) and ->functionURI (ns)"
9493 );
9494 super::xmlXPathFreeObject(res);
9495 super::xmlXPathFreeContext(ctxt);
9496 crate::xml::tree::free_doc(doc);
9497 }
9498 }
9499}