Skip to main content

libxml_rs/xml/xpath/
exports.rs

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