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