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