Skip to main content

libxml_rs/xml/xpath/
exports.rs

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