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