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.
208pub unsafe fn new_number(n: f64) -> *mut _xmlXPathObject {
209    let obj = xmlMallocZero(size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject;
210    if !obj.is_null() {
211        unsafe {
212            (*obj).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
213            (*obj).floatval = n;
214        }
215    }
216    obj
217}
218
219/// Build a string object (copies the value).
220///
221/// # SAFETY
222///
223/// - `s` must be a valid NUL-terminated string or NULL.
224pub unsafe fn new_string(s: *const xmlChar) -> *mut _xmlXPathObject {
225    crate::xml::xpath::exports::xmlXPathNewString(s)
226}
227
228/// Pop a number (upstream `xmlXPathPopNumber`): converts the top of the stack
229/// to a number, frees it, returns the value.
230///
231/// # SAFETY
232///
233/// - `pc` must be a valid parser context or NULL.
234pub unsafe fn pop_number(pc: *mut XmlXPathParserContext) -> f64 {
235    if pc.is_null() {
236        return f64::NAN;
237    }
238    let arg = unsafe { value_pop(pc) };
239    if arg.is_null() {
240        return f64::NAN;
241    }
242    let n = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_number();
243    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
244    n
245}
246
247/// Pop a boolean.
248///
249/// # SAFETY
250///
251/// - `pc` must be a valid parser context or NULL.
252pub unsafe fn pop_boolean(pc: *mut XmlXPathParserContext) -> c_int {
253    if pc.is_null() {
254        return 0;
255    }
256    let arg = unsafe { value_pop(pc) };
257    if arg.is_null() {
258        return 0;
259    }
260    let b = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_boolean();
261    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
262    b as c_int
263}
264
265/// Pop a string (freshly allocated; caller frees with xmlFree).
266///
267/// # SAFETY
268///
269/// - `pc` must be a valid parser context or NULL.
270pub unsafe fn pop_string(pc: *mut XmlXPathParserContext) -> *mut xmlChar {
271    if pc.is_null() {
272        return ptr::null_mut();
273    }
274    let arg = unsafe { value_pop(pc) };
275    if arg.is_null() {
276        return ptr::null_mut();
277    }
278    let s = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg).as_string();
279    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
280    // xml_strndup (not xml_strdup): a Rust String's as_bytes() is not
281    // NUL-terminated, and xml_strdup would scan past the allocation.
282    crate::xml::string::xml_strndup(s.as_bytes().as_ptr() as *const xmlChar, s.len())
283}
284
285/// Pop a node set (freshly allocated; caller frees with xmlXPathFreeNodeSet).
286///
287/// # SAFETY
288///
289/// - `pc` must be a valid parser context or NULL.
290pub unsafe fn pop_node_set(pc: *mut XmlXPathParserContext) -> *mut _xmlNodeSet {
291    if pc.is_null() {
292        return ptr::null_mut();
293    }
294    let arg = unsafe { value_pop(pc) };
295    if arg.is_null() {
296        return ptr::null_mut();
297    }
298    let ns_ptr = unsafe { (*arg).nodesetval as *mut _xmlNodeSet };
299    // Detach the node set from the object before freeing the object.
300    unsafe { (*arg).nodesetval = ptr::null_mut() };
301    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
302    ns_ptr
303}
304
305/// Pop an external (user) pointer.
306///
307/// # SAFETY
308///
309/// - `pc` must be a valid parser context or NULL.
310pub unsafe fn pop_external(pc: *mut XmlXPathParserContext) -> *mut c_void {
311    if pc.is_null() {
312        return ptr::null_mut();
313    }
314    let arg = unsafe { value_pop(pc) };
315    if arg.is_null() {
316        return ptr::null_mut();
317    }
318    let user = unsafe { (*arg).user };
319    crate::abi::exports_xml2::xmlXPathFreeObject(arg);
320    user
321}
322
323/// Pop two number operands from the stack and apply `op`.
324///
325/// UPSTREAM-PARITY: the first popped value is the RIGHT operand
326/// (xmlXPathAddValues pops arg (rhs) then arg (lhs)).
327///
328/// # SAFETY
329///
330/// - `pc` must be a valid parser context.
331pub unsafe fn binary_number_op(pc: *mut XmlXPathParserContext, op: impl Fn(f64, f64) -> f64) {
332    let rhs = unsafe { pop_number(pc) };
333    let lhs = unsafe { pop_number(pc) };
334    let r = op(lhs, rhs);
335    unsafe { value_push(pc, new_number(r)) };
336}
337
338/// Number → number conversion of the top-of-stack object, in place
339/// (upstream `CAST_TO_NUMBER`): the object's type becomes XPATH_NUMBER and
340/// `floatval` holds the converted value. A NULL top (or a USERS object)
341/// reports XPATH_INVALID_OPERAND.
342///
343/// # SAFETY
344///
345/// - `pc` must be a valid parser context with a non-NULL `value`.
346pub unsafe fn cast_top_to_number(pc: *mut XmlXPathParserContext) {
347    unsafe {
348        let val = (*pc).value;
349        if val.is_null() {
350            pc_set_error(pc, crate::abi::types::XPATH_INVALID_OPERAND as c_int);
351            return;
352        }
353        if (*val).type_ != xmlXPathObjectType::XPATH_NUMBER as c_int {
354            let v = crate::abi::exports_xml2::object_to_xpathvalue_pub(val);
355            (*val).floatval = v.as_number();
356            (*val).type_ = xmlXPathObjectType::XPATH_NUMBER as c_int;
357        }
358    }
359}
360
361/// String-value equality of two node-set members (upstream
362/// `xmlXPathEqualNodeSets` core loop; the hash fast-path is not observable).
363fn node_set_pair_equal(a: &NodeSet, b: &NodeSet, neq: bool) -> bool {
364    if a.is_empty() || b.is_empty() {
365        return false;
366    }
367    // For "=", an identical node pointer in both sets short-circuits to true
368    // (upstream `xmlXPathEqualNodeSets`).
369    if !neq {
370        for na in a.iter() {
371            if b.contains(na) {
372                return true;
373            }
374        }
375    }
376    for na in a.iter() {
377        let sa = node_string_value(na);
378        for nb in b.iter() {
379            let sb = node_string_value(nb);
380            if (sa == sb) ^ neq {
381                return true;
382            }
383        }
384    }
385    false
386}
387
388/// Node-set vs string comparison (upstream `xmlXPathEqualNodeSetString`).
389fn node_set_string_equal(a: &NodeSet, s: &str, neq: bool) -> bool {
390    if a.is_empty() {
391        return false;
392    }
393    for n in a.iter() {
394        let sv = node_string_value(n);
395        if sv == s {
396            if neq {
397                continue;
398            }
399            return true;
400        } else if neq {
401            return true;
402        }
403    }
404    false
405}
406
407/// Node-set vs number comparison (upstream `xmlXPathEqualNodeSetFloat`).
408fn node_set_number_equal(a: &NodeSet, f: f64, neq: bool) -> bool {
409    for n in a.iter() {
410        let sv = node_string_value(n);
411        let v = crate::xml::xpath::types::string_to_number(&sv);
412        if v.is_nan() {
413            if neq {
414                return true;
415            }
416        } else if (!neq && v == f) || (neq && v != f) {
417            return true;
418        }
419    }
420    false
421}
422
423/// Full XPath 1.0 equality matrix (§3.4) matching upstream 2.15
424/// `xmlXPathEqualValues` / `xmlXPathNotEqualValues` (including the node-set
425/// pair/string/number/boolean special cases).
426pub fn equal_values_inner(v1: &XPathValue, v2: &XPathValue, neq: bool) -> bool {
427    // Normalise so that, when exactly one side is a node-set, `ns` is the
428    // node-set side (upstream swaps so arg1 is the node-set).
429    match (v1, v2) {
430        (XPathValue::NodeSet(a), XPathValue::NodeSet(b)) => node_set_pair_equal(a, b, neq),
431        (XPathValue::NodeSet(a), other) => node_set_vs_value(a, other, neq),
432        (other, XPathValue::NodeSet(b)) => node_set_vs_value(b, other, neq),
433        _ => common_equal(v1, v2) ^ neq,
434    }
435}
436
437fn node_set_vs_value(ns: &NodeSet, other: &XPathValue, neq: bool) -> bool {
438    match other {
439        XPathValue::Boolean(b) => (!ns.is_empty()) == *b,
440        XPathValue::Number(f) => node_set_number_equal(ns, *f, neq),
441        XPathValue::String(s) => node_set_string_equal(ns, s, neq),
442        // Both sides node-sets are handled by the caller before this point.
443        XPathValue::NodeSet(_) => unreachable!(),
444    }
445}
446
447/// The non-node-set equality matrix (upstream `xmlXPathEqualValuesCommon`).
448fn common_equal(v1: &XPathValue, v2: &XPathValue) -> bool {
449    // Boolean involved → boolean conversion of both sides.
450    if matches!(v1, XPathValue::Boolean(_)) || matches!(v2, XPathValue::Boolean(_)) {
451        return v1.as_boolean() == v2.as_boolean();
452    }
453    // Number involved (and no boolean) → number conversion of both sides.
454    // IEEE 754 equality gives exactly the upstream NaN/±Infinity rules.
455    if matches!(v1, XPathValue::Number(_)) || matches!(v2, XPathValue::Number(_)) {
456        return v1.as_number() == v2.as_number();
457    }
458    // Both strings (or undefined → converted to strings).
459    v1.as_string() == v2.as_string()
460}
461
462/// Pop two objects and compare for equality (upstream xmlXPathEqualValues /
463/// xmlXPathNotEqualValues semantics): pushes the boolean result object and
464/// returns the comparison value.
465///
466/// # SAFETY
467///
468/// - `pc` must be a valid parser context.
469pub unsafe fn equal_values_impl(pc: *mut XmlXPathParserContext, neq: bool) -> c_int {
470    let arg2 = unsafe { value_pop(pc) };
471    if arg2.is_null() {
472        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
473        return 0;
474    }
475    let arg1 = unsafe { value_pop(pc) };
476    if arg1.is_null() {
477        unsafe {
478            value_push(pc, arg2);
479        }
480        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
481        return 0;
482    }
483    // UPSTREAM-PARITY: comparing an object with itself yields 1 for
484    // equality / 0 for inequality without pushing a result.
485    if arg1 == arg2 {
486        crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
487        return if neq { 0 } else { 1 };
488    }
489    let v1 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg1);
490    let v2 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg2);
491    crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
492    crate::abi::exports_xml2::xmlXPathFreeObject(arg2);
493
494    let eq = equal_values_inner(&v1, &v2, neq);
495    unsafe { value_push(pc, new_bool(eq)) };
496    eq as c_int
497}
498
499/// Pop two objects and compare with `<`, `<=`, `>`, `>=` (upstream
500/// `xmlXPathCompareValues` with the `inf` / `strict` encoding).
501///
502/// # SAFETY
503///
504/// - `pc` must be a valid parser context.
505pub unsafe fn compare_values_impl(
506    pc: *mut XmlXPathParserContext,
507    inf: bool,
508    strict: bool,
509) -> c_int {
510    let arg2 = unsafe { value_pop(pc) };
511    if arg2.is_null() {
512        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
513        return 0;
514    }
515    let arg1 = unsafe { value_pop(pc) };
516    if arg1.is_null() {
517        unsafe {
518            value_push(pc, arg2);
519        }
520        unsafe { (*pc).error = crate::abi::types::XPATH_INVALID_OPERAND as c_int };
521        return 0;
522    }
523
524    let v1 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg1);
525    let v2 = crate::abi::exports_xml2::object_to_xpathvalue_pub(arg2);
526    crate::abi::exports_xml2::xmlXPathFreeObject(arg1);
527    crate::abi::exports_xml2::xmlXPathFreeObject(arg2);
528
529    let (ns_side, other): (Option<&NodeSet>, Option<(&XPathValue, &XPathValue)>) = match (&v1, &v2)
530    {
531        (XPathValue::NodeSet(a), XPathValue::NodeSet(b)) => {
532            return compare_node_sets(a, b, inf, strict);
533        }
534        (XPathValue::NodeSet(a), _) => (Some(a), None),
535        (_, XPathValue::NodeSet(b)) => (Some(b), None),
536        _ => (None, Some((&v1, &v2))),
537    };
538
539    if let Some(ns) = ns_side {
540        // One side is a node-set, the other a scalar.
541        let other = if matches!(&v1, XPathValue::NodeSet(_)) {
542            &v2
543        } else {
544            &v1
545        };
546        // The node-set is on the left of the operator (upstream swaps
547        // direction when the value is on the left).
548        let (ns_is_left, scalar) = if matches!(&v1, XPathValue::NodeSet(_)) {
549            (true, other)
550        } else {
551            (false, other)
552        };
553        return compare_node_set_value(ns, scalar, ns_is_left, inf, strict);
554    }
555
556    let (l, r) = other.unwrap();
557    // Neither side is a node-set: convert both to numbers and compare.
558    // NaN comparisons are always false (upstream).
559    let a = l.as_number();
560    let b = r.as_number();
561    if a.is_nan() || b.is_nan() {
562        return 0;
563    }
564    let ret = if inf && strict {
565        a < b
566    } else if inf && !strict {
567        a <= b
568    } else if !inf && strict {
569        a > b
570    } else {
571        a >= b
572    };
573    ret as c_int
574}
575
576fn compare_node_sets(a: &NodeSet, b: &NodeSet, inf: bool, strict: bool) -> c_int {
577    if a.is_empty() || b.is_empty() {
578        return 0;
579    }
580    let b_nums: Vec<f64> = b.iter().map(|n| unsafe { node_to_number(n) }).collect();
581    for na in a.iter() {
582        let va = unsafe { node_to_number(na) };
583        if va.is_nan() {
584            continue;
585        }
586        for &vb in &b_nums {
587            if vb.is_nan() {
588                continue;
589            }
590            let ret = if inf && strict {
591                va < vb
592            } else if inf && !strict {
593                va <= vb
594            } else if !inf && strict {
595                va > vb
596            } else {
597                va >= vb
598            };
599            if ret {
600                return 1;
601            }
602        }
603    }
604    0
605}
606
607fn compare_node_set_value(
608    ns: &NodeSet,
609    scalar: &XPathValue,
610    ns_is_left: bool,
611    inf: bool,
612    strict: bool,
613) -> c_int {
614    match scalar {
615        XPathValue::Number(f) => compare_node_set_number(ns, *f, ns_is_left, inf, strict),
616        XPathValue::String(s) => compare_node_set_string(ns, s, ns_is_left, inf, strict),
617        XPathValue::Boolean(_) => {
618            // Convert the node-set to a boolean and compare.
619            let ns_bool = !ns.is_empty();
620            let b = scalar.as_boolean();
621            let (a, b) = if ns_is_left {
622                (ns_bool, b)
623            } else {
624                (b, ns_bool)
625            };
626            let ret = if inf && strict {
627                a < b
628            } else if inf && !strict {
629                a <= b
630            } else if !inf && strict {
631                a > b
632            } else {
633                a >= b
634            };
635            ret as c_int
636        }
637        _ => 0,
638    }
639}
640
641fn compare_node_set_number(
642    ns: &NodeSet,
643    f: f64,
644    ns_is_left: bool,
645    inf: bool,
646    strict: bool,
647) -> c_int {
648    for n in ns.iter() {
649        let v = unsafe { node_to_number(n) };
650        if v.is_nan() {
651            continue;
652        }
653        let (a, b) = if ns_is_left { (v, f) } else { (f, v) };
654        let ret = if inf && strict {
655            a < b
656        } else if inf && !strict {
657            a <= b
658        } else if !inf && strict {
659            a > b
660        } else {
661            a >= b
662        };
663        if ret {
664            return 1;
665        }
666    }
667    0
668}
669
670fn compare_node_set_string(
671    ns: &NodeSet,
672    s: &str,
673    ns_is_left: bool,
674    inf: bool,
675    strict: bool,
676) -> c_int {
677    for n in ns.iter() {
678        let sv = node_string_value(n);
679        let v = crate::xml::xpath::types::string_to_number(&sv);
680        let w = crate::xml::xpath::types::string_to_number(s);
681        if v.is_nan() || w.is_nan() {
682            continue;
683        }
684        let (a, b) = if ns_is_left { (v, w) } else { (w, v) };
685        let ret = if inf && strict {
686            a < b
687        } else if inf && !strict {
688            a <= b
689        } else if !inf && strict {
690            a > b
691        } else {
692            a >= b
693        };
694        if ret {
695            return 1;
696        }
697    }
698    0
699}
700
701/// String-value of a node converted to a number (upstream
702/// `xmlXPathNodeToNumber`).
703unsafe fn node_to_number(node: *mut _xmlNode) -> f64 {
704    if node.is_null() {
705        return f64::NAN;
706    }
707    let sv = node_string_value(node);
708    crate::xml::xpath::types::string_to_number(&sv)
709}
710
711/// Convert an internal value to a C object.
712///
713/// # SAFETY
714///
715/// - The returned object is heap-allocated.
716pub unsafe fn value_to_object(v: XPathValue) -> *mut _xmlXPathObject {
717    crate::abi::exports_xml2::xpath_to_object_pub(v)
718}
719
720#[allow(unused)]
721fn _unused(_: *mut _xmlDoc) {}
722
723#[allow(unused)]
724fn _unused_char(_: *const c_char) {}