Skip to main content

libxml_rs/xml/xpath/
exports.rs

1//! XPath / XPointer C export bridge (§25, 11.1-I XPath family closure).
2//!
3//! Implements the remaining `xmlXPath*` / `xmlXPtr*` C ABI surface over the
4//! internal Rust XPath engine. The bridge converts between the C ABI
5//! `_xmlXPathObject` / `_xmlNodeSet` representation and the internal
6//! `XPathValue` / `NodeSet` model, and provides the parser-context stack
7//! (upstream `xmlXPathParserContext`) that the exported core-function
8//! implementations operate on.
9//!
10//! UPSTREAM-PARITY notes are recorded per function; behaviors verified by the
11//! XPATH-001 differential court.
12//!
13//! # Upstream contract
14//!
15//! Mirrors upstream `xpath.c` / `xpathInternals.h`
16//! (`SRC-LIBXML2-2.15.0-XPATH-C`, parity target libxml2 2.15.3 oracle):
17//! the exported `xmlXPath*` / `xmlXPtr*` surface — object constructors and
18//! wrappers (xmlXPathNewString, xmlXPathWrapNodeSet, ...), the value-stack
19//! push/pop functions and the compiled-expression entry points
20//! (xmlXPathCtxtCompile, xmlXPathCompiledEval).
21//!
22//! # Conceptual behavior
23//!
24//! Bridges the C ABI `_xmlXPathObject` / `_xmlNodeSet` representation to
25//! the internal `XPathValue` / `NodeSet` model and provides the
26//! parser-context value stack that exported core-function implementations
27//! operate on. Object storage is xmlMalloc'd C-layout; the bridge converts
28//! both directions on every crossing.
29//!
30//! # Ownership & safety invariants
31//!
32//! Objects returned to C are owned by the caller (freed with
33//! `xmlXPathFreeObject`); wrapped strings/node-sets transfer ownership of
34//! the backing storage, not the tree nodes. `dup_rust_string` must NOT use
35//! xml_strdup on a Rust String as_ptr (not NUL-terminated) — the R-000169
36//! heap-buffer-overflow fix is visible here and in pop_string.
37//!
38//! # Historical quirks & epochs
39//!
40//! This bridge was built during the 11.1-I XPath family closure
41//! (R-000126 header surface, R-000128 struct widths) and hardened in
42//! 11.1-X (R-000169 xml_strndup ownership fix); the target is the 2.15.3
43//! oracle epoch.
44//!
45//! # Deliberate oddities
46//!
47//! Wrapping functions take ownership of caller storage exactly like
48//! upstream (xmlXPathWrapString adopts the xmlChar*), including on the
49//! error path where upstream frees the input.
50//!
51//! # Proving courts
52//!
53//! XPATH-001 (courts/suites/data-abi/xpath-family-probe.c) exercises the
54//! object/wrapper surface byte-identical against the oracle DSO; cargo
55//! test covers the bridge unit suites.
56//!
57//! # Tempting simplifications that would break parity
58//!
59//! Do not copy strings with xml_strdup on Rust String pointers: that is
60//! the R-000169 heap-buffer-overflow. Do not return internal Rust values
61//! directly as _xmlXPathObject: C consumers read the C-layout fields.
62
63#![allow(
64    missing_docs,
65    non_snake_case,
66    non_camel_case_types,
67    non_upper_case_globals
68)]
69
70// SAFETY-SCOPE: EXPORTS-MECHANICAL-001
71// (11.1-Z.3 proof scope, classified-generated) — this module is the
72// mechanical extern-"C" export surface: every `unsafe` block in it is
73// the documented indirection/registry-access pattern whose validity
74// rests on the upstream C contract, and the exported signatures are
75// machine-measured by the ABI-FUNCTION-SIGNATURE and DSO-LOADER
76// courts and the C-API differential probes. The safety contract of
77// each export is stated in its own doc comment; this scope covers the
78// mechanical wrappers' unsafe blocks.
79
80use core::ffi::c_void;
81use core::ptr;
82use std::os::raw::{c_char, c_double, c_int, c_long};
83
84use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl, xmlMallocZero};
85use crate::abi::structs::{
86    _xmlDoc, _xmlNode, _xmlNodeSet, _xmlNs, _xmlXPathContext, _xmlXPathObject,
87};
88use crate::abi::types::{xmlChar, xmlXPathObjectType};
89use crate::xml::string::xml_strdup;
90use crate::xml::xpath::types::{node_string_value, NodeSet, XPathValue};
91
92/// Number formatting for XPath (upstream xmlXPathCastNumberToString).
93fn number_to_xmlstring(val: c_double) -> *mut xmlChar {
94    let s = crate::xml::xpath::types::number_to_string(val);
95    dup_rust_string(&s)
96}
97
98/// Copy a Rust string into a NUL-terminated xmlChar buffer (xmlMalloc'd).
99/// NOTE: `s.as_bytes().as_ptr()` is *not* NUL-terminated, so xml_strdup
100/// cannot be used on it directly.
101fn dup_rust_string(s: &str) -> *mut xmlChar {
102    let bytes = s.as_bytes();
103    let buf = unsafe { xmlMallocImpl(bytes.len() + 1) } as *mut xmlChar;
104    if buf.is_null() {
105        return ptr::null_mut();
106    }
107    unsafe {
108        ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
109        *buf.add(bytes.len()) = 0;
110    }
111    buf
112}
113
114// ═══════════════════════════════════════════════════════════════════════════════
115// Object construction / wrapping
116// ═══════════════════════════════════════════════════════════════════════════════
117
118/// `xmlXPathObjectPtr xmlXPathNewString(const xmlChar *val)`.
119///
120/// # SAFETY
121///
122/// - `val` must be a valid NUL-terminated string or NULL.
123#[no_mangle]
124pub unsafe extern "C" fn xmlXPathNewString(val: *const xmlChar) -> *mut _xmlXPathObject {
125    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
126    if obj.is_null() {
127        return ptr::null_mut();
128    }
129    (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
130    (*obj).stringval = if val.is_null() {
131        xml_strdup(c"".as_ptr() as *const xmlChar)
132    } else {
133        xml_strdup(val)
134    };
135    obj
136}
137
138/// `xmlXPathObjectPtr xmlXPathNewValueTree(xmlNodePtr val)` — a node-set object
139/// containing a single node whose subtree is owned by the object.
140///
141/// # SAFETY
142///
143/// - `val` must be a valid node pointer or NULL.
144#[no_mangle]
145pub unsafe extern "C" fn xmlXPathNewValueTree(val: *mut _xmlNode) -> *mut _xmlXPathObject {
146    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
147    if obj.is_null() {
148        return ptr::null_mut();
149    }
150    (*obj).type_ = xmlXPathObjectType::XPATH_XSLT_TREE as c_int;
151    let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
152    if ns.is_null() {
153        xmlFreeImpl(obj as *mut c_void);
154        return ptr::null_mut();
155    }
156    (*ns).nodeNr = 0;
157    (*ns).nodeMax = 1;
158    let tab = xmlMallocImpl(size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
159    if tab.is_null() {
160        xmlFreeImpl(ns as *mut c_void);
161        xmlFreeImpl(obj as *mut c_void);
162        return ptr::null_mut();
163    }
164    if val.is_null() {
165        (*ns).nodeNr = 0;
166        (*ns).nodeMax = 0;
167        xmlFreeImpl(tab as *mut c_void);
168        (*ns).nodeTab = ptr::null_mut();
169    } else {
170        ptr::write(tab, val);
171        (*ns).nodeTab = tab;
172        (*ns).nodeNr = 1;
173    }
174    (*obj).nodesetval = ns as *mut c_void;
175    obj
176}
177
178/// `xmlXPathObjectPtr xmlXPathNewNodeSetList(xmlNodeSetPtr val)` — a node-set
179/// object that COPIES the given node set.
180///
181/// # SAFETY
182///
183/// - `val` must be a valid node-set pointer or NULL.
184#[no_mangle]
185pub unsafe extern "C" fn xmlXPathNewNodeSetList(val: *mut _xmlNodeSet) -> *mut _xmlXPathObject {
186    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
187    if obj.is_null() {
188        return ptr::null_mut();
189    }
190    (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
191    if val.is_null() {
192        (*obj).nodesetval = ptr::null_mut();
193        return obj;
194    }
195    let src = &*val;
196    let nr = src.nodeNr;
197    let ns = xmlMallocZero(size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
198    if ns.is_null() {
199        xmlFreeImpl(obj as *mut c_void);
200        return ptr::null_mut();
201    }
202    (*ns).nodeNr = nr;
203    (*ns).nodeMax = nr;
204    if nr > 0 && !src.nodeTab.is_null() {
205        let tab = xmlMallocImpl((nr as usize) * size_of::<*mut _xmlNode>()) as *mut *mut _xmlNode;
206        if tab.is_null() {
207            xmlFreeImpl(ns as *mut c_void);
208            xmlFreeImpl(obj as *mut c_void);
209            return ptr::null_mut();
210        }
211        ptr::copy_nonoverlapping(src.nodeTab, tab, nr as usize);
212        (*ns).nodeTab = tab;
213    } else {
214        (*ns).nodeTab = ptr::null_mut();
215    }
216    (*obj).nodesetval = ns as *mut c_void;
217    obj
218}
219
220/// `xmlXPathObjectPtr xmlXPathWrapString(xmlChar *val)` — wraps a string,
221/// TAKING OWNERSHIP of `val`.
222///
223/// # SAFETY
224///
225/// - `val` must be a heap-allocated NUL-terminated string or NULL.
226#[no_mangle]
227pub unsafe extern "C" fn xmlXPathWrapString(val: *mut xmlChar) -> *mut _xmlXPathObject {
228    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
229    if obj.is_null() {
230        if !val.is_null() {
231            xmlFreeImpl(val as *mut c_void);
232        }
233        return ptr::null_mut();
234    }
235    (*obj).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
236    (*obj).stringval = val;
237    obj
238}
239
240/// `xmlXPathObjectPtr xmlXPathWrapCString(char *val)`.
241///
242/// # SAFETY
243///
244/// - `val` must be a heap-allocated NUL-terminated string or NULL.
245#[no_mangle]
246pub unsafe extern "C" fn xmlXPathWrapCString(val: *mut c_char) -> *mut _xmlXPathObject {
247    unsafe { xmlXPathWrapString(val as *mut xmlChar) }
248}
249
250/// `xmlXPathObjectPtr xmlXPathWrapNodeSet(xmlNodeSetPtr val)` — wraps a node
251/// set, TAKING OWNERSHIP.
252///
253/// # SAFETY
254///
255/// - `val` must be a heap-allocated node set or NULL.
256#[no_mangle]
257pub unsafe extern "C" fn xmlXPathWrapNodeSet(val: *mut _xmlNodeSet) -> *mut _xmlXPathObject {
258    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
259    if obj.is_null() {
260        if !val.is_null() {
261            xmlFreeImpl(val as *mut c_void);
262        }
263        return ptr::null_mut();
264    }
265    (*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
266    (*obj).nodesetval = val as *mut c_void;
267    obj
268}
269
270/// `xmlXPathObjectPtr xmlXPathWrapExternal(void *val)`.
271///
272/// # SAFETY
273///
274/// - `val` must be a valid pointer or NULL.
275#[no_mangle]
276pub unsafe extern "C" fn xmlXPathWrapExternal(val: *mut c_void) -> *mut _xmlXPathObject {
277    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
278    if obj.is_null() {
279        return ptr::null_mut();
280    }
281    (*obj).type_ = xmlXPathObjectType::XPATH_USERS as c_int;
282    (*obj).user = val;
283    obj
284}
285
286/// `void xmlXPathFreeNodeSetList(xmlXPathObjectPtr obj)` — frees a node-set
287/// typed object and its node set.
288///
289/// # SAFETY
290///
291/// - `obj` must be a valid object pointer or NULL.
292#[no_mangle]
293pub unsafe extern "C" fn xmlXPathFreeNodeSetList(obj: *mut _xmlXPathObject) {
294    if obj.is_null() {
295        return;
296    }
297    let typ = (*obj).type_;
298    if typ == xmlXPathObjectType::XPATH_NODESET as c_int
299        || typ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int
300    {
301        let ns = (*obj).nodesetval as *mut _xmlNodeSet;
302        if !ns.is_null() {
303            if !(*ns).nodeTab.is_null() {
304                xmlFreeImpl((*ns).nodeTab as *mut c_void);
305            }
306            xmlFreeImpl(ns as *mut c_void);
307        }
308    }
309    xmlFreeImpl(obj as *mut c_void);
310}
311
312// ═══════════════════════════════════════════════════════════════════════════════
313// Conversion (in-place, upstream semantics: the old object is freed unless the
314// type already matches)
315// ═══════════════════════════════════════════════════════════════════════════════
316
317/// `xmlXPathObjectPtr xmlXPathConvertBoolean(xmlXPathObjectPtr val)`.
318///
319/// # SAFETY
320///
321/// - `val` must be a valid object pointer or NULL.
322#[no_mangle]
323pub unsafe extern "C" fn xmlXPathConvertBoolean(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
324    if val.is_null() {
325        return ptr::null_mut();
326    }
327    let typ = (*val).type_;
328    if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
329        return val;
330    }
331    let b = crate::abi::exports_xml2::object_to_xpathvalue_pub(val).as_boolean();
332    crate::abi::exports_xml2::xmlXPathFreeObject(val);
333    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
334    if obj.is_null() {
335        return ptr::null_mut();
336    }
337    (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
338    (*obj).boolval = if b { 1 } else { 0 };
339    obj
340}
341
342/// `xmlXPathObjectPtr xmlXPathConvertNumber(xmlXPathObjectPtr val)`.
343///
344/// # SAFETY
345///
346/// - `val` must be a valid object pointer or NULL.
347#[no_mangle]
348pub unsafe extern "C" fn xmlXPathConvertNumber(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
349    if val.is_null() {
350        return ptr::null_mut();
351    }
352    let typ = (*val).type_;
353    if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
354        return val;
355    }
356    let n = crate::abi::exports_xml2::object_to_xpathvalue_pub(val).as_number();
357    crate::abi::exports_xml2::xmlXPathFreeObject(val);
358    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
359    if obj.is_null() {
360        return ptr::null_mut();
361    }
362    (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
363    (*obj).floatval = n;
364    obj
365}
366
367/// `xmlXPathObjectPtr xmlXPathConvertString(xmlXPathObjectPtr val)`.
368///
369/// # SAFETY
370///
371/// - `val` must be a valid object pointer or NULL.
372#[no_mangle]
373pub unsafe extern "C" fn xmlXPathConvertString(val: *mut _xmlXPathObject) -> *mut _xmlXPathObject {
374    if val.is_null() {
375        return unsafe { xmlXPathNewString(ptr::null()) };
376    }
377    let typ = (*val).type_;
378    if typ == xmlXPathObjectType::XPATH_STRING as c_int {
379        return val;
380    }
381    let s = crate::abi::exports_xml2::object_to_xpathvalue_pub(val).as_string();
382    crate::abi::exports_xml2::xmlXPathFreeObject(val);
383    let buf = dup_rust_string(&s);
384    unsafe { xmlXPathWrapString(buf) }
385}
386
387// ═══════════════════════════════════════════════════════════════════════════════
388// Casts (value-level, no object allocation)
389// ═══════════════════════════════════════════════════════════════════════════════
390
391/// `int xmlXPathCastToBoolean(xmlXPathObjectPtr val)`.
392///
393/// # SAFETY
394///
395/// - `val` must be a valid object pointer or NULL.
396#[no_mangle]
397pub unsafe extern "C" fn xmlXPathCastToBoolean(val: *mut _xmlXPathObject) -> c_int {
398    if val.is_null() {
399        return 0;
400    }
401    crate::abi::exports_xml2::object_to_xpathvalue_pub(val).as_boolean() as c_int
402}
403
404/// `double xmlXPathCastToNumber(xmlXPathObjectPtr val)`.
405///
406/// # SAFETY
407///
408/// - `val` must be a valid object pointer or NULL.
409#[no_mangle]
410pub unsafe extern "C" fn xmlXPathCastToNumber(val: *mut _xmlXPathObject) -> c_double {
411    if val.is_null() {
412        return f64::NAN;
413    }
414    crate::abi::exports_xml2::object_to_xpathvalue_pub(val).as_number()
415}
416
417/// `double xmlXPathCastBooleanToNumber(int val)`.
418///
419/// # SAFETY
420///
421/// The function touches crate-global state only; it is safe
422/// as long as the caller respects the library's global
423/// initialization/cleanup ordering (xmlInitParser before use,
424/// xmlCleanupParser only after all users are done).
425///
426/// Violating the global lifecycle ordering, or calling this after
427/// teardown or from a signal handler, is undefined behavior.
428#[no_mangle]
429pub const unsafe extern "C" fn xmlXPathCastBooleanToNumber(val: c_int) -> c_double {
430    if val != 0 {
431        1.0
432    } else {
433        0.0
434    }
435}
436
437/// `xmlChar *xmlXPathCastBooleanToString(int val)`.
438///
439/// # SAFETY
440///
441/// The function touches crate-global state only; it is safe
442/// as long as the caller respects the library's global
443/// initialization/cleanup ordering (xmlInitParser before use,
444/// xmlCleanupParser only after all users are done).
445///
446/// Violating the global lifecycle ordering, or calling this after
447/// teardown or from a signal handler, is undefined behavior.
448#[no_mangle]
449pub unsafe extern "C" fn xmlXPathCastBooleanToString(val: c_int) -> *mut xmlChar {
450    if val != 0 {
451        xml_strdup(c"true".as_ptr() as *const xmlChar)
452    } else {
453        xml_strdup(c"false".as_ptr() as *const xmlChar)
454    }
455}
456
457/// `int xmlXPathCastNodeSetToBoolean(xmlNodeSetPtr ns)`.
458///
459/// # SAFETY
460///
461/// - `ns` must be a valid node-set pointer or NULL.
462#[no_mangle]
463pub unsafe extern "C" fn xmlXPathCastNodeSetToBoolean(ns: *mut _xmlNodeSet) -> c_int {
464    if ns.is_null() {
465        return 0;
466    }
467    (unsafe { (*ns).nodeNr > 0 }) as c_int
468}
469
470/// `double xmlXPathCastNodeSetToNumber(xmlNodeSetPtr ns)`.
471///
472/// # SAFETY
473///
474/// - `ns` must be a valid node-set pointer or NULL.
475#[no_mangle]
476pub unsafe extern "C" fn xmlXPathCastNodeSetToNumber(ns: *mut _xmlNodeSet) -> c_double {
477    unsafe {
478        let val = crate::abi::exports_xml2::object_to_xpathvalue_pub(xmlXPathWrapNodeSet(ns));
479        let n = val.as_number();
480        // The wrapper owns the node set — release without freeing.
481        n
482    }
483}
484
485/// `xmlChar *xmlXPathCastNodeSetToString(xmlNodeSetPtr ns)`.
486///
487/// # SAFETY
488///
489/// - `ns` must be a valid node-set pointer or NULL.
490#[no_mangle]
491pub unsafe extern "C" fn xmlXPathCastNodeSetToString(ns: *mut _xmlNodeSet) -> *mut xmlChar {
492    let val = crate::xml::xpath::types::XPathValue::NodeSet(unsafe { node_set_to_internal(ns) });
493    let s = val.as_string();
494    dup_rust_string(&s)
495}
496
497/// Convert a C node set into an internal NodeSet (copying the node pointers).
498unsafe fn node_set_to_internal(ns: *mut _xmlNodeSet) -> NodeSet {
499    let mut out = NodeSet::new();
500    if ns.is_null() {
501        return out;
502    }
503    let nr = unsafe { (*ns).nodeNr };
504    let tab = unsafe { (*ns).nodeTab };
505    if !tab.is_null() {
506        for i in 0..nr as isize {
507            out.push(unsafe { *tab.add(i as usize) });
508        }
509    }
510    out
511}
512
513/// `double xmlXPathCastNodeToNumber(xmlNodePtr node)`.
514///
515/// # SAFETY
516///
517/// - `node` must be a valid node pointer or NULL.
518#[no_mangle]
519pub unsafe extern "C" fn xmlXPathCastNodeToNumber(node: *mut _xmlNode) -> c_double {
520    let s = node_string_value(node);
521    crate::xml::xpath::types::string_to_number(&s)
522}
523
524/// `xmlChar *xmlXPathCastNodeToString(xmlNodePtr node)`.
525///
526/// # SAFETY
527///
528/// - `node` must be a valid node pointer or NULL.
529#[no_mangle]
530pub unsafe extern "C" fn xmlXPathCastNodeToString(node: *mut _xmlNode) -> *mut xmlChar {
531    let s = node_string_value(node);
532    dup_rust_string(&s)
533}
534
535/// `int xmlXPathCastNumberToBoolean(double val)`.
536///
537/// # SAFETY
538///
539/// The function touches crate-global state only; it is safe
540/// as long as the caller respects the library's global
541/// initialization/cleanup ordering (xmlInitParser before use,
542/// xmlCleanupParser only after all users are done).
543///
544/// Violating the global lifecycle ordering, or calling this after
545/// teardown or from a signal handler, is undefined behavior.
546#[no_mangle]
547pub unsafe extern "C" fn xmlXPathCastNumberToBoolean(val: c_double) -> c_int {
548    (val != 0.0 && !val.is_nan()) as c_int
549}
550
551/// `xmlChar *xmlXPathCastNumberToString(double val)`.
552///
553/// # SAFETY
554///
555/// The function touches crate-global state only; it is safe
556/// as long as the caller respects the library's global
557/// initialization/cleanup ordering (xmlInitParser before use,
558/// xmlCleanupParser only after all users are done).
559///
560/// Violating the global lifecycle ordering, or calling this after
561/// teardown or from a signal handler, is undefined behavior.
562#[no_mangle]
563pub unsafe extern "C" fn xmlXPathCastNumberToString(val: c_double) -> *mut xmlChar {
564    number_to_xmlstring(val)
565}
566
567/// `int xmlXPathCastStringToBoolean(const xmlChar *val)`.
568///
569/// # SAFETY
570///
571/// - `val` must be a valid NUL-terminated string or NULL.
572#[no_mangle]
573pub const unsafe extern "C" fn xmlXPathCastStringToBoolean(val: *const xmlChar) -> c_int {
574    if val.is_null() || unsafe { *val } == 0 {
575        0
576    } else {
577        1
578    }
579}
580
581/// `int xmlXPathIsNaN(double val)`.
582///
583/// # SAFETY
584///
585/// The function touches crate-global state only; it is safe
586/// as long as the caller respects the library's global
587/// initialization/cleanup ordering (xmlInitParser before use,
588/// xmlCleanupParser only after all users are done).
589///
590/// Violating the global lifecycle ordering, or calling this after
591/// teardown or from a signal handler, is undefined behavior.
592#[no_mangle]
593pub const unsafe extern "C" fn xmlXPathIsNaN(val: c_double) -> c_int {
594    val.is_nan() as c_int
595}
596
597/// `int xmlXPathIsInf(double val)` — 1 for +inf, -1 for -inf, 0 otherwise.
598///
599/// # SAFETY
600///
601/// The function touches crate-global state only; it is safe
602/// as long as the caller respects the library's global
603/// initialization/cleanup ordering (xmlInitParser before use,
604/// xmlCleanupParser only after all users are done).
605///
606/// Violating the global lifecycle ordering, or calling this after
607/// teardown or from a signal handler, is undefined behavior.
608#[no_mangle]
609pub unsafe extern "C" fn xmlXPathIsInf(val: c_double) -> c_int {
610    if val.is_infinite() {
611        if val > 0.0 {
612            1
613        } else {
614            -1
615        }
616    } else {
617        0
618    }
619}
620
621/// `double xmlXPathStringEvalNumber(const xmlChar *str)`.
622///
623/// # SAFETY
624///
625/// - `str` must be a valid NUL-terminated string or NULL.
626#[no_mangle]
627pub unsafe extern "C" fn xmlXPathStringEvalNumber(str_: *const xmlChar) -> c_double {
628    if str_.is_null() {
629        return f64::NAN;
630    }
631    let s = unsafe { crate::xml::string::xmlstr_to_string(str_) };
632    crate::xml::xpath::types::string_to_number(&s)
633}
634
635/// `int xmlXPathIsNodeType(const xmlChar *name)` — whether `name` is one of
636/// the XPath node-type names.
637///
638/// # SAFETY
639///
640/// - `name` must be a valid NUL-terminated string or NULL.
641#[no_mangle]
642pub unsafe extern "C" fn xmlXPathIsNodeType(name: *const xmlChar) -> c_int {
643    if name.is_null() {
644        return 0;
645    }
646    let s = unsafe { crate::xml::string::xmlstr_to_string(name) };
647    match s.as_str() {
648        "comment" | "text" | "processing-instruction" | "node" => 1,
649        _ => 0,
650    }
651}
652
653/// `void xmlXPathInit(void)` — no-op (the candidate needs no initialization).
654///
655/// # SAFETY
656///
657/// The function touches crate-global state only; it is safe
658/// as long as the caller respects the library's global
659/// initialization/cleanup ordering (xmlInitParser before use,
660/// xmlCleanupParser only after all users are done).
661///
662/// Violating the global lifecycle ordering, or calling this after
663/// teardown or from a signal handler, is undefined behavior.
664#[no_mangle]
665pub const unsafe extern "C" fn xmlXPathInit() {}
666
667/// `void xmlXPathErr(xmlXPathParserContextPtr ctxt, int error)` — stub entry
668/// (the parser-context error channel is set via the context bridge).
669///
670/// # SAFETY
671///
672/// - `ctxt` must be a valid parser context or NULL.
673#[no_mangle]
674pub unsafe extern "C" fn xmlXPathErr(
675    ctxt: *mut crate::xml::xpath::parser_context::XmlXPathParserContext,
676    error: c_int,
677) {
678    if !ctxt.is_null() {
679        unsafe { (*ctxt).error = error };
680    }
681}
682
683/// `void xmlXPatherror(xmlXPathParserContextPtr ctxt, const char *file, int line, int no)`.
684///
685/// # SAFETY
686///
687/// - `ctxt` must be a valid parser context or NULL.
688/// - `file` must be a valid string or NULL.
689#[no_mangle]
690pub unsafe extern "C" fn xmlXPatherror(
691    ctxt: *mut crate::xml::xpath::parser_context::XmlXPathParserContext,
692    _file: *const c_char,
693    _line: c_int,
694    _no: c_int,
695) {
696    if !ctxt.is_null() {
697        unsafe { (*ctxt).error = _no };
698    }
699}
700
701#[allow(unused)]
702const fn _unused_doc(_d: *mut _xmlDoc) {}
703
704// ═══════════════════════════════════════════════════════════════════════════════
705// Node-set operations
706// ═══════════════════════════════════════════════════════════════════════════════
707
708/// Internal: ensure a node set has room for one more node.
709unsafe fn node_set_grow(ns: *mut _xmlNodeSet) {
710    if ns.is_null() {
711        return;
712    }
713    unsafe {
714        let nr = (*ns).nodeNr;
715        let max = (*ns).nodeMax;
716        if nr < max {
717            return;
718        }
719        let new_max = if max <= 0 { 8 } else { max * 2 };
720        let new_tab = crate::abi::allocator::xmlReallocImpl(
721            (*ns).nodeTab as *mut c_void,
722            (new_max as usize) * size_of::<*mut _xmlNode>(),
723        ) as *mut *mut _xmlNode;
724        if !new_tab.is_null() {
725            (*ns).nodeTab = new_tab;
726            (*ns).nodeMax = new_max;
727        }
728    }
729}
730
731/// `int xmlXPathNodeSetContains(xmlNodeSetPtr cur, xmlNodePtr val)`.
732///
733/// # SAFETY
734///
735/// - `cur` must be a valid node set or NULL; `val` a valid node or NULL.
736#[no_mangle]
737pub unsafe extern "C" fn xmlXPathNodeSetContains(
738    cur: *mut _xmlNodeSet,
739    val: *mut _xmlNode,
740) -> c_int {
741    if cur.is_null() || val.is_null() {
742        return 0;
743    }
744    unsafe {
745        let nr = (*cur).nodeNr;
746        let tab = (*cur).nodeTab;
747        if !tab.is_null() {
748            for i in 0..nr as isize {
749                if *tab.add(i as usize) == val {
750                    return 1;
751                }
752            }
753        }
754    }
755    0
756}
757
758/// `int xmlXPathNodeSetAdd(xmlNodeSetPtr cur, xmlNodePtr val)` — adds `val` if
759/// not already present; returns 0 on success, -1 on error.
760///
761/// # SAFETY
762///
763/// - `cur` must be a valid node set or NULL; `val` a valid node or NULL.
764#[no_mangle]
765pub unsafe extern "C" fn xmlXPathNodeSetAdd(cur: *mut _xmlNodeSet, val: *mut _xmlNode) -> c_int {
766    if cur.is_null() || val.is_null() {
767        return -1;
768    }
769    if xmlXPathNodeSetContains(cur, val) != 0 {
770        return 0;
771    }
772    unsafe {
773        node_set_grow(cur);
774        if (*cur).nodeNr >= (*cur).nodeMax {
775            return -1;
776        }
777        let idx = (*cur).nodeNr as usize;
778        ptr::write((*cur).nodeTab.add(idx), val);
779        (*cur).nodeNr += 1;
780    }
781    0
782}
783
784/// `int xmlXPathNodeSetAddUnique(xmlNodeSetPtr cur, xmlNodePtr val)` — adds
785/// without a duplicate check.
786///
787/// # SAFETY
788///
789/// - `cur` must be a valid node set or NULL; `val` a valid node or NULL.
790#[no_mangle]
791pub unsafe extern "C" fn xmlXPathNodeSetAddUnique(
792    cur: *mut _xmlNodeSet,
793    val: *mut _xmlNode,
794) -> c_int {
795    if cur.is_null() || val.is_null() {
796        return -1;
797    }
798    unsafe {
799        node_set_grow(cur);
800        if (*cur).nodeNr >= (*cur).nodeMax {
801            return -1;
802        }
803        let idx = (*cur).nodeNr as usize;
804        ptr::write((*cur).nodeTab.add(idx), val);
805        (*cur).nodeNr += 1;
806    }
807    0
808}
809
810/// `int xmlXPathNodeSetAddNs(xmlNodeSetPtr cur, xmlNodePtr node, xmlNsPtr ns)`
811/// — adds the namespace declaration as a namespace node.
812///
813/// # SAFETY
814///
815/// - `cur` must be a valid node set or NULL.
816#[no_mangle]
817pub unsafe extern "C" fn xmlXPathNodeSetAddNs(
818    cur: *mut _xmlNodeSet,
819    _node: *mut _xmlNode,
820    ns: *mut _xmlNs,
821) -> c_int {
822    if cur.is_null() || ns.is_null() {
823        return -1;
824    }
825    // UPSTREAM-PARITY: namespace nodes are represented as the _xmlNs pointer
826    // cast to a node pointer; the reader exposes them via the same encoding.
827    let ns_node = ns as *mut _xmlNode;
828    if xmlXPathNodeSetContains(cur, ns_node) != 0 {
829        return 0;
830    }
831    xmlXPathNodeSetAddUnique(cur, ns_node)
832}
833
834/// `void xmlXPathNodeSetDel(xmlNodeSetPtr cur, xmlNodePtr val)` (upstream
835/// xpath.h — R-000176, the candidate previously returned an int).
836///
837/// # SAFETY
838///
839/// - `cur` must be a valid node set or NULL; `val` a valid node or NULL.
840#[no_mangle]
841pub unsafe extern "C" fn xmlXPathNodeSetDel(cur: *mut _xmlNodeSet, val: *mut _xmlNode) {
842    if cur.is_null() || val.is_null() {
843        return;
844    }
845    unsafe {
846        let nr = (*cur).nodeNr;
847        let tab = (*cur).nodeTab;
848        let mut found = -1;
849        if !tab.is_null() {
850            for i in 0..nr as isize {
851                if *tab.add(i as usize) == val {
852                    found = i as c_int;
853                    break;
854                }
855            }
856        }
857        if found >= 0 {
858            let fi = found as usize;
859            for i in fi..(nr as usize - 1) {
860                ptr::write(tab.add(i), *tab.add(i + 1));
861            }
862            (*cur).nodeNr -= 1;
863        }
864    }
865}
866
867/// `void xmlXPathNodeSetRemove(xmlNodeSetPtr cur, int val)` — remove by
868/// index (upstream xpath.h — R-000176).
869///
870/// # SAFETY
871///
872/// - `cur` must be a valid node set or NULL.
873#[no_mangle]
874pub unsafe extern "C" fn xmlXPathNodeSetRemove(cur: *mut _xmlNodeSet, val: c_int) {
875    if cur.is_null() || val < 0 {
876        return;
877    }
878    unsafe {
879        let nr = (*cur).nodeNr;
880        if val >= nr {
881            return;
882        }
883        let tab = (*cur).nodeTab;
884        let vi = val as usize;
885        for i in vi..(nr as usize - 1) {
886            ptr::write(tab.add(i), *tab.add(i + 1));
887        }
888        (*cur).nodeNr -= 1;
889    }
890}
891
892/// `void xmlXPathNodeSetSort(xmlNodeSetPtr set)` — sort in document order
893/// (duplicates removed, matching upstream xmlXPathNodeSetSort).
894///
895/// # SAFETY
896///
897/// - `set` must be a valid node set or NULL.
898#[no_mangle]
899pub unsafe extern "C" fn xmlXPathNodeSetSort(set: *mut _xmlNodeSet) {
900    if set.is_null() {
901        return;
902    }
903    unsafe {
904        let nr = (*set).nodeNr;
905        let tab = (*set).nodeTab;
906        if nr <= 1 || tab.is_null() {
907            return;
908        }
909        // Insertion sort in document order (upstream uses a bubble-ish sort
910        // with the same ordering predicate).
911        for i in 1..nr as usize {
912            let key = *tab.add(i);
913            let mut j = i;
914            while j > 0 {
915                let prev = *tab.add(j - 1);
916                if crate::xml::xpath::types::compare_document_order(prev, key)
917                    == core::cmp::Ordering::Greater
918                {
919                    ptr::write(tab.add(j), prev);
920                    j -= 1;
921                } else {
922                    break;
923                }
924            }
925            ptr::write(tab.add(j), key);
926        }
927        // Deduplicate (upstream xmlXPathNodeSetSort removes duplicates).
928        let mut w = 0usize;
929        for r in 0..nr as usize {
930            if w == 0 || *tab.add(w - 1) != *tab.add(r) {
931                ptr::write(tab.add(w), *tab.add(r));
932                w += 1;
933            }
934        }
935        (*set).nodeNr = w as c_int;
936    }
937}
938
939/// `xmlNodeSetPtr xmlXPathNodeSetMerge(xmlNodeSetPtr val1, xmlNodeSetPtr val2)`
940/// — merges val2 into val1 (nodes not already present), returns val1.
941///
942/// # SAFETY
943///
944/// - `val1`/`val2` must be valid node sets or NULL.
945#[no_mangle]
946pub unsafe extern "C" fn xmlXPathNodeSetMerge(
947    val1: *mut _xmlNodeSet,
948    val2: *mut _xmlNodeSet,
949) -> *mut _xmlNodeSet {
950    if val1.is_null() && val2.is_null() {
951        return ptr::null_mut();
952    }
953    if val1.is_null() {
954        // UPSTREAM-PARITY: merging into NULL returns a copy of val2.
955        let obj = xmlXPathNewNodeSetList(val2);
956        let ns = unsafe { (*obj).nodesetval as *mut _xmlNodeSet };
957        if obj.is_null() {
958            return ptr::null_mut();
959        }
960        return ns;
961    }
962    if val2.is_null() {
963        return val1;
964    }
965    unsafe {
966        let nr2 = (*val2).nodeNr;
967        let tab2 = (*val2).nodeTab;
968        if !tab2.is_null() {
969            for i in 0..nr2 as isize {
970                let n = *tab2.add(i as usize);
971                if xmlXPathNodeSetContains(val1, n) == 0 {
972                    xmlXPathNodeSetAddUnique(val1, n);
973                }
974            }
975        }
976    }
977    val1
978}
979
980/// `xmlNodeSetPtr xmlXPathDifference(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`
981/// — nodes in nodes1 not in nodes2 (document order).
982///
983/// # SAFETY
984///
985/// - `nodes1`/`nodes2` must be valid node sets or NULL.
986#[no_mangle]
987pub unsafe extern "C" fn xmlXPathDifference(
988    nodes1: *mut _xmlNodeSet,
989    nodes2: *mut _xmlNodeSet,
990) -> *mut _xmlNodeSet {
991    if nodes1.is_null() {
992        return ptr::null_mut();
993    }
994    let mut a = unsafe { node_set_to_internal(nodes1) };
995    a.sort();
996    let b = unsafe { node_set_to_internal(nodes2) };
997    let mut out = NodeSet::new();
998    for n in a.iter() {
999        if !b.contains(n) {
1000            out.push(n);
1001        }
1002    }
1003    out.sort();
1004    out.to_raw()
1005}
1006
1007/// `xmlNodeSetPtr xmlXPathIntersection(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1008///
1009/// # SAFETY
1010///
1011/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1012#[no_mangle]
1013pub unsafe extern "C" fn xmlXPathIntersection(
1014    nodes1: *mut _xmlNodeSet,
1015    nodes2: *mut _xmlNodeSet,
1016) -> *mut _xmlNodeSet {
1017    let a = unsafe { node_set_to_internal(nodes1) };
1018    let b = unsafe { node_set_to_internal(nodes2) };
1019    let mut out = NodeSet::new();
1020    for n in a.iter() {
1021        if b.contains(n) {
1022            out.push(n);
1023        }
1024    }
1025    out.sort();
1026    out.to_raw()
1027}
1028
1029/// `xmlNodeSetPtr xmlXPathDistinct(xmlNodeSetPtr nodes)`.
1030///
1031/// # SAFETY
1032///
1033/// - `nodes` must be a valid node set or NULL.
1034#[no_mangle]
1035pub unsafe extern "C" fn xmlXPathDistinct(nodes: *mut _xmlNodeSet) -> *mut _xmlNodeSet {
1036    if nodes.is_null() {
1037        return ptr::null_mut();
1038    }
1039    unsafe {
1040        xmlXPathNodeSetSort(nodes);
1041        nodes
1042    }
1043}
1044
1045/// `xmlNodeSetPtr xmlXPathDistinctSorted(xmlNodeSetPtr nodes)`.
1046///
1047/// # SAFETY
1048///
1049/// - `nodes` must be a valid node set or NULL.
1050#[no_mangle]
1051pub unsafe extern "C" fn xmlXPathDistinctSorted(nodes: *mut _xmlNodeSet) -> *mut _xmlNodeSet {
1052    if nodes.is_null() {
1053        return ptr::null_mut();
1054    }
1055    unsafe {
1056        let nr = (*nodes).nodeNr;
1057        let tab = (*nodes).nodeTab;
1058        let mut w = 0usize;
1059        if !tab.is_null() {
1060            for r in 0..nr as usize {
1061                if w == 0 || *tab.add(w - 1) != *tab.add(r) {
1062                    ptr::write(tab.add(w), *tab.add(r));
1063                    w += 1;
1064                }
1065            }
1066        }
1067        (*nodes).nodeNr = w as c_int;
1068        nodes
1069    }
1070}
1071
1072/// `int xmlXPathHasSameNodes(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1073///
1074/// # SAFETY
1075///
1076/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1077#[no_mangle]
1078pub unsafe extern "C" fn xmlXPathHasSameNodes(
1079    nodes1: *mut _xmlNodeSet,
1080    nodes2: *mut _xmlNodeSet,
1081) -> c_int {
1082    if nodes1.is_null() || nodes2.is_null() {
1083        return 0;
1084    }
1085    unsafe {
1086        let nr1 = (*nodes1).nodeNr;
1087        let nr2 = (*nodes2).nodeNr;
1088        if nr1 != nr2 {
1089            return 0;
1090        }
1091        let tab1 = (*nodes1).nodeTab;
1092        let tab2 = (*nodes2).nodeTab;
1093        for i in 0..nr1 as isize {
1094            let mut found = false;
1095            for j in 0..nr2 as isize {
1096                if *tab1.add(i as usize) == *tab2.add(j as usize) {
1097                    found = true;
1098                    break;
1099                }
1100            }
1101            if !found {
1102                return 0;
1103            }
1104        }
1105    }
1106    1
1107}
1108
1109/// Internal: leading/trailing helpers.
1110unsafe fn leading_nodes(nodes: &NodeSet, node: *mut _xmlNode) -> NodeSet {
1111    let mut out = NodeSet::new();
1112    for n in nodes.iter() {
1113        if n == node {
1114            break;
1115        }
1116        out.push(n);
1117    }
1118    out
1119}
1120
1121unsafe fn trailing_nodes(nodes: &NodeSet, node: *mut _xmlNode) -> NodeSet {
1122    let mut out = NodeSet::new();
1123    let mut seen = false;
1124    for n in nodes.iter() {
1125        if n == node {
1126            seen = true;
1127            continue;
1128        }
1129        if seen {
1130            out.push(n);
1131        }
1132    }
1133    out
1134}
1135
1136/// `xmlNodeSetPtr xmlXPathLeading(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1137///
1138/// # SAFETY
1139///
1140/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1141#[no_mangle]
1142pub unsafe extern "C" fn xmlXPathLeading(
1143    nodes1: *mut _xmlNodeSet,
1144    nodes2: *mut _xmlNodeSet,
1145) -> *mut _xmlNodeSet {
1146    if nodes1.is_null() {
1147        return ptr::null_mut();
1148    }
1149    let mut a = unsafe { node_set_to_internal(nodes1) };
1150    a.sort();
1151    let b = unsafe { node_set_to_internal(nodes2) };
1152    if b.is_empty() {
1153        let raw = a.to_raw();
1154        return raw;
1155    }
1156    let first = b.first().unwrap();
1157    let out = unsafe { leading_nodes(&a, first) };
1158    out.to_raw()
1159}
1160
1161/// `xmlNodeSetPtr xmlXPathLeadingSorted(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1162///
1163/// # SAFETY
1164///
1165/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1166#[no_mangle]
1167pub unsafe extern "C" fn xmlXPathLeadingSorted(
1168    nodes1: *mut _xmlNodeSet,
1169    nodes2: *mut _xmlNodeSet,
1170) -> *mut _xmlNodeSet {
1171    unsafe { xmlXPathLeading(nodes1, nodes2) }
1172}
1173
1174/// `xmlNodeSetPtr xmlXPathTrailing(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1175///
1176/// # SAFETY
1177///
1178/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1179#[no_mangle]
1180pub unsafe extern "C" fn xmlXPathTrailing(
1181    nodes1: *mut _xmlNodeSet,
1182    nodes2: *mut _xmlNodeSet,
1183) -> *mut _xmlNodeSet {
1184    if nodes1.is_null() {
1185        return ptr::null_mut();
1186    }
1187    let mut a = unsafe { node_set_to_internal(nodes1) };
1188    a.sort();
1189    let b = unsafe { node_set_to_internal(nodes2) };
1190    if b.is_empty() {
1191        return a.to_raw();
1192    }
1193    let last = b.last().unwrap();
1194    let out = unsafe { trailing_nodes(&a, last) };
1195    out.to_raw()
1196}
1197
1198/// `xmlNodeSetPtr xmlXPathTrailingSorted(xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2)`.
1199///
1200/// # SAFETY
1201///
1202/// - `nodes1`/`nodes2` must be valid node sets or NULL.
1203#[no_mangle]
1204pub unsafe extern "C" fn xmlXPathTrailingSorted(
1205    nodes1: *mut _xmlNodeSet,
1206    nodes2: *mut _xmlNodeSet,
1207) -> *mut _xmlNodeSet {
1208    unsafe { xmlXPathTrailing(nodes1, nodes2) }
1209}
1210
1211/// `xmlNodeSetPtr xmlXPathNodeLeading(xmlNodeSetPtr nodes, xmlNodePtr node)`.
1212///
1213/// # SAFETY
1214///
1215/// - `nodes` must be a valid node set or NULL; `node` a valid node or NULL.
1216#[no_mangle]
1217pub unsafe extern "C" fn xmlXPathNodeLeading(
1218    nodes: *mut _xmlNodeSet,
1219    node: *mut _xmlNode,
1220) -> *mut _xmlNodeSet {
1221    if nodes.is_null() {
1222        return ptr::null_mut();
1223    }
1224    let mut a = unsafe { node_set_to_internal(nodes) };
1225    a.sort();
1226    let out = unsafe { leading_nodes(&a, node) };
1227    out.to_raw()
1228}
1229
1230/// `xmlNodeSetPtr xmlXPathNodeLeadingSorted(xmlNodeSetPtr nodes, xmlNodePtr node)`.
1231///
1232/// # SAFETY
1233///
1234/// - `nodes` must be a valid node set or NULL; `node` a valid node or NULL.
1235#[no_mangle]
1236pub unsafe extern "C" fn xmlXPathNodeLeadingSorted(
1237    nodes: *mut _xmlNodeSet,
1238    node: *mut _xmlNode,
1239) -> *mut _xmlNodeSet {
1240    unsafe { xmlXPathNodeLeading(nodes, node) }
1241}
1242
1243/// `xmlNodeSetPtr xmlXPathNodeTrailing(xmlNodeSetPtr nodes, xmlNodePtr node)`.
1244///
1245/// # SAFETY
1246///
1247/// - `nodes` must be a valid node set or NULL; `node` a valid node or NULL.
1248#[no_mangle]
1249pub unsafe extern "C" fn xmlXPathNodeTrailing(
1250    nodes: *mut _xmlNodeSet,
1251    node: *mut _xmlNode,
1252) -> *mut _xmlNodeSet {
1253    if nodes.is_null() {
1254        return ptr::null_mut();
1255    }
1256    let mut a = unsafe { node_set_to_internal(nodes) };
1257    a.sort();
1258    let out = unsafe { trailing_nodes(&a, node) };
1259    out.to_raw()
1260}
1261
1262/// `xmlNodeSetPtr xmlXPathNodeTrailingSorted(xmlNodeSetPtr nodes, xmlNodePtr node)`.
1263///
1264/// # SAFETY
1265///
1266/// - `nodes` must be a valid node set or NULL; `node` a valid node or NULL.
1267#[no_mangle]
1268pub unsafe extern "C" fn xmlXPathNodeTrailingSorted(
1269    nodes: *mut _xmlNodeSet,
1270    node: *mut _xmlNode,
1271) -> *mut _xmlNodeSet {
1272    unsafe { xmlXPathNodeTrailing(nodes, node) }
1273}
1274
1275/// `void xmlXPathNodeSetFreeNs(xmlNsPtr ns)` — releases a synthesized
1276/// namespace node (upstream xpath.c `xmlXPathNodeSetFreeNs`).
1277///
1278/// UPSTREAM-PARITY: an XPath node-set that contains namespace nodes holds
1279/// *synthesized* copies whose `next` field points at the owner element (not
1280/// at another namespace declaration). Such nodes are freed here along with
1281/// their href/prefix; real namespace declarations (owned by the tree) are
1282/// left untouched.
1283///
1284/// # SAFETY
1285///
1286/// - `ns` must be a valid namespace pointer or NULL.
1287#[no_mangle]
1288pub unsafe extern "C" fn xmlXPathNodeSetFreeNs(ns: *mut _xmlNs) {
1289    unsafe {
1290        if ns.is_null() {
1291            return;
1292        }
1293        if (*ns).type_ != crate::abi::types::xmlElementType::XML_NAMESPACE_DECL as c_int {
1294            return;
1295        }
1296        // A synthesized namespace node's `next` is the owner element.
1297        if !(*ns).next.is_null()
1298            && (*(*ns).next).type_ != crate::abi::types::xmlElementType::XML_NAMESPACE_DECL as c_int
1299        {
1300            if !(*ns).href.is_null() {
1301                libc::free((*ns).href as *mut libc::c_void);
1302            }
1303            if !(*ns).prefix.is_null() {
1304                libc::free((*ns).prefix as *mut libc::c_void);
1305            }
1306            libc::free(ns as *mut libc::c_void);
1307        }
1308    }
1309}
1310
1311/// `XML_INTPTR_T xmlXPathOrderDocElems(xmlDocPtr doc)` (2.15 signature) —
1312/// indexes the document's elements in document order: each element's
1313/// `content` field is set to `-(n)` where n is its 1-based document-order
1314/// position, and the total element count is returned (-1 for NULL).
1315///
1316/// # UPSTREAM-PARITY
1317///
1318/// Upstream 2.13+ changed the return type from `xmlNodeSetPtr` to
1319/// `XML_INTPTR_T` (long). The element `content` slots are repurposed as the
1320/// document-order index (XML_INT_TO_PTR(-count)).
1321///
1322/// # SAFETY
1323///
1324/// - `doc` must be a valid document or NULL.
1325#[no_mangle]
1326pub unsafe extern "C" fn xmlXPathOrderDocElems(doc: *mut _xmlDoc) -> c_long {
1327    if doc.is_null() {
1328        return -1;
1329    }
1330    let mut count: c_long = 0;
1331    unsafe {
1332        let mut cur = (*doc).children;
1333        while !cur.is_null() {
1334            if (*cur).type_ == crate::abi::types::xmlElementType::XML_ELEMENT_NODE as c_int {
1335                count += 1;
1336                // Upstream stores the negative 1-based index in `content`
1337                // (XML_INT_TO_PTR(-count)); element nodes keep content NULL
1338                // otherwise, so this is non-destructive for our tree.
1339                (*cur).content = (-count) as *mut xmlChar;
1340                if !(*cur).children.is_null() {
1341                    cur = (*cur).children;
1342                    continue;
1343                }
1344            }
1345            if !(*cur).next.is_null() {
1346                cur = (*cur).next;
1347                continue;
1348            }
1349            loop {
1350                cur = (*cur).parent;
1351                if cur.is_null() {
1352                    break;
1353                }
1354                if cur == doc as *mut _xmlNode {
1355                    cur = ptr::null_mut();
1356                    break;
1357                }
1358                if !(*cur).next.is_null() {
1359                    cur = (*cur).next;
1360                    break;
1361                }
1362            }
1363        }
1364    }
1365    count
1366}
1367use std::collections::HashMap;
1368use std::ffi::{CStr, CString};
1369
1370use crate::abi::structs::_xmlAttr;
1371use crate::xml::validation::{get_id, is_xml_name_char, is_xml_name_start};
1372use crate::xml::xpath::context::XPathContext;
1373use crate::xml::xpath::parser_context::{
1374    cast_top_to_number, compare_values_impl, equal_values_impl, free_parser_context, new_bool,
1375    new_number, new_parser_context, pc_set_error, pop_boolean, pop_external, pop_node_set,
1376    pop_number, pop_string, value_pop, value_push, XmlXPathParserContext,
1377};
1378
1379// ── Shared helpers ──────────────────────────────────────────────────────
1380
1381/// Opaque `xmlXPathParserContextPtr` → typed pointer.
1382const unsafe fn pc_from(p: *mut c_void) -> *mut XmlXPathParserContext {
1383    p as *mut XmlXPathParserContext
1384}
1385
1386/// Byte-wise C string equality (upstream `xmlStrEqual`).
1387unsafe fn cstr_eq(a: *const xmlChar, b: *const xmlChar) -> bool {
1388    if a.is_null() || b.is_null() {
1389        return a == b;
1390    }
1391    let mut i = 0usize;
1392    loop {
1393        let ca = unsafe { *a.add(i) };
1394        let cb = unsafe { *b.add(i) };
1395        if ca != cb {
1396            return false;
1397        }
1398        if ca == 0 {
1399            return true;
1400        }
1401        i += 1;
1402    }
1403}
1404
1405/// Upstream IS_BLANK_CH: space, tab, LF, CR.
1406const unsafe fn is_blank_ch(c: xmlChar) -> bool {
1407    c == b' ' || c == b'\t' || c == b'\n' || c == b'\r'
1408}
1409
1410/// CAST_TO_STRING equivalent on the top-of-stack object (in place).
1411///
1412/// # SAFETY
1413///
1414/// - `pc` must be a valid parser context with a non-NULL `value`.
1415unsafe fn cast_top_to_string(pc: *mut XmlXPathParserContext) {
1416    unsafe {
1417        let val = (*pc).value;
1418        if val.is_null() {
1419            pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
1420            return;
1421        }
1422        if (*val).type_ != xmlXPathObjectType::XPATH_STRING as c_int {
1423            let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(val);
1424            let s = v.as_string();
1425            if !(*val).stringval.is_null() {
1426                xmlFreeImpl((*val).stringval as *mut c_void);
1427            }
1428            (*val).stringval = dup_rust_string(&s);
1429            (*val).type_ = xmlXPathObjectType::XPATH_STRING as c_int;
1430        }
1431    }
1432}
1433
1434/// CAST_TO_BOOLEAN equivalent on the top-of-stack object (in place).
1435///
1436/// # SAFETY
1437///
1438/// - `pc` must be a valid parser context with a non-NULL `value`.
1439unsafe fn cast_top_to_boolean(pc: *mut XmlXPathParserContext) {
1440    unsafe {
1441        let val = (*pc).value;
1442        if val.is_null() {
1443            pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
1444            return;
1445        }
1446        if (*val).type_ != xmlXPathObjectType::XPATH_BOOLEAN as c_int {
1447            let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(val);
1448            (*val).boolval = v.as_boolean() as c_int;
1449            (*val).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
1450        }
1451    }
1452}
1453
1454/// CHECK_ARITY equivalent: fails with XPATH_INVALID_ARITY when fewer than `n`
1455/// values are stacked.
1456unsafe fn check_arity(pc: *mut XmlXPathParserContext, n: c_int) -> bool {
1457    if pc.is_null() || (*pc).value_nr < n {
1458        pc_set_error(pc, crate::abi::types::XPATH_INVALID_ARITY as c_int);
1459        return false;
1460    }
1461    true
1462}
1463
1464/// Consume an XML Name / NCName from `cur` (NUL-terminated), returning the
1465/// number of bytes consumed. Mirrors upstream `xmlScanName(ptr, SIZE_MAX,
1466/// flags)` with XML 1.0 Fifth-Edition character classes.
1467const unsafe fn scan_c_name(cur: *const xmlChar, nc: bool) -> usize {
1468    if cur.is_null() {
1469        return 0;
1470    }
1471    let mut i = 0usize;
1472    let mut first = true;
1473    loop {
1474        let b = unsafe { *cur.add(i) };
1475        if b == 0 {
1476            break;
1477        }
1478        if nc && b == b':' {
1479            break;
1480        }
1481        let (ch, adv): (char, usize) = if b < 0x80 {
1482            (b as char, 1)
1483        } else if b >= 0xC0 && b <= 0xDF {
1484            (
1485                unsafe {
1486                    char::from_u32_unchecked(
1487                        ((b as u32 & 0x1F) << 6) | (*cur.add(i + 1) as u32 & 0x3F),
1488                    )
1489                },
1490                2,
1491            )
1492        } else if b >= 0xE0 && b <= 0xEF {
1493            (
1494                unsafe {
1495                    char::from_u32_unchecked(
1496                        ((b as u32 & 0x0F) << 12)
1497                            | ((*cur.add(i + 1) as u32 & 0x3F) << 6)
1498                            | (*cur.add(i + 2) as u32 & 0x3F),
1499                    )
1500                },
1501                3,
1502            )
1503        } else if b >= 0xF0 && b <= 0xF7 {
1504            (
1505                unsafe {
1506                    char::from_u32_unchecked(
1507                        ((b as u32 & 0x07) << 18)
1508                            | ((*cur.add(i + 1) as u32 & 0x3F) << 12)
1509                            | ((*cur.add(i + 2) as u32 & 0x3F) << 6)
1510                            | (*cur.add(i + 3) as u32 & 0x3F),
1511                    )
1512                },
1513                4,
1514            )
1515        } else {
1516            break;
1517        };
1518        let ok = if first {
1519            is_xml_name_start(ch)
1520        } else {
1521            is_xml_name_char(ch)
1522        };
1523        if !ok {
1524            break;
1525        }
1526        first = false;
1527        i += adv;
1528    }
1529    i
1530}
1531
1532/// Byte-wise substring search (upstream `xmlStrstr`).
1533unsafe fn cstr_find(hay: *const xmlChar, needle: *const xmlChar) -> *const xmlChar {
1534    if hay.is_null() || needle.is_null() {
1535        return ptr::null();
1536    }
1537    if unsafe { *needle } == 0 {
1538        return hay;
1539    }
1540    let hlen = unsafe { crate::xml::string::xml_strlen(hay) };
1541    let nlen = unsafe { crate::xml::string::xml_strlen(needle) };
1542    if nlen > hlen {
1543        return ptr::null();
1544    }
1545    let hay_b = unsafe { core::slice::from_raw_parts(hay, hlen) };
1546    let needle_b = unsafe { core::slice::from_raw_parts(needle, nlen) };
1547    for off in 0..=hlen - nlen {
1548        if &hay_b[off..off + nlen] == needle_b {
1549            return unsafe { hay.add(off) };
1550        }
1551    }
1552    ptr::null()
1553}
1554
1555/// The `xml:` namespace URI (upstream `XML_XML_NAMESPACE`).
1556const XML_XML_NAMESPACE_BYTES: &[u8] = b"http://www.w3.org/XML/1998/namespace\0";
1557
1558/// Static fake `xml` namespace node (upstream `xmlXPathXMLNamespace`).
1559/// Wrapped so the raw-pointer struct can live in a `static` (the pointer
1560/// fields are never written after construction).
1561struct XmlXPathXmlNs(_xmlNs);
1562unsafe impl Sync for XmlXPathXmlNs {}
1563static XML_XPATH_XML_NS: XmlXPathXmlNs = XmlXPathXmlNs(crate::abi::structs::_xmlNs {
1564    next: ptr::null_mut(),
1565    type_: crate::abi::types::xmlElementType::XML_NAMESPACE_DECL as c_int,
1566    href: XML_XML_NAMESPACE_BYTES.as_ptr() as *const xmlChar,
1567    prefix: c"xml".as_ptr() as *const xmlChar,
1568    _private: ptr::null_mut(),
1569    context: ptr::null_mut(),
1570});
1571
1572// Upstream `xmlXPathStringHash` (FNV-ish over the string bytes) is not
1573// observable through the public API; the node-set equality helpers below
1574// perform the full string comparison the hash only gates.
1575// ═══════════════════════════════════════════════════════════════════════════════
1576// Value stack operators (upstream xmlXPathValuePush/Pop + typed Pop*)
1577// ═══════════════════════════════════════════════════════════════════════════════
1578
1579/// `int xmlXPathValuePush(xmlXPathParserContextPtr ctxt, xmlXPathObjectPtr
1580/// value)` — returns the stack slot index of the pushed value (upstream
1581/// xpath.c `return (ctxt->valueNr++)`), -1 on NULL arguments. R-000176: the
1582/// candidate previously returned the pushed value pointer.
1583///
1584/// # SAFETY
1585///
1586/// - `ctxt` must be a valid parser context or NULL.
1587#[no_mangle]
1588pub unsafe extern "C" fn xmlXPathValuePush(
1589    ctxt: *mut c_void,
1590    value: *mut _xmlXPathObject,
1591) -> c_int {
1592    if ctxt.is_null() || value.is_null() {
1593        return -1;
1594    }
1595    let pc = pc_from(ctxt);
1596    // SAFETY: pc is non-NULL here; the slot index is the pre-increment
1597    // value_nr (upstream `return (ctxt->valueNr++)`).
1598    let idx = unsafe { (*pc).value_nr };
1599    value_push(pc, value);
1600    idx
1601}
1602
1603/// `xmlXPathObjectPtr xmlXPathValuePop(xmlXPathParserContextPtr ctxt)`.
1604///
1605/// # SAFETY
1606///
1607/// - `ctxt` must be a valid parser context or NULL.
1608#[no_mangle]
1609pub unsafe extern "C" fn xmlXPathValuePop(ctxt: *mut c_void) -> *mut _xmlXPathObject {
1610    value_pop(pc_from(ctxt))
1611}
1612
1613/// `int xmlXPathPopBoolean(xmlXPathParserContextPtr ctxt)`.
1614///
1615/// # SAFETY
1616///
1617/// - `ctxt` must be a valid parser context or NULL.
1618#[no_mangle]
1619pub unsafe extern "C" fn xmlXPathPopBoolean(ctxt: *mut c_void) -> c_int {
1620    pop_boolean(pc_from(ctxt))
1621}
1622
1623/// `void *xmlXPathPopExternal(xmlXPathParserContextPtr ctxt)`.
1624///
1625/// # SAFETY
1626///
1627/// - `ctxt` must be a valid parser context or NULL.
1628#[no_mangle]
1629pub unsafe extern "C" fn xmlXPathPopExternal(ctxt: *mut c_void) -> *mut c_void {
1630    pop_external(pc_from(ctxt))
1631}
1632
1633/// `xmlNodeSetPtr xmlXPathPopNodeSet(xmlXPathParserContextPtr ctxt)`.
1634///
1635/// # SAFETY
1636///
1637/// - `ctxt` must be a valid parser context or NULL.
1638#[no_mangle]
1639pub unsafe extern "C" fn xmlXPathPopNodeSet(ctxt: *mut c_void) -> *mut _xmlNodeSet {
1640    pop_node_set(pc_from(ctxt))
1641}
1642
1643/// `double xmlXPathPopNumber(xmlXPathParserContextPtr ctxt)`.
1644///
1645/// # SAFETY
1646///
1647/// - `ctxt` must be a valid parser context or NULL.
1648#[no_mangle]
1649pub unsafe extern "C" fn xmlXPathPopNumber(ctxt: *mut c_void) -> c_double {
1650    pop_number(pc_from(ctxt))
1651}
1652
1653/// `xmlChar *xmlXPathPopString(xmlXPathParserContextPtr ctxt)`.
1654///
1655/// # SAFETY
1656///
1657/// - `ctxt` must be a valid parser context or NULL.
1658#[no_mangle]
1659pub unsafe extern "C" fn xmlXPathPopString(ctxt: *mut c_void) -> *mut xmlChar {
1660    pop_string(pc_from(ctxt))
1661}
1662
1663/// Shared body of the in-place arithmetic operators: pops the right operand,
1664/// converts it to a number, converts the (remaining) top of stack to a number
1665/// and applies `op` to it in place. UPSTREAM-PARITY: `xmlXPathAddValues` etc.
1666/// operate on `ctxt->value` in place instead of pushing a fresh object.
1667unsafe fn binary_inplace(ctxt: *mut c_void, op: impl Fn(&mut f64, f64)) {
1668    let pc = pc_from(ctxt);
1669    if pc.is_null() {
1670        return;
1671    }
1672    let arg = value_pop(pc);
1673    if arg.is_null() {
1674        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
1675        return;
1676    }
1677    let val = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_number();
1678    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
1679    if unsafe { (*pc).value.is_null() } {
1680        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
1681        return;
1682    }
1683    cast_top_to_number(pc);
1684    if (*pc).error != 0 {
1685        return;
1686    }
1687    // Bind the field as a place before passing it by mutable reference
1688    // (a bare `&mut unsafe { ... }` would take the address of a temporary
1689    // copy of the float and the arithmetic would be lost).
1690    unsafe {
1691        let float_ref: &mut f64 = &mut (*(*pc).value).floatval;
1692        op(float_ref, val);
1693    }
1694}
1695
1696/// `void xmlXPathAddValues(xmlXPathParserContextPtr ctxt)`.
1697///
1698/// # SAFETY
1699///
1700/// - `ctxt` must be valid pointers (or NULL
1701///   where the upstream C contract allows), obtained from the
1702///   matching constructor/owner and not yet freed; the callee may
1703///   take or keep ownership exactly as the C API specifies.
1704///
1705/// The caller must not race this call with concurrent mutation of the
1706/// same objects from other threads (per-object state is not internally
1707/// synchronized). Violating any of the above is undefined behavior.
1708///
1709/// Exercised by the C-API differential courts
1710/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1711/// courts; those pass byte-for-byte against the upstream oracle.
1712#[no_mangle]
1713pub unsafe extern "C" fn xmlXPathAddValues(ctxt: *mut c_void) {
1714    binary_inplace(ctxt, |x, v| *x += v);
1715}
1716
1717/// `void xmlXPathSubValues(xmlXPathParserContextPtr ctxt)`.
1718///
1719/// # SAFETY
1720///
1721/// - `ctxt` must be valid pointers (or NULL
1722///   where the upstream C contract allows), obtained from the
1723///   matching constructor/owner and not yet freed; the callee may
1724///   take or keep ownership exactly as the C API specifies.
1725///
1726/// The caller must not race this call with concurrent mutation of the
1727/// same objects from other threads (per-object state is not internally
1728/// synchronized). Violating any of the above is undefined behavior.
1729///
1730/// Exercised by the C-API differential courts
1731/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1732/// courts; those pass byte-for-byte against the upstream oracle.
1733#[no_mangle]
1734pub unsafe extern "C" fn xmlXPathSubValues(ctxt: *mut c_void) {
1735    binary_inplace(ctxt, |x, v| *x -= v);
1736}
1737
1738/// `void xmlXPathMultValues(xmlXPathParserContextPtr ctxt)`.
1739///
1740/// # SAFETY
1741///
1742/// - `ctxt` must be valid pointers (or NULL
1743///   where the upstream C contract allows), obtained from the
1744///   matching constructor/owner and not yet freed; the callee may
1745///   take or keep ownership exactly as the C API specifies.
1746///
1747/// The caller must not race this call with concurrent mutation of the
1748/// same objects from other threads (per-object state is not internally
1749/// synchronized). Violating any of the above is undefined behavior.
1750///
1751/// Exercised by the C-API differential courts
1752/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1753/// courts; those pass byte-for-byte against the upstream oracle.
1754#[no_mangle]
1755pub unsafe extern "C" fn xmlXPathMultValues(ctxt: *mut c_void) {
1756    binary_inplace(ctxt, |x, v| *x *= v);
1757}
1758
1759/// `void xmlXPathDivValues(xmlXPathParserContextPtr ctxt)`.
1760///
1761/// # SAFETY
1762///
1763/// - `ctxt` must be valid pointers (or NULL
1764///   where the upstream C contract allows), obtained from the
1765///   matching constructor/owner and not yet freed; the callee may
1766///   take or keep ownership exactly as the C API specifies.
1767///
1768/// The caller must not race this call with concurrent mutation of the
1769/// same objects from other threads (per-object state is not internally
1770/// synchronized). Violating any of the above is undefined behavior.
1771///
1772/// Exercised by the C-API differential courts
1773/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1774/// courts; those pass byte-for-byte against the upstream oracle.
1775#[no_mangle]
1776pub unsafe extern "C" fn xmlXPathDivValues(ctxt: *mut c_void) {
1777    binary_inplace(ctxt, |x, v| *x /= v);
1778}
1779
1780/// `void xmlXPathModValues(xmlXPathParserContextPtr ctxt)`.
1781///
1782/// # SAFETY
1783///
1784/// - `ctxt` must be valid pointers (or NULL
1785///   where the upstream C contract allows), obtained from the
1786///   matching constructor/owner and not yet freed; the callee may
1787///   take or keep ownership exactly as the C API specifies.
1788///
1789/// The caller must not race this call with concurrent mutation of the
1790/// same objects from other threads (per-object state is not internally
1791/// synchronized). Violating any of the above is undefined behavior.
1792///
1793/// Exercised by the C-API differential courts
1794/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1795/// courts; those pass byte-for-byte against the upstream oracle.
1796#[no_mangle]
1797pub unsafe extern "C" fn xmlXPathModValues(ctxt: *mut c_void) {
1798    binary_inplace(ctxt, |x, v| *x %= v);
1799}
1800
1801/// `void xmlXPathValueFlipSign(xmlXPathParserContextPtr ctxt)` — unary minus.
1802///
1803/// # SAFETY
1804///
1805/// - `ctxt` must be valid pointers (or NULL
1806///   where the upstream C contract allows), obtained from the
1807///   matching constructor/owner and not yet freed; the callee may
1808///   take or keep ownership exactly as the C API specifies.
1809///
1810/// The caller must not race this call with concurrent mutation of the
1811/// same objects from other threads (per-object state is not internally
1812/// synchronized). Violating any of the above is undefined behavior.
1813///
1814/// Exercised by the C-API differential courts
1815/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1816/// courts; those pass byte-for-byte against the upstream oracle.
1817#[no_mangle]
1818pub unsafe extern "C" fn xmlXPathValueFlipSign(ctxt: *mut c_void) {
1819    let pc = pc_from(ctxt);
1820    if pc.is_null() {
1821        return;
1822    }
1823    cast_top_to_number(pc);
1824    if (*pc).error != 0 {
1825        return;
1826    }
1827    unsafe { (*(*pc).value).floatval = -(*(*pc).value).floatval };
1828}
1829
1830/// `int xmlXPathEqualValues(xmlXPathParserContextPtr ctxt)` — pops two values,
1831/// pushes the boolean result and returns it.
1832///
1833/// # SAFETY
1834///
1835/// - `ctxt` must be valid pointers (or NULL
1836///   where the upstream C contract allows), obtained from the
1837///   matching constructor/owner and not yet freed; the callee may
1838///   take or keep ownership exactly as the C API specifies.
1839///
1840/// The caller must not race this call with concurrent mutation of the
1841/// same objects from other threads (per-object state is not internally
1842/// synchronized). Violating any of the above is undefined behavior.
1843///
1844/// Exercised by the C-API differential courts
1845/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1846/// courts; those pass byte-for-byte against the upstream oracle.
1847#[no_mangle]
1848pub unsafe extern "C" fn xmlXPathEqualValues(ctxt: *mut c_void) -> c_int {
1849    equal_values_impl(pc_from(ctxt), false)
1850}
1851
1852/// `int xmlXPathNotEqualValues(xmlXPathParserContextPtr ctxt)`.
1853///
1854/// # SAFETY
1855///
1856/// - `ctxt` must be valid pointers (or NULL
1857///   where the upstream C contract allows), obtained from the
1858///   matching constructor/owner and not yet freed; the callee may
1859///   take or keep ownership exactly as the C API specifies.
1860///
1861/// The caller must not race this call with concurrent mutation of the
1862/// same objects from other threads (per-object state is not internally
1863/// synchronized). Violating any of the above is undefined behavior.
1864///
1865/// Exercised by the C-API differential courts
1866/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1867/// courts; those pass byte-for-byte against the upstream oracle.
1868#[no_mangle]
1869pub unsafe extern "C" fn xmlXPathNotEqualValues(ctxt: *mut c_void) -> c_int {
1870    equal_values_impl(pc_from(ctxt), true)
1871}
1872
1873/// `int xmlXPathCompareValues(xmlXPathParserContextPtr ctxt, int inf, int strict)`.
1874///
1875/// `inf`/`strict` encode the operator: `<`=(1,1), `<=`=(1,0), `>`=(0,1),
1876/// `>=`=(0,0). Returns the comparison result without pushing (upstream callers
1877/// push the boolean themselves).
1878///
1879/// # SAFETY
1880///
1881/// - `ctxt` must be valid pointers (or NULL
1882///   where the upstream C contract allows), obtained from the
1883///   matching constructor/owner and not yet freed; the callee may
1884///   take or keep ownership exactly as the C API specifies.
1885///
1886/// The caller must not race this call with concurrent mutation of the
1887/// same objects from other threads (per-object state is not internally
1888/// synchronized). Violating any of the above is undefined behavior.
1889///
1890/// Exercised by the C-API differential courts
1891/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1892/// courts; those pass byte-for-byte against the upstream oracle.
1893#[no_mangle]
1894pub unsafe extern "C" fn xmlXPathCompareValues(
1895    ctxt: *mut c_void,
1896    inf: c_int,
1897    strict: c_int,
1898) -> c_int {
1899    compare_values_impl(pc_from(ctxt), inf != 0, strict != 0)
1900}
1901
1902// ═══════════════════════════════════════════════════════════════════════════════
1903// Parser context
1904// ═══════════════════════════════════════════════════════════════════════════════
1905
1906/// `xmlXPathParserContextPtr xmlXPathNewParserContext(const xmlChar *str, xmlXPathContextPtr ctxt)`.
1907///
1908/// # SAFETY
1909///
1910/// - `str` must be a valid NUL-terminated string or NULL.
1911/// - `ctxt` must be a valid context or NULL.
1912#[no_mangle]
1913pub unsafe extern "C" fn xmlXPathNewParserContext(
1914    str_: *const xmlChar,
1915    ctxt: *mut _xmlXPathContext,
1916) -> *mut c_void {
1917    new_parser_context(str_, ctxt) as *mut c_void
1918}
1919
1920/// `void xmlXPathFreeParserContext(xmlXPathParserContextPtr ctxt)`.
1921///
1922/// # SAFETY
1923///
1924/// - `ctxt` must be a valid parser context or NULL.
1925#[no_mangle]
1926pub unsafe extern "C" fn xmlXPathFreeParserContext(ctxt: *mut c_void) {
1927    free_parser_context(pc_from(ctxt));
1928}
1929
1930/// `xmlChar *xmlXPathParseNCName(xmlXPathParserContextPtr ctxt)` — parses an
1931/// NCName from `ctxt->cur`, advancing it past the name.
1932///
1933/// # SAFETY
1934///
1935/// - `ctxt` must be a valid parser context.
1936#[no_mangle]
1937pub unsafe extern "C" fn xmlXPathParseNCName(ctxt: *mut c_void) -> *mut xmlChar {
1938    let pc = pc_from(ctxt);
1939    if pc.is_null() {
1940        return ptr::null_mut();
1941    }
1942    let cur = unsafe { (*pc).cur };
1943    if cur.is_null() {
1944        return ptr::null_mut();
1945    }
1946    let len = scan_c_name(cur, true);
1947    if len == 0 {
1948        return ptr::null_mut();
1949    }
1950    let ret = crate::xml::string::xml_strndup(cur, len);
1951    unsafe { (*pc).cur = cur.add(len) };
1952    ret
1953}
1954
1955/// `xmlChar *xmlXPathParseName(xmlXPathParserContextPtr ctxt)` — parses an XML
1956/// Name from `ctxt->cur`, advancing it past the name.
1957///
1958/// # SAFETY
1959///
1960/// - `ctxt` must be a valid parser context.
1961#[no_mangle]
1962pub unsafe extern "C" fn xmlXPathParseName(ctxt: *mut c_void) -> *mut xmlChar {
1963    let pc = pc_from(ctxt);
1964    if pc.is_null() {
1965        return ptr::null_mut();
1966    }
1967    let cur = unsafe { (*pc).cur };
1968    if cur.is_null() {
1969        return ptr::null_mut();
1970    }
1971    let len = scan_c_name(cur, false);
1972    if len == 0 {
1973        return ptr::null_mut();
1974    }
1975    let ret = crate::xml::string::xml_strndup(cur, len);
1976    unsafe { (*pc).cur = cur.add(len) };
1977    ret
1978}
1979
1980/// `void xmlXPathRoot(xmlXPathParserContextPtr ctxt)` — pushes a node-set
1981/// containing the document node.
1982///
1983/// # SAFETY
1984///
1985/// - `ctxt` must be a valid parser context.
1986#[no_mangle]
1987pub unsafe extern "C" fn xmlXPathRoot(ctxt: *mut c_void) {
1988    let pc = pc_from(ctxt);
1989    if pc.is_null() {
1990        return;
1991    }
1992    let ctx = unsafe { (*pc).context };
1993    if ctx.is_null() {
1994        return;
1995    }
1996    let ns = NodeSet::singleton(unsafe { (*ctx).doc } as *mut _xmlNode);
1997    let obj = crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(ns));
1998    value_push(pc, obj);
1999}
2000
2001/// `void xmlXPathEvalExpr(xmlXPathParserContextPtr ctxt)` — compiles and
2002/// evaluates the expression in `ctxt->base` against `ctxt->context` and pushes
2003/// the result object (upstream `xmlXPathCompileExpr` + `xmlXPathRunEval`).
2004///
2005/// # SAFETY
2006///
2007/// - `ctxt` must be a valid parser context.
2008#[no_mangle]
2009pub unsafe extern "C" fn xmlXPathEvalExpr(ctxt: *mut c_void) {
2010    let pc = pc_from(ctxt);
2011    if pc.is_null() {
2012        return;
2013    }
2014    let ctx = unsafe { (*pc).context };
2015    if ctx.is_null() {
2016        return;
2017    }
2018    let base = unsafe { (*pc).base };
2019    if base.is_null() {
2020        return;
2021    }
2022    let expr_str = match CStr::from_ptr(base as *const c_char).to_str() {
2023        Ok(s) => s,
2024        Err(_) => {
2025            pc_set_error(pc, crate::abi::types::XPATH_EXPR_ERROR as c_int);
2026            return;
2027        }
2028    };
2029    let internal = unsafe { (*ctx).extra } as *mut XPathContext;
2030    if internal.is_null() {
2031        return;
2032    }
2033    let internal = unsafe { &mut *internal };
2034    match crate::xml::xpath::evaluate_str(expr_str, internal) {
2035        Some(val) => {
2036            let obj = crate::abi::exports_xml2::xpath_to_object_pub(val);
2037            value_push(pc, obj);
2038        }
2039        None => {
2040            pc_set_error(pc, crate::abi::types::XPATH_EXPR_ERROR as c_int);
2041        }
2042    }
2043}
2044
2045/// Shared predicate-result evaluation (upstream `xmlXPathEvalPredicate`).
2046unsafe fn eval_predicate_result(ctxt: *mut _xmlXPathContext, res: *mut _xmlXPathObject) -> c_int {
2047    if ctxt.is_null() || res.is_null() {
2048        return 0;
2049    }
2050    unsafe {
2051        let t = (*res).type_;
2052        if t == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
2053            (*res).boolval
2054        } else if t == xmlXPathObjectType::XPATH_NUMBER as c_int {
2055            ((*res).floatval == (*ctxt).proximityPosition as f64) as c_int
2056        } else if t == xmlXPathObjectType::XPATH_NODESET as c_int
2057            || t == xmlXPathObjectType::XPATH_XSLT_TREE as c_int
2058        {
2059            let nsp = (*res).nodesetval as *mut _xmlNodeSet;
2060            if nsp.is_null() || (*nsp).nodeNr == 0 {
2061                0
2062            } else {
2063                1
2064            }
2065        } else if t == xmlXPathObjectType::XPATH_STRING as c_int {
2066            if (*res).stringval.is_null() || *(*res).stringval == 0 {
2067                0
2068            } else {
2069                1
2070            }
2071        } else {
2072            0
2073        }
2074    }
2075}
2076
2077/// `int xmlXPathEvalPredicate(xmlXPathContext *ctxt, xmlXPathObject *res)`
2078/// (2.15 signature).
2079///
2080/// # SAFETY
2081///
2082/// - `ctxt` must be a valid context or NULL; `res` a valid object or NULL.
2083#[no_mangle]
2084pub unsafe extern "C" fn xmlXPathEvalPredicate(
2085    ctxt: *mut _xmlXPathContext,
2086    res: *mut _xmlXPathObject,
2087) -> c_int {
2088    eval_predicate_result(ctxt, res)
2089}
2090
2091/// `int xmlXPathEvaluatePredicateResult(xmlXPathParserContextPtr ctxt, xmlXPathObject *res)`.
2092///
2093/// # SAFETY
2094///
2095/// - `ctxt` must be a valid parser context; `res` a valid object or NULL.
2096#[no_mangle]
2097pub unsafe extern "C" fn xmlXPathEvaluatePredicateResult(
2098    ctxt: *mut c_void,
2099    res: *mut _xmlXPathObject,
2100) -> c_int {
2101    let pc = pc_from(ctxt);
2102    if pc.is_null() {
2103        return 0;
2104    }
2105    let ctx = unsafe { (*pc).context };
2106    eval_predicate_result(ctx, res)
2107}
2108
2109// ═══════════════════════════════════════════════════════════════════════════════
2110// Axis traversal (xmlXPathNext*)
2111// ═══════════════════════════════════════════════════════════════════════════════
2112
2113/// `xmlNodePtr xmlXPathNextSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2114///
2115/// # SAFETY
2116///
2117/// - `ctxt`, `cur` must be valid pointers (or NULL
2118///   where the upstream C contract allows), obtained from the
2119///   matching constructor/owner and not yet freed; the callee may
2120///   take or keep ownership exactly as the C API specifies.
2121///
2122/// The caller must not race this call with concurrent mutation of the
2123/// same objects from other threads (per-object state is not internally
2124/// synchronized). Violating any of the above is undefined behavior.
2125///
2126/// Exercised by the C-API differential courts
2127/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2128/// courts; those pass byte-for-byte against the upstream oracle.
2129#[no_mangle]
2130pub unsafe extern "C" fn xmlXPathNextSelf(ctxt: *mut c_void, cur: *mut _xmlNode) -> *mut _xmlNode {
2131    let pc = pc_from(ctxt);
2132    if pc.is_null() {
2133        return ptr::null_mut();
2134    }
2135    let ctx = unsafe { (*pc).context };
2136    if ctx.is_null() {
2137        return ptr::null_mut();
2138    }
2139    if cur.is_null() {
2140        return unsafe { (*ctx).node };
2141    }
2142    ptr::null_mut()
2143}
2144
2145/// `xmlNodePtr xmlXPathNextChild(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2146///
2147/// # SAFETY
2148///
2149/// - `ctxt`, `cur` must be valid pointers (or NULL
2150///   where the upstream C contract allows), obtained from the
2151///   matching constructor/owner and not yet freed; the callee may
2152///   take or keep ownership exactly as the C API specifies.
2153///
2154/// The caller must not race this call with concurrent mutation of the
2155/// same objects from other threads (per-object state is not internally
2156/// synchronized). Violating any of the above is undefined behavior.
2157///
2158/// Exercised by the C-API differential courts
2159/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2160/// courts; those pass byte-for-byte against the upstream oracle.
2161#[no_mangle]
2162pub unsafe extern "C" fn xmlXPathNextChild(ctxt: *mut c_void, cur: *mut _xmlNode) -> *mut _xmlNode {
2163    let pc = pc_from(ctxt);
2164    if pc.is_null() {
2165        return ptr::null_mut();
2166    }
2167    let ctx = unsafe { (*pc).context };
2168    if ctx.is_null() {
2169        return ptr::null_mut();
2170    }
2171    use crate::abi::types::xmlElementType as ET;
2172    if cur.is_null() {
2173        let node = unsafe { (*ctx).node };
2174        if node.is_null() {
2175            return ptr::null_mut();
2176        }
2177        return match unsafe { (*node).type_ } {
2178            t if t == ET::XML_ELEMENT_NODE as c_int
2179                || t == ET::XML_TEXT_NODE as c_int
2180                || t == ET::XML_CDATA_SECTION_NODE as c_int
2181                || t == ET::XML_ENTITY_REF_NODE as c_int
2182                || t == ET::XML_ENTITY_NODE as c_int
2183                || t == ET::XML_PI_NODE as c_int
2184                || t == ET::XML_COMMENT_NODE as c_int
2185                || t == ET::XML_NOTATION_NODE as c_int
2186                || t == ET::XML_DTD_NODE as c_int =>
2187            unsafe { (*node).children },
2188            t if t == ET::XML_DOCUMENT_NODE as c_int
2189                || t == ET::XML_DOCUMENT_TYPE_NODE as c_int
2190                || t == ET::XML_DOCUMENT_FRAG_NODE as c_int
2191                || t == ET::XML_HTML_DOCUMENT_NODE as c_int =>
2192            unsafe { (*(node as *mut _xmlDoc)).children },
2193            _ => ptr::null_mut(),
2194        };
2195    }
2196    let t = unsafe { (*cur).type_ };
2197    if t == ET::XML_DOCUMENT_NODE as c_int || t == ET::XML_HTML_DOCUMENT_NODE as c_int {
2198        return ptr::null_mut();
2199    }
2200    unsafe { (*cur).next }
2201}
2202
2203/// `xmlNodePtr xmlXPathNextDescendant(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2204///
2205/// # SAFETY
2206///
2207/// - `ctxt` must be valid pointers (or NULL
2208///   where the upstream C contract allows), obtained from the
2209///   matching constructor/owner and not yet freed; the callee may
2210///   take or keep ownership exactly as the C API specifies.
2211///
2212/// The caller must not race this call with concurrent mutation of the
2213/// same objects from other threads (per-object state is not internally
2214/// synchronized). Violating any of the above is undefined behavior.
2215///
2216/// Exercised by the C-API differential courts
2217/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2218/// courts; those pass byte-for-byte against the upstream oracle.
2219#[no_mangle]
2220pub unsafe extern "C" fn xmlXPathNextDescendant(
2221    ctxt: *mut c_void,
2222    mut cur: *mut _xmlNode,
2223) -> *mut _xmlNode {
2224    let pc = pc_from(ctxt);
2225    if pc.is_null() {
2226        return ptr::null_mut();
2227    }
2228    let ctx = unsafe { (*pc).context };
2229    if ctx.is_null() {
2230        return ptr::null_mut();
2231    }
2232    use crate::abi::types::xmlElementType as ET;
2233    if cur.is_null() {
2234        let node = unsafe { (*ctx).node };
2235        if node.is_null() {
2236            return ptr::null_mut();
2237        }
2238        let t = unsafe { (*node).type_ };
2239        if t == ET::XML_ATTRIBUTE_NODE as c_int || t == ET::XML_NAMESPACE_DECL as c_int {
2240            return ptr::null_mut();
2241        }
2242        if node == unsafe { (*ctx).doc } as *mut _xmlNode {
2243            return unsafe { (*(*ctx).doc).children };
2244        }
2245        return unsafe { (*node).children };
2246    }
2247    unsafe {
2248        if (*cur).type_ == ET::XML_NAMESPACE_DECL as c_int {
2249            return ptr::null_mut();
2250        }
2251        if !(*cur).children.is_null() && (*(*cur).children).type_ != ET::XML_ENTITY_DECL as c_int {
2252            cur = (*cur).children;
2253            if (*cur).type_ != ET::XML_DTD_NODE as c_int {
2254                return cur;
2255            }
2256        }
2257        if cur == (*ctx).node {
2258            return ptr::null_mut();
2259        }
2260        while !(*cur).next.is_null() {
2261            cur = (*cur).next;
2262            if (*cur).type_ != ET::XML_ENTITY_DECL as c_int
2263                && (*cur).type_ != ET::XML_DTD_NODE as c_int
2264            {
2265                return cur;
2266            }
2267        }
2268        loop {
2269            cur = (*cur).parent;
2270            if cur.is_null() {
2271                break;
2272            }
2273            if cur == (*ctx).node {
2274                return ptr::null_mut();
2275            }
2276            if !(*cur).next.is_null() {
2277                cur = (*cur).next;
2278                return cur;
2279            }
2280        }
2281        cur
2282    }
2283}
2284
2285/// `xmlNodePtr xmlXPathNextDescendantOrSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2286///
2287/// # SAFETY
2288///
2289/// - `ctxt`, `cur` must be valid pointers (or NULL
2290///   where the upstream C contract allows), obtained from the
2291///   matching constructor/owner and not yet freed; the callee may
2292///   take or keep ownership exactly as the C API specifies.
2293///
2294/// The caller must not race this call with concurrent mutation of the
2295/// same objects from other threads (per-object state is not internally
2296/// synchronized). Violating any of the above is undefined behavior.
2297///
2298/// Exercised by the C-API differential courts
2299/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2300/// courts; those pass byte-for-byte against the upstream oracle.
2301#[no_mangle]
2302pub unsafe extern "C" fn xmlXPathNextDescendantOrSelf(
2303    ctxt: *mut c_void,
2304    cur: *mut _xmlNode,
2305) -> *mut _xmlNode {
2306    let pc = pc_from(ctxt);
2307    if pc.is_null() {
2308        return ptr::null_mut();
2309    }
2310    let ctx = unsafe { (*pc).context };
2311    if ctx.is_null() {
2312        return ptr::null_mut();
2313    }
2314    if cur.is_null() {
2315        return unsafe { (*ctx).node };
2316    }
2317    let node = unsafe { (*ctx).node };
2318    if node.is_null() {
2319        return ptr::null_mut();
2320    }
2321    use crate::abi::types::xmlElementType as ET;
2322    let t = unsafe { (*node).type_ };
2323    if t == ET::XML_ATTRIBUTE_NODE as c_int || t == ET::XML_NAMESPACE_DECL as c_int {
2324        return ptr::null_mut();
2325    }
2326    xmlXPathNextDescendant(ctxt, cur)
2327}
2328
2329/// `xmlNodePtr xmlXPathNextParent(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2330///
2331/// # SAFETY
2332///
2333/// - `ctxt`, `cur` must be valid pointers (or NULL
2334///   where the upstream C contract allows), obtained from the
2335///   matching constructor/owner and not yet freed; the callee may
2336///   take or keep ownership exactly as the C API specifies.
2337///
2338/// The caller must not race this call with concurrent mutation of the
2339/// same objects from other threads (per-object state is not internally
2340/// synchronized). Violating any of the above is undefined behavior.
2341///
2342/// Exercised by the C-API differential courts
2343/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2344/// courts; those pass byte-for-byte against the upstream oracle.
2345#[no_mangle]
2346pub unsafe extern "C" fn xmlXPathNextParent(
2347    ctxt: *mut c_void,
2348    cur: *mut _xmlNode,
2349) -> *mut _xmlNode {
2350    let pc = pc_from(ctxt);
2351    if pc.is_null() {
2352        return ptr::null_mut();
2353    }
2354    let ctx = unsafe { (*pc).context };
2355    if ctx.is_null() {
2356        return ptr::null_mut();
2357    }
2358    if !cur.is_null() {
2359        return ptr::null_mut();
2360    }
2361    next_parent_impl(ctx)
2362}
2363
2364/// Shared parent resolution (upstream `xmlXPathNextParent` / `xmlXPathNextAncestor`).
2365unsafe fn next_parent_impl(ctx: *mut _xmlXPathContext) -> *mut _xmlNode {
2366    use crate::abi::types::xmlElementType as ET;
2367    let node = unsafe { (*ctx).node };
2368    if node.is_null() {
2369        return ptr::null_mut();
2370    }
2371    match unsafe { (*node).type_ } {
2372        t if t == ET::XML_ELEMENT_NODE as c_int
2373            || t == ET::XML_TEXT_NODE as c_int
2374            || t == ET::XML_CDATA_SECTION_NODE as c_int
2375            || t == ET::XML_ENTITY_REF_NODE as c_int
2376            || t == ET::XML_ENTITY_NODE as c_int
2377            || t == ET::XML_PI_NODE as c_int
2378            || t == ET::XML_COMMENT_NODE as c_int
2379            || t == ET::XML_NOTATION_NODE as c_int
2380            || t == ET::XML_DTD_NODE as c_int
2381            || t == ET::XML_ELEMENT_DECL as c_int
2382            || t == ET::XML_ATTRIBUTE_DECL as c_int
2383            || t == ET::XML_ENTITY_DECL as c_int
2384            || t == ET::XML_XINCLUDE_START as c_int
2385            || t == ET::XML_XINCLUDE_END as c_int =>
2386        unsafe {
2387            let parent = (*node).parent;
2388            if parent.is_null() {
2389                return (*ctx).doc as *mut _xmlNode;
2390            }
2391            if (*parent).type_ == ET::XML_ELEMENT_NODE as c_int
2392                && ((*parent).name.is_null() || *(*parent).name == b' ')
2393            {
2394                return ptr::null_mut();
2395            }
2396            parent
2397        },
2398        t if t == ET::XML_ATTRIBUTE_NODE as c_int => unsafe { (*(node as *mut _xmlAttr)).parent },
2399        t if t == ET::XML_DOCUMENT_NODE as c_int
2400            || t == ET::XML_DOCUMENT_TYPE_NODE as c_int
2401            || t == ET::XML_DOCUMENT_FRAG_NODE as c_int
2402            || t == ET::XML_HTML_DOCUMENT_NODE as c_int =>
2403        {
2404            ptr::null_mut()
2405        }
2406        t if t == ET::XML_NAMESPACE_DECL as c_int => unsafe {
2407            let ns = node as *mut crate::abi::structs::_xmlNs;
2408            if !(*ns).next.is_null() && (*(*ns).next).type_ != ET::XML_NAMESPACE_DECL as c_int {
2409                (*ns).next as *mut _xmlNode
2410            } else {
2411                ptr::null_mut()
2412            }
2413        },
2414        _ => ptr::null_mut(),
2415    }
2416}
2417
2418/// `xmlNodePtr xmlXPathNextAncestor(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2419///
2420/// # SAFETY
2421///
2422/// - `ctxt`, `cur` must be valid pointers (or NULL
2423///   where the upstream C contract allows), obtained from the
2424///   matching constructor/owner and not yet freed; the callee may
2425///   take or keep ownership exactly as the C API specifies.
2426///
2427/// The caller must not race this call with concurrent mutation of the
2428/// same objects from other threads (per-object state is not internally
2429/// synchronized). Violating any of the above is undefined behavior.
2430///
2431/// Exercised by the C-API differential courts
2432/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2433/// courts; those pass byte-for-byte against the upstream oracle.
2434#[no_mangle]
2435pub unsafe extern "C" fn xmlXPathNextAncestor(
2436    ctxt: *mut c_void,
2437    cur: *mut _xmlNode,
2438) -> *mut _xmlNode {
2439    let pc = pc_from(ctxt);
2440    if pc.is_null() {
2441        return ptr::null_mut();
2442    }
2443    let ctx = unsafe { (*pc).context };
2444    if ctx.is_null() {
2445        return ptr::null_mut();
2446    }
2447    use crate::abi::types::xmlElementType as ET;
2448    if cur.is_null() {
2449        let node = unsafe { (*ctx).node };
2450        if node.is_null() {
2451            return ptr::null_mut();
2452        }
2453        let t = unsafe { (*node).type_ };
2454        if t == ET::XML_ATTRIBUTE_NODE as c_int {
2455            return unsafe { (*(node as *mut _xmlAttr)).parent };
2456        }
2457        if t == ET::XML_NAMESPACE_DECL as c_int {
2458            let ns = node as *mut crate::abi::structs::_xmlNs;
2459            return unsafe {
2460                if !(*ns).next.is_null() && (*(*ns).next).type_ != ET::XML_NAMESPACE_DECL as c_int {
2461                    (*ns).next as *mut _xmlNode
2462                } else {
2463                    ptr::null_mut()
2464                }
2465            };
2466        }
2467        if t == ET::XML_DOCUMENT_NODE as c_int
2468            || t == ET::XML_DOCUMENT_TYPE_NODE as c_int
2469            || t == ET::XML_DOCUMENT_FRAG_NODE as c_int
2470            || t == ET::XML_HTML_DOCUMENT_NODE as c_int
2471        {
2472            return ptr::null_mut();
2473        }
2474        // element/text/cdata/entity-ref/entity/pi/comment/dtd/decls: parent or doc
2475        return next_parent_impl(ctx);
2476    }
2477    if cur == unsafe { (*ctx).doc } as *mut _xmlNode {
2478        return ptr::null_mut();
2479    }
2480    if cur == unsafe { (*(*ctx).doc).children } {
2481        return unsafe { (*ctx).doc } as *mut _xmlNode;
2482    }
2483    let t = unsafe { (*cur).type_ };
2484    if t == ET::XML_ATTRIBUTE_NODE as c_int {
2485        return unsafe { (*(cur as *mut _xmlAttr)).parent };
2486    }
2487    if t == ET::XML_NAMESPACE_DECL as c_int {
2488        let ns = cur as *mut crate::abi::structs::_xmlNs;
2489        return unsafe {
2490            if !(*ns).next.is_null() && (*(*ns).next).type_ != ET::XML_NAMESPACE_DECL as c_int {
2491                (*ns).next as *mut _xmlNode
2492            } else {
2493                ptr::null_mut()
2494            }
2495        };
2496    }
2497    if t == ET::XML_DOCUMENT_NODE as c_int
2498        || t == ET::XML_DOCUMENT_TYPE_NODE as c_int
2499        || t == ET::XML_DOCUMENT_FRAG_NODE as c_int
2500        || t == ET::XML_HTML_DOCUMENT_NODE as c_int
2501    {
2502        return ptr::null_mut();
2503    }
2504    unsafe {
2505        let parent = (*cur).parent;
2506        if parent.is_null() {
2507            return ptr::null_mut();
2508        }
2509        if (*parent).type_ == ET::XML_ELEMENT_NODE as c_int
2510            && ((*parent).name.is_null() || *(*parent).name == b' ')
2511        {
2512            return ptr::null_mut();
2513        }
2514        parent
2515    }
2516}
2517
2518/// `xmlNodePtr xmlXPathNextAncestorOrSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2519///
2520/// # SAFETY
2521///
2522/// - `ctxt`, `cur` must be valid pointers (or NULL
2523///   where the upstream C contract allows), obtained from the
2524///   matching constructor/owner and not yet freed; the callee may
2525///   take or keep ownership exactly as the C API specifies.
2526///
2527/// The caller must not race this call with concurrent mutation of the
2528/// same objects from other threads (per-object state is not internally
2529/// synchronized). Violating any of the above is undefined behavior.
2530///
2531/// Exercised by the C-API differential courts
2532/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2533/// courts; those pass byte-for-byte against the upstream oracle.
2534#[no_mangle]
2535pub unsafe extern "C" fn xmlXPathNextAncestorOrSelf(
2536    ctxt: *mut c_void,
2537    cur: *mut _xmlNode,
2538) -> *mut _xmlNode {
2539    let pc = pc_from(ctxt);
2540    if pc.is_null() {
2541        return ptr::null_mut();
2542    }
2543    let ctx = unsafe { (*pc).context };
2544    if ctx.is_null() {
2545        return ptr::null_mut();
2546    }
2547    if cur.is_null() {
2548        return unsafe { (*ctx).node };
2549    }
2550    xmlXPathNextAncestor(ctxt, cur)
2551}
2552
2553/// `xmlNodePtr xmlXPathNextFollowingSibling(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2554///
2555/// # SAFETY
2556///
2557/// - `ctxt` must be valid pointers (or NULL
2558///   where the upstream C contract allows), obtained from the
2559///   matching constructor/owner and not yet freed; the callee may
2560///   take or keep ownership exactly as the C API specifies.
2561///
2562/// The caller must not race this call with concurrent mutation of the
2563/// same objects from other threads (per-object state is not internally
2564/// synchronized). Violating any of the above is undefined behavior.
2565///
2566/// Exercised by the C-API differential courts
2567/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2568/// courts; those pass byte-for-byte against the upstream oracle.
2569#[no_mangle]
2570pub unsafe extern "C" fn xmlXPathNextFollowingSibling(
2571    ctxt: *mut c_void,
2572    mut cur: *mut _xmlNode,
2573) -> *mut _xmlNode {
2574    let pc = pc_from(ctxt);
2575    if pc.is_null() {
2576        return ptr::null_mut();
2577    }
2578    let ctx = unsafe { (*pc).context };
2579    if ctx.is_null() {
2580        return ptr::null_mut();
2581    }
2582    use crate::abi::types::xmlElementType as ET;
2583    unsafe {
2584        let cnode = (*ctx).node;
2585        if !cnode.is_null() {
2586            let t = (*cnode).type_;
2587            if t == ET::XML_ATTRIBUTE_NODE as c_int || t == ET::XML_NAMESPACE_DECL as c_int {
2588                return ptr::null_mut();
2589            }
2590        }
2591        if cur == (*ctx).doc as *mut _xmlNode {
2592            return ptr::null_mut();
2593        }
2594        if cur.is_null() {
2595            cur = cnode;
2596        }
2597        if cur.is_null() {
2598            return ptr::null_mut();
2599        }
2600        if (*cur).type_ == ET::XML_DOCUMENT_NODE as c_int {
2601            return ptr::null_mut();
2602        }
2603        (*cur).next
2604    }
2605}
2606
2607/// `xmlNodePtr xmlXPathNextPrecedingSibling(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2608///
2609/// # SAFETY
2610///
2611/// - `ctxt` must be valid pointers (or NULL
2612///   where the upstream C contract allows), obtained from the
2613///   matching constructor/owner and not yet freed; the callee may
2614///   take or keep ownership exactly as the C API specifies.
2615///
2616/// The caller must not race this call with concurrent mutation of the
2617/// same objects from other threads (per-object state is not internally
2618/// synchronized). Violating any of the above is undefined behavior.
2619///
2620/// Exercised by the C-API differential courts
2621/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2622/// courts; those pass byte-for-byte against the upstream oracle.
2623#[no_mangle]
2624pub unsafe extern "C" fn xmlXPathNextPrecedingSibling(
2625    ctxt: *mut c_void,
2626    mut cur: *mut _xmlNode,
2627) -> *mut _xmlNode {
2628    let pc = pc_from(ctxt);
2629    if pc.is_null() {
2630        return ptr::null_mut();
2631    }
2632    let ctx = unsafe { (*pc).context };
2633    if ctx.is_null() {
2634        return ptr::null_mut();
2635    }
2636    use crate::abi::types::xmlElementType as ET;
2637    unsafe {
2638        let cnode = (*ctx).node;
2639        if !cnode.is_null() {
2640            let t = (*cnode).type_;
2641            if t == ET::XML_ATTRIBUTE_NODE as c_int || t == ET::XML_NAMESPACE_DECL as c_int {
2642                return ptr::null_mut();
2643            }
2644        }
2645        if cur == (*ctx).doc as *mut _xmlNode {
2646            return ptr::null_mut();
2647        }
2648        if cur.is_null() {
2649            cur = cnode;
2650        } else if !(*cur).prev.is_null() && (*(*cur).prev).type_ == ET::XML_DTD_NODE as c_int {
2651            cur = (*cur).prev;
2652            if cur.is_null() {
2653                return ptr::null_mut();
2654            }
2655        }
2656        if cur.is_null() {
2657            return ptr::null_mut();
2658        }
2659        if (*cur).type_ == ET::XML_DOCUMENT_NODE as c_int {
2660            return ptr::null_mut();
2661        }
2662        (*cur).prev
2663    }
2664}
2665
2666/// `xmlNodePtr xmlXPathNextFollowing(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2667///
2668/// # SAFETY
2669///
2670/// - `ctxt` must be valid pointers (or NULL
2671///   where the upstream C contract allows), obtained from the
2672///   matching constructor/owner and not yet freed; the callee may
2673///   take or keep ownership exactly as the C API specifies.
2674///
2675/// The caller must not race this call with concurrent mutation of the
2676/// same objects from other threads (per-object state is not internally
2677/// synchronized). Violating any of the above is undefined behavior.
2678///
2679/// Exercised by the C-API differential courts
2680/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2681/// courts; those pass byte-for-byte against the upstream oracle.
2682#[no_mangle]
2683pub unsafe extern "C" fn xmlXPathNextFollowing(
2684    ctxt: *mut c_void,
2685    mut cur: *mut _xmlNode,
2686) -> *mut _xmlNode {
2687    let pc = pc_from(ctxt);
2688    if pc.is_null() {
2689        return ptr::null_mut();
2690    }
2691    let ctx = unsafe { (*pc).context };
2692    if ctx.is_null() {
2693        return ptr::null_mut();
2694    }
2695    use crate::abi::types::xmlElementType as ET;
2696    unsafe {
2697        if !cur.is_null()
2698            && (*cur).type_ != ET::XML_ATTRIBUTE_NODE as c_int
2699            && (*cur).type_ != ET::XML_NAMESPACE_DECL as c_int
2700            && !(*cur).children.is_null()
2701        {
2702            return (*cur).children;
2703        }
2704        if cur.is_null() {
2705            cur = (*ctx).node;
2706            if cur.is_null() {
2707                return ptr::null_mut();
2708            }
2709            if (*cur).type_ == ET::XML_ATTRIBUTE_NODE as c_int {
2710                cur = (*cur).parent;
2711            } else if (*cur).type_ == ET::XML_NAMESPACE_DECL as c_int {
2712                let ns = cur as *mut crate::abi::structs::_xmlNs;
2713                if (*ns).next.is_null() || (*(*ns).next).type_ == ET::XML_NAMESPACE_DECL as c_int {
2714                    return ptr::null_mut();
2715                }
2716                cur = (*ns).next as *mut _xmlNode;
2717            }
2718        }
2719        if cur.is_null() {
2720            return ptr::null_mut();
2721        }
2722        if (*cur).type_ == ET::XML_DOCUMENT_NODE as c_int {
2723            return ptr::null_mut();
2724        }
2725        if !(*cur).next.is_null() {
2726            return (*cur).next;
2727        }
2728        loop {
2729            cur = (*cur).parent;
2730            if cur.is_null() {
2731                break;
2732            }
2733            if cur == (*ctx).doc as *mut _xmlNode {
2734                return ptr::null_mut();
2735            }
2736            if !(*cur).next.is_null() && (*cur).type_ != ET::XML_DOCUMENT_NODE as c_int {
2737                return (*cur).next;
2738            }
2739        }
2740        cur
2741    }
2742}
2743
2744/// `xmlNodePtr xmlXPathNextPreceding(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2745///
2746/// # SAFETY
2747///
2748/// - `ctxt` must be valid pointers (or NULL
2749///   where the upstream C contract allows), obtained from the
2750///   matching constructor/owner and not yet freed; the callee may
2751///   take or keep ownership exactly as the C API specifies.
2752///
2753/// The caller must not race this call with concurrent mutation of the
2754/// same objects from other threads (per-object state is not internally
2755/// synchronized). Violating any of the above is undefined behavior.
2756///
2757/// Exercised by the C-API differential courts
2758/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2759/// courts; those pass byte-for-byte against the upstream oracle.
2760#[no_mangle]
2761pub unsafe extern "C" fn xmlXPathNextPreceding(
2762    ctxt: *mut c_void,
2763    mut cur: *mut _xmlNode,
2764) -> *mut _xmlNode {
2765    let pc = pc_from(ctxt);
2766    if pc.is_null() {
2767        return ptr::null_mut();
2768    }
2769    let ctx = unsafe { (*pc).context };
2770    if ctx.is_null() {
2771        return ptr::null_mut();
2772    }
2773    use crate::abi::types::xmlElementType as ET;
2774    unsafe {
2775        let is_ancestor = |ancestor: *mut _xmlNode, node: *mut _xmlNode| -> bool {
2776            if ancestor.is_null() || node.is_null() {
2777                return false;
2778            }
2779            if (*node).type_ == ET::XML_NAMESPACE_DECL as c_int
2780                || (*ancestor).type_ == ET::XML_NAMESPACE_DECL as c_int
2781            {
2782                return false;
2783            }
2784            if (*ancestor).doc != (*node).doc {
2785                return false;
2786            }
2787            if ancestor == (*node).doc as *mut _xmlNode {
2788                return true;
2789            }
2790            if node == (*ancestor).doc as *mut _xmlNode {
2791                return false;
2792            }
2793            let mut n = node;
2794            while !(*n).parent.is_null() {
2795                if (*n).parent == ancestor {
2796                    return true;
2797                }
2798                n = (*n).parent;
2799            }
2800            false
2801        };
2802        if cur.is_null() {
2803            cur = (*ctx).node;
2804            if cur.is_null() {
2805                return ptr::null_mut();
2806            }
2807            if (*cur).type_ == ET::XML_ATTRIBUTE_NODE as c_int {
2808                cur = (*cur).parent;
2809            } else if (*cur).type_ == ET::XML_NAMESPACE_DECL as c_int {
2810                let ns = cur as *mut crate::abi::structs::_xmlNs;
2811                if (*ns).next.is_null() || (*(*ns).next).type_ == ET::XML_NAMESPACE_DECL as c_int {
2812                    return ptr::null_mut();
2813                }
2814                cur = (*ns).next as *mut _xmlNode;
2815            }
2816        }
2817        if cur.is_null() || (*cur).type_ == ET::XML_NAMESPACE_DECL as c_int {
2818            return ptr::null_mut();
2819        }
2820        if !(*cur).prev.is_null() && (*(*cur).prev).type_ == ET::XML_DTD_NODE as c_int {
2821            cur = (*cur).prev;
2822        }
2823        loop {
2824            if !(*cur).prev.is_null() {
2825                let mut n = (*cur).prev;
2826                while !(*n).last.is_null() {
2827                    n = (*n).last;
2828                }
2829                return n;
2830            }
2831            cur = (*cur).parent;
2832            if cur.is_null() {
2833                return ptr::null_mut();
2834            }
2835            if cur == (*(*ctx).doc).children {
2836                return ptr::null_mut();
2837            }
2838            if !is_ancestor(cur, (*ctx).node) {
2839                return cur;
2840            }
2841        }
2842    }
2843}
2844
2845/// `xmlNodePtr xmlXPathNextNamespace(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2846///
2847/// # SAFETY
2848///
2849/// - `ctxt`, `cur` must be valid pointers (or NULL
2850///   where the upstream C contract allows), obtained from the
2851///   matching constructor/owner and not yet freed; the callee may
2852///   take or keep ownership exactly as the C API specifies.
2853///
2854/// The caller must not race this call with concurrent mutation of the
2855/// same objects from other threads (per-object state is not internally
2856/// synchronized). Violating any of the above is undefined behavior.
2857///
2858/// Exercised by the C-API differential courts
2859/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2860/// courts; those pass byte-for-byte against the upstream oracle.
2861#[no_mangle]
2862pub unsafe extern "C" fn xmlXPathNextNamespace(
2863    ctxt: *mut c_void,
2864    cur: *mut _xmlNode,
2865) -> *mut _xmlNode {
2866    let pc = pc_from(ctxt);
2867    if pc.is_null() {
2868        return ptr::null_mut();
2869    }
2870    let ctx = unsafe { (*pc).context };
2871    if ctx.is_null() {
2872        return ptr::null_mut();
2873    }
2874    use crate::abi::types::xmlElementType as ET;
2875    unsafe {
2876        let cnode = (*ctx).node;
2877        if cnode.is_null() || (*cnode).type_ != ET::XML_ELEMENT_NODE as c_int {
2878            return ptr::null_mut();
2879        }
2880        if cur.is_null() {
2881            if !(*ctx).tmpNsList.is_null() {
2882                xmlFreeImpl((*ctx).tmpNsList as *mut c_void);
2883            }
2884            (*ctx).tmpNsNr = 0;
2885            (*ctx).tmpNsList = crate::xml::tree::get_ns_list((*ctx).doc, cnode);
2886            if !(*ctx).tmpNsList.is_null() {
2887                while !(*(*ctx).tmpNsList.add((*ctx).tmpNsNr as usize)).is_null() {
2888                    (*ctx).tmpNsNr += 1;
2889                }
2890            }
2891            return (&XML_XPATH_XML_NS.0) as *const crate::abi::structs::_xmlNs as *mut _xmlNode;
2892        }
2893        if (*ctx).tmpNsNr > 0 {
2894            (*ctx).tmpNsNr -= 1;
2895            return (*(*ctx).tmpNsList.add((*ctx).tmpNsNr as usize)) as *mut _xmlNode;
2896        }
2897        if !(*ctx).tmpNsList.is_null() {
2898            xmlFreeImpl((*ctx).tmpNsList as *mut c_void);
2899        }
2900        (*ctx).tmpNsList = ptr::null_mut();
2901        ptr::null_mut()
2902    }
2903}
2904
2905/// `xmlNodePtr xmlXPathNextAttribute(xmlXPathParserContextPtr ctxt, xmlNodePtr cur)`.
2906///
2907/// # SAFETY
2908///
2909/// - `ctxt`, `cur` must be valid pointers (or NULL
2910///   where the upstream C contract allows), obtained from the
2911///   matching constructor/owner and not yet freed; the callee may
2912///   take or keep ownership exactly as the C API specifies.
2913///
2914/// The caller must not race this call with concurrent mutation of the
2915/// same objects from other threads (per-object state is not internally
2916/// synchronized). Violating any of the above is undefined behavior.
2917///
2918/// Exercised by the C-API differential courts
2919/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2920/// courts; those pass byte-for-byte against the upstream oracle.
2921#[no_mangle]
2922pub unsafe extern "C" fn xmlXPathNextAttribute(
2923    ctxt: *mut c_void,
2924    cur: *mut _xmlNode,
2925) -> *mut _xmlNode {
2926    let pc = pc_from(ctxt);
2927    if pc.is_null() {
2928        return ptr::null_mut();
2929    }
2930    let ctx = unsafe { (*pc).context };
2931    if ctx.is_null() {
2932        return ptr::null_mut();
2933    }
2934    use crate::abi::types::xmlElementType as ET;
2935    unsafe {
2936        let cnode = (*ctx).node;
2937        if cnode.is_null() || (*cnode).type_ != ET::XML_ELEMENT_NODE as c_int {
2938            return ptr::null_mut();
2939        }
2940        if cur.is_null() {
2941            if cnode == (*ctx).doc as *mut _xmlNode {
2942                return ptr::null_mut();
2943            }
2944            return (*cnode).properties as *mut _xmlNode;
2945        }
2946        (*cur).next
2947    }
2948}
2949
2950// ═══════════════════════════════════════════════════════════════════════════════
2951// The explicit core function library (xmlXPath*Function)
2952// ═══════════════════════════════════════════════════════════════════════════════
2953
2954/// `void xmlXPathBooleanFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
2955///
2956/// # SAFETY
2957///
2958/// - `ctxt` must be valid pointers (or NULL
2959///   where the upstream C contract allows), obtained from the
2960///   matching constructor/owner and not yet freed; the callee may
2961///   take or keep ownership exactly as the C API specifies.
2962///
2963/// The caller must not race this call with concurrent mutation of the
2964/// same objects from other threads (per-object state is not internally
2965/// synchronized). Violating any of the above is undefined behavior.
2966///
2967/// Exercised by the C-API differential courts
2968/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2969/// courts; those pass byte-for-byte against the upstream oracle.
2970#[no_mangle]
2971pub unsafe extern "C" fn xmlXPathBooleanFunction(ctxt: *mut c_void, _nargs: c_int) {
2972    let pc = pc_from(ctxt);
2973    if pc.is_null() {
2974        return;
2975    }
2976    if !check_arity(pc, 1) {
2977        return;
2978    }
2979    let cur = value_pop(pc);
2980    if cur.is_null() {
2981        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
2982        return;
2983    }
2984    let b = crate::abi::exports_xml2::object_to_xpathvalue_pub(cur).as_boolean();
2985    crate::abi::exports_xml2::xmlXPathFreeObject(cur);
2986    value_push(pc, new_bool(b));
2987}
2988
2989/// `void xmlXPathNotFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
2990///
2991/// # SAFETY
2992///
2993/// - `ctxt` must be valid pointers (or NULL
2994///   where the upstream C contract allows), obtained from the
2995///   matching constructor/owner and not yet freed; the callee may
2996///   take or keep ownership exactly as the C API specifies.
2997///
2998/// The caller must not race this call with concurrent mutation of the
2999/// same objects from other threads (per-object state is not internally
3000/// synchronized). Violating any of the above is undefined behavior.
3001///
3002/// Exercised by the C-API differential courts
3003/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3004/// courts; those pass byte-for-byte against the upstream oracle.
3005#[no_mangle]
3006pub unsafe extern "C" fn xmlXPathNotFunction(ctxt: *mut c_void, _nargs: c_int) {
3007    let pc = pc_from(ctxt);
3008    if pc.is_null() {
3009        return;
3010    }
3011    if !check_arity(pc, 1) {
3012        return;
3013    }
3014    cast_top_to_boolean(pc);
3015    if (*pc).error != 0 {
3016        return;
3017    }
3018    unsafe {
3019        (*(*pc).value).boolval = if (*(*pc).value).boolval == 0 { 1 } else { 0 };
3020    }
3021}
3022
3023/// `void xmlXPathTrueFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3024///
3025/// # SAFETY
3026///
3027/// - `ctxt` must be valid pointers (or NULL
3028///   where the upstream C contract allows), obtained from the
3029///   matching constructor/owner and not yet freed; the callee may
3030///   take or keep ownership exactly as the C API specifies.
3031///
3032/// The caller must not race this call with concurrent mutation of the
3033/// same objects from other threads (per-object state is not internally
3034/// synchronized). Violating any of the above is undefined behavior.
3035///
3036/// Exercised by the C-API differential courts
3037/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3038/// courts; those pass byte-for-byte against the upstream oracle.
3039#[no_mangle]
3040pub unsafe extern "C" fn xmlXPathTrueFunction(ctxt: *mut c_void, _nargs: c_int) {
3041    let pc = pc_from(ctxt);
3042    if pc.is_null() {
3043        return;
3044    }
3045
3046    value_push(pc, new_bool(true));
3047}
3048
3049/// `void xmlXPathFalseFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3050///
3051/// # SAFETY
3052///
3053/// - `ctxt` must be valid pointers (or NULL
3054///   where the upstream C contract allows), obtained from the
3055///   matching constructor/owner and not yet freed; the callee may
3056///   take or keep ownership exactly as the C API specifies.
3057///
3058/// The caller must not race this call with concurrent mutation of the
3059/// same objects from other threads (per-object state is not internally
3060/// synchronized). Violating any of the above is undefined behavior.
3061///
3062/// Exercised by the C-API differential courts
3063/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3064/// courts; those pass byte-for-byte against the upstream oracle.
3065#[no_mangle]
3066pub unsafe extern "C" fn xmlXPathFalseFunction(ctxt: *mut c_void, _nargs: c_int) {
3067    let pc = pc_from(ctxt);
3068    if pc.is_null() {
3069        return;
3070    }
3071
3072    value_push(pc, new_bool(false));
3073}
3074
3075/// Upstream `lang()` semantics: `lang` matches the nearest ancestor/self
3076/// `xml:lang` attribute value, case-insensitively, with `-` sublanguage.
3077const unsafe fn lang_matches(lang: *const xmlChar, the_lang: *const xmlChar) -> bool {
3078    if lang.is_null() || the_lang.is_null() {
3079        return false;
3080    }
3081    let mut i = 0usize;
3082    loop {
3083        let lc = unsafe { *lang.add(i) };
3084        if lc == 0 {
3085            break;
3086        }
3087        let tc = unsafe { *the_lang.add(i) };
3088        if tc == 0 {
3089            return false;
3090        }
3091        if !lc.eq_ignore_ascii_case(&tc) {
3092            return false;
3093        }
3094        i += 1;
3095    }
3096    let c = unsafe { *the_lang.add(i) };
3097    c == 0 || c == b'-'
3098}
3099
3100/// `void xmlXPathLangFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3101///
3102/// # SAFETY
3103///
3104/// - `ctxt` must be valid pointers (or NULL
3105///   where the upstream C contract allows), obtained from the
3106///   matching constructor/owner and not yet freed; the callee may
3107///   take or keep ownership exactly as the C API specifies.
3108///
3109/// The caller must not race this call with concurrent mutation of the
3110/// same objects from other threads (per-object state is not internally
3111/// synchronized). Violating any of the above is undefined behavior.
3112///
3113/// Exercised by the C-API differential courts
3114/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3115/// courts; those pass byte-for-byte against the upstream oracle.
3116#[no_mangle]
3117pub unsafe extern "C" fn xmlXPathLangFunction(ctxt: *mut c_void, _nargs: c_int) {
3118    let pc = pc_from(ctxt);
3119    if pc.is_null() {
3120        return;
3121    }
3122    let ctx = unsafe { (*pc).context };
3123    if ctx.is_null() {
3124        return;
3125    }
3126    if !check_arity(pc, 1) {
3127        return;
3128    }
3129    cast_top_to_string(pc);
3130    if (*pc).error != 0 {
3131        return;
3132    }
3133    let val = value_pop(pc);
3134    if val.is_null() {
3135        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3136        return;
3137    }
3138    let lang = unsafe { (*val).stringval };
3139    let mut ret = 0;
3140    unsafe {
3141        let mut n = (*ctx).node;
3142        let mut found: *mut xmlChar = ptr::null_mut();
3143        while !n.is_null() {
3144            let got = crate::xml::tree::get_ns_prop(
3145                n,
3146                c"lang".as_ptr() as *const xmlChar,
3147                XML_XML_NAMESPACE_BYTES.as_ptr() as *const xmlChar,
3148            );
3149            if !got.is_null() {
3150                found = got;
3151                break;
3152            }
3153            n = (*n).parent;
3154        }
3155        if !found.is_null() && lang_matches(lang, found) {
3156            ret = 1;
3157        }
3158        if !found.is_null() {
3159            xmlFreeImpl(found as *mut c_void);
3160        }
3161    }
3162    crate::abi::exports_xml2::xmlXPathFreeObject(val);
3163    value_push(pc, new_bool(ret != 0));
3164}
3165
3166/// `void xmlXPathNumberFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3167///
3168/// # SAFETY
3169///
3170/// - `ctxt` must be valid pointers (or NULL
3171///   where the upstream C contract allows), obtained from the
3172///   matching constructor/owner and not yet freed; the callee may
3173///   take or keep ownership exactly as the C API specifies.
3174///
3175/// The caller must not race this call with concurrent mutation of the
3176/// same objects from other threads (per-object state is not internally
3177/// synchronized). Violating any of the above is undefined behavior.
3178///
3179/// Exercised by the C-API differential courts
3180/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3181/// courts; those pass byte-for-byte against the upstream oracle.
3182#[no_mangle]
3183pub unsafe extern "C" fn xmlXPathNumberFunction(ctxt: *mut c_void, nargs: c_int) {
3184    let pc = pc_from(ctxt);
3185    if pc.is_null() {
3186        return;
3187    }
3188    let ctx = unsafe { (*pc).context };
3189    if ctx.is_null() {
3190        return;
3191    }
3192    if nargs == 0 {
3193        let node = unsafe { (*ctx).node };
3194        let res = if node.is_null() {
3195            0.0
3196        } else {
3197            let sv = node_string_value(node);
3198            crate::xml::xpath::types::string_to_number(&sv)
3199        };
3200        value_push(pc, new_number(res));
3201        return;
3202    }
3203    if !check_arity(pc, 1) {
3204        return;
3205    }
3206    cast_top_to_number(pc);
3207}
3208
3209/// `void xmlXPathSumFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3210///
3211/// # SAFETY
3212///
3213/// - `ctxt` must be valid pointers (or NULL
3214///   where the upstream C contract allows), obtained from the
3215///   matching constructor/owner and not yet freed; the callee may
3216///   take or keep ownership exactly as the C API specifies.
3217///
3218/// The caller must not race this call with concurrent mutation of the
3219/// same objects from other threads (per-object state is not internally
3220/// synchronized). Violating any of the above is undefined behavior.
3221///
3222/// Exercised by the C-API differential courts
3223/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3224/// courts; those pass byte-for-byte against the upstream oracle.
3225#[no_mangle]
3226pub unsafe extern "C" fn xmlXPathSumFunction(ctxt: *mut c_void, _nargs: c_int) {
3227    let pc = pc_from(ctxt);
3228    if pc.is_null() {
3229        return;
3230    }
3231    if !check_arity(pc, 1) {
3232        return;
3233    }
3234    let cur = value_pop(pc);
3235    if cur.is_null() {
3236        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3237        return;
3238    }
3239    let typ = unsafe { (*cur).type_ };
3240    if typ != xmlXPathObjectType::XPATH_NODESET as c_int
3241        && typ != xmlXPathObjectType::XPATH_XSLT_TREE as c_int
3242    {
3243        crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3244        pc_set_error(pc, crate::abi::types::XPATH_INVALID_TYPE as c_int);
3245        return;
3246    }
3247    let mut res = 0.0;
3248    unsafe {
3249        let ns = (*cur).nodesetval as *mut _xmlNodeSet;
3250        if !ns.is_null() {
3251            let nr = (*ns).nodeNr;
3252            let tab = (*ns).nodeTab;
3253            if !tab.is_null() {
3254                for i in 0..nr as isize {
3255                    let node = *tab.add(i as usize);
3256                    let sv = node_string_value(node);
3257                    res += crate::xml::xpath::types::string_to_number(&sv);
3258                }
3259            }
3260        }
3261    }
3262    crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3263    value_push(pc, new_number(res));
3264}
3265
3266/// `void xmlXPathFloorFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3267///
3268/// # SAFETY
3269///
3270/// - `ctxt` must be valid pointers (or NULL
3271///   where the upstream C contract allows), obtained from the
3272///   matching constructor/owner and not yet freed; the callee may
3273///   take or keep ownership exactly as the C API specifies.
3274///
3275/// The caller must not race this call with concurrent mutation of the
3276/// same objects from other threads (per-object state is not internally
3277/// synchronized). Violating any of the above is undefined behavior.
3278///
3279/// Exercised by the C-API differential courts
3280/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3281/// courts; those pass byte-for-byte against the upstream oracle.
3282#[no_mangle]
3283pub unsafe extern "C" fn xmlXPathFloorFunction(ctxt: *mut c_void, _nargs: c_int) {
3284    let pc = pc_from(ctxt);
3285    if pc.is_null() {
3286        return;
3287    }
3288    if !check_arity(pc, 1) {
3289        return;
3290    }
3291    cast_top_to_number(pc);
3292    if (*pc).error != 0 {
3293        return;
3294    }
3295    unsafe {
3296        (*(*pc).value).floatval = (*(*pc).value).floatval.floor();
3297    }
3298}
3299
3300/// `void xmlXPathCeilingFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3301///
3302/// # SAFETY
3303///
3304/// - `ctxt` must be valid pointers (or NULL
3305///   where the upstream C contract allows), obtained from the
3306///   matching constructor/owner and not yet freed; the callee may
3307///   take or keep ownership exactly as the C API specifies.
3308///
3309/// The caller must not race this call with concurrent mutation of the
3310/// same objects from other threads (per-object state is not internally
3311/// synchronized). Violating any of the above is undefined behavior.
3312///
3313/// Exercised by the C-API differential courts
3314/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3315/// courts; those pass byte-for-byte against the upstream oracle.
3316#[no_mangle]
3317pub unsafe extern "C" fn xmlXPathCeilingFunction(ctxt: *mut c_void, _nargs: c_int) {
3318    let pc = pc_from(ctxt);
3319    if pc.is_null() {
3320        return;
3321    }
3322    if !check_arity(pc, 1) {
3323        return;
3324    }
3325    cast_top_to_number(pc);
3326    if (*pc).error != 0 {
3327        return;
3328    }
3329    unsafe {
3330        (*(*pc).value).floatval = (*(*pc).value).floatval.ceil();
3331    }
3332}
3333
3334/// `void xmlXPathRoundFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3335///
3336/// # SAFETY
3337///
3338/// - `ctxt` must be valid pointers (or NULL
3339///   where the upstream C contract allows), obtained from the
3340///   matching constructor/owner and not yet freed; the callee may
3341///   take or keep ownership exactly as the C API specifies.
3342///
3343/// The caller must not race this call with concurrent mutation of the
3344/// same objects from other threads (per-object state is not internally
3345/// synchronized). Violating any of the above is undefined behavior.
3346///
3347/// Exercised by the C-API differential courts
3348/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3349/// courts; those pass byte-for-byte against the upstream oracle.
3350#[no_mangle]
3351pub unsafe extern "C" fn xmlXPathRoundFunction(ctxt: *mut c_void, _nargs: c_int) {
3352    let pc = pc_from(ctxt);
3353    if pc.is_null() {
3354        return;
3355    }
3356    if !check_arity(pc, 1) {
3357        return;
3358    }
3359    cast_top_to_number(pc);
3360    if (*pc).error != 0 {
3361        return;
3362    }
3363    unsafe {
3364        let f = (*(*pc).value).floatval;
3365        if (-0.5..0.5).contains(&f) {
3366            // Handles negative zero.
3367            (*(*pc).value).floatval *= 0.0;
3368        } else {
3369            let mut rounded = f.floor();
3370            if f - rounded >= 0.5 {
3371                rounded += 1.0;
3372            }
3373            (*(*pc).value).floatval = rounded;
3374        }
3375    }
3376}
3377
3378/// `void xmlXPathLastFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3379///
3380/// # SAFETY
3381///
3382/// - `ctxt` must be valid pointers (or NULL
3383///   where the upstream C contract allows), obtained from the
3384///   matching constructor/owner and not yet freed; the callee may
3385///   take or keep ownership exactly as the C API specifies.
3386///
3387/// The caller must not race this call with concurrent mutation of the
3388/// same objects from other threads (per-object state is not internally
3389/// synchronized). Violating any of the above is undefined behavior.
3390///
3391/// Exercised by the C-API differential courts
3392/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3393/// courts; those pass byte-for-byte against the upstream oracle.
3394#[no_mangle]
3395pub unsafe extern "C" fn xmlXPathLastFunction(ctxt: *mut c_void, _nargs: c_int) {
3396    let pc = pc_from(ctxt);
3397    if pc.is_null() {
3398        return;
3399    }
3400    let ctx = unsafe { (*pc).context };
3401    if ctx.is_null() {
3402        return;
3403    }
3404
3405    value_push(pc, new_number(unsafe { (*ctx).contextSize } as f64));
3406}
3407
3408/// `void xmlXPathPositionFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3409///
3410/// # SAFETY
3411///
3412/// - `ctxt` must be valid pointers (or NULL
3413///   where the upstream C contract allows), obtained from the
3414///   matching constructor/owner and not yet freed; the callee may
3415///   take or keep ownership exactly as the C API specifies.
3416///
3417/// The caller must not race this call with concurrent mutation of the
3418/// same objects from other threads (per-object state is not internally
3419/// synchronized). Violating any of the above is undefined behavior.
3420///
3421/// Exercised by the C-API differential courts
3422/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3423/// courts; those pass byte-for-byte against the upstream oracle.
3424#[no_mangle]
3425pub unsafe extern "C" fn xmlXPathPositionFunction(ctxt: *mut c_void, _nargs: c_int) {
3426    let pc = pc_from(ctxt);
3427    if pc.is_null() {
3428        return;
3429    }
3430    let ctx = unsafe { (*pc).context };
3431    if ctx.is_null() {
3432        return;
3433    }
3434
3435    value_push(pc, new_number(unsafe { (*ctx).proximityPosition } as f64));
3436}
3437
3438/// `void xmlXPathCountFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3439///
3440/// # SAFETY
3441///
3442/// - `ctxt` must be valid pointers (or NULL
3443///   where the upstream C contract allows), obtained from the
3444///   matching constructor/owner and not yet freed; the callee may
3445///   take or keep ownership exactly as the C API specifies.
3446///
3447/// The caller must not race this call with concurrent mutation of the
3448/// same objects from other threads (per-object state is not internally
3449/// synchronized). Violating any of the above is undefined behavior.
3450///
3451/// Exercised by the C-API differential courts
3452/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3453/// courts; those pass byte-for-byte against the upstream oracle.
3454#[no_mangle]
3455pub unsafe extern "C" fn xmlXPathCountFunction(ctxt: *mut c_void, _nargs: c_int) {
3456    let pc = pc_from(ctxt);
3457    if pc.is_null() {
3458        return;
3459    }
3460    if !check_arity(pc, 1) {
3461        return;
3462    }
3463    let cur = value_pop(pc);
3464    if cur.is_null() {
3465        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3466        return;
3467    }
3468    let typ = unsafe { (*cur).type_ };
3469    if typ != xmlXPathObjectType::XPATH_NODESET as c_int
3470        && typ != xmlXPathObjectType::XPATH_XSLT_TREE as c_int
3471    {
3472        crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3473        pc_set_error(pc, crate::abi::types::XPATH_INVALID_TYPE as c_int);
3474        return;
3475    }
3476    let count = unsafe {
3477        let ns = (*cur).nodesetval as *mut _xmlNodeSet;
3478        if ns.is_null() {
3479            0
3480        } else {
3481            (*ns).nodeNr
3482        }
3483    };
3484    crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3485    value_push(pc, new_number(count as f64));
3486}
3487
3488/// Elements selected by whitespace-separated ID tokens (upstream
3489/// `xmlXPathGetElementsByIds`).
3490unsafe fn get_elements_by_ids(doc: *mut _xmlDoc, ids: *const xmlChar) -> *mut _xmlNodeSet {
3491    use crate::abi::types::xmlElementType as ET;
3492    if ids.is_null() {
3493        return ptr::null_mut();
3494    }
3495    let mut out = NodeSet::new();
3496    unsafe {
3497        let mut p = ids;
3498        while *p != 0 {
3499            while is_blank_ch(*p) {
3500                p = p.add(1);
3501            }
3502            if *p == 0 {
3503                break;
3504            }
3505            let start = p;
3506            while *p != 0 && !is_blank_ch(*p) {
3507                p = p.add(1);
3508            }
3509            let id_c = crate::xml::string::xml_strndup(start, p.offset_from(start) as usize);
3510            if id_c.is_null() {
3511                break;
3512            }
3513            let attr = get_id(doc, id_c);
3514            xmlFreeImpl(id_c as *mut c_void);
3515            if !attr.is_null() {
3516                let t = (*attr).type_;
3517                let elem = if t == ET::XML_ATTRIBUTE_NODE as c_int {
3518                    (*attr).parent
3519                } else if t == ET::XML_ELEMENT_NODE as c_int {
3520                    attr as *mut _xmlNode
3521                } else {
3522                    ptr::null_mut()
3523                };
3524                if !elem.is_null() {
3525                    out.push(elem);
3526                }
3527            }
3528        }
3529    }
3530    out.to_raw()
3531}
3532
3533/// `void xmlXPathIdFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3534///
3535/// # SAFETY
3536///
3537/// - `ctxt` must be valid pointers (or NULL
3538///   where the upstream C contract allows), obtained from the
3539///   matching constructor/owner and not yet freed; the callee may
3540///   take or keep ownership exactly as the C API specifies.
3541///
3542/// The caller must not race this call with concurrent mutation of the
3543/// same objects from other threads (per-object state is not internally
3544/// synchronized). Violating any of the above is undefined behavior.
3545///
3546/// Exercised by the C-API differential courts
3547/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3548/// courts; those pass byte-for-byte against the upstream oracle.
3549#[no_mangle]
3550pub unsafe extern "C" fn xmlXPathIdFunction(ctxt: *mut c_void, _nargs: c_int) {
3551    let pc = pc_from(ctxt);
3552    if pc.is_null() {
3553        return;
3554    }
3555    let ctx = unsafe { (*pc).context };
3556    if ctx.is_null() {
3557        return;
3558    }
3559    if !check_arity(pc, 1) {
3560        return;
3561    }
3562    let obj = value_pop(pc);
3563    if obj.is_null() {
3564        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3565        return;
3566    }
3567    let doc = unsafe { (*ctx).doc };
3568    let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(obj);
3569    crate::abi::exports_xml2::xmlXPathFreeObject(obj);
3570    match &v {
3571        XPathValue::NodeSet(ns) => {
3572            let mut merged = NodeSet::new();
3573            for n in ns.iter() {
3574                let sv = node_string_value(n);
3575                let c = dup_rust_string(&sv);
3576                let sub = get_elements_by_ids(doc, c);
3577                xmlFreeImpl(c as *mut c_void);
3578                if !sub.is_null() {
3579                    let sub_internal = node_set_to_internal(sub);
3580                    for m in sub_internal.iter() {
3581                        if !merged.contains(m) {
3582                            merged.push(m);
3583                        }
3584                    }
3585                    // Release the raw node-set (nodes are borrowed).
3586                    crate::abi::exports_xml2::xmlXPathFreeNodeSet(sub);
3587                }
3588            }
3589            value_push(
3590                pc,
3591                crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(merged)),
3592            );
3593        }
3594        _ => {
3595            let s = v.as_string();
3596            let c = dup_rust_string(&s);
3597            let ret = get_elements_by_ids(doc, c);
3598            xmlFreeImpl(c as *mut c_void);
3599            if ret.is_null() {
3600                value_push(
3601                    pc,
3602                    crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(
3603                        NodeSet::new(),
3604                    )),
3605                );
3606            } else {
3607                let obj2 = crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(
3608                    node_set_to_internal(ret),
3609                ));
3610                crate::abi::exports_xml2::xmlXPathFreeNodeSet(ret);
3611                value_push(pc, obj2);
3612            }
3613        }
3614    }
3615}
3616
3617/// Local part of a node name (upstream `xmlXPathLocalNameFunction` first-node
3618/// logic). Returns an empty string when the node has no local name.
3619unsafe fn node_local_name(node: *mut _xmlNode) -> String {
3620    use crate::abi::types::xmlElementType as ET;
3621    if node.is_null() {
3622        return String::new();
3623    }
3624    unsafe {
3625        match (*node).type_ {
3626            t if t == ET::XML_ELEMENT_NODE as c_int
3627                || t == ET::XML_ATTRIBUTE_NODE as c_int
3628                || t == ET::XML_PI_NODE as c_int =>
3629            {
3630                let name = (*node).name;
3631                if name.is_null() || *name == b' ' {
3632                    String::new()
3633                } else {
3634                    let s = CStr::from_ptr(name as *const c_char)
3635                        .to_string_lossy()
3636                        .into_owned();
3637                    match s.split_once(':') {
3638                        Some((_, local)) => local.to_string(),
3639                        None => s,
3640                    }
3641                }
3642            }
3643            t if t == ET::XML_NAMESPACE_DECL as c_int => {
3644                let ns = node as *mut crate::abi::structs::_xmlNs;
3645                let p = (*ns).prefix;
3646                if p.is_null() {
3647                    String::new()
3648                } else {
3649                    CStr::from_ptr(p as *const c_char)
3650                        .to_string_lossy()
3651                        .into_owned()
3652                }
3653            }
3654            _ => String::new(),
3655        }
3656    }
3657}
3658
3659/// `void xmlXPathLocalNameFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3660///
3661/// # SAFETY
3662///
3663/// - `ctxt` must be valid pointers (or NULL
3664///   where the upstream C contract allows), obtained from the
3665///   matching constructor/owner and not yet freed; the callee may
3666///   take or keep ownership exactly as the C API specifies.
3667///
3668/// The caller must not race this call with concurrent mutation of the
3669/// same objects from other threads (per-object state is not internally
3670/// synchronized). Violating any of the above is undefined behavior.
3671///
3672/// Exercised by the C-API differential courts
3673/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3674/// courts; those pass byte-for-byte against the upstream oracle.
3675#[no_mangle]
3676pub unsafe extern "C" fn xmlXPathLocalNameFunction(ctxt: *mut c_void, nargs: c_int) {
3677    let pc = pc_from(ctxt);
3678    if pc.is_null() {
3679        return;
3680    }
3681    let ctx = unsafe { (*pc).context };
3682    if ctx.is_null() {
3683        return;
3684    }
3685    if nargs == 0 {
3686        value_push(
3687            pc,
3688            crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(NodeSet::singleton(
3689                unsafe { (*ctx).node },
3690            ))),
3691        );
3692        // fallthrough with nargs = 1
3693    }
3694
3695    if !check_arity(pc, 1) {
3696        return;
3697    }
3698    let cur = value_pop(pc);
3699    if cur.is_null() {
3700        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3701        return;
3702    }
3703    let typ = unsafe { (*cur).type_ };
3704    if typ != xmlXPathObjectType::XPATH_NODESET as c_int
3705        && typ != xmlXPathObjectType::XPATH_XSLT_TREE as c_int
3706    {
3707        crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3708        pc_set_error(pc, crate::abi::types::XPATH_INVALID_TYPE as c_int);
3709        return;
3710    }
3711    let name = unsafe {
3712        let ns = (*cur).nodesetval as *mut _xmlNodeSet;
3713        if ns.is_null() || (*ns).nodeNr == 0 {
3714            String::new()
3715        } else {
3716            node_local_name(*(*ns).nodeTab)
3717        }
3718    };
3719    crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3720    let out = dup_rust_string(&name);
3721    value_push(pc, xmlXPathWrapString(out));
3722}
3723
3724/// Namespace URI of a node (upstream `xmlXPathNamespaceURIFunction`).
3725unsafe fn node_namespace_uri(node: *mut _xmlNode) -> String {
3726    use crate::abi::types::xmlElementType as ET;
3727    if node.is_null() {
3728        return String::new();
3729    }
3730    unsafe {
3731        match (*node).type_ {
3732            t if t == ET::XML_ELEMENT_NODE as c_int || t == ET::XML_ATTRIBUTE_NODE as c_int => {
3733                let ns = (*node).ns;
3734                if ns.is_null() || (*ns).href.is_null() {
3735                    String::new()
3736                } else {
3737                    CStr::from_ptr((*ns).href as *const c_char)
3738                        .to_string_lossy()
3739                        .into_owned()
3740                }
3741            }
3742            t if t == ET::XML_NAMESPACE_DECL as c_int => {
3743                let ns = node as *mut crate::abi::structs::_xmlNs;
3744                if (*ns).href.is_null() {
3745                    String::new()
3746                } else {
3747                    CStr::from_ptr((*ns).href as *const c_char)
3748                        .to_string_lossy()
3749                        .into_owned()
3750                }
3751            }
3752            _ => String::new(),
3753        }
3754    }
3755}
3756
3757/// `void xmlXPathNamespaceURIFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3758///
3759/// # SAFETY
3760///
3761/// - `ctxt` must be valid pointers (or NULL
3762///   where the upstream C contract allows), obtained from the
3763///   matching constructor/owner and not yet freed; the callee may
3764///   take or keep ownership exactly as the C API specifies.
3765///
3766/// The caller must not race this call with concurrent mutation of the
3767/// same objects from other threads (per-object state is not internally
3768/// synchronized). Violating any of the above is undefined behavior.
3769///
3770/// Exercised by the C-API differential courts
3771/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3772/// courts; those pass byte-for-byte against the upstream oracle.
3773#[no_mangle]
3774pub unsafe extern "C" fn xmlXPathNamespaceURIFunction(ctxt: *mut c_void, nargs: c_int) {
3775    let pc = pc_from(ctxt);
3776    if pc.is_null() {
3777        return;
3778    }
3779    let ctx = unsafe { (*pc).context };
3780    if ctx.is_null() {
3781        return;
3782    }
3783    if nargs == 0 {
3784        value_push(
3785            pc,
3786            crate::abi::exports_xml2::xpath_to_object_pub(XPathValue::NodeSet(NodeSet::singleton(
3787                unsafe { (*ctx).node },
3788            ))),
3789        );
3790    }
3791
3792    if !check_arity(pc, 1) {
3793        return;
3794    }
3795    let cur = value_pop(pc);
3796    if cur.is_null() {
3797        pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
3798        return;
3799    }
3800    let typ = unsafe { (*cur).type_ };
3801    if typ != xmlXPathObjectType::XPATH_NODESET as c_int
3802        && typ != xmlXPathObjectType::XPATH_XSLT_TREE as c_int
3803    {
3804        crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3805        pc_set_error(pc, crate::abi::types::XPATH_INVALID_TYPE as c_int);
3806        return;
3807    }
3808    let uri = unsafe {
3809        let ns = (*cur).nodesetval as *mut _xmlNodeSet;
3810        if ns.is_null() || (*ns).nodeNr == 0 {
3811            String::new()
3812        } else {
3813            node_namespace_uri(*(*ns).nodeTab)
3814        }
3815    };
3816    crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3817    let out = dup_rust_string(&uri);
3818    value_push(pc, xmlXPathWrapString(out));
3819}
3820
3821/// `void xmlXPathStringFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3822///
3823/// # SAFETY
3824///
3825/// - `ctxt` must be valid pointers (or NULL
3826///   where the upstream C contract allows), obtained from the
3827///   matching constructor/owner and not yet freed; the callee may
3828///   take or keep ownership exactly as the C API specifies.
3829///
3830/// The caller must not race this call with concurrent mutation of the
3831/// same objects from other threads (per-object state is not internally
3832/// synchronized). Violating any of the above is undefined behavior.
3833///
3834/// Exercised by the C-API differential courts
3835/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3836/// courts; those pass byte-for-byte against the upstream oracle.
3837#[no_mangle]
3838pub unsafe extern "C" fn xmlXPathStringFunction(ctxt: *mut c_void, nargs: c_int) {
3839    let pc = pc_from(ctxt);
3840    if pc.is_null() {
3841        return;
3842    }
3843    let ctx = unsafe { (*pc).context };
3844    if ctx.is_null() {
3845        return;
3846    }
3847    if nargs == 0 {
3848        let node = unsafe { (*ctx).node };
3849        let sv = if node.is_null() {
3850            String::new()
3851        } else {
3852            node_string_value(node)
3853        };
3854        let out = dup_rust_string(&sv);
3855        value_push(pc, xmlXPathWrapString(out));
3856        return;
3857    }
3858    if !check_arity(pc, 1) {
3859        return;
3860    }
3861    cast_top_to_string(pc);
3862}
3863
3864/// `void xmlXPathStringLengthFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3865///
3866/// # SAFETY
3867///
3868/// - `ctxt` must be valid pointers (or NULL
3869///   where the upstream C contract allows), obtained from the
3870///   matching constructor/owner and not yet freed; the callee may
3871///   take or keep ownership exactly as the C API specifies.
3872///
3873/// The caller must not race this call with concurrent mutation of the
3874/// same objects from other threads (per-object state is not internally
3875/// synchronized). Violating any of the above is undefined behavior.
3876///
3877/// Exercised by the C-API differential courts
3878/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3879/// courts; those pass byte-for-byte against the upstream oracle.
3880#[no_mangle]
3881pub unsafe extern "C" fn xmlXPathStringLengthFunction(ctxt: *mut c_void, nargs: c_int) {
3882    let pc = pc_from(ctxt);
3883    if pc.is_null() {
3884        return;
3885    }
3886    let ctx = unsafe { (*pc).context };
3887    if ctx.is_null() {
3888        return;
3889    }
3890    if nargs == 0 {
3891        let node = unsafe { (*ctx).node };
3892        let len = if node.is_null() {
3893            0
3894        } else {
3895            let sv = node_string_value(node);
3896            sv.chars().count()
3897        };
3898        value_push(pc, new_number(len as f64));
3899        return;
3900    }
3901    if !check_arity(pc, 1) {
3902        return;
3903    }
3904    cast_top_to_string(pc);
3905    if (*pc).error != 0 {
3906        return;
3907    }
3908    let len = unsafe {
3909        let s = (*(*pc).value).stringval;
3910        if s.is_null() {
3911            0
3912        } else {
3913            let sv = CStr::from_ptr(s as *const c_char).to_string_lossy();
3914            sv.chars().count()
3915        }
3916    };
3917    let cur = value_pop(pc);
3918    if !cur.is_null() {
3919        crate::abi::exports_xml2::xmlXPathFreeObject(cur);
3920    }
3921    value_push(pc, new_number(len as f64));
3922}
3923
3924/// `void xmlXPathConcatFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3925///
3926/// # SAFETY
3927///
3928/// - `ctxt` must be valid pointers (or NULL
3929///   where the upstream C contract allows), obtained from the
3930///   matching constructor/owner and not yet freed; the callee may
3931///   take or keep ownership exactly as the C API specifies.
3932///
3933/// The caller must not race this call with concurrent mutation of the
3934/// same objects from other threads (per-object state is not internally
3935/// synchronized). Violating any of the above is undefined behavior.
3936///
3937/// Exercised by the C-API differential courts
3938/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3939/// courts; those pass byte-for-byte against the upstream oracle.
3940#[no_mangle]
3941pub unsafe extern "C" fn xmlXPathConcatFunction(ctxt: *mut c_void, nargs: c_int) {
3942    let pc = pc_from(ctxt);
3943    if pc.is_null() {
3944        return;
3945    }
3946    if nargs < 2 && !check_arity(pc, 2) {
3947        return;
3948    }
3949    if !check_arity(pc, nargs) {
3950        return;
3951    }
3952    let mut parts: Vec<String> = Vec::with_capacity(nargs as usize);
3953    for _ in 0..nargs {
3954        cast_top_to_string(pc);
3955        if (*pc).error != 0 {
3956            return;
3957        }
3958        let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(unsafe { (*pc).value });
3959        let s = v.as_string();
3960        let obj = value_pop(pc);
3961        if !obj.is_null() {
3962            crate::abi::exports_xml2::xmlXPathFreeObject(obj);
3963        }
3964        parts.push(s);
3965    }
3966    parts.reverse();
3967    let joined = parts.concat();
3968    let out = dup_rust_string(&joined);
3969    value_push(pc, xmlXPathWrapString(out));
3970}
3971
3972/// `void xmlXPathContainsFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
3973///
3974/// # SAFETY
3975///
3976/// - `ctxt` must be valid pointers (or NULL
3977///   where the upstream C contract allows), obtained from the
3978///   matching constructor/owner and not yet freed; the callee may
3979///   take or keep ownership exactly as the C API specifies.
3980///
3981/// The caller must not race this call with concurrent mutation of the
3982/// same objects from other threads (per-object state is not internally
3983/// synchronized). Violating any of the above is undefined behavior.
3984///
3985/// Exercised by the C-API differential courts
3986/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3987/// courts; those pass byte-for-byte against the upstream oracle.
3988#[no_mangle]
3989pub unsafe extern "C" fn xmlXPathContainsFunction(ctxt: *mut c_void, _nargs: c_int) {
3990    let pc = pc_from(ctxt);
3991    if pc.is_null() {
3992        return;
3993    }
3994    if !check_arity(pc, 2) {
3995        return;
3996    }
3997    cast_top_to_string(pc);
3998    if (*pc).error != 0 {
3999        return;
4000    }
4001    let needle = value_pop(pc);
4002    cast_top_to_string(pc);
4003    if (*pc).error != 0 {
4004        if !needle.is_null() {
4005            crate::abi::exports_xml2::xmlXPathFreeObject(needle);
4006        }
4007        return;
4008    }
4009    let hay = value_pop(pc);
4010    let found = if hay.is_null() || needle.is_null() {
4011        false
4012    } else {
4013        unsafe { !cstr_find((*hay).stringval, (*needle).stringval).is_null() }
4014    };
4015    if !hay.is_null() {
4016        crate::abi::exports_xml2::xmlXPathFreeObject(hay);
4017    }
4018    if !needle.is_null() {
4019        crate::abi::exports_xml2::xmlXPathFreeObject(needle);
4020    }
4021    value_push(pc, new_bool(found));
4022}
4023
4024/// `void xmlXPathStartsWithFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4025///
4026/// # SAFETY
4027///
4028/// - `ctxt` must be valid pointers (or NULL
4029///   where the upstream C contract allows), obtained from the
4030///   matching constructor/owner and not yet freed; the callee may
4031///   take or keep ownership exactly as the C API specifies.
4032///
4033/// The caller must not race this call with concurrent mutation of the
4034/// same objects from other threads (per-object state is not internally
4035/// synchronized). Violating any of the above is undefined behavior.
4036///
4037/// Exercised by the C-API differential courts
4038/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4039/// courts; those pass byte-for-byte against the upstream oracle.
4040#[no_mangle]
4041pub unsafe extern "C" fn xmlXPathStartsWithFunction(ctxt: *mut c_void, _nargs: c_int) {
4042    let pc = pc_from(ctxt);
4043    if pc.is_null() {
4044        return;
4045    }
4046    if !check_arity(pc, 2) {
4047        return;
4048    }
4049    cast_top_to_string(pc);
4050    if (*pc).error != 0 {
4051        return;
4052    }
4053    let needle = value_pop(pc);
4054    cast_top_to_string(pc);
4055    if (*pc).error != 0 {
4056        if !needle.is_null() {
4057            crate::abi::exports_xml2::xmlXPathFreeObject(needle);
4058        }
4059        return;
4060    }
4061    let hay = value_pop(pc);
4062    let found = if hay.is_null() || needle.is_null() {
4063        false
4064    } else {
4065        unsafe { crate::xml::string::xml_str_starts_with((*hay).stringval, (*needle).stringval) }
4066    };
4067    if !hay.is_null() {
4068        crate::abi::exports_xml2::xmlXPathFreeObject(hay);
4069    }
4070    if !needle.is_null() {
4071        crate::abi::exports_xml2::xmlXPathFreeObject(needle);
4072    }
4073    value_push(pc, new_bool(found));
4074}
4075
4076/// `void xmlXPathSubstringFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4077///
4078/// # SAFETY
4079///
4080/// - `ctxt` must be valid pointers (or NULL
4081///   where the upstream C contract allows), obtained from the
4082///   matching constructor/owner and not yet freed; the callee may
4083///   take or keep ownership exactly as the C API specifies.
4084///
4085/// The caller must not race this call with concurrent mutation of the
4086/// same objects from other threads (per-object state is not internally
4087/// synchronized). Violating any of the above is undefined behavior.
4088///
4089/// Exercised by the C-API differential courts
4090/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4091/// courts; those pass byte-for-byte against the upstream oracle.
4092#[no_mangle]
4093pub unsafe extern "C" fn xmlXPathSubstringFunction(ctxt: *mut c_void, nargs: c_int) {
4094    let pc = pc_from(ctxt);
4095    if pc.is_null() {
4096        return;
4097    }
4098    if nargs < 2 {
4099        if !check_arity(pc, 2) {
4100            return;
4101        }
4102    } else if nargs > 3 && !check_arity(pc, 3) {
4103        return;
4104    }
4105    let mut le = 0.0;
4106    if nargs == 3 {
4107        cast_top_to_number(pc);
4108        if (*pc).error != 0 {
4109            return;
4110        }
4111        let len_obj = value_pop(pc);
4112        if !len_obj.is_null() {
4113            le = unsafe { (*len_obj).floatval };
4114            crate::abi::exports_xml2::xmlXPathFreeObject(len_obj);
4115        }
4116    }
4117    cast_top_to_number(pc);
4118    if (*pc).error != 0 {
4119        return;
4120    }
4121    let start_obj = value_pop(pc);
4122    let in_ = if start_obj.is_null() {
4123        f64::NAN
4124    } else {
4125        let v = unsafe { (*start_obj).floatval };
4126        crate::abi::exports_xml2::xmlXPathFreeObject(start_obj);
4127        v
4128    };
4129    cast_top_to_string(pc);
4130    if (*pc).error != 0 {
4131        return;
4132    }
4133    let str_obj = value_pop(pc);
4134    let s = if str_obj.is_null() {
4135        String::new()
4136    } else {
4137        let v = unsafe { CStr::from_ptr((*str_obj).stringval as *const c_char) }
4138            .to_string_lossy()
4139            .into_owned();
4140        crate::abi::exports_xml2::xmlXPathFreeObject(str_obj);
4141        v
4142    };
4143
4144    let int_max = i32::MAX as f64;
4145    let mut i: i64 = 1;
4146    let mut j: i64 = i32::MAX as i64;
4147    // UPSTREAM-PARITY: `!(in < int_max)` mirrors xpath.c xmlXPathSubstring
4148    // verbatim; rewriting it as `in >= int_max` would change NaN handling
4149    // (the oracle treats NaN as "not less" -> clamps to INT_MAX).
4150    #[allow(clippy::neg_cmp_op_on_partial_ord)]
4151    if !(in_ < int_max) {
4152        i = i32::MAX as i64;
4153    } else if in_ >= 1.0 {
4154        i = in_ as i64;
4155        if in_ - in_.floor() >= 0.5 {
4156            i += 1;
4157        }
4158    }
4159    if nargs == 3 {
4160        let mut rin = in_.floor();
4161        if in_ - rin >= 0.5 {
4162            rin += 1.0;
4163        }
4164        let mut rle = le.floor();
4165        if le - rle >= 0.5 {
4166            rle += 1.0;
4167        }
4168        let end = rin + rle;
4169        #[allow(clippy::neg_cmp_op_on_partial_ord)]
4170        if !(end >= 1.0) {
4171            j = 1;
4172        } else if end < int_max {
4173            j = end as i64;
4174        }
4175    }
4176    i -= 1;
4177    j -= 1;
4178    let chars: Vec<char> = s.chars().collect();
4179    let slen = chars.len() as i64;
4180    let out = if i < j && i < slen {
4181        let start_i = i.max(0) as usize;
4182        let end_i = (j.min(slen)).max(start_i as i64) as usize;
4183        chars[start_i..end_i].iter().collect()
4184    } else {
4185        String::new()
4186    };
4187    let c = dup_rust_string(&out);
4188    value_push(pc, xmlXPathWrapString(c));
4189}
4190
4191/// `void xmlXPathSubstringBeforeFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4192///
4193/// # SAFETY
4194///
4195/// - `ctxt` must be valid pointers (or NULL
4196///   where the upstream C contract allows), obtained from the
4197///   matching constructor/owner and not yet freed; the callee may
4198///   take or keep ownership exactly as the C API specifies.
4199///
4200/// The caller must not race this call with concurrent mutation of the
4201/// same objects from other threads (per-object state is not internally
4202/// synchronized). Violating any of the above is undefined behavior.
4203///
4204/// Exercised by the C-API differential courts
4205/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4206/// courts; those pass byte-for-byte against the upstream oracle.
4207#[no_mangle]
4208pub unsafe extern "C" fn xmlXPathSubstringBeforeFunction(ctxt: *mut c_void, _nargs: c_int) {
4209    let pc = pc_from(ctxt);
4210    if pc.is_null() {
4211        return;
4212    }
4213    if !check_arity(pc, 2) {
4214        return;
4215    }
4216    cast_top_to_string(pc);
4217    if (*pc).error != 0 {
4218        return;
4219    }
4220    let find = value_pop(pc);
4221    cast_top_to_string(pc);
4222    if (*pc).error != 0 {
4223        if !find.is_null() {
4224            crate::abi::exports_xml2::xmlXPathFreeObject(find);
4225        }
4226        return;
4227    }
4228    let str_obj = value_pop(pc);
4229    let out: String = if str_obj.is_null() || find.is_null() {
4230        String::new()
4231    } else {
4232        unsafe {
4233            let hay = (*str_obj).stringval;
4234            let needle = (*find).stringval;
4235            let point = cstr_find(hay, needle);
4236            if point.is_null() {
4237                String::new()
4238            } else {
4239                let len = point.offset_from(hay) as usize;
4240                let bytes = core::slice::from_raw_parts(hay as *const u8, len);
4241                String::from_utf8_lossy(bytes).into_owned()
4242            }
4243        }
4244    };
4245    if !str_obj.is_null() {
4246        crate::abi::exports_xml2::xmlXPathFreeObject(str_obj);
4247    }
4248    if !find.is_null() {
4249        crate::abi::exports_xml2::xmlXPathFreeObject(find);
4250    }
4251    let c = dup_rust_string(&out);
4252    value_push(pc, xmlXPathWrapString(c));
4253}
4254
4255/// `void xmlXPathSubstringAfterFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4256///
4257/// # SAFETY
4258///
4259/// - `ctxt` must be valid pointers (or NULL
4260///   where the upstream C contract allows), obtained from the
4261///   matching constructor/owner and not yet freed; the callee may
4262///   take or keep ownership exactly as the C API specifies.
4263///
4264/// The caller must not race this call with concurrent mutation of the
4265/// same objects from other threads (per-object state is not internally
4266/// synchronized). Violating any of the above is undefined behavior.
4267///
4268/// Exercised by the C-API differential courts
4269/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4270/// courts; those pass byte-for-byte against the upstream oracle.
4271#[no_mangle]
4272pub unsafe extern "C" fn xmlXPathSubstringAfterFunction(ctxt: *mut c_void, _nargs: c_int) {
4273    let pc = pc_from(ctxt);
4274    if pc.is_null() {
4275        return;
4276    }
4277    if !check_arity(pc, 2) {
4278        return;
4279    }
4280    cast_top_to_string(pc);
4281    if (*pc).error != 0 {
4282        return;
4283    }
4284    let find = value_pop(pc);
4285    cast_top_to_string(pc);
4286    if (*pc).error != 0 {
4287        if !find.is_null() {
4288            crate::abi::exports_xml2::xmlXPathFreeObject(find);
4289        }
4290        return;
4291    }
4292    let str_obj = value_pop(pc);
4293    let out: String = if str_obj.is_null() || find.is_null() {
4294        String::new()
4295    } else {
4296        unsafe {
4297            let hay = (*str_obj).stringval;
4298            let needle = (*find).stringval;
4299            let point = cstr_find(hay, needle);
4300            if point.is_null() {
4301                String::new()
4302            } else {
4303                let nlen = crate::xml::string::xml_strlen(needle);
4304                let rest = point.add(nlen);
4305                let len = crate::xml::string::xml_strlen(rest);
4306                let bytes = core::slice::from_raw_parts(rest, len);
4307                String::from_utf8_lossy(bytes).into_owned()
4308            }
4309        }
4310    };
4311    if !str_obj.is_null() {
4312        crate::abi::exports_xml2::xmlXPathFreeObject(str_obj);
4313    }
4314    if !find.is_null() {
4315        crate::abi::exports_xml2::xmlXPathFreeObject(find);
4316    }
4317    let c = dup_rust_string(&out);
4318    value_push(pc, xmlXPathWrapString(c));
4319}
4320
4321/// `void xmlXPathNormalizeFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4322///
4323/// # SAFETY
4324///
4325/// - `ctxt` must be valid pointers (or NULL
4326///   where the upstream C contract allows), obtained from the
4327///   matching constructor/owner and not yet freed; the callee may
4328///   take or keep ownership exactly as the C API specifies.
4329///
4330/// The caller must not race this call with concurrent mutation of the
4331/// same objects from other threads (per-object state is not internally
4332/// synchronized). Violating any of the above is undefined behavior.
4333///
4334/// Exercised by the C-API differential courts
4335/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4336/// courts; those pass byte-for-byte against the upstream oracle.
4337#[no_mangle]
4338pub unsafe extern "C" fn xmlXPathNormalizeFunction(ctxt: *mut c_void, nargs: c_int) {
4339    let pc = pc_from(ctxt);
4340    if pc.is_null() {
4341        return;
4342    }
4343    let ctx = unsafe { (*pc).context };
4344    if ctx.is_null() {
4345        return;
4346    }
4347    if nargs == 0 {
4348        let node = unsafe { (*ctx).node };
4349        let sv = if node.is_null() {
4350            String::new()
4351        } else {
4352            node_string_value(node)
4353        };
4354        let c = dup_rust_string(&sv);
4355        value_push(pc, xmlXPathWrapString(c));
4356        // fallthrough with nargs = 1
4357    }
4358
4359    if !check_arity(pc, 1) {
4360        return;
4361    }
4362    cast_top_to_string(pc);
4363    if (*pc).error != 0 {
4364        return;
4365    }
4366    let s = unsafe {
4367        let p = (*(*pc).value).stringval;
4368        if p.is_null() {
4369            String::new()
4370        } else {
4371            CStr::from_ptr(p as *const c_char)
4372                .to_string_lossy()
4373                .into_owned()
4374        }
4375    };
4376    // Strip leading/trailing blanks; collapse internal runs to a single space.
4377    let mut out = String::with_capacity(s.len());
4378    let mut blank = false;
4379    let mut started = false;
4380    for c in s.chars() {
4381        let is_b = c == ' ' || c == '\t' || c == '\n' || c == '\r';
4382        if is_b {
4383            if started {
4384                blank = true;
4385            }
4386        } else {
4387            if blank {
4388                out.push(' ');
4389                blank = false;
4390            }
4391            out.push(c);
4392            started = true;
4393        }
4394    }
4395    unsafe {
4396        let val = (*pc).value;
4397        if !(*val).stringval.is_null() {
4398            xmlFreeImpl((*val).stringval as *mut c_void);
4399        }
4400        (*val).stringval = dup_rust_string(&out);
4401    }
4402}
4403
4404/// `void xmlXPathTranslateFunction(xmlXPathParserContextPtr ctxt, int nargs)`.
4405///
4406/// # SAFETY
4407///
4408/// - `ctxt` must be valid pointers (or NULL
4409///   where the upstream C contract allows), obtained from the
4410///   matching constructor/owner and not yet freed; the callee may
4411///   take or keep ownership exactly as the C API specifies.
4412///
4413/// The caller must not race this call with concurrent mutation of the
4414/// same objects from other threads (per-object state is not internally
4415/// synchronized). Violating any of the above is undefined behavior.
4416///
4417/// Exercised by the C-API differential courts
4418/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4419/// courts; those pass byte-for-byte against the upstream oracle.
4420#[no_mangle]
4421pub unsafe extern "C" fn xmlXPathTranslateFunction(ctxt: *mut c_void, _nargs: c_int) {
4422    let pc = pc_from(ctxt);
4423    if pc.is_null() {
4424        return;
4425    }
4426    if !check_arity(pc, 3) {
4427        return;
4428    }
4429    cast_top_to_string(pc);
4430    if (*pc).error != 0 {
4431        return;
4432    }
4433    let to = value_pop(pc);
4434    cast_top_to_string(pc);
4435    if (*pc).error != 0 {
4436        if !to.is_null() {
4437            crate::abi::exports_xml2::xmlXPathFreeObject(to);
4438        }
4439        return;
4440    }
4441    let from = value_pop(pc);
4442    cast_top_to_string(pc);
4443    if (*pc).error != 0 {
4444        if !to.is_null() {
4445            crate::abi::exports_xml2::xmlXPathFreeObject(to);
4446        }
4447        if !from.is_null() {
4448            crate::abi::exports_xml2::xmlXPathFreeObject(from);
4449        }
4450        return;
4451    }
4452    let str_obj = value_pop(pc);
4453    let (s, f, t) = unsafe {
4454        let s = if str_obj.is_null() || (*str_obj).stringval.is_null() {
4455            String::new()
4456        } else {
4457            CStr::from_ptr((*str_obj).stringval as *const c_char)
4458                .to_string_lossy()
4459                .into_owned()
4460        };
4461        let f = if from.is_null() || (*from).stringval.is_null() {
4462            String::new()
4463        } else {
4464            CStr::from_ptr((*from).stringval as *const c_char)
4465                .to_string_lossy()
4466                .into_owned()
4467        };
4468        let t = if to.is_null() || (*to).stringval.is_null() {
4469            String::new()
4470        } else {
4471            CStr::from_ptr((*to).stringval as *const c_char)
4472                .to_string_lossy()
4473                .into_owned()
4474        };
4475        (s, f, t)
4476    };
4477    if !str_obj.is_null() {
4478        crate::abi::exports_xml2::xmlXPathFreeObject(str_obj);
4479    }
4480    if !from.is_null() {
4481        crate::abi::exports_xml2::xmlXPathFreeObject(from);
4482    }
4483    if !to.is_null() {
4484        crate::abi::exports_xml2::xmlXPathFreeObject(to);
4485    }
4486    let from_chars: Vec<char> = f.chars().collect();
4487    let to_chars: Vec<char> = t.chars().collect();
4488    // UPSTREAM-PARITY: a character in `from` with no corresponding `to`
4489    // character (from longer than to) is removed from the output.
4490    let out: String = s
4491        .chars()
4492        .filter_map(|c| match from_chars.iter().position(|&x| x == c) {
4493            Some(i) if i < to_chars.len() => Some(to_chars[i]),
4494            Some(_) => None,
4495            _ => Some(c),
4496        })
4497        .collect();
4498    let c = dup_rust_string(&out);
4499    value_push(pc, xmlXPathWrapString(c));
4500}
4501
4502/// `void xmlXPathRegisterAllFunctions(xmlXPathContextPtr ctxt)` — no-op since
4503/// 2.14.0 (the core library is compiled in; upstream keeps an empty body).
4504///
4505/// # SAFETY
4506///
4507/// - `_ctxt` must be valid pointers (or NULL
4508///   where the upstream C contract allows), obtained from the
4509///   matching constructor/owner and not yet freed; the callee may
4510///   take or keep ownership exactly as the C API specifies.
4511///
4512/// The caller must not race this call with concurrent mutation of the
4513/// same objects from other threads (per-object state is not internally
4514/// synchronized). Violating any of the above is undefined behavior.
4515///
4516/// Exercised by the C-API differential courts
4517/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4518/// courts; those pass byte-for-byte against the upstream oracle.
4519#[no_mangle]
4520pub const unsafe extern "C" fn xmlXPathRegisterAllFunctions(_ctxt: *mut _xmlXPathContext) {}
4521
4522/// Standard core function name → exported C shim pointer (upstream
4523/// `xmlXPathStandardFunctions` table).
4524unsafe fn standard_function_pointer(
4525    name: &str,
4526) -> Option<unsafe extern "C" fn(*mut c_void, c_int)> {
4527    let f: unsafe extern "C" fn(*mut c_void, c_int) = match name {
4528        "boolean" => xmlXPathBooleanFunction,
4529        "not" => xmlXPathNotFunction,
4530        "true" => xmlXPathTrueFunction,
4531        "false" => xmlXPathFalseFunction,
4532        "lang" => xmlXPathLangFunction,
4533        "number" => xmlXPathNumberFunction,
4534        "sum" => xmlXPathSumFunction,
4535        "floor" => xmlXPathFloorFunction,
4536        "ceiling" => xmlXPathCeilingFunction,
4537        "round" => xmlXPathRoundFunction,
4538        "last" => xmlXPathLastFunction,
4539        "position" => xmlXPathPositionFunction,
4540        "count" => xmlXPathCountFunction,
4541        "id" => xmlXPathIdFunction,
4542        "local-name" => xmlXPathLocalNameFunction,
4543        "namespace-uri" => xmlXPathNamespaceURIFunction,
4544        "string" => xmlXPathStringFunction,
4545        "string-length" => xmlXPathStringLengthFunction,
4546        "concat" => xmlXPathConcatFunction,
4547        "contains" => xmlXPathContainsFunction,
4548        "starts-with" => xmlXPathStartsWithFunction,
4549        "substring" => xmlXPathSubstringFunction,
4550        "substring-before" => xmlXPathSubstringBeforeFunction,
4551        "substring-after" => xmlXPathSubstringAfterFunction,
4552        "normalize-space" => xmlXPathNormalizeFunction,
4553        "translate" => xmlXPathTranslateFunction,
4554        _ => return None,
4555    };
4556    Some(f)
4557}
4558
4559/// `xmlXPathFunction xmlXPathFunctionLookup(xmlXPathContextPtr ctxt, const xmlChar *name)`.
4560///
4561/// # SAFETY
4562///
4563/// - `ctxt` must be a valid context or NULL; `name` a valid string or NULL.
4564#[no_mangle]
4565pub unsafe extern "C" fn xmlXPathFunctionLookup(
4566    ctxt: *mut _xmlXPathContext,
4567    name: *const xmlChar,
4568) -> Option<unsafe extern "C" fn(*mut c_void, c_int)> {
4569    xmlXPathFunctionLookupNS(ctxt, name, ptr::null())
4570}
4571
4572/// `xmlXPathFunction xmlXPathFunctionLookupNS(xmlXPathContextPtr ctxt, const xmlChar *name, const xmlChar *ns_uri)`.
4573///
4574/// # SAFETY
4575///
4576/// - `ctxt` must be a valid context or NULL; `name` a valid string or NULL.
4577#[no_mangle]
4578pub unsafe extern "C" fn xmlXPathFunctionLookupNS(
4579    ctxt: *mut _xmlXPathContext,
4580    name: *const xmlChar,
4581    ns_uri: *const xmlChar,
4582) -> Option<unsafe extern "C" fn(*mut c_void, c_int)> {
4583    if ctxt.is_null() || name.is_null() {
4584        return None;
4585    }
4586    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4587        Ok(s) => s.to_string(),
4588        Err(_) => return None,
4589    };
4590    if ns_uri.is_null() {
4591        if let Some(f) = standard_function_pointer(&name_str) {
4592            return Some(f);
4593        }
4594    }
4595    // User function-lookup callback first, then the C-registered hash.
4596    if let Some(f) = (*ctxt).funcLookupFunc {
4597        let ret = f((*ctxt).funcLookupData, name, ns_uri);
4598        if !ret.is_null() {
4599            // The callback stores an xmlXPathFunction (fn pointer) as void*.
4600            let fp =
4601                std::mem::transmute::<*mut c_void, unsafe extern "C" fn(*mut c_void, c_int)>(ret);
4602            return Some(fp);
4603        }
4604    }
4605    let qualified = if ns_uri.is_null() {
4606        name_str
4607    } else {
4608        let ns = match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4609            Ok(s) => s,
4610            Err(_) => return None,
4611        };
4612        format!("{{{}}}{}", ns, name_str)
4613    };
4614    crate::abi::exports_xml2::xpath_cfunc_lookup((*ctxt).extra, &qualified)
4615}
4616
4617// ═══════════════════════════════════════════════════════════════════════════════
4618// Context / compiled-expression handling
4619// ═══════════════════════════════════════════════════════════════════════════════
4620
4621/// `xmlXPathCompExpr *xmlXPathCtxtCompile(xmlXPathContextPtr ctxt, const xmlChar *str)`.
4622///
4623/// The candidate compiles name tests without context-dependent prefix
4624/// resolution at compile time (prefixes resolve during evaluation), so the
4625/// result equals `xmlXPathCompile` for every expression.
4626///
4627/// # SAFETY
4628///
4629/// - `ctxt` may be NULL; `str` must be a valid string or NULL.
4630#[no_mangle]
4631pub unsafe extern "C" fn xmlXPathCtxtCompile(
4632    _ctxt: *mut _xmlXPathContext,
4633    str_: *const xmlChar,
4634) -> *mut c_void {
4635    crate::abi::exports_xml2::xmlXPathCompile(str_)
4636}
4637
4638/// `xmlXPathObject *xmlXPathCompiledEval(xmlXPathCompExpr *comp, xmlXPathContext *ctx)`.
4639///
4640/// # SAFETY
4641///
4642/// - `comp` must be a compiled expression or NULL; `ctx` a valid context.
4643#[no_mangle]
4644pub unsafe extern "C" fn xmlXPathCompiledEval(
4645    comp: *mut c_void,
4646    ctx: *mut _xmlXPathContext,
4647) -> *mut _xmlXPathObject {
4648    if comp.is_null() || ctx.is_null() {
4649        return ptr::null_mut();
4650    }
4651    let internal = (*ctx).extra as *mut XPathContext;
4652    if internal.is_null() {
4653        return ptr::null_mut();
4654    }
4655    let internal = &mut *internal;
4656    let registry = crate::abi::exports_xml2::xpath_compiled_registry();
4657    let map = registry.lock();
4658    match map.get(&(comp as u64)) {
4659        Some(compiled) => match crate::xml::xpath::evaluate(compiled, internal) {
4660            Some(val) => crate::abi::exports_xml2::xpath_to_object_pub(val),
4661            None => ptr::null_mut(),
4662        },
4663        None => ptr::null_mut(),
4664    }
4665}
4666
4667/// `int xmlXPathCompiledEvalToBoolean(xmlXPathCompExpr *comp, xmlXPathContext *ctxt)`.
4668///
4669/// Returns 1 / 0 for the boolean result, -1 on error.
4670///
4671/// # SAFETY
4672///
4673/// - `comp` must be a compiled expression or NULL; `ctxt` a valid context.
4674#[no_mangle]
4675pub unsafe extern "C" fn xmlXPathCompiledEvalToBoolean(
4676    comp: *mut c_void,
4677    ctxt: *mut _xmlXPathContext,
4678) -> c_int {
4679    let obj = xmlXPathCompiledEval(comp, ctxt);
4680    if obj.is_null() {
4681        return -1;
4682    }
4683    let b = crate::abi::exports_xml2::object_to_xpathvalue_pub(obj).as_boolean();
4684    crate::abi::exports_xml2::xmlXPathFreeObject(obj);
4685    b as c_int
4686}
4687
4688/// `int xmlXPathSetContextNode(xmlNodePtr node, xmlXPathContextPtr ctx)` —
4689/// sets the context node; fails when the node belongs to a different document.
4690///
4691/// # SAFETY
4692///
4693/// - `node` / `ctx` must be valid or NULL.
4694#[no_mangle]
4695pub unsafe extern "C" fn xmlXPathSetContextNode(
4696    node: *mut _xmlNode,
4697    ctx: *mut _xmlXPathContext,
4698) -> c_int {
4699    if node.is_null() || ctx.is_null() {
4700        return -1;
4701    }
4702    if (*node).doc != (*ctx).doc {
4703        return -1;
4704    }
4705    (*ctx).node = node;
4706    let internal = (*ctx).extra as *mut XPathContext;
4707    if !internal.is_null() {
4708        (*internal).context_node = node;
4709    }
4710    0
4711}
4712
4713/// `xmlXPathObject *xmlXPathNodeEval(xmlNodePtr node, const xmlChar *str, xmlXPathContextPtr ctx)`.
4714///
4715/// # SAFETY
4716///
4717/// - `node` / `ctx` must be valid or NULL; `str` a valid string or NULL.
4718#[no_mangle]
4719pub unsafe extern "C" fn xmlXPathNodeEval(
4720    node: *mut _xmlNode,
4721    str_: *const xmlChar,
4722    ctx: *mut _xmlXPathContext,
4723) -> *mut _xmlXPathObject {
4724    if str_.is_null() {
4725        return ptr::null_mut();
4726    }
4727    if xmlXPathSetContextNode(node, ctx) < 0 {
4728        return ptr::null_mut();
4729    }
4730    crate::abi::exports_xml2::xmlXPathEvalExpression(str_, ctx)
4731}
4732
4733/// `int xmlXPathContextSetCache(xmlXPathContextPtr ctxt, int active, int value, int options)`.
4734///
4735/// The candidate has no object cache; the call is accepted and recorded
4736/// (active ⇒ a marker in `ctxt->cache`), returning 0 on success.
4737///
4738/// # SAFETY
4739///
4740/// - `ctxt` must be a valid context or NULL.
4741#[no_mangle]
4742pub unsafe extern "C" fn xmlXPathContextSetCache(
4743    ctxt: *mut _xmlXPathContext,
4744    active: c_int,
4745    _value: c_int,
4746    _options: c_int,
4747) -> c_int {
4748    if ctxt.is_null() {
4749        return -1;
4750    }
4751    (*ctxt).cache = if active != 0 {
4752        (&XML_XPATH_XML_NS.0) as *const crate::abi::structs::_xmlNs as *mut c_void
4753    } else {
4754        ptr::null_mut()
4755    };
4756    0
4757}
4758
4759/// `void xmlXPathRegisterFuncLookup(xmlXPathContextPtr ctxt, xmlXPathFuncLookupFunc f, void *funcCtxt)`.
4760///
4761/// # SAFETY
4762///
4763/// - `ctxt` must be a valid context or NULL.
4764#[no_mangle]
4765pub unsafe extern "C" fn xmlXPathRegisterFuncLookup(
4766    ctxt: *mut _xmlXPathContext,
4767    f: Option<crate::abi::callbacks::xmlXPathFuncLookupFunc>,
4768    data: *mut c_void,
4769) {
4770    if ctxt.is_null() {
4771        return;
4772    }
4773    (*ctxt).funcLookupFunc = f;
4774    (*ctxt).funcLookupData = data;
4775    let internal = (*ctxt).extra as *mut XPathContext;
4776    if !internal.is_null() {
4777        (*internal).func_lookup_func = f;
4778        (*internal).func_lookup_data = data;
4779    }
4780}
4781
4782/// `void xmlXPathRegisterVariableLookup(xmlXPathContextPtr ctxt, xmlXPathVariableLookupFunc f, void *data)`.
4783///
4784/// # SAFETY
4785///
4786/// - `ctxt` must be a valid context or NULL.
4787#[no_mangle]
4788pub unsafe extern "C" fn xmlXPathRegisterVariableLookup(
4789    ctxt: *mut _xmlXPathContext,
4790    f: Option<crate::abi::callbacks::xmlXPathVariableLookupFunc>,
4791    data: *mut c_void,
4792) {
4793    if ctxt.is_null() {
4794        return;
4795    }
4796    (*ctxt).varLookupFunc = f;
4797    (*ctxt).varLookupData = data;
4798    let internal = (*ctxt).extra as *mut XPathContext;
4799    if !internal.is_null() {
4800        (*internal).var_lookup_func = f;
4801        (*internal).var_lookup_data = data;
4802    }
4803}
4804
4805/// `int xmlXPathRegisterVariableNS(xmlXPathContextPtr ctxt, const xmlChar *name, const xmlChar *ns_uri, xmlXPathObjectPtr value)`.
4806///
4807/// # SAFETY
4808///
4809/// - `ctxt` must be a valid context; `name`/`value` valid; `ns_uri` may be NULL.
4810#[no_mangle]
4811pub unsafe extern "C" fn xmlXPathRegisterVariableNS(
4812    ctxt: *mut _xmlXPathContext,
4813    name: *const xmlChar,
4814    ns_uri: *const xmlChar,
4815    value: *mut _xmlXPathObject,
4816) -> c_int {
4817    if ctxt.is_null() || name.is_null() || value.is_null() {
4818        return -1;
4819    }
4820    let internal = (*ctxt).extra as *mut XPathContext;
4821    if internal.is_null() {
4822        return -1;
4823    }
4824    let internal = &mut *internal;
4825    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4826        Ok(s) => s.to_string(),
4827        Err(_) => return -1,
4828    };
4829    let qualified = if ns_uri.is_null() {
4830        name_str
4831    } else {
4832        match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4833            Ok(s) => format!("{{{}}}{}", s, name_str),
4834            Err(_) => return -1,
4835        }
4836    };
4837    let xpath_val = crate::abi::exports_xml2::object_to_xpathvalue_pub(value);
4838    internal.register_variable(&qualified, xpath_val);
4839    0
4840}
4841
4842/// `xmlXPathObjectPtr xmlXPathVariableLookup(xmlXPathContextPtr ctxt, const xmlChar *name)`.
4843///
4844/// # SAFETY
4845///
4846/// - `ctxt` must be a valid context; `name` a valid string or NULL.
4847#[no_mangle]
4848pub unsafe extern "C" fn xmlXPathVariableLookup(
4849    ctxt: *mut _xmlXPathContext,
4850    name: *const xmlChar,
4851) -> *mut _xmlXPathObject {
4852    if ctxt.is_null() {
4853        return ptr::null_mut();
4854    }
4855    if let Some(f) = (*ctxt).varLookupFunc {
4856        let ret = f((*ctxt).varLookupData, name, ptr::null());
4857        return ret;
4858    }
4859    xmlXPathVariableLookupNS(ctxt, name, ptr::null())
4860}
4861
4862/// `xmlXPathObjectPtr xmlXPathVariableLookupNS(xmlXPathContextPtr ctxt, const xmlChar *name, const xmlChar *ns_uri)`.
4863///
4864/// # SAFETY
4865///
4866/// - `ctxt` must be a valid context; `name` a valid string or NULL.
4867#[no_mangle]
4868pub unsafe extern "C" fn xmlXPathVariableLookupNS(
4869    ctxt: *mut _xmlXPathContext,
4870    name: *const xmlChar,
4871    ns_uri: *const xmlChar,
4872) -> *mut _xmlXPathObject {
4873    if ctxt.is_null() || name.is_null() {
4874        return ptr::null_mut();
4875    }
4876    if let Some(f) = (*ctxt).varLookupFunc {
4877        let ret = f((*ctxt).varLookupData, name, ns_uri);
4878        if !ret.is_null() {
4879            return ret;
4880        }
4881    }
4882    let internal = (*ctxt).extra as *mut XPathContext;
4883    if internal.is_null() {
4884        return ptr::null_mut();
4885    }
4886    let internal = &*internal;
4887    let name_str = match CStr::from_ptr(name as *const c_char).to_str() {
4888        Ok(s) => s.to_string(),
4889        Err(_) => return ptr::null_mut(),
4890    };
4891    let qualified = if ns_uri.is_null() {
4892        name_str
4893    } else {
4894        match CStr::from_ptr(ns_uri as *const c_char).to_str() {
4895            Ok(s) => format!("{{{}}}{}", s, name_str),
4896            Err(_) => return ptr::null_mut(),
4897        }
4898    };
4899    match internal.variables.get(&qualified) {
4900        Some(v) => crate::abi::exports_xml2::xpath_to_object_pub(v.clone()),
4901        None => ptr::null_mut(),
4902    }
4903}
4904
4905/// `const xmlChar *xmlXPathNsLookup(xmlXPathContextPtr ctxt, const xmlChar *prefix)`.
4906///
4907/// # SAFETY
4908///
4909/// - `ctxt` must be a valid context; `prefix` a valid string or NULL.
4910#[no_mangle]
4911pub unsafe extern "C" fn xmlXPathNsLookup(
4912    ctxt: *mut _xmlXPathContext,
4913    prefix: *const xmlChar,
4914) -> *const xmlChar {
4915    if ctxt.is_null() || prefix.is_null() {
4916        return ptr::null();
4917    }
4918    // The xml prefix always maps to the XML namespace (upstream).
4919    if cstr_eq(prefix, c"xml".as_ptr() as *const xmlChar) {
4920        return XML_XML_NAMESPACE_BYTES.as_ptr() as *const xmlChar;
4921    }
4922    // In-scope namespace declarations on the context.
4923    let namespaces = (*ctxt).namespaces;
4924    if !namespaces.is_null() {
4925        for i in 0..(*ctxt).nsNr as isize {
4926            let ns = *namespaces.add(i as usize);
4927            if !ns.is_null() && !(*ns).prefix.is_null() && cstr_eq((*ns).prefix, prefix) {
4928                return (*ns).href;
4929            }
4930        }
4931    }
4932    // Registered namespace hash (owned C strings, upstream xmlXPathRegisterNs
4933    // stores strdup'd URIs in ctxt->nsHash; the candidate mirrors that).
4934    if !(*ctxt).nsHash.is_null() {
4935        let map = &*((*ctxt).nsHash as *const HashMap<String, CString>);
4936        let p = CStr::from_ptr(prefix as *const c_char)
4937            .to_string_lossy()
4938            .into_owned();
4939        if let Some(c) = map.get(&p) {
4940            return c.as_ptr() as *const xmlChar;
4941        }
4942    }
4943    ptr::null()
4944}
4945
4946/// `void xmlXPathRegisteredFuncsCleanup(xmlXPathContextPtr ctxt)`.
4947///
4948/// # SAFETY
4949///
4950/// - `ctxt` must be a valid context or NULL.
4951#[no_mangle]
4952pub unsafe extern "C" fn xmlXPathRegisteredFuncsCleanup(ctxt: *mut _xmlXPathContext) {
4953    if ctxt.is_null() {
4954        return;
4955    }
4956    let internal = (*ctxt).extra as *mut XPathContext;
4957    if !internal.is_null() {
4958        (*internal).functions.clear();
4959    }
4960    crate::abi::exports_xml2::xpath_cfunc_cleanup((*ctxt).extra);
4961}
4962
4963/// `void xmlXPathRegisteredVariablesCleanup(xmlXPathContextPtr ctxt)`.
4964///
4965/// # SAFETY
4966///
4967/// - `ctxt` must be a valid context or NULL.
4968#[no_mangle]
4969pub unsafe extern "C" fn xmlXPathRegisteredVariablesCleanup(ctxt: *mut _xmlXPathContext) {
4970    if ctxt.is_null() {
4971        return;
4972    }
4973    let internal = (*ctxt).extra as *mut XPathContext;
4974    if !internal.is_null() {
4975        (*internal).variables.clear();
4976    }
4977}
4978
4979/// `void xmlXPathRegisteredNsCleanup(xmlXPathContextPtr ctxt)`.
4980///
4981/// # SAFETY
4982///
4983/// - `ctxt` must be a valid context or NULL.
4984#[no_mangle]
4985pub unsafe extern "C" fn xmlXPathRegisteredNsCleanup(ctxt: *mut _xmlXPathContext) {
4986    if ctxt.is_null() {
4987        return;
4988    }
4989    let internal = (*ctxt).extra as *mut XPathContext;
4990    if !internal.is_null() {
4991        (*internal).namespaces.clear();
4992    }
4993    if !(*ctxt).nsHash.is_null() {
4994        drop(Box::from_raw(
4995            (*ctxt).nsHash as *mut HashMap<String, CString>,
4996        ));
4997        (*ctxt).nsHash = ptr::null_mut();
4998    }
4999}
5000
5001/// `void xmlXPathSetErrorHandler(xmlXPathContextPtr ctxt, xmlStructuredErrorFunc handler, void *context)`.
5002///
5003/// # SAFETY
5004///
5005/// - `ctxt` must be a valid context or NULL.
5006#[no_mangle]
5007pub unsafe extern "C" fn xmlXPathSetErrorHandler(
5008    ctxt: *mut _xmlXPathContext,
5009    handler: Option<crate::abi::callbacks::xmlStructuredErrorFunc>,
5010    data: *mut c_void,
5011) {
5012    if ctxt.is_null() {
5013        return;
5014    }
5015    (*ctxt).error = handler;
5016    (*ctxt).userData = data;
5017}
5018
5019extern "C" {
5020    fn fwrite(ptr: *const c_void, size: usize, nmemb: usize, stream: *mut c_void) -> usize;
5021}
5022
5023unsafe fn dump_write(output: *mut c_void, s: &str) {
5024    unsafe {
5025        fwrite(s.as_ptr() as *const c_void, 1, s.len(), output);
5026    }
5027}
5028
5029/// `void xmlXPathDebugDumpObject(FILE *output, xmlXPathObject *cur, int depth)`.
5030///
5031/// # SAFETY
5032///
5033/// - `output` must be a valid FILE* or NULL; `cur` a valid object or NULL.
5034#[no_mangle]
5035pub unsafe extern "C" fn xmlXPathDebugDumpObject(
5036    output: *mut c_void,
5037    cur: *mut _xmlXPathObject,
5038    depth: c_int,
5039) {
5040    if output.is_null() {
5041        return;
5042    }
5043    let mut s = String::new();
5044    for _ in 0..depth.clamp(0, 25) {
5045        s.push_str("  ");
5046    }
5047    if cur.is_null() {
5048        s.push_str("Object is empty (NULL)\n");
5049        dump_write(output, &s);
5050        return;
5051    }
5052    unsafe {
5053        match (*cur).type_ {
5054            t if t == xmlXPathObjectType::XPATH_BOOLEAN as c_int => {
5055                s.push_str("Object is a Boolean : ");
5056                s.push_str(if (*cur).boolval != 0 {
5057                    "true\n"
5058                } else {
5059                    "false\n"
5060                });
5061            }
5062            t if t == xmlXPathObjectType::XPATH_NUMBER as c_int => {
5063                let f = (*cur).floatval;
5064                if f.is_nan() {
5065                    s.push_str("Object is a number : NaN\n");
5066                } else if f == f64::INFINITY {
5067                    s.push_str("Object is a number : Infinity\n");
5068                } else if f == f64::NEG_INFINITY {
5069                    s.push_str("Object is a number : -Infinity\n");
5070                } else if f == 0.0 {
5071                    s.push_str("Object is a number : 0\n");
5072                } else {
5073                    s.push_str("Object is a number : ");
5074                    s.push_str(&f.to_string());
5075                    s.push('\n');
5076                }
5077            }
5078            t if t == xmlXPathObjectType::XPATH_STRING as c_int => {
5079                s.push_str("Object is a string : ");
5080                if (*cur).stringval.is_null() {
5081                    s.push_str("(null)");
5082                } else {
5083                    let sv = CStr::from_ptr((*cur).stringval as *const c_char).to_string_lossy();
5084                    s.push_str(&sv);
5085                }
5086                s.push('\n');
5087            }
5088            t if t == xmlXPathObjectType::XPATH_NODESET as c_int => {
5089                s.push_str("Object is a Node Set :\n");
5090                let ns = (*cur).nodesetval as *mut _xmlNodeSet;
5091                if !ns.is_null() {
5092                    for _ in 0..=depth.min(24) {
5093                        s.push_str("  ");
5094                    }
5095                    s.push_str(&format!("Object contains {} nodes\n", (*ns).nodeNr));
5096                }
5097            }
5098            t if t == xmlXPathObjectType::XPATH_XSLT_TREE as c_int => {
5099                s.push_str("Object is an XSLT value tree :\n");
5100            }
5101            t if t == xmlXPathObjectType::XPATH_USERS as c_int => {
5102                s.push_str("Object is user defined\n");
5103            }
5104            _ => {
5105                s.push_str("Object is uninitialized\n");
5106            }
5107        }
5108    }
5109    dump_write(output, &s);
5110}
5111
5112/// `void xmlXPathDebugDumpCompExpr(FILE *output, xmlXPathCompExpr *comp, int depth)`.
5113///
5114/// The candidate's compiled expressions are opaque registry handles; the dump
5115/// prints the original expression text. NULL handles print nothing (matching
5116/// upstream's early return).
5117///
5118/// # SAFETY
5119///
5120/// - `output` must be a valid FILE* or NULL; `comp` a compiled expression or NULL.
5121#[no_mangle]
5122pub unsafe extern "C" fn xmlXPathDebugDumpCompExpr(
5123    output: *mut c_void,
5124    comp: *mut c_void,
5125    depth: c_int,
5126) {
5127    if output.is_null() || comp.is_null() {
5128        return;
5129    }
5130    let registry = crate::abi::exports_xml2::xpath_compiled_registry();
5131    let map = registry.lock();
5132    if let Some(compiled) = map.get(&(comp as u64)) {
5133        let mut s = String::new();
5134        for _ in 0..depth.clamp(0, 25) {
5135            s.push_str("  ");
5136        }
5137        s.push_str("Compiled Expression : ");
5138        s.push_str(&compiled.original);
5139        s.push('\n');
5140        dump_write(output, &s);
5141    }
5142}
5143
5144#[allow(unused)]
5145const fn _unused_xpath_batch(_: *mut _xmlAttr) {}