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