Skip to main content

libxml_rs/xml/xpath/
exports.rs

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