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