Skip to main content

libxml_rs/xml/xpath/
parser_context.rs

1//! XPath parser context and value stack (§25, 11.1-I XPath family closure).
2//!
3//! Upstream libxml2 exposes `xmlXPathParserContextPtr` (a fully public struct
4//! in the headers) together with a value stack (`xmlXPathPush*` /
5//! `xmlXPathPop*`) and stack-based value operators (`xmlXPathAddValues` etc.).
6//!
7//! # UPSTREAM-PARITY (field layout)
8//!
9//! The candidate struct layout mirrors the upstream `_xmlXPathParserContext`
10//! field-for-field (see include/libxml/xpath.h and upstream xpath.h):
11//!
12//! ```c
13//! struct _xmlXPathParserContext {
14//!     const xmlChar *cur;      /* the current char being parsed */
15//!     const xmlChar *base;     /* the full expression */
16//!     int error;               /* error code */
17//!     xmlXPathContext *context;/* the evaluation context */
18//!     xmlXPathObject *value;   /* the current value */
19//!     int valueNr;             /* number of values stacked */
20//!     int valueMax;            /* max number of values stacked */
21//!     xmlXPathObject **valueTab;/* stack of values */
22//!     xmlXPathCompExpr *comp;  /* the precompiled expression */
23//!     int xptr;                /* it this an XPointer expression */
24//!     xmlNode *ancestor;       /* used for walking preceding axis */
25//!     int valueFrame;          /* always zero for compatibility */
26//! };
27//! ```
28//!
29//! The XPATH-001 differential court verifies the stack operators and the
30//! parser-context APIs against the oracle.
31//!
32//! # Historical quirks & epochs
33//!
34//! The `_xmlXPathParserContext` layout has been stable across the whole
35//! libxml2 matrix (2.7.8..2.15.3): the `valueFrame` field is documented
36//! upstream as always zero for compatibility, and the value-stack
37//! operators (xmlXPathPush*/xmlXPathPop*) have not changed semantics. The
38//! XPath node-set serialization capability E-001 (2.9.10 boundary) is
39//! handled at the CLI layer, not here.
40//!
41//! # Tempting simplifications that would break parity
42//!
43//! A tempting simplification is to represent the value stack with a Rust
44//! Vec and pop by truncation. Upstream pops return the object and shift
45//! the stack; the public xmlXPathPop* functions are ABI-observable and
46//! their NULL-on-empty / last-value behavior is court-checked (XPATH-001).
47//! Keep the explicit valueTab/valueNr/valueMax fields byte-compatible.
48
49#![allow(missing_docs)]
50
51use core::ffi::c_void;
52use core::ptr;
53use std::os::raw::{c_char, c_int};
54
55use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl, xmlMallocZero};
56use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlNodeSet, _xmlXPathContext, _xmlXPathObject};
57use crate::abi::types::{xmlChar, xmlXPathObjectType};
58use crate::xml::xpath::types::{node_string_value, NodeSet, XPathValue};
59
60/// The parser-context stack depth (upstream `XML_XPATH_STACK_BYTES` / 10 slots).
61const VALUE_TAB_SIZE: usize = 10;
62
63/// The public `xmlXPathParserContext` (layout mirrors upstream xpath.h).
64#[repr(C)]
65#[derive(Debug)]
66pub struct XmlXPathParserContext {
67    pub cur: *const xmlChar,
68    pub base: *const xmlChar,
69    pub error: c_int,
70    pub context: *mut _xmlXPathContext,
71    pub value: *mut _xmlXPathObject,
72    pub value_nr: c_int,
73    pub value_max: c_int,
74    pub value_tab: *mut *mut _xmlXPathObject,
75    pub comp: *mut c_void,
76    pub xptr: c_int,
77    pub ancestor: *mut _xmlNode,
78    pub value_frame: c_int,
79}
80
81/// Set the parser-context error code (upstream `xmlXPathSetError` /
82/// `XP_ERROR`).
83///
84/// # SAFETY
85///
86/// - `pc` must be a valid parser context.
87pub unsafe fn pc_set_error(pc: *mut XmlXPathParserContext, code: c_int) {
88    if pc.is_null() {
89        return;
90    }
91    unsafe { (*pc).error = code };
92}
93
94/// Create a new parser context over `str` with the given evaluation context.
95///
96/// # SAFETY
97///
98/// - `str` must be a valid NUL-terminated string or NULL.
99/// - `ctxt` must be a valid `_xmlXPathContext` or NULL.
100pub unsafe fn new_parser_context(
101    str_: *const xmlChar,
102    ctxt: *mut _xmlXPathContext,
103) -> *mut XmlXPathParserContext {
104    let pc = xmlMallocZero(size_of::<XmlXPathParserContext>()) as *mut XmlXPathParserContext;
105    if pc.is_null() {
106        return ptr::null_mut();
107    }
108    let tab = xmlMallocImpl(VALUE_TAB_SIZE * size_of::<*mut _xmlXPathObject>())
109        as *mut *mut _xmlXPathObject;
110    if tab.is_null() {
111        xmlFreeImpl(pc as *mut c_void);
112        return ptr::null_mut();
113    }
114    unsafe {
115        (*pc).cur = str_;
116        (*pc).base = str_;
117        (*pc).context = ctxt;
118        (*pc).value_nr = 0;
119        (*pc).value_max = VALUE_TAB_SIZE as c_int;
120        (*pc).value_tab = tab;
121        (*pc).value = ptr::null_mut();
122        (*pc).ancestor = ptr::null_mut();
123        (*pc).value_frame = 0;
124    }
125    pc
126}
127
128/// Free a parser context.
129///
130/// # SAFETY
131///
132/// - `pc` must be a valid parser context or NULL.
133pub unsafe fn free_parser_context(pc: *mut XmlXPathParserContext) {
134    if pc.is_null() {
135        return;
136    }
137    unsafe {
138        // Free any remaining stack values. `value` always aliases the top of
139        // the stack (or NULL when empty), so it must not be freed separately.
140        let tab = (*pc).value_tab;
141        if !tab.is_null() {
142            for i in 0..(*pc).value_nr as usize {
143                let obj = *tab.add(i);
144                if !obj.is_null() {
145                    crate::abi::exports_xml2::xmlXPathFreeObject(obj);
146                }
147            }
148            xmlFreeImpl(tab as *mut c_void);
149        }
150        xmlFreeImpl(pc as *mut c_void);
151    }
152}
153
154/// Push a value object onto the stack (upstream `valuePush`).
155///
156/// # SAFETY
157///
158/// - `pc` must be a valid parser context.
159pub unsafe fn value_push(
160    pc: *mut XmlXPathParserContext,
161    val: *mut _xmlXPathObject,
162) -> *mut _xmlXPathObject {
163    if pc.is_null() {
164        return ptr::null_mut();
165    }
166    unsafe {
167        let nr = (*pc).value_nr as usize;
168        if nr >= VALUE_TAB_SIZE {
169            // UPSTREAM-PARITY: stack overflow leaves the value untouched and
170            // reports an XPATH_STACK_ERROR.
171            (*pc).error = crate::abi::types::XPATH_STACK_ERROR as c_int;
172            return val;
173        }
174        ptr::write((*pc).value_tab.add(nr), val);
175        (*pc).value_nr = nr as c_int + 1;
176        (*pc).value = val;
177        val
178    }
179}
180
181/// Pop a value object from the stack (upstream `valuePop`).
182///
183/// # SAFETY
184///
185/// - `pc` must be a valid parser context.
186pub unsafe fn value_pop(pc: *mut XmlXPathParserContext) -> *mut _xmlXPathObject {
187    if pc.is_null() {
188        return ptr::null_mut();
189    }
190    unsafe {
191        let nr = (*pc).value_nr;
192        if nr <= 0 {
193            return ptr::null_mut();
194        }
195        let idx = (nr - 1) as usize;
196        let val = *(*pc).value_tab.add(idx);
197        ptr::write((*pc).value_tab.add(idx), ptr::null_mut());
198        (*pc).value_nr = nr - 1;
199        (*pc).value = if (*pc).value_nr > 0 {
200            *(*pc).value_tab.add((*pc).value_nr as usize - 1)
201        } else {
202            ptr::null_mut()
203        };
204        val
205    }
206}
207
208/// Build a boolean object.
209///
210/// # SAFETY
211///
212/// - The returned object is heap-allocated; the caller owns it.
213pub unsafe fn new_bool(b: bool) -> *mut _xmlXPathObject {
214    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
215    if !obj.is_null() {
216        unsafe {
217            (*obj).type_ = xmlXPathObjectType::XPATH_BOOLEAN as c_int;
218            (*obj).boolval = if b { 1 } else { 0 };
219        }
220    }
221    obj
222}
223
224/// Build a number object.
225///
226/// # SAFETY
227///
228/// The function touches crate-global state only; it is safe
229/// as long as the caller respects the library's global
230/// initialization/cleanup ordering (xmlInitParser before use,
231/// xmlCleanupParser only after all users are done).
232///
233/// Violating the global lifecycle ordering, or calling this after
234/// teardown or from a signal handler, is undefined behavior.
235pub unsafe fn new_number(n: f64) -> *mut _xmlXPathObject {
236    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
237    if !obj.is_null() {
238        unsafe {
239            (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
240            (*obj).floatval = n;
241        }
242    }
243    obj
244}
245
246/// Build a string object (copies the value).
247///
248/// # SAFETY
249///
250/// - `s` must be a valid NUL-terminated string or NULL.
251pub unsafe fn new_string(s: *const xmlChar) -> *mut _xmlXPathObject {
252    crate::xml::xpath::exports::xmlXPathNewString(s)
253}
254
255/// Pop a number (upstream `xmlXPathPopNumber`): converts the top of the stack
256/// to a number, frees it, returns the value.
257///
258/// # SAFETY
259///
260/// - `pc` must be a valid parser context or NULL.
261pub unsafe fn pop_number(pc: *mut XmlXPathParserContext) -> f64 {
262    if pc.is_null() {
263        return f64::NAN;
264    }
265    let arg = unsafe { value_pop(pc) };
266    if arg.is_null() {
267        return f64::NAN;
268    }
269    let n = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_number();
270    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
271    n
272}
273
274/// Pop a boolean.
275///
276/// # SAFETY
277///
278/// - `pc` must be a valid parser context or NULL.
279pub unsafe fn pop_boolean(pc: *mut XmlXPathParserContext) -> c_int {
280    if pc.is_null() {
281        return 0;
282    }
283    let arg = unsafe { value_pop(pc) };
284    if arg.is_null() {
285        return 0;
286    }
287    let b = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_boolean();
288    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
289    b as c_int
290}
291
292/// Pop a string (freshly allocated; caller frees with xmlFree).
293///
294/// # SAFETY
295///
296/// - `pc` must be a valid parser context or NULL.
297pub unsafe fn pop_string(pc: *mut XmlXPathParserContext) -> *mut xmlChar {
298    if pc.is_null() {
299        return ptr::null_mut();
300    }
301    let arg = unsafe { value_pop(pc) };
302    if arg.is_null() {
303        return ptr::null_mut();
304    }
305    let s = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_string();
306    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
307    // xml_strndup (not xml_strdup): a Rust String's as_bytes() is not
308    // NUL-terminated, and xml_strdup would scan past the allocation.
309    crate::xml::string::xml_strndup(s.as_bytes().as_ptr() as *const xmlChar, s.len())
310}
311
312/// Pop a node set (freshly allocated; caller frees with xmlXPathFreeNodeSet).
313///
314/// # SAFETY
315///
316/// - `pc` must be a valid parser context or NULL.
317pub unsafe fn pop_node_set(pc: *mut XmlXPathParserContext) -> *mut _xmlNodeSet {
318    if pc.is_null() {
319        return ptr::null_mut();
320    }
321    let arg = unsafe { value_pop(pc) };
322    if arg.is_null() {
323        return ptr::null_mut();
324    }
325    let ns_ptr = unsafe { (*arg).nodesetval as *mut _xmlNodeSet };
326    // Detach the node set from the object before freeing the object.
327    unsafe { (*arg).nodesetval = ptr::null_mut() };
328    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
329    ns_ptr
330}
331
332/// Pop an external (user) pointer.
333///
334/// # SAFETY
335///
336/// - `pc` must be a valid parser context or NULL.
337pub unsafe fn pop_external(pc: *mut XmlXPathParserContext) -> *mut c_void {
338    if pc.is_null() {
339        return ptr::null_mut();
340    }
341    let arg = unsafe { value_pop(pc) };
342    if arg.is_null() {
343        return ptr::null_mut();
344    }
345    let user = unsafe { (*arg).user };
346    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
347    user
348}
349
350/// Pop two number operands from the stack and apply `op`.
351///
352/// UPSTREAM-PARITY: the first popped value is the RIGHT operand
353/// (xmlXPathAddValues pops arg (rhs) then arg (lhs)).
354///
355/// # SAFETY
356///
357/// - `pc` must be a valid parser context.
358pub unsafe fn binary_number_op(pc: *mut XmlXPathParserContext, op: impl Fn(f64, f64) -> f64) {
359    let rhs = unsafe { pop_number(pc) };
360    let lhs = unsafe { pop_number(pc) };
361    let r = op(lhs, rhs);
362    unsafe { value_push(pc, new_number(r)) };
363}
364
365/// Number → number conversion of the top-of-stack object, in place
366/// (upstream `CAST_TO_NUMBER`): the object's type becomes XPATH_NUMBER and
367/// `floatval` holds the converted value. A NULL top (or a USERS object)
368/// reports XPATH_INVALID_OPERAND.
369///
370/// # SAFETY
371///
372/// - `pc` must be a valid parser context with a non-NULL `value`.
373pub unsafe fn cast_top_to_number(pc: *mut XmlXPathParserContext) {
374    unsafe {
375        let val = (*pc).value;
376        if val.is_null() {
377            pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
378            return;
379        }
380        if (*val).type_ != xmlXPathObjectType::XPATH_NUMBER as c_int {
381            let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(val);
382            (*val).floatval = v.as_number();
383            (*val).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
384        }
385    }
386}
387
388/// String-value equality of two node-set members (upstream
389/// `xmlXPathEqualNodeSets` core loop; the hash fast-path is not observable).
390fn node_set_pair_equal(a: &NodeSet, b: &NodeSet, neq: bool) -> bool {
391    if a.is_empty() || b.is_empty() {
392        return false;
393    }
394    // For "=", an identical node pointer in both sets short-circuits to true
395    // (upstream `xmlXPathEqualNodeSets`).
396    if !neq {
397        for na in a.iter() {
398            if b.contains(na) {
399                return true;
400            }
401        }
402    }
403    for na in a.iter() {
404        let sa = node_string_value(na);
405        for nb in b.iter() {
406            let sb = node_string_value(nb);
407            if (sa == sb) ^ neq {
408                return true;
409            }
410        }
411    }
412    false
413}
414
415/// Node-set vs string comparison (upstream `xmlXPathEqualNodeSetString`).
416fn node_set_string_equal(a: &NodeSet, s: &str, neq: bool) -> bool {
417    if a.is_empty() {
418        return false;
419    }
420    for n in a.iter() {
421        let sv = node_string_value(n);
422        if sv == s {
423            if neq {
424                continue;
425            }
426            return true;
427        } else if neq {
428            return true;
429        }
430    }
431    false
432}
433
434/// Node-set vs number comparison (upstream `xmlXPathEqualNodeSetFloat`).
435fn node_set_number_equal(a: &NodeSet, f: f64, neq: bool) -> bool {
436    for n in a.iter() {
437        let sv = node_string_value(n);
438        let v = crate::xml::xpath::types::string_to_number(&sv);
439        if v.is_nan() {
440            if neq {
441                return true;
442            }
443        } else if (!neq && v == f) || (neq && v != f) {
444            return true;
445        }
446    }
447    false
448}
449
450/// Full XPath 1.0 equality matrix (§3.4) matching upstream 2.15
451/// `xmlXPathEqualValues` / `xmlXPathNotEqualValues` (including the node-set
452/// pair/string/number/boolean special cases).
453pub fn equal_values_inner(v1: &XPathValue, v2: &XPathValue, neq: bool) -> bool {
454    // Normalise so that, when exactly one side is a node-set, `ns` is the
455    // node-set side (upstream swaps so arg1 is the node-set).
456    match (v1, v2) {
457        (XPathValue::NodeSet(a), XPathValue::NodeSet(b)) => node_set_pair_equal(a, b, neq),
458        (XPathValue::NodeSet(a), other) => node_set_vs_value(a, other, neq),
459        (other, XPathValue::NodeSet(b)) => node_set_vs_value(b, other, neq),
460        _ => common_equal(v1, v2) ^ neq,
461    }
462}
463
464fn node_set_vs_value(ns: &NodeSet, other: &XPathValue, neq: bool) -> bool {
465    match other {
466        XPathValue::Boolean(b) => (!ns.is_empty()) == *b,
467        XPathValue::Number(f) => node_set_number_equal(ns, *f, neq),
468        XPathValue::String(s) => node_set_string_equal(ns, s, neq),
469        // Both sides node-sets are handled by the caller before this point.
470        XPathValue::NodeSet(_) => unreachable!(),
471    }
472}
473
474/// The non-node-set equality matrix (upstream `xmlXPathEqualValuesCommon`).
475fn common_equal(v1: &XPathValue, v2: &XPathValue) -> bool {
476    // Boolean involved → boolean conversion of both sides.
477    if matches!(v1, XPathValue::Boolean(_)) || matches!(v2, XPathValue::Boolean(_)) {
478        return v1.as_boolean() == v2.as_boolean();
479    }
480    // Number involved (and no boolean) → number conversion of both sides.
481    // IEEE 754 equality gives exactly the upstream NaN/±Infinity rules.
482    if matches!(v1, XPathValue::Number(_)) || matches!(v2, XPathValue::Number(_)) {
483        return v1.as_number() == v2.as_number();
484    }
485    // Both strings (or undefined → converted to strings).
486    v1.as_string() == v2.as_string()
487}
488
489/// Pop two objects and compare for equality (upstream xmlXPathEqualValues /
490/// xmlXPathNotEqualValues semantics): pushes the boolean result object and
491/// returns the comparison value.
492///
493/// # SAFETY
494///
495/// - `pc` must be a valid parser context.
496pub unsafe fn equal_values_impl(pc: *mut XmlXPathParserContext, neq: bool) -> c_int {
497    let arg2 = unsafe { value_pop(pc) };
498    if arg2.is_null() {
499        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
500        return 0;
501    }
502    let arg1 = unsafe { value_pop(pc) };
503    if arg1.is_null() {
504        unsafe {
505            value_push(pc, arg2);
506        }
507        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
508        return 0;
509    }
510    // UPSTREAM-PARITY: comparing an object with itself yields 1 for
511    // equality / 0 for inequality without pushing a result.
512    if arg1 == arg2 {
513        crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
514        return if neq { 0 } else { 1 };
515    }
516    let v1 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg1);
517    let v2 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg2);
518    crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
519    crate::abi::exports_xml2::xmlXPathFreeObject(arg2);
520
521    let eq = equal_values_inner(&v1, &v2, neq);
522    unsafe { value_push(pc, new_bool(eq)) };
523    eq as c_int
524}
525
526/// Pop two objects and compare with `<`, `<=`, `>`, `>=` (upstream
527/// `xmlXPathCompareValues` with the `inf` / `strict` encoding).
528///
529/// # SAFETY
530///
531/// - `pc` must be a valid parser context.
532pub unsafe fn compare_values_impl(
533    pc: *mut XmlXPathParserContext,
534    inf: bool,
535    strict: bool,
536) -> c_int {
537    let arg2 = unsafe { value_pop(pc) };
538    if arg2.is_null() {
539        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
540        return 0;
541    }
542    let arg1 = unsafe { value_pop(pc) };
543    if arg1.is_null() {
544        unsafe {
545            value_push(pc, arg2);
546        }
547        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
548        return 0;
549    }
550
551    let v1 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg1);
552    let v2 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg2);
553    crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
554    crate::abi::exports_xml2::xmlXPathFreeObject(arg2);
555
556    let (ns_side, other): (Option<&NodeSet>, Option<(&XPathValue, &XPathValue)>) = match (&v1, &v2)
557    {
558        (XPathValue::NodeSet(a), XPathValue::NodeSet(b)) => {
559            return compare_node_sets(a, b, inf, strict);
560        }
561        (XPathValue::NodeSet(a), _) => (Some(a), None),
562        (_, XPathValue::NodeSet(b)) => (Some(b), None),
563        _ => (None, Some((&v1, &v2))),
564    };
565
566    if let Some(ns) = ns_side {
567        // One side is a node-set, the other a scalar.
568        let other = if matches!(&v1, XPathValue::NodeSet(_)) {
569            &v2
570        } else {
571            &v1
572        };
573        // The node-set is on the left of the operator (upstream swaps
574        // direction when the value is on the left).
575        let (ns_is_left, scalar) = if matches!(&v1, XPathValue::NodeSet(_)) {
576            (true, other)
577        } else {
578            (false, other)
579        };
580        return compare_node_set_value(ns, scalar, ns_is_left, inf, strict);
581    }
582
583    let (l, r) = other.unwrap();
584    // Neither side is a node-set: convert both to numbers and compare.
585    // NaN comparisons are always false (upstream).
586    let a = l.as_number();
587    let b = r.as_number();
588    if a.is_nan() || b.is_nan() {
589        return 0;
590    }
591    let ret = if inf && strict {
592        a < b
593    } else if inf && !strict {
594        a <= b
595    } else if !inf && strict {
596        a > b
597    } else {
598        a >= b
599    };
600    ret as c_int
601}
602
603/// Compare two node-sets numerically (any node pair satisfying the relation).
604///
605/// # Safety
606///
607/// - Every node pointer in `a` and `b` must be NULL or a valid `_xmlNode`
608///   that stays alive for the call, since `node_to_number` reads each
609///   node's string value.
610fn compare_node_sets(a: &NodeSet, b: &NodeSet, inf: bool, strict: bool) -> c_int {
611    if a.is_empty() || b.is_empty() {
612        return 0;
613    }
614    let b_nums: Vec<f64> = b.iter().map(|n| unsafe { node_to_number(n) }).collect();
615    for na in a.iter() {
616        let va = unsafe { node_to_number(na) };
617        if va.is_nan() {
618            continue;
619        }
620        for &vb in &b_nums {
621            if vb.is_nan() {
622                continue;
623            }
624            let ret = if inf && strict {
625                va < vb
626            } else if inf && !strict {
627                va <= vb
628            } else if !inf && strict {
629                va > vb
630            } else {
631                va >= vb
632            };
633            if ret {
634                return 1;
635            }
636        }
637    }
638    0
639}
640
641fn compare_node_set_value(
642    ns: &NodeSet,
643    scalar: &XPathValue,
644    ns_is_left: bool,
645    inf: bool,
646    strict: bool,
647) -> c_int {
648    match scalar {
649        XPathValue::Number(f) => compare_node_set_number(ns, *f, ns_is_left, inf, strict),
650        XPathValue::String(s) => compare_node_set_string(ns, s, ns_is_left, inf, strict),
651        XPathValue::Boolean(_) => {
652            // Convert the node-set to a boolean and compare.
653            let ns_bool = !ns.is_empty();
654            let b = scalar.as_boolean();
655            let (a, b) = if ns_is_left {
656                (ns_bool, b)
657            } else {
658                (b, ns_bool)
659            };
660            let ret = if inf && strict {
661                !a & b
662            } else if inf && !strict {
663                a <= b
664            } else if !inf && strict {
665                a & !b
666            } else {
667                a >= b
668            };
669            ret as c_int
670        }
671        _ => 0,
672    }
673}
674
675/// Compare a node-set against a scalar number.
676///
677/// # Safety
678///
679/// - Every node pointer in `ns` must be NULL or a valid `_xmlNode` that
680///   stays alive for the call, since `node_to_number` reads each node's
681///   string value.
682fn compare_node_set_number(
683    ns: &NodeSet,
684    f: f64,
685    ns_is_left: bool,
686    inf: bool,
687    strict: bool,
688) -> c_int {
689    for n in ns.iter() {
690        let v = unsafe { node_to_number(n) };
691        if v.is_nan() {
692            continue;
693        }
694        let (a, b) = if ns_is_left { (v, f) } else { (f, v) };
695        let ret = if inf && strict {
696            a < b
697        } else if inf && !strict {
698            a <= b
699        } else if !inf && strict {
700            a > b
701        } else {
702            a >= b
703        };
704        if ret {
705            return 1;
706        }
707    }
708    0
709}
710
711fn compare_node_set_string(
712    ns: &NodeSet,
713    s: &str,
714    ns_is_left: bool,
715    inf: bool,
716    strict: bool,
717) -> c_int {
718    for n in ns.iter() {
719        let sv = node_string_value(n);
720        let v = crate::xml::xpath::types::string_to_number(&sv);
721        let w = crate::xml::xpath::types::string_to_number(s);
722        if v.is_nan() || w.is_nan() {
723            continue;
724        }
725        let (a, b) = if ns_is_left { (v, w) } else { (w, v) };
726        let ret = if inf && strict {
727            a < b
728        } else if inf && !strict {
729            a <= b
730        } else if !inf && strict {
731            a > b
732        } else {
733            a >= b
734        };
735        if ret {
736            return 1;
737        }
738    }
739    0
740}
741
742/// String-value of a node converted to a number (upstream
743/// `xmlXPathNodeToNumber`).
744unsafe fn node_to_number(node: *mut _xmlNode) -> f64 {
745    if node.is_null() {
746        return f64::NAN;
747    }
748    let sv = node_string_value(node);
749    crate::xml::xpath::types::string_to_number(&sv)
750}
751
752/// Convert an internal value to a C object.
753///
754/// # SAFETY
755///
756/// - The returned object is heap-allocated.
757pub unsafe fn value_to_object(v: XPathValue) -> *mut _xmlXPathObject {
758    crate::abi::exports_xml2::xpath_to_object_pub(v)
759}
760
761#[allow(unused)]
762const fn _unused(_: *mut _xmlDoc) {}
763
764#[allow(unused)]
765const fn _unused_char(_: *const c_char) {}