Skip to main content

libxml_rs/xml/xpath/
exports.rs

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