Skip to main content

xsd_schema/xpath/
operators.rs

1//! XPath operator evaluation helpers.
2//!
3//! This module hosts AST-only operator evaluation logic, using `XmlValue`
4//! and `XmlTypeCode` directly (no ValueProxy layer). See
5//! `XPATH_OPERATORS_DESIGN.md` for the target behavior.
6
7use std::cmp::Ordering;
8
9use chrono::Local;
10use num_bigint::BigInt;
11use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
12use rust_decimal::Decimal;
13
14use crate::ids::{SimpleTypeKey, TypeKey};
15use crate::namespace::qname::QualifiedName;
16use crate::schema::model::SchemaSet;
17use crate::types::validators::VALIDATOR_REGISTRY;
18use crate::types::value::{
19    DateTimeValue, DateValue, DayTimeDurationValue, DurationValue, TimeValue, TimezoneOffset,
20    XmlAtomicValue, XmlValue, XmlValueKind, YearMonthDurationValue,
21};
22use crate::types::XmlTypeCode;
23use crate::xpath::ast::{BinaryOpKind, UnaryOpKind};
24use crate::xpath::cast::cast_to;
25use crate::xpath::context::XPathContext;
26use crate::xpath::error::XPathError;
27use crate::xpath::iterator::{BufferedNodeIterator, XmlItemRef, XmlNodeIterator};
28use crate::xpath::type_info::type_code_to_name;
29use crate::xpath::DomNavigator;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32enum NumericClass {
33    Byte,
34    UnsignedByte,
35    Short,
36    UnsignedShort,
37    Int,
38    UnsignedInt,
39    Long,
40    UnsignedLong,
41    Integer,
42    Decimal,
43    Float,
44    Double,
45}
46
47#[derive(Debug, Clone)]
48enum NumericValue {
49    Integer(BigInt),
50    Decimal(Decimal),
51    Float(f32),
52    Double(f64),
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum Promote {
57    Left,
58    Right,
59    None,
60}
61
62/// XQTS-compatible scale for non-terminating decimal division results.
63///
64/// XPath 2.0 allows implementation-defined precision for xs:decimal division
65/// (with a minimum of 18 fractional digits). We normalize to 18 here to keep
66/// stable lexical output across platforms and match expected XQTS baselines.
67const XPATH_DECIMAL_DIV_SCALE: u32 = 18;
68
69/// Evaluate a unary operator for a single atomic value.
70pub fn eval_unary(op: UnaryOpKind, value: &XmlValue) -> Result<XmlValue, XPathError> {
71    match op {
72        UnaryOpKind::Identity => Ok(value.clone()),
73        UnaryOpKind::Negate => eval_numeric_unary(value),
74    }
75}
76
77/// Evaluate a binary operator for two atomic values.
78pub fn eval_binary(
79    op: BinaryOpKind,
80    left: &XmlValue,
81    right: &XmlValue,
82) -> Result<XmlValue, XPathError> {
83    match op {
84        BinaryOpKind::Add | BinaryOpKind::Sub | BinaryOpKind::Mul | BinaryOpKind::Div => {
85            if let Some(result) = eval_temporal_binary(op, left, right)? {
86                return Ok(result);
87            }
88            eval_numeric_binary(op, left, right)
89        }
90        BinaryOpKind::IDiv | BinaryOpKind::Mod => eval_numeric_binary(op, left, right),
91        BinaryOpKind::GeneralEq | BinaryOpKind::ValueEq => {
92            Ok(XmlValue::boolean(compare_eq(left, right)?))
93        }
94        BinaryOpKind::GeneralNe | BinaryOpKind::ValueNe => {
95            Ok(XmlValue::boolean(!compare_eq(left, right)?))
96        }
97        BinaryOpKind::GeneralGt | BinaryOpKind::ValueGt => {
98            Ok(XmlValue::boolean(compare_gt(left, right)?))
99        }
100        BinaryOpKind::GeneralGe | BinaryOpKind::ValueGe => {
101            Ok(XmlValue::boolean(compare_ge(left, right)?))
102        }
103        BinaryOpKind::GeneralLt | BinaryOpKind::ValueLt => {
104            Ok(XmlValue::boolean(compare_lt(left, right)?))
105        }
106        BinaryOpKind::GeneralLe | BinaryOpKind::ValueLe => {
107            Ok(XmlValue::boolean(compare_le(left, right)?))
108        }
109        BinaryOpKind::And | BinaryOpKind::Or => eval_boolean_logic(op, left, right),
110        BinaryOpKind::Is | BinaryOpKind::Before | BinaryOpKind::After => {
111            Err(unsupported_operator(op, left, right))
112        }
113        BinaryOpKind::Union | BinaryOpKind::Intersect | BinaryOpKind::Except => {
114            Err(unsupported_operator(op, left, right))
115        }
116    }
117}
118
119/// Evaluate an XPath range expression (`expr to expr`).
120///
121/// Per XPath 2.0 spec, both operands must be of type `xs:integer`.
122/// Non-integer types produce XPTY0004 type mismatch errors.
123pub fn eval_range(start: &XmlValue, end: &XmlValue) -> Result<Vec<XmlValue>, XPathError> {
124    let start_class = numeric_class(start.type_code).ok_or_else(|| {
125        XPathError::type_mismatch("xs:integer", type_code_to_name(start.type_code))
126    })?;
127    let end_class = numeric_class(end.type_code)
128        .ok_or_else(|| XPathError::type_mismatch("xs:integer", type_code_to_name(end.type_code)))?;
129
130    if !is_integer_class(start_class) {
131        return Err(XPathError::type_mismatch(
132            "xs:integer",
133            type_code_to_name(start.type_code),
134        ));
135    }
136    if !is_integer_class(end_class) {
137        return Err(XPathError::type_mismatch(
138            "xs:integer",
139            type_code_to_name(end.type_code),
140        ));
141    }
142
143    let start_val = to_integer_value(start)?;
144    let end_val = to_integer_value(end)?;
145
146    if start_val > end_val {
147        return Ok(Vec::new());
148    }
149
150    let mut result = Vec::new();
151    let mut current = start_val;
152    let one = BigInt::from(1);
153    while current <= end_val {
154        result.push(XmlValue {
155            type_code: XmlTypeCode::Integer,
156            schema_type: None,
157            value: XmlValueKind::Atomic(XmlAtomicValue::Integer(current.clone())),
158        });
159        current += &one;
160    }
161
162    Ok(result)
163}
164
165/// Check if a type code is numeric for operator dispatch.
166pub fn is_numeric(code: XmlTypeCode) -> bool {
167    code.is_numeric()
168}
169
170fn eval_boolean_logic(
171    op: BinaryOpKind,
172    left: &XmlValue,
173    right: &XmlValue,
174) -> Result<XmlValue, XPathError> {
175    let left_bool = left
176        .as_boolean()
177        .ok_or_else(|| XPathError::internal("Boolean operator requires boolean operands"))?;
178    let right_bool = right
179        .as_boolean()
180        .ok_or_else(|| XPathError::internal("Boolean operator requires boolean operands"))?;
181
182    let result = match op {
183        BinaryOpKind::And => left_bool && right_bool,
184        BinaryOpKind::Or => left_bool || right_bool,
185        _ => return Err(XPathError::internal("Unexpected boolean operator")),
186    };
187
188    Ok(XmlValue::boolean(result))
189}
190
191fn compare_eq(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
192    let left = unwrap_union_value(left);
193    let right = unwrap_union_value(right);
194
195    if let Some(result) = compare_temporal_eq(left, right)? {
196        return Ok(result);
197    }
198
199    if left.type_code.is_list() || right.type_code.is_list() {
200        if left.type_code != right.type_code {
201            return Err(operator_not_defined("op:eq", left, right));
202        }
203        return Ok(list_values_equal(left, right));
204    }
205
206    if left.type_code.is_numeric() && right.type_code.is_numeric() {
207        return numeric_eq(left, right);
208    }
209
210    if left.type_code == XmlTypeCode::Boolean && right.type_code == XmlTypeCode::Boolean {
211        return Ok(left.as_boolean() == right.as_boolean());
212    }
213
214    if is_string_like(left.type_code) && is_string_like(right.type_code) {
215        return Ok(left.to_string_value() == right.to_string_value());
216    }
217
218    if left.type_code == right.type_code {
219        // Special case: QName equality compares namespace URI + local name only, ignoring prefix
220        if left.type_code == XmlTypeCode::QName {
221            if let (Some(lq), Some(rq)) = (left.as_qname(), right.as_qname()) {
222                return Ok(lq.namespace_uri == rq.namespace_uri && lq.local_name == rq.local_name);
223            }
224        }
225        return Ok(left.value == right.value);
226    }
227
228    Err(operator_not_defined("op:eq", left, right))
229}
230
231fn compare_gt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
232    let left = unwrap_union_value(left);
233    let right = unwrap_union_value(right);
234
235    if let Some(result) = compare_temporal_gt(left, right)? {
236        return Ok(result);
237    }
238
239    if left.type_code.is_numeric() && right.type_code.is_numeric() {
240        return numeric_gt(left, right);
241    }
242
243    if left.type_code == XmlTypeCode::Boolean && right.type_code == XmlTypeCode::Boolean {
244        let l = left.as_boolean().unwrap_or(false);
245        let r = right.as_boolean().unwrap_or(false);
246        return Ok(l && !r);
247    }
248
249    if is_string_like(left.type_code) && is_string_like(right.type_code) {
250        let left_value = left.to_string_value();
251        let right_value = right.to_string_value();
252        return Ok(compare_string_values(&left_value, &right_value) == Ordering::Greater);
253    }
254
255    Err(operator_not_defined("op:gt", left, right))
256}
257
258fn compare_ge(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
259    match compare_eq(left, right) {
260        Ok(true) => Ok(true),
261        Ok(false) => compare_gt(left, right),
262        Err(err) if is_operator_not_defined(&err) => compare_gt(left, right),
263        Err(err) => Err(err),
264    }
265}
266
267fn compare_lt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
268    compare_gt(right, left)
269}
270
271fn compare_le(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
272    match compare_eq(left, right) {
273        Ok(true) => Ok(true),
274        Ok(false) => compare_lt(left, right),
275        Err(err) if is_operator_not_defined(&err) => compare_lt(left, right),
276        Err(err) => Err(err),
277    }
278}
279
280fn eval_temporal_binary(
281    op: BinaryOpKind,
282    left: &XmlValue,
283    right: &XmlValue,
284) -> Result<Option<XmlValue>, XPathError> {
285    if !is_temporal_type(left.type_code) && !is_temporal_type(right.type_code) {
286        return Ok(None);
287    }
288
289    let result = match op {
290        BinaryOpKind::Add => eval_temporal_add(left, right),
291        BinaryOpKind::Sub => eval_temporal_sub(left, right),
292        BinaryOpKind::Mul => eval_temporal_mul(left, right),
293        BinaryOpKind::Div => eval_temporal_div(left, right),
294        _ => return Ok(None),
295    }?;
296
297    Ok(Some(result))
298}
299
300fn compare_temporal_eq(left: &XmlValue, right: &XmlValue) -> Result<Option<bool>, XPathError> {
301    if !is_temporal_type(left.type_code) && !is_temporal_type(right.type_code) {
302        return Ok(None);
303    }
304
305    if is_date_time_code(left.type_code) || is_date_time_code(right.type_code) {
306        if !(is_date_time_code(left.type_code) && is_date_time_code(right.type_code)) {
307            return Err(operator_not_defined("op:eq", left, right));
308        }
309        let left_dt =
310            as_datetime(left).ok_or_else(|| XPathError::internal("Expected dateTime value"))?;
311        let right_dt =
312            as_datetime(right).ok_or_else(|| XPathError::internal("Expected dateTime value"))?;
313        return Ok(Some(compare_datetime_eq(left_dt, right_dt)?));
314    }
315
316    if left.type_code == XmlTypeCode::Date || right.type_code == XmlTypeCode::Date {
317        if left.type_code != XmlTypeCode::Date || right.type_code != XmlTypeCode::Date {
318            return Err(operator_not_defined("op:eq", left, right));
319        }
320        let left_date = as_date(left).ok_or_else(|| XPathError::internal("Expected date value"))?;
321        let right_date =
322            as_date(right).ok_or_else(|| XPathError::internal("Expected date value"))?;
323        return Ok(Some(compare_date_eq(left_date, right_date)?));
324    }
325
326    if left.type_code == XmlTypeCode::Time || right.type_code == XmlTypeCode::Time {
327        if left.type_code != XmlTypeCode::Time || right.type_code != XmlTypeCode::Time {
328            return Err(operator_not_defined("op:eq", left, right));
329        }
330        let left_time = as_time(left).ok_or_else(|| XPathError::internal("Expected time value"))?;
331        let right_time =
332            as_time(right).ok_or_else(|| XPathError::internal("Expected time value"))?;
333        return Ok(Some(compare_time_eq(left_time, right_time)?));
334    }
335
336    if is_duration_code(left.type_code) || is_duration_code(right.type_code) {
337        if !(is_duration_code(left.type_code) && is_duration_code(right.type_code)) {
338            return Err(operator_not_defined("op:eq", left, right));
339        }
340        let left_parts =
341            duration_parts(left)?.ok_or_else(|| XPathError::internal("Expected duration value"))?;
342        let right_parts = duration_parts(right)?
343            .ok_or_else(|| XPathError::internal("Expected duration value"))?;
344        return Ok(Some(left_parts == right_parts));
345    }
346
347    Err(operator_not_defined("op:eq", left, right))
348}
349
350fn compare_temporal_gt(left: &XmlValue, right: &XmlValue) -> Result<Option<bool>, XPathError> {
351    if !is_temporal_type(left.type_code) && !is_temporal_type(right.type_code) {
352        return Ok(None);
353    }
354
355    if is_date_time_code(left.type_code) || is_date_time_code(right.type_code) {
356        if !(is_date_time_code(left.type_code) && is_date_time_code(right.type_code)) {
357            return Err(operator_not_defined("op:gt", left, right));
358        }
359        let left_dt =
360            as_datetime(left).ok_or_else(|| XPathError::internal("Expected dateTime value"))?;
361        let right_dt =
362            as_datetime(right).ok_or_else(|| XPathError::internal("Expected dateTime value"))?;
363        return Ok(Some(compare_datetime_gt(left_dt, right_dt)?));
364    }
365
366    if left.type_code == XmlTypeCode::Date || right.type_code == XmlTypeCode::Date {
367        if left.type_code != XmlTypeCode::Date || right.type_code != XmlTypeCode::Date {
368            return Err(operator_not_defined("op:gt", left, right));
369        }
370        let left_date = as_date(left).ok_or_else(|| XPathError::internal("Expected date value"))?;
371        let right_date =
372            as_date(right).ok_or_else(|| XPathError::internal("Expected date value"))?;
373        return Ok(Some(compare_date_gt(left_date, right_date)?));
374    }
375
376    if left.type_code == XmlTypeCode::Time || right.type_code == XmlTypeCode::Time {
377        if left.type_code != XmlTypeCode::Time || right.type_code != XmlTypeCode::Time {
378            return Err(operator_not_defined("op:gt", left, right));
379        }
380        let left_time = as_time(left).ok_or_else(|| XPathError::internal("Expected time value"))?;
381        let right_time =
382            as_time(right).ok_or_else(|| XPathError::internal("Expected time value"))?;
383        return Ok(Some(compare_time_gt(left_time, right_time)?));
384    }
385
386    if left.type_code == XmlTypeCode::YearMonthDuration
387        || right.type_code == XmlTypeCode::YearMonthDuration
388    {
389        if left.type_code != XmlTypeCode::YearMonthDuration
390            || right.type_code != XmlTypeCode::YearMonthDuration
391        {
392            return Err(operator_not_defined("op:gt", left, right));
393        }
394        let left_duration = as_year_month_duration(left)
395            .ok_or_else(|| XPathError::internal("Expected yearMonthDuration value"))?;
396        let right_duration = as_year_month_duration(right)
397            .ok_or_else(|| XPathError::internal("Expected yearMonthDuration value"))?;
398        return Ok(Some(
399            year_month_total_months(left_duration) > year_month_total_months(right_duration),
400        ));
401    }
402
403    if left.type_code == XmlTypeCode::DayTimeDuration
404        || right.type_code == XmlTypeCode::DayTimeDuration
405    {
406        if left.type_code != XmlTypeCode::DayTimeDuration
407            || right.type_code != XmlTypeCode::DayTimeDuration
408        {
409            return Err(operator_not_defined("op:gt", left, right));
410        }
411        let left_duration = as_day_time_duration(left)
412            .ok_or_else(|| XPathError::internal("Expected dayTimeDuration value"))?;
413        let right_duration = as_day_time_duration(right)
414            .ok_or_else(|| XPathError::internal("Expected dayTimeDuration value"))?;
415        return Ok(Some(
416            day_time_total_seconds(left_duration)? > day_time_total_seconds(right_duration)?,
417        ));
418    }
419
420    Err(operator_not_defined("op:gt", left, right))
421}
422
423fn operator_not_defined(op: &str, left: &XmlValue, right: &XmlValue) -> XPathError {
424    XPathError::BinaryOperatorNotDefined {
425        operator: op.to_string(),
426        left_type: type_code_to_name(left.type_code).to_string(),
427        right_type: type_code_to_name(right.type_code).to_string(),
428    }
429}
430
431fn is_operator_not_defined(err: &XPathError) -> bool {
432    matches!(err, XPathError::BinaryOperatorNotDefined { .. })
433}
434
435fn unwrap_union_value(value: &XmlValue) -> &XmlValue {
436    let mut current = value;
437    loop {
438        match &current.value {
439            XmlValueKind::Union(inner) => current = inner,
440            _ => return current,
441        }
442    }
443}
444
445fn list_values_equal(left: &XmlValue, right: &XmlValue) -> bool {
446    match (&left.value, &right.value) {
447        (
448            XmlValueKind::List {
449                item_type: left_item_type,
450                items: left_items,
451            },
452            XmlValueKind::List {
453                item_type: right_item_type,
454                items: right_items,
455            },
456        ) => left_item_type == right_item_type && left_items == right_items,
457        _ => false,
458    }
459}
460
461fn compare_string_values(left: &str, right: &str) -> Ordering {
462    left.cmp(right)
463}
464
465fn eval_temporal_add(left: &XmlValue, right: &XmlValue) -> Result<XmlValue, XPathError> {
466    if let Some(left_dt) = as_datetime(left) {
467        if let Some(duration) = as_year_month_duration(right) {
468            let result = add_datetime_year_month(left_dt, duration)?;
469            return Ok(xml_datetime_value(left.type_code, result));
470        }
471        if let Some(duration) = as_day_time_duration(right) {
472            let result = add_datetime_day_time(left_dt, duration)?;
473            return Ok(xml_datetime_value(left.type_code, result));
474        }
475        return Err(unsupported_operator(BinaryOpKind::Add, left, right));
476    }
477
478    if let Some(left_date) = as_date(left) {
479        if let Some(duration) = as_year_month_duration(right) {
480            let result = add_date_year_month(left_date, duration)?;
481            return Ok(xml_date_value(left.type_code, result));
482        }
483        if let Some(duration) = as_day_time_duration(right) {
484            let result = add_date_day_time(left_date, duration)?;
485            return Ok(xml_date_value(left.type_code, result));
486        }
487        return Err(unsupported_operator(BinaryOpKind::Add, left, right));
488    }
489
490    if let Some(left_time) = as_time(left) {
491        if let Some(duration) = as_day_time_duration(right) {
492            let result = add_time_day_time(left_time, duration)?;
493            return Ok(xml_time_value(left.type_code, result));
494        }
495        return Err(unsupported_operator(BinaryOpKind::Add, left, right));
496    }
497
498    if let Some(left_duration) = as_year_month_duration(left) {
499        if let Some(right_duration) = as_year_month_duration(right) {
500            let total =
501                year_month_total_months(left_duration) + year_month_total_months(right_duration);
502            return Ok(xml_year_month_duration_value(year_month_from_months(
503                total,
504            )?));
505        }
506        if let Some(right_dt) = as_datetime(right) {
507            let result = add_datetime_year_month(right_dt, left_duration)?;
508            return Ok(xml_datetime_value(right.type_code, result));
509        }
510        if let Some(right_date) = as_date(right) {
511            let result = add_date_year_month(right_date, left_duration)?;
512            return Ok(xml_date_value(right.type_code, result));
513        }
514        return Err(unsupported_operator(BinaryOpKind::Add, left, right));
515    }
516
517    if let Some(left_duration) = as_day_time_duration(left) {
518        if let Some(right_duration) = as_day_time_duration(right) {
519            let total =
520                day_time_total_seconds(left_duration)? + day_time_total_seconds(right_duration)?;
521            return Ok(xml_day_time_duration_value(day_time_from_seconds(total)?));
522        }
523        if let Some(right_dt) = as_datetime(right) {
524            let result = add_datetime_day_time(right_dt, left_duration)?;
525            return Ok(xml_datetime_value(right.type_code, result));
526        }
527        if let Some(right_date) = as_date(right) {
528            let result = add_date_day_time(right_date, left_duration)?;
529            return Ok(xml_date_value(right.type_code, result));
530        }
531        if let Some(right_time) = as_time(right) {
532            let result = add_time_day_time(right_time, left_duration)?;
533            return Ok(xml_time_value(right.type_code, result));
534        }
535        return Err(unsupported_operator(BinaryOpKind::Add, left, right));
536    }
537
538    Err(unsupported_operator(BinaryOpKind::Add, left, right))
539}
540
541fn eval_temporal_sub(left: &XmlValue, right: &XmlValue) -> Result<XmlValue, XPathError> {
542    if let Some(left_dt) = as_datetime(left) {
543        if let Some(right_dt) = as_datetime(right) {
544            let result = diff_datetime(left_dt, right_dt)?;
545            return Ok(xml_day_time_duration_value(result));
546        }
547        if let Some(duration) = as_year_month_duration(right) {
548            let result = add_datetime_year_month(left_dt, &negate_year_month_duration(duration))?;
549            return Ok(xml_datetime_value(left.type_code, result));
550        }
551        if let Some(duration) = as_day_time_duration(right) {
552            let result = add_datetime_day_time(left_dt, &negate_day_time_duration(duration))?;
553            return Ok(xml_datetime_value(left.type_code, result));
554        }
555        return Err(unsupported_operator(BinaryOpKind::Sub, left, right));
556    }
557
558    if let Some(left_date) = as_date(left) {
559        if let Some(right_date) = as_date(right) {
560            let result = diff_date(left_date, right_date)?;
561            return Ok(xml_day_time_duration_value(result));
562        }
563        if let Some(duration) = as_year_month_duration(right) {
564            let result = add_date_year_month(left_date, &negate_year_month_duration(duration))?;
565            return Ok(xml_date_value(left.type_code, result));
566        }
567        if let Some(duration) = as_day_time_duration(right) {
568            let result = add_date_day_time(left_date, &negate_day_time_duration(duration))?;
569            return Ok(xml_date_value(left.type_code, result));
570        }
571        return Err(unsupported_operator(BinaryOpKind::Sub, left, right));
572    }
573
574    if let Some(left_time) = as_time(left) {
575        if let Some(right_time) = as_time(right) {
576            let result = diff_time(left_time, right_time)?;
577            return Ok(xml_day_time_duration_value(result));
578        }
579        if let Some(duration) = as_day_time_duration(right) {
580            let result = add_time_day_time(left_time, &negate_day_time_duration(duration))?;
581            return Ok(xml_time_value(left.type_code, result));
582        }
583        return Err(unsupported_operator(BinaryOpKind::Sub, left, right));
584    }
585
586    if let Some(left_duration) = as_year_month_duration(left) {
587        if let Some(right_duration) = as_year_month_duration(right) {
588            let total =
589                year_month_total_months(left_duration) - year_month_total_months(right_duration);
590            return Ok(xml_year_month_duration_value(year_month_from_months(
591                total,
592            )?));
593        }
594        return Err(unsupported_operator(BinaryOpKind::Sub, left, right));
595    }
596
597    if let Some(left_duration) = as_day_time_duration(left) {
598        if let Some(right_duration) = as_day_time_duration(right) {
599            let total =
600                day_time_total_seconds(left_duration)? - day_time_total_seconds(right_duration)?;
601            return Ok(xml_day_time_duration_value(day_time_from_seconds(total)?));
602        }
603        return Err(unsupported_operator(BinaryOpKind::Sub, left, right));
604    }
605
606    Err(unsupported_operator(BinaryOpKind::Sub, left, right))
607}
608
609fn eval_temporal_mul(left: &XmlValue, right: &XmlValue) -> Result<XmlValue, XPathError> {
610    if let Some(duration) = as_year_month_duration(left) {
611        if right.type_code.is_numeric() {
612            let factor = numeric_to_f64(right)?;
613            return Ok(xml_year_month_duration_value(year_month_mul_numeric(
614                duration, factor,
615            )?));
616        }
617        return Err(unsupported_operator(BinaryOpKind::Mul, left, right));
618    }
619
620    if let Some(duration) = as_day_time_duration(left) {
621        if right.type_code.is_numeric() {
622            return Ok(xml_day_time_duration_value(day_time_mul_numeric_value(
623                duration, right,
624            )?));
625        }
626        return Err(unsupported_operator(BinaryOpKind::Mul, left, right));
627    }
628
629    if left.type_code.is_numeric() {
630        if let Some(duration) = as_year_month_duration(right) {
631            let factor = numeric_to_f64(left)?;
632            return Ok(xml_year_month_duration_value(year_month_mul_numeric(
633                duration, factor,
634            )?));
635        }
636        if let Some(duration) = as_day_time_duration(right) {
637            return Ok(xml_day_time_duration_value(day_time_mul_numeric_value(
638                duration, left,
639            )?));
640        }
641    }
642
643    Err(unsupported_operator(BinaryOpKind::Mul, left, right))
644}
645
646fn eval_temporal_div(left: &XmlValue, right: &XmlValue) -> Result<XmlValue, XPathError> {
647    if let Some(duration) = as_year_month_duration(left) {
648        if right.type_code.is_numeric() {
649            let divisor = numeric_to_f64(right)?;
650            return Ok(xml_year_month_duration_value(year_month_div_numeric(
651                duration, divisor,
652            )?));
653        }
654        if let Some(right_duration) = as_year_month_duration(right) {
655            let ratio = year_month_div_duration(duration, right_duration)?;
656            return Ok(XmlValue::decimal(ratio));
657        }
658        return Err(unsupported_operator(BinaryOpKind::Div, left, right));
659    }
660
661    if let Some(duration) = as_day_time_duration(left) {
662        if right.type_code.is_numeric() {
663            return Ok(xml_day_time_duration_value(day_time_div_numeric_value(
664                duration, right,
665            )?));
666        }
667        if let Some(right_duration) = as_day_time_duration(right) {
668            let ratio = day_time_div_duration(duration, right_duration)?;
669            return Ok(XmlValue::decimal(ratio));
670        }
671        return Err(unsupported_operator(BinaryOpKind::Div, left, right));
672    }
673
674    Err(unsupported_operator(BinaryOpKind::Div, left, right))
675}
676
677fn numeric_eq(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
678    let (left_val, right_val, class) = promote_numeric(left, right)?;
679    Ok(match class {
680        NumericClass::Float => {
681            let (l, r) = float_pair(left_val, right_val)?;
682            l == r
683        }
684        NumericClass::Double => {
685            let (l, r) = double_pair(left_val, right_val)?;
686            l == r
687        }
688        NumericClass::Decimal => {
689            let (l, r) = decimal_pair(left_val, right_val)?;
690            l == r
691        }
692        _ => {
693            let (l, r) = integer_pair(left_val, right_val)?;
694            l == r
695        }
696    })
697}
698
699fn numeric_gt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
700    let (left_val, right_val, class) = promote_numeric(left, right)?;
701    Ok(match class {
702        NumericClass::Float => {
703            let (l, r) = float_pair(left_val, right_val)?;
704            l > r
705        }
706        NumericClass::Double => {
707            let (l, r) = double_pair(left_val, right_val)?;
708            l > r
709        }
710        NumericClass::Decimal => {
711            let (l, r) = decimal_pair(left_val, right_val)?;
712            l > r
713        }
714        _ => {
715            let (l, r) = integer_pair(left_val, right_val)?;
716            l > r
717        }
718    })
719}
720
721fn eval_numeric_unary(value: &XmlValue) -> Result<XmlValue, XPathError> {
722    // Per XPath 2.0 spec, UntypedAtomic is cast to xs:double for arithmetic
723    let promoted;
724    let value = if value.type_code == XmlTypeCode::UntypedAtomic {
725        promoted = cast_untyped_to_double(value)?;
726        &promoted
727    } else {
728        value
729    };
730    let class = numeric_class(value.type_code)
731        .ok_or_else(|| XPathError::internal("Unary operator requires numeric operand"))?;
732
733    let result_type = unary_result_type(class);
734    let value = to_numeric_value(value, class)?;
735
736    let result = match value {
737        NumericValue::Integer(v) => NumericValue::Integer(-v),
738        NumericValue::Decimal(v) => NumericValue::Decimal(-v),
739        NumericValue::Float(v) => NumericValue::Float(-v),
740        NumericValue::Double(v) => NumericValue::Double(-v),
741    };
742
743    Ok(numeric_to_xml_value(result, result_type))
744}
745
746fn eval_numeric_binary(
747    op: BinaryOpKind,
748    left: &XmlValue,
749    right: &XmlValue,
750) -> Result<XmlValue, XPathError> {
751    let (left_val, right_val, class) = promote_numeric(left, right)?;
752    let result_type = binary_result_type(op, class);
753
754    match op {
755        BinaryOpKind::Add => Ok(numeric_add(left_val, right_val, result_type)?),
756        BinaryOpKind::Sub => Ok(numeric_sub(left_val, right_val, result_type)?),
757        BinaryOpKind::Mul => Ok(numeric_mul(left_val, right_val, result_type)?),
758        BinaryOpKind::Div => Ok(numeric_div(left_val, right_val, class, result_type)?),
759        BinaryOpKind::IDiv => Ok(numeric_idiv(left_val, right_val)?),
760        BinaryOpKind::Mod => Ok(numeric_mod(left_val, right_val, result_type)?),
761        _ => Err(XPathError::internal("Unsupported numeric operator")),
762    }
763}
764
765fn numeric_add(
766    left: NumericValue,
767    right: NumericValue,
768    result_type: XmlTypeCode,
769) -> Result<XmlValue, XPathError> {
770    let result = match (left, right) {
771        (NumericValue::Integer(l), NumericValue::Integer(r)) => NumericValue::Integer(l + r),
772        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => NumericValue::Decimal(l + r),
773        (NumericValue::Float(l), NumericValue::Float(r)) => NumericValue::Float(l + r),
774        (NumericValue::Double(l), NumericValue::Double(r)) => NumericValue::Double(l + r),
775        _ => return Err(XPathError::internal("Numeric add type mismatch")),
776    };
777    Ok(numeric_to_xml_value(result, result_type))
778}
779
780fn numeric_sub(
781    left: NumericValue,
782    right: NumericValue,
783    result_type: XmlTypeCode,
784) -> Result<XmlValue, XPathError> {
785    let result = match (left, right) {
786        (NumericValue::Integer(l), NumericValue::Integer(r)) => NumericValue::Integer(l - r),
787        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => NumericValue::Decimal(l - r),
788        (NumericValue::Float(l), NumericValue::Float(r)) => NumericValue::Float(l - r),
789        (NumericValue::Double(l), NumericValue::Double(r)) => NumericValue::Double(l - r),
790        _ => return Err(XPathError::internal("Numeric sub type mismatch")),
791    };
792    Ok(numeric_to_xml_value(result, result_type))
793}
794
795fn numeric_mul(
796    left: NumericValue,
797    right: NumericValue,
798    result_type: XmlTypeCode,
799) -> Result<XmlValue, XPathError> {
800    let result = match (left, right) {
801        (NumericValue::Integer(l), NumericValue::Integer(r)) => NumericValue::Integer(l * r),
802        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => NumericValue::Decimal(l * r),
803        (NumericValue::Float(l), NumericValue::Float(r)) => NumericValue::Float(l * r),
804        (NumericValue::Double(l), NumericValue::Double(r)) => NumericValue::Double(l * r),
805        _ => return Err(XPathError::internal("Numeric mul type mismatch")),
806    };
807    Ok(numeric_to_xml_value(result, result_type))
808}
809
810fn numeric_div(
811    left: NumericValue,
812    right: NumericValue,
813    class: NumericClass,
814    result_type: XmlTypeCode,
815) -> Result<XmlValue, XPathError> {
816    let result = match (left, right) {
817        (NumericValue::Integer(l), NumericValue::Integer(r)) => {
818            if r == BigInt::from(0) {
819                return Err(XPathError::FOAR0001);
820            }
821            let left_dec = decimal_from_bigint(&l)?;
822            let right_dec = decimal_from_bigint(&r)?;
823            NumericValue::Decimal((left_dec / right_dec).round_dp(XPATH_DECIMAL_DIV_SCALE))
824        }
825        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => {
826            if r.is_zero() {
827                return Err(XPathError::FOAR0001);
828            }
829            NumericValue::Decimal((l / r).round_dp(XPATH_DECIMAL_DIV_SCALE))
830        }
831        (NumericValue::Float(l), NumericValue::Float(r)) => NumericValue::Float(l / r),
832        (NumericValue::Double(l), NumericValue::Double(r)) => NumericValue::Double(l / r),
833        _ => return Err(XPathError::internal("Numeric div type mismatch")),
834    };
835
836    let result_type = match class {
837        NumericClass::Decimal | NumericClass::Float | NumericClass::Double => result_type,
838        _ => XmlTypeCode::Decimal,
839    };
840
841    Ok(numeric_to_xml_value(result, result_type))
842}
843
844fn numeric_idiv(left: NumericValue, right: NumericValue) -> Result<XmlValue, XPathError> {
845    let result = match (left, right) {
846        (NumericValue::Integer(l), NumericValue::Integer(r)) => {
847            if r == BigInt::from(0) {
848                return Err(XPathError::FOAR0001);
849            }
850            NumericValue::Integer(l / r)
851        }
852        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => {
853            if r.is_zero() {
854                return Err(XPathError::FOAR0001);
855            }
856            let q = (l / r).trunc();
857            NumericValue::Integer(decimal_to_bigint(&q)?)
858        }
859        (NumericValue::Float(l), NumericValue::Float(r)) => {
860            let q = l / r;
861            if q.is_nan() || q.is_infinite() {
862                return Err(XPathError::FOAR0001);
863            }
864            NumericValue::Integer(BigInt::from(q.trunc() as i64))
865        }
866        (NumericValue::Double(l), NumericValue::Double(r)) => {
867            let q = l / r;
868            if q.is_nan() || q.is_infinite() {
869                return Err(XPathError::FOAR0001);
870            }
871            NumericValue::Integer(BigInt::from(q.trunc() as i64))
872        }
873        _ => return Err(XPathError::internal("Numeric idiv type mismatch")),
874    };
875
876    Ok(numeric_to_xml_value(result, XmlTypeCode::Integer))
877}
878
879fn numeric_mod(
880    left: NumericValue,
881    right: NumericValue,
882    result_type: XmlTypeCode,
883) -> Result<XmlValue, XPathError> {
884    let result = match (left, right) {
885        (NumericValue::Integer(l), NumericValue::Integer(r)) => {
886            if r == BigInt::from(0) {
887                return Err(XPathError::FOAR0001);
888            }
889            NumericValue::Integer(l % r)
890        }
891        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => {
892            if r.is_zero() {
893                return Err(XPathError::FOAR0001);
894            }
895            NumericValue::Decimal(l % r)
896        }
897        (NumericValue::Float(l), NumericValue::Float(r)) => NumericValue::Float(l % r),
898        (NumericValue::Double(l), NumericValue::Double(r)) => NumericValue::Double(l % r),
899        _ => return Err(XPathError::internal("Numeric mod type mismatch")),
900    };
901    Ok(numeric_to_xml_value(result, result_type))
902}
903
904/// Cast an `UntypedAtomic` value to `xs:double` for arithmetic promotion.
905///
906/// Per XPath 2.0 Section 3.5.1, `UntypedAtomic` operands in arithmetic
907/// expressions are cast to `xs:double`.
908fn cast_untyped_to_double(value: &XmlValue) -> Result<XmlValue, XPathError> {
909    let s = value.to_string_value();
910    let d: f64 = s
911        .trim()
912        .parse()
913        .map_err(|_| XPathError::invalid_cast_value(&s, "xs:double"))?;
914    Ok(XmlValue::double(d))
915}
916
917fn promote_numeric(
918    left: &XmlValue,
919    right: &XmlValue,
920) -> Result<(NumericValue, NumericValue, NumericClass), XPathError> {
921    // Per XPath 2.0 spec, UntypedAtomic is cast to xs:double for arithmetic
922    let left_promoted;
923    let left_ref = if left.type_code == XmlTypeCode::UntypedAtomic {
924        left_promoted = cast_untyped_to_double(left)?;
925        &left_promoted
926    } else {
927        left
928    };
929    let right_promoted;
930    let right_ref = if right.type_code == XmlTypeCode::UntypedAtomic {
931        right_promoted = cast_untyped_to_double(right)?;
932        &right_promoted
933    } else {
934        right
935    };
936
937    let left_class = numeric_class(left_ref.type_code)
938        .ok_or_else(|| XPathError::internal("Left operand not numeric"))?;
939    let right_class = numeric_class(right_ref.type_code)
940        .ok_or_else(|| XPathError::internal("Right operand not numeric"))?;
941
942    let promotion = numeric_promotion(left_class, right_class);
943    let target_class = match promotion {
944        Promote::Left => left_class,
945        Promote::Right => right_class,
946        Promote::None => left_class,
947    };
948
949    let left_val = to_numeric_value(left_ref, target_class)?;
950    let right_val = to_numeric_value(right_ref, target_class)?;
951
952    Ok((left_val, right_val, target_class))
953}
954
955fn numeric_class(code: XmlTypeCode) -> Option<NumericClass> {
956    match code {
957        XmlTypeCode::Byte => Some(NumericClass::Byte),
958        XmlTypeCode::UnsignedByte => Some(NumericClass::UnsignedByte),
959        XmlTypeCode::Short => Some(NumericClass::Short),
960        XmlTypeCode::UnsignedShort => Some(NumericClass::UnsignedShort),
961        XmlTypeCode::Int => Some(NumericClass::Int),
962        XmlTypeCode::UnsignedInt => Some(NumericClass::UnsignedInt),
963        XmlTypeCode::Long => Some(NumericClass::Long),
964        XmlTypeCode::UnsignedLong => Some(NumericClass::UnsignedLong),
965        XmlTypeCode::Integer
966        | XmlTypeCode::NonPositiveInteger
967        | XmlTypeCode::NegativeInteger
968        | XmlTypeCode::NonNegativeInteger
969        | XmlTypeCode::PositiveInteger => Some(NumericClass::Integer),
970        XmlTypeCode::Decimal => Some(NumericClass::Decimal),
971        XmlTypeCode::Float => Some(NumericClass::Float),
972        XmlTypeCode::Double => Some(NumericClass::Double),
973        _ => None,
974    }
975}
976
977fn numeric_precedence(class: NumericClass) -> u8 {
978    match class {
979        NumericClass::Byte => 0,
980        NumericClass::UnsignedByte => 1,
981        NumericClass::Short => 2,
982        NumericClass::UnsignedShort => 3,
983        NumericClass::Int => 4,
984        NumericClass::UnsignedInt => 5,
985        NumericClass::Long => 6,
986        NumericClass::UnsignedLong => 7,
987        NumericClass::Integer => 8,
988        NumericClass::Decimal => 9,
989        NumericClass::Float => 10,
990        NumericClass::Double => 11,
991    }
992}
993
994fn numeric_promotion(left: NumericClass, right: NumericClass) -> Promote {
995    let left_rank = numeric_precedence(left);
996    let right_rank = numeric_precedence(right);
997
998    match left_rank.cmp(&right_rank) {
999        Ordering::Less => Promote::Right,
1000        Ordering::Greater => Promote::Left,
1001        Ordering::Equal => Promote::None,
1002    }
1003}
1004
1005fn binary_result_type(op: BinaryOpKind, class: NumericClass) -> XmlTypeCode {
1006    match op {
1007        BinaryOpKind::Div => div_result_type(class),
1008        BinaryOpKind::IDiv => XmlTypeCode::Integer,
1009        BinaryOpKind::Add | BinaryOpKind::Sub | BinaryOpKind::Mul | BinaryOpKind::Mod => {
1010            arithmetic_result_type(class)
1011        }
1012        _ => arithmetic_result_type(class),
1013    }
1014}
1015
1016fn unary_result_type(class: NumericClass) -> XmlTypeCode {
1017    match class {
1018        NumericClass::Byte
1019        | NumericClass::UnsignedByte
1020        | NumericClass::Short
1021        | NumericClass::UnsignedShort => XmlTypeCode::Int,
1022        NumericClass::UnsignedInt => XmlTypeCode::Long,
1023        NumericClass::UnsignedLong => XmlTypeCode::Integer,
1024        NumericClass::Int => XmlTypeCode::Int,
1025        NumericClass::Long => XmlTypeCode::Long,
1026        NumericClass::Integer => XmlTypeCode::Integer,
1027        NumericClass::Decimal => XmlTypeCode::Decimal,
1028        NumericClass::Float => XmlTypeCode::Float,
1029        NumericClass::Double => XmlTypeCode::Double,
1030    }
1031}
1032
1033fn arithmetic_result_type(class: NumericClass) -> XmlTypeCode {
1034    match class {
1035        NumericClass::Byte
1036        | NumericClass::UnsignedByte
1037        | NumericClass::Short
1038        | NumericClass::UnsignedShort => XmlTypeCode::Int,
1039        NumericClass::UnsignedInt => XmlTypeCode::UnsignedInt,
1040        NumericClass::Int => XmlTypeCode::Int,
1041        NumericClass::Long => XmlTypeCode::Long,
1042        NumericClass::UnsignedLong => XmlTypeCode::Integer,
1043        NumericClass::Integer => XmlTypeCode::Integer,
1044        NumericClass::Decimal => XmlTypeCode::Decimal,
1045        NumericClass::Float => XmlTypeCode::Float,
1046        NumericClass::Double => XmlTypeCode::Double,
1047    }
1048}
1049
1050fn div_result_type(class: NumericClass) -> XmlTypeCode {
1051    match class {
1052        NumericClass::Decimal => XmlTypeCode::Decimal,
1053        NumericClass::Float => XmlTypeCode::Float,
1054        NumericClass::Double => XmlTypeCode::Double,
1055        _ => XmlTypeCode::Decimal,
1056    }
1057}
1058
1059fn is_integer_class(class: NumericClass) -> bool {
1060    matches!(
1061        class,
1062        NumericClass::Byte
1063            | NumericClass::UnsignedByte
1064            | NumericClass::Short
1065            | NumericClass::UnsignedShort
1066            | NumericClass::Int
1067            | NumericClass::UnsignedInt
1068            | NumericClass::Long
1069            | NumericClass::UnsignedLong
1070            | NumericClass::Integer
1071    )
1072}
1073
1074fn to_integer_value(value: &XmlValue) -> Result<BigInt, XPathError> {
1075    match &value.value {
1076        XmlValueKind::Atomic(XmlAtomicValue::Integer(v)) => Ok(v.clone()),
1077        _ => Err(XPathError::internal("Expected integer value")),
1078    }
1079}
1080
1081fn to_numeric_value(value: &XmlValue, class: NumericClass) -> Result<NumericValue, XPathError> {
1082    match class {
1083        NumericClass::Decimal => {
1084            let decimal = value
1085                .as_decimal()
1086                .ok_or_else(|| XPathError::internal("Expected decimal value"))?;
1087            Ok(NumericValue::Decimal(decimal))
1088        }
1089        NumericClass::Float => {
1090            let val = value
1091                .as_double()
1092                .ok_or_else(|| XPathError::internal("Expected numeric value"))?;
1093            Ok(NumericValue::Float(val as f32))
1094        }
1095        NumericClass::Double => {
1096            let val = value
1097                .as_double()
1098                .ok_or_else(|| XPathError::internal("Expected numeric value"))?;
1099            Ok(NumericValue::Double(val))
1100        }
1101        _ => Ok(NumericValue::Integer(to_integer_value(value)?)),
1102    }
1103}
1104
1105fn decimal_from_bigint(value: &BigInt) -> Result<Decimal, XPathError> {
1106    value
1107        .to_string()
1108        .parse::<Decimal>()
1109        .map_err(|_| XPathError::internal("Failed to convert integer to decimal"))
1110}
1111
1112fn decimal_to_bigint(value: &Decimal) -> Result<BigInt, XPathError> {
1113    value
1114        .trunc()
1115        .to_string()
1116        .parse::<BigInt>()
1117        .map_err(|_| XPathError::internal("Failed to convert decimal to integer"))
1118}
1119
1120fn numeric_to_xml_value(value: NumericValue, type_code: XmlTypeCode) -> XmlValue {
1121    let value = match value {
1122        NumericValue::Integer(v) => XmlValueKind::Atomic(XmlAtomicValue::Integer(v)),
1123        NumericValue::Decimal(v) => XmlValueKind::Atomic(XmlAtomicValue::Decimal(v)),
1124        NumericValue::Float(v) => XmlValueKind::Atomic(XmlAtomicValue::Float(v)),
1125        NumericValue::Double(v) => XmlValueKind::Atomic(XmlAtomicValue::Double(v)),
1126    };
1127
1128    XmlValue {
1129        type_code,
1130        schema_type: None,
1131        value,
1132    }
1133}
1134
1135fn integer_pair(left: NumericValue, right: NumericValue) -> Result<(BigInt, BigInt), XPathError> {
1136    match (left, right) {
1137        (NumericValue::Integer(l), NumericValue::Integer(r)) => Ok((l, r)),
1138        _ => Err(XPathError::internal("Expected integer values")),
1139    }
1140}
1141
1142fn decimal_pair(left: NumericValue, right: NumericValue) -> Result<(Decimal, Decimal), XPathError> {
1143    match (left, right) {
1144        (NumericValue::Decimal(l), NumericValue::Decimal(r)) => Ok((l, r)),
1145        _ => Err(XPathError::internal("Expected decimal values")),
1146    }
1147}
1148
1149fn float_pair(left: NumericValue, right: NumericValue) -> Result<(f32, f32), XPathError> {
1150    match (left, right) {
1151        (NumericValue::Float(l), NumericValue::Float(r)) => Ok((l, r)),
1152        _ => Err(XPathError::internal("Expected float values")),
1153    }
1154}
1155
1156fn double_pair(left: NumericValue, right: NumericValue) -> Result<(f64, f64), XPathError> {
1157    match (left, right) {
1158        (NumericValue::Double(l), NumericValue::Double(r)) => Ok((l, r)),
1159        _ => Err(XPathError::internal("Expected double values")),
1160    }
1161}
1162
1163fn xml_datetime_value(type_code: XmlTypeCode, value: DateTimeValue) -> XmlValue {
1164    XmlValue {
1165        type_code,
1166        schema_type: None,
1167        value: XmlValueKind::Atomic(XmlAtomicValue::DateTime(value)),
1168    }
1169}
1170
1171fn xml_date_value(type_code: XmlTypeCode, value: DateValue) -> XmlValue {
1172    XmlValue {
1173        type_code,
1174        schema_type: None,
1175        value: XmlValueKind::Atomic(XmlAtomicValue::Date(value)),
1176    }
1177}
1178
1179fn xml_time_value(type_code: XmlTypeCode, value: TimeValue) -> XmlValue {
1180    XmlValue {
1181        type_code,
1182        schema_type: None,
1183        value: XmlValueKind::Atomic(XmlAtomicValue::Time(value)),
1184    }
1185}
1186
1187fn xml_year_month_duration_value(value: YearMonthDurationValue) -> XmlValue {
1188    XmlValue {
1189        type_code: XmlTypeCode::YearMonthDuration,
1190        schema_type: None,
1191        value: XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(value)),
1192    }
1193}
1194
1195fn xml_day_time_duration_value(value: DayTimeDurationValue) -> XmlValue {
1196    XmlValue {
1197        type_code: XmlTypeCode::DayTimeDuration,
1198        schema_type: None,
1199        value: XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(value)),
1200    }
1201}
1202
1203fn is_temporal_type(code: XmlTypeCode) -> bool {
1204    matches!(
1205        code,
1206        XmlTypeCode::DateTime
1207            | XmlTypeCode::DateTimeStamp
1208            | XmlTypeCode::Date
1209            | XmlTypeCode::Time
1210            | XmlTypeCode::Duration
1211            | XmlTypeCode::YearMonthDuration
1212            | XmlTypeCode::DayTimeDuration
1213    )
1214}
1215
1216fn is_date_time_code(code: XmlTypeCode) -> bool {
1217    matches!(code, XmlTypeCode::DateTime | XmlTypeCode::DateTimeStamp)
1218}
1219
1220fn is_duration_code(code: XmlTypeCode) -> bool {
1221    matches!(
1222        code,
1223        XmlTypeCode::Duration | XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration
1224    )
1225}
1226
1227fn as_datetime(value: &XmlValue) -> Option<&DateTimeValue> {
1228    match &value.value {
1229        XmlValueKind::Atomic(XmlAtomicValue::DateTime(v)) => Some(v),
1230        _ => None,
1231    }
1232}
1233
1234fn as_date(value: &XmlValue) -> Option<&DateValue> {
1235    match &value.value {
1236        XmlValueKind::Atomic(XmlAtomicValue::Date(v)) => Some(v),
1237        _ => None,
1238    }
1239}
1240
1241fn as_time(value: &XmlValue) -> Option<&TimeValue> {
1242    match &value.value {
1243        XmlValueKind::Atomic(XmlAtomicValue::Time(v)) => Some(v),
1244        _ => None,
1245    }
1246}
1247
1248fn as_duration(value: &XmlValue) -> Option<&DurationValue> {
1249    match &value.value {
1250        XmlValueKind::Atomic(XmlAtomicValue::Duration(v)) => Some(v),
1251        _ => None,
1252    }
1253}
1254
1255fn as_year_month_duration(value: &XmlValue) -> Option<&YearMonthDurationValue> {
1256    match &value.value {
1257        XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(v)) => Some(v),
1258        _ => None,
1259    }
1260}
1261
1262fn as_day_time_duration(value: &XmlValue) -> Option<&DayTimeDurationValue> {
1263    match &value.value {
1264        XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(v)) => Some(v),
1265        _ => None,
1266    }
1267}
1268
1269fn duration_parts(value: &XmlValue) -> Result<Option<(i64, Decimal)>, XPathError> {
1270    if let Some(duration) = as_duration(value) {
1271        let months = duration_total_months(duration);
1272        let seconds = duration_total_seconds(duration)?;
1273        return Ok(Some((months, seconds)));
1274    }
1275    if let Some(duration) = as_year_month_duration(value) {
1276        return Ok(Some((year_month_total_months(duration), Decimal::ZERO)));
1277    }
1278    if let Some(duration) = as_day_time_duration(value) {
1279        let seconds = day_time_total_seconds(duration)?;
1280        return Ok(Some((0, seconds)));
1281    }
1282    Ok(None)
1283}
1284
1285fn numeric_to_f64(value: &XmlValue) -> Result<f64, XPathError> {
1286    let val = value
1287        .as_double()
1288        .ok_or_else(|| XPathError::internal("Expected numeric value"))?;
1289    if !val.is_finite() {
1290        return Err(XPathError::internal("Numeric value is not finite"));
1291    }
1292    Ok(val)
1293}
1294
1295fn numeric_to_decimal(value: &XmlValue) -> Result<Decimal, XPathError> {
1296    match value.type_code {
1297        XmlTypeCode::Float | XmlTypeCode::Double => {
1298            let val = numeric_to_f64(value)?;
1299            Decimal::from_f64(val)
1300                .ok_or_else(|| XPathError::internal("Failed to convert float to decimal"))
1301        }
1302        _ => value
1303            .as_decimal()
1304            .ok_or_else(|| XPathError::internal("Expected decimal value")),
1305    }
1306}
1307
1308fn compare_datetime_eq(left: &DateTimeValue, right: &DateTimeValue) -> Result<bool, XPathError> {
1309    let left_inst = datetime_instant_for_compare(left)?;
1310    let right_inst = datetime_instant_for_compare(right)?;
1311    Ok(left_inst == right_inst)
1312}
1313
1314fn compare_datetime_gt(left: &DateTimeValue, right: &DateTimeValue) -> Result<bool, XPathError> {
1315    let left_inst = datetime_instant_for_compare(left)?;
1316    let right_inst = datetime_instant_for_compare(right)?;
1317    Ok(left_inst > right_inst)
1318}
1319
1320fn compare_date_eq(left: &DateValue, right: &DateValue) -> Result<bool, XPathError> {
1321    let left_inst = date_instant_for_compare(left)?;
1322    let right_inst = date_instant_for_compare(right)?;
1323    Ok(left_inst == right_inst)
1324}
1325
1326fn compare_date_gt(left: &DateValue, right: &DateValue) -> Result<bool, XPathError> {
1327    let left_inst = date_instant_for_compare(left)?;
1328    let right_inst = date_instant_for_compare(right)?;
1329    Ok(left_inst > right_inst)
1330}
1331
1332fn compare_time_eq(left: &TimeValue, right: &TimeValue) -> Result<bool, XPathError> {
1333    let left_inst = time_seconds_for_compare(left)?;
1334    let right_inst = time_seconds_for_compare(right)?;
1335    Ok(left_inst == right_inst)
1336}
1337
1338fn compare_time_gt(left: &TimeValue, right: &TimeValue) -> Result<bool, XPathError> {
1339    let left_inst = time_seconds_for_compare(left)?;
1340    let right_inst = time_seconds_for_compare(right)?;
1341    Ok(left_inst > right_inst)
1342}
1343
1344fn datetime_instant_for_compare(value: &DateTimeValue) -> Result<Decimal, XPathError> {
1345    let instant = datetime_to_instant(value)?;
1346    apply_timezone_offset(instant, value.timezone)
1347}
1348
1349fn date_instant_for_compare(value: &DateValue) -> Result<Decimal, XPathError> {
1350    let instant = date_to_instant(value)?;
1351    apply_timezone_offset(instant, value.timezone)
1352}
1353
1354fn time_seconds_for_compare(value: &TimeValue) -> Result<Decimal, XPathError> {
1355    let seconds = time_to_seconds(value)?;
1356    apply_timezone_offset(seconds, value.timezone)
1357}
1358
1359fn apply_timezone_offset(
1360    instant: Decimal,
1361    timezone: Option<TimezoneOffset>,
1362) -> Result<Decimal, XPathError> {
1363    let offset = timezone.unwrap_or_else(implicit_timezone_offset);
1364    let offset_minutes = decimal_from_i64(offset.0 as i64)?;
1365    Ok(instant - offset_minutes * Decimal::from(60))
1366}
1367
1368fn implicit_timezone_offset() -> TimezoneOffset {
1369    let seconds = Local::now().offset().local_minus_utc();
1370    TimezoneOffset((seconds / 60) as i16)
1371}
1372
1373fn add_datetime_year_month(
1374    value: &DateTimeValue,
1375    duration: &YearMonthDurationValue,
1376) -> Result<DateTimeValue, XPathError> {
1377    let delta = year_month_total_months(duration);
1378    let (year, month, day) = add_months_to_date(value.year, value.month, value.day, delta)?;
1379    Ok(DateTimeValue {
1380        year,
1381        month,
1382        day,
1383        hour: value.hour,
1384        minute: value.minute,
1385        second: value.second,
1386        timezone: value.timezone,
1387    })
1388}
1389
1390fn add_date_year_month(
1391    value: &DateValue,
1392    duration: &YearMonthDurationValue,
1393) -> Result<DateValue, XPathError> {
1394    let delta = year_month_total_months(duration);
1395    let (year, month, day) = add_months_to_date(value.year, value.month, value.day, delta)?;
1396    Ok(DateValue {
1397        year,
1398        month,
1399        day,
1400        timezone: value.timezone,
1401    })
1402}
1403
1404fn add_datetime_day_time(
1405    value: &DateTimeValue,
1406    duration: &DayTimeDurationValue,
1407) -> Result<DateTimeValue, XPathError> {
1408    let instant = datetime_to_instant(value)?;
1409    let delta = day_time_total_seconds(duration)?;
1410    instant_to_datetime(instant + delta, value.timezone)
1411}
1412
1413fn add_date_day_time(
1414    value: &DateValue,
1415    duration: &DayTimeDurationValue,
1416) -> Result<DateValue, XPathError> {
1417    let instant = date_to_instant(value)?;
1418    let delta = day_time_total_seconds(duration)?;
1419    instant_to_date(instant + delta, value.timezone)
1420}
1421
1422fn add_time_day_time(
1423    value: &TimeValue,
1424    duration: &DayTimeDurationValue,
1425) -> Result<TimeValue, XPathError> {
1426    let seconds = time_to_seconds(value)?;
1427    let delta = day_time_total_seconds(duration)?;
1428    let normalized = normalize_seconds_in_day(seconds + delta)?;
1429    let (hour, minute, second) = time_components_from_seconds(normalized)?;
1430    Ok(TimeValue {
1431        hour,
1432        minute,
1433        second,
1434        timezone: value.timezone,
1435    })
1436}
1437
1438fn diff_datetime(
1439    left: &DateTimeValue,
1440    right: &DateTimeValue,
1441) -> Result<DayTimeDurationValue, XPathError> {
1442    let left_inst = datetime_instant_for_compare(left)?;
1443    let right_inst = datetime_instant_for_compare(right)?;
1444    day_time_from_seconds(left_inst - right_inst)
1445}
1446
1447fn diff_date(left: &DateValue, right: &DateValue) -> Result<DayTimeDurationValue, XPathError> {
1448    let left_inst = date_instant_for_compare(left)?;
1449    let right_inst = date_instant_for_compare(right)?;
1450    day_time_from_seconds(left_inst - right_inst)
1451}
1452
1453fn diff_time(left: &TimeValue, right: &TimeValue) -> Result<DayTimeDurationValue, XPathError> {
1454    let left_inst = time_seconds_for_compare(left)?;
1455    let right_inst = time_seconds_for_compare(right)?;
1456    day_time_from_seconds(left_inst - right_inst)
1457}
1458
1459fn year_month_total_months(value: &YearMonthDurationValue) -> i64 {
1460    let total = value.years as i64 * 12 + value.months as i64;
1461    if value.negative {
1462        -total
1463    } else {
1464        total
1465    }
1466}
1467
1468fn duration_total_months(value: &DurationValue) -> i64 {
1469    let total = value.years as i64 * 12 + value.months as i64;
1470    if value.negative {
1471        -total
1472    } else {
1473        total
1474    }
1475}
1476
1477fn day_time_total_seconds(value: &DayTimeDurationValue) -> Result<Decimal, XPathError> {
1478    let days = decimal_from_i64(value.days as i64)?;
1479    let hours = decimal_from_i64(value.hours as i64)?;
1480    let minutes = decimal_from_i64(value.minutes as i64)?;
1481    let total = days * Decimal::from(86_400)
1482        + hours * Decimal::from(3_600)
1483        + minutes * Decimal::from(60)
1484        + value.seconds;
1485    Ok(if value.negative { -total } else { total })
1486}
1487
1488fn duration_total_seconds(value: &DurationValue) -> Result<Decimal, XPathError> {
1489    let days = decimal_from_i64(value.days as i64)?;
1490    let hours = decimal_from_i64(value.hours as i64)?;
1491    let minutes = decimal_from_i64(value.minutes as i64)?;
1492    let total = days * Decimal::from(86_400)
1493        + hours * Decimal::from(3_600)
1494        + minutes * Decimal::from(60)
1495        + value.seconds;
1496    Ok(if value.negative { -total } else { total })
1497}
1498
1499fn negate_year_month_duration(value: &YearMonthDurationValue) -> YearMonthDurationValue {
1500    let negative = if value.years == 0 && value.months == 0 {
1501        false
1502    } else {
1503        !value.negative
1504    };
1505    YearMonthDurationValue {
1506        negative,
1507        years: value.years,
1508        months: value.months,
1509    }
1510}
1511
1512fn negate_day_time_duration(value: &DayTimeDurationValue) -> DayTimeDurationValue {
1513    let negative =
1514        if value.days == 0 && value.hours == 0 && value.minutes == 0 && value.seconds.is_zero() {
1515            false
1516        } else {
1517            !value.negative
1518        };
1519    DayTimeDurationValue {
1520        negative,
1521        days: value.days,
1522        hours: value.hours,
1523        minutes: value.minutes,
1524        seconds: value.seconds,
1525    }
1526}
1527
1528fn year_month_from_months(months: i64) -> Result<YearMonthDurationValue, XPathError> {
1529    let negative = months < 0;
1530    let abs_months = months.abs();
1531    let years = abs_months / 12;
1532    let months = abs_months % 12;
1533    let years = u32::try_from(years)
1534        .map_err(|_| XPathError::internal("yearMonthDuration years out of range"))?;
1535    let months = u32::try_from(months)
1536        .map_err(|_| XPathError::internal("yearMonthDuration months out of range"))?;
1537    Ok(YearMonthDurationValue {
1538        negative,
1539        years,
1540        months,
1541    })
1542}
1543
1544fn day_time_from_seconds(seconds: Decimal) -> Result<DayTimeDurationValue, XPathError> {
1545    let negative = seconds.is_sign_negative();
1546    let abs = if negative { -seconds } else { seconds };
1547    let seconds_per_day = decimal_from_i64(86_400)?;
1548    let days = (abs / seconds_per_day).floor();
1549    let mut remainder = abs - days * seconds_per_day;
1550    let hours = (remainder / Decimal::from(3_600)).floor();
1551    remainder -= hours * Decimal::from(3_600);
1552    let minutes = (remainder / Decimal::from(60)).floor();
1553    let seconds = remainder - minutes * Decimal::from(60);
1554    let days = decimal_to_u32(days, "days")?;
1555    let hours = decimal_to_u32(hours, "hours")?;
1556    let minutes = decimal_to_u32(minutes, "minutes")?;
1557    Ok(DayTimeDurationValue {
1558        negative,
1559        days,
1560        hours,
1561        minutes,
1562        seconds,
1563    })
1564}
1565
1566fn year_month_mul_numeric(
1567    value: &YearMonthDurationValue,
1568    factor: f64,
1569) -> Result<YearMonthDurationValue, XPathError> {
1570    if !factor.is_finite() {
1571        return Err(XPathError::internal("Numeric value is not finite"));
1572    }
1573    let months = year_month_total_months(value) as f64 * factor;
1574    let rounded = (months + 0.5).floor();
1575    year_month_from_months(rounded as i64)
1576}
1577
1578fn year_month_div_numeric(
1579    value: &YearMonthDurationValue,
1580    divisor: f64,
1581) -> Result<YearMonthDurationValue, XPathError> {
1582    if divisor == 0.0 {
1583        return Err(XPathError::internal("Division by zero"));
1584    }
1585    if !divisor.is_finite() {
1586        return Err(XPathError::internal("Numeric value is not finite"));
1587    }
1588    let months = year_month_total_months(value) as f64 / divisor;
1589    let rounded = (months + 0.5).floor();
1590    year_month_from_months(rounded as i64)
1591}
1592
1593fn year_month_div_duration(
1594    left: &YearMonthDurationValue,
1595    right: &YearMonthDurationValue,
1596) -> Result<Decimal, XPathError> {
1597    let left_months = year_month_total_months(left);
1598    let right_months = year_month_total_months(right);
1599    if right_months == 0 {
1600        return Err(XPathError::internal("Division by zero"));
1601    }
1602    let left = decimal_from_i64(left_months)?;
1603    let right = decimal_from_i64(right_months)?;
1604    Ok(left / right)
1605}
1606
1607fn day_time_mul_numeric(
1608    value: &DayTimeDurationValue,
1609    factor: Decimal,
1610) -> Result<DayTimeDurationValue, XPathError> {
1611    let total = day_time_total_seconds(value)? * factor;
1612    day_time_from_seconds(total)
1613}
1614
1615fn day_time_div_numeric(
1616    value: &DayTimeDurationValue,
1617    divisor: Decimal,
1618) -> Result<DayTimeDurationValue, XPathError> {
1619    if divisor.is_zero() {
1620        return Err(XPathError::internal("Division by zero"));
1621    }
1622    let total = day_time_total_seconds(value)? / divisor;
1623    day_time_from_seconds(total)
1624}
1625
1626/// Multiply dayTimeDuration by a numeric value, falling back to f64 arithmetic
1627/// when the numeric value is outside Decimal's representable range (e.g. ±1.8e308).
1628fn day_time_mul_numeric_value(
1629    value: &DayTimeDurationValue,
1630    factor: &XmlValue,
1631) -> Result<DayTimeDurationValue, XPathError> {
1632    match numeric_to_decimal(factor) {
1633        Ok(d) => day_time_mul_numeric(value, d),
1634        Err(_) => {
1635            let f = numeric_to_f64(factor)?;
1636            let total = day_time_total_seconds(value)?
1637                .to_f64()
1638                .ok_or_else(|| XPathError::internal("Failed to convert seconds to f64"))?;
1639            let result = Decimal::from_f64(total * f).ok_or(XPathError::FODT0002)?;
1640            day_time_from_seconds(result)
1641        }
1642    }
1643}
1644
1645/// Divide dayTimeDuration by a numeric value, falling back to f64 arithmetic
1646/// when the numeric value is outside Decimal's representable range.
1647fn day_time_div_numeric_value(
1648    value: &DayTimeDurationValue,
1649    divisor: &XmlValue,
1650) -> Result<DayTimeDurationValue, XPathError> {
1651    match numeric_to_decimal(divisor) {
1652        Ok(d) => day_time_div_numeric(value, d),
1653        Err(_) => {
1654            let f = numeric_to_f64(divisor)?;
1655            if f == 0.0 {
1656                return Err(XPathError::FODT0002);
1657            }
1658            let total = day_time_total_seconds(value)?
1659                .to_f64()
1660                .ok_or_else(|| XPathError::internal("Failed to convert seconds to f64"))?;
1661            let result = Decimal::from_f64(total / f).ok_or(XPathError::FODT0002)?;
1662            day_time_from_seconds(result)
1663        }
1664    }
1665}
1666
1667fn day_time_div_duration(
1668    left: &DayTimeDurationValue,
1669    right: &DayTimeDurationValue,
1670) -> Result<Decimal, XPathError> {
1671    let left_seconds = day_time_total_seconds(left)?;
1672    let right_seconds = day_time_total_seconds(right)?;
1673    if right_seconds.is_zero() {
1674        return Err(XPathError::internal("Division by zero"));
1675    }
1676    Ok(left_seconds / right_seconds)
1677}
1678
1679fn add_months_to_date(
1680    year: i32,
1681    month: u8,
1682    day: u8,
1683    delta_months: i64,
1684) -> Result<(i32, u8, u8), XPathError> {
1685    // Convert XSD year (no year 0) to astronomical year (continuous, with year 0)
1686    // XSD year > 0 → astro = year; XSD year < 0 → astro = year + 1
1687    let astro_year = if year <= 0 {
1688        year as i64 + 1
1689    } else {
1690        year as i64
1691    };
1692    let month_index = month as i64 - 1;
1693    let total = astro_year * 12 + month_index + delta_months;
1694    let new_astro_year = total.div_euclid(12);
1695    let new_month = total.rem_euclid(12) + 1;
1696    // Convert back to XSD year (skip year 0)
1697    let new_year = if new_astro_year <= 0 {
1698        new_astro_year - 1
1699    } else {
1700        new_astro_year
1701    };
1702    let year = i32::try_from(new_year).map_err(|_| XPathError::internal("Year out of range"))?;
1703    let month = u8::try_from(new_month).map_err(|_| XPathError::internal("Month out of range"))?;
1704    let max_day = days_in_month(year, month)?;
1705    let day = day.min(max_day);
1706    Ok((year, month, day))
1707}
1708
1709fn days_in_month(year: i32, month: u8) -> Result<u8, XPathError> {
1710    let days = match month {
1711        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
1712        4 | 6 | 9 | 11 => 30,
1713        2 => {
1714            if is_leap_year(year) {
1715                29
1716            } else {
1717                28
1718            }
1719        }
1720        _ => return Err(XPathError::internal("Invalid month value")),
1721    };
1722    Ok(days)
1723}
1724
1725fn is_leap_year(xsd_year: i32) -> bool {
1726    // Convert XSD year (no year 0) to astronomical year (with year 0) for correct leap year calc
1727    let year = if xsd_year <= 0 {
1728        xsd_year as i64 + 1
1729    } else {
1730        xsd_year as i64
1731    };
1732    year.rem_euclid(4) == 0 && (year.rem_euclid(100) != 0 || year.rem_euclid(400) == 0)
1733}
1734
1735fn datetime_to_instant(value: &DateTimeValue) -> Result<Decimal, XPathError> {
1736    // Convert XSD year (no year 0) to astronomical year for days_from_civil
1737    let astro_year = if value.year <= 0 {
1738        value.year + 1
1739    } else {
1740        value.year
1741    };
1742    let days = days_from_civil(astro_year, value.month, value.day);
1743    let seconds = time_to_seconds_components(value.hour, value.minute, value.second)?;
1744    Ok(decimal_from_i64(days)? * Decimal::from(86_400) + seconds)
1745}
1746
1747fn date_to_instant(value: &DateValue) -> Result<Decimal, XPathError> {
1748    // Convert XSD year (no year 0) to astronomical year for days_from_civil
1749    let astro_year = if value.year <= 0 {
1750        value.year + 1
1751    } else {
1752        value.year
1753    };
1754    let days = days_from_civil(astro_year, value.month, value.day);
1755    Ok(decimal_from_i64(days)? * Decimal::from(86_400))
1756}
1757
1758fn time_to_seconds(value: &TimeValue) -> Result<Decimal, XPathError> {
1759    time_to_seconds_components(value.hour, value.minute, value.second)
1760}
1761
1762fn time_to_seconds_components(
1763    hour: u8,
1764    minute: u8,
1765    second: Decimal,
1766) -> Result<Decimal, XPathError> {
1767    let hours = decimal_from_i64(hour as i64)?;
1768    let minutes = decimal_from_i64(minute as i64)?;
1769    Ok(hours * Decimal::from(3_600) + minutes * Decimal::from(60) + second)
1770}
1771
1772fn instant_to_datetime(
1773    instant: Decimal,
1774    timezone: Option<TimezoneOffset>,
1775) -> Result<DateTimeValue, XPathError> {
1776    let day_seconds = decimal_from_i64(86_400)?;
1777    let days = (instant / day_seconds).floor();
1778    let mut seconds_in_day = instant - days * day_seconds;
1779    if seconds_in_day < Decimal::ZERO {
1780        seconds_in_day += day_seconds;
1781    }
1782    let days = decimal_to_i64(days, "day count")?;
1783    let (astro_year, month, day) = civil_from_days(days);
1784    // Convert astronomical year back to XSD year (skip year 0)
1785    let year = if astro_year <= 0 {
1786        astro_year - 1
1787    } else {
1788        astro_year
1789    };
1790    let (hour, minute, second) = time_components_from_seconds(seconds_in_day)?;
1791    Ok(DateTimeValue {
1792        year,
1793        month,
1794        day,
1795        hour,
1796        minute,
1797        second,
1798        timezone,
1799    })
1800}
1801
1802fn instant_to_date(
1803    instant: Decimal,
1804    timezone: Option<TimezoneOffset>,
1805) -> Result<DateValue, XPathError> {
1806    let day_seconds = decimal_from_i64(86_400)?;
1807    let days = (instant / day_seconds).floor();
1808    let days = decimal_to_i64(days, "day count")?;
1809    let (astro_year, month, day) = civil_from_days(days);
1810    // Convert astronomical year back to XSD year (skip year 0)
1811    let year = if astro_year <= 0 {
1812        astro_year - 1
1813    } else {
1814        astro_year
1815    };
1816    Ok(DateValue {
1817        year,
1818        month,
1819        day,
1820        timezone,
1821    })
1822}
1823
1824fn normalize_seconds_in_day(seconds: Decimal) -> Result<Decimal, XPathError> {
1825    let day_seconds = decimal_from_i64(86_400)?;
1826    let days = (seconds / day_seconds).floor();
1827    let mut remainder = seconds - days * day_seconds;
1828    if remainder < Decimal::ZERO {
1829        remainder += day_seconds;
1830    }
1831    Ok(remainder)
1832}
1833
1834fn time_components_from_seconds(seconds: Decimal) -> Result<(u8, u8, Decimal), XPathError> {
1835    let hours = (seconds / Decimal::from(3_600)).floor();
1836    let mut remainder = seconds - hours * Decimal::from(3_600);
1837    let minutes = (remainder / Decimal::from(60)).floor();
1838    remainder -= minutes * Decimal::from(60);
1839    let hour = decimal_to_u8(hours, "hours")?;
1840    let minute = decimal_to_u8(minutes, "minutes")?;
1841    Ok((hour, minute, remainder))
1842}
1843
1844fn days_from_civil(year: i32, month: u8, day: u8) -> i64 {
1845    let y = year as i64 - if month <= 2 { 1 } else { 0 };
1846    let m = month as i64;
1847    let d = day as i64;
1848    let era = if y >= 0 { y } else { y - 399 } / 400;
1849    let yoe = y - era * 400;
1850    let mp = m + if m > 2 { -3 } else { 9 };
1851    let doy = (153 * mp + 2) / 5 + d - 1;
1852    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
1853    era * 146_097 + doe - 719_468
1854}
1855
1856fn civil_from_days(days: i64) -> (i32, u8, u8) {
1857    let z = days + 719_468;
1858    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
1859    let doe = z - era * 146_097;
1860    let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
1861    let y = yoe + era * 400;
1862    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1863    let mp = (5 * doy + 2) / 153;
1864    let d = doy - (153 * mp + 2) / 5 + 1;
1865    let m = mp + if mp < 10 { 3 } else { -9 };
1866    let year = y + if m <= 2 { 1 } else { 0 };
1867    (year as i32, m as u8, d as u8)
1868}
1869
1870fn decimal_from_i64(value: i64) -> Result<Decimal, XPathError> {
1871    Decimal::from_i64(value)
1872        .ok_or_else(|| XPathError::internal("Failed to convert integer to decimal"))
1873}
1874
1875fn decimal_to_i64(value: Decimal, label: &str) -> Result<i64, XPathError> {
1876    value
1877        .to_i64()
1878        .ok_or_else(|| XPathError::internal(format!("Failed to convert {} to integer", label)))
1879}
1880
1881fn decimal_to_u32(value: Decimal, label: &str) -> Result<u32, XPathError> {
1882    let val = decimal_to_i64(value, label)?;
1883    u32::try_from(val).map_err(|_| XPathError::internal(format!("{} out of range", label)))
1884}
1885
1886fn decimal_to_u8(value: Decimal, label: &str) -> Result<u8, XPathError> {
1887    let val = decimal_to_i64(value, label)?;
1888    u8::try_from(val).map_err(|_| XPathError::internal(format!("{} out of range", label)))
1889}
1890
1891fn is_string_like(code: XmlTypeCode) -> bool {
1892    code.is_string_derived() || matches!(code, XmlTypeCode::AnyUri | XmlTypeCode::UntypedAtomic)
1893}
1894
1895// ============================================================================
1896// General Comparison Support (for sequence comparisons)
1897// ============================================================================
1898
1899/// Perform magnitude relationship promotion for general comparisons.
1900///
1901/// When comparing values in a general comparison, UntypedAtomic values are
1902/// promoted to match the type of the other operand according to XPath 2.0 rules:
1903///
1904/// - If the other operand is numeric, UntypedAtomic is promoted to xs:double
1905/// - If the other operand is string, UntypedAtomic is kept as string
1906/// - If the other operand is a typed value, UntypedAtomic is cast to that type
1907///
1908/// # Arguments
1909///
1910/// * `left` - The left operand
1911/// * `right` - The right operand
1912///
1913/// # Returns
1914///
1915/// A tuple of (promoted_left, promoted_right) suitable for comparison
1916pub fn magnitude_relationship(
1917    left: &XmlValue,
1918    right: &XmlValue,
1919) -> Result<(XmlValue, XmlValue), XPathError> {
1920    let mut left_result = left.clone();
1921    let mut right_result = right.clone();
1922
1923    // Promote left if it's UntypedAtomic
1924    if left.type_code == XmlTypeCode::UntypedAtomic {
1925        if right.type_code.is_numeric() {
1926            // Promote to double
1927            let s = left.to_string_value();
1928            let d: f64 = s
1929                .trim()
1930                .parse()
1931                .map_err(|_| XPathError::invalid_cast_value(&s, "xs:double"))?;
1932            left_result = XmlValue::double(d);
1933        } else if is_string_like(right.type_code) {
1934            // Keep as string
1935            left_result = XmlValue::string(left.to_string_value());
1936        }
1937        // For other types, we'd need full cast support - for now, keep as string
1938    }
1939
1940    // Promote right if it's UntypedAtomic
1941    if right.type_code == XmlTypeCode::UntypedAtomic {
1942        if left_result.type_code.is_numeric() {
1943            // Promote to double
1944            let s = right.to_string_value();
1945            let d: f64 = s
1946                .trim()
1947                .parse()
1948                .map_err(|_| XPathError::invalid_cast_value(&s, "xs:double"))?;
1949            right_result = XmlValue::double(d);
1950        } else if is_string_like(left_result.type_code) {
1951            // Keep as string
1952            right_result = XmlValue::string(right.to_string_value());
1953        }
1954        // For other types, we'd need full cast support - for now, keep as string
1955    }
1956
1957    Ok((left_result, right_result))
1958}
1959
1960// ============================================================================
1961// Schema-aware primitive type resolution for magnitude relationship
1962// ============================================================================
1963
1964/// Get the primitive base type for a schema-defined simple type.
1965///
1966/// Per XPath 2.0 spec, when casting untypedAtomic in general comparisons,
1967/// we cast to the **primitive base type** of the other operand's dynamic type.
1968/// This walks the base type chain to find the built-in primitive type.
1969///
1970/// Returns the XmlTypeCode of the primitive base type, or the input type_code
1971/// if no schema information is available.
1972fn get_primitive_base_type(
1973    schema_set: Option<&SchemaSet>,
1974    schema_type: Option<SimpleTypeKey>,
1975    type_code: XmlTypeCode,
1976) -> XmlTypeCode {
1977    // If we have schema info, resolve the primitive base type
1978    if let (Some(schema_set), Some(type_key)) = (schema_set, schema_type) {
1979        let builtin_types = schema_set.builtin_types();
1980
1981        // Check if the type itself is a built-in
1982        if let Some(code) = builtin_types.get_type_code(type_key) {
1983            return get_xsd_primitive_type(code);
1984        }
1985
1986        // Walk base type chain to find a built-in type
1987        if let Some(type_def) = schema_set.arenas.simple_types.get(type_key) {
1988            let mut current_def = type_def;
1989            loop {
1990                if let Some(TypeKey::Simple(base_key)) = current_def.resolved_base_type {
1991                    if let Some(code) = builtin_types.get_type_code(base_key) {
1992                        return get_xsd_primitive_type(code);
1993                    }
1994                    if let Some(base_def) = schema_set.arenas.simple_types.get(base_key) {
1995                        current_def = base_def;
1996                        continue;
1997                    }
1998                }
1999                break;
2000            }
2001        }
2002    }
2003
2004    // Fallback: use the type_code's primitive base type
2005    get_xsd_primitive_type(type_code)
2006}
2007
2008/// Map an XmlTypeCode to its XSD primitive base type.
2009///
2010/// Per XPath 2.0 spec, these are the 19 primitive types plus special handling
2011/// for dayTimeDuration and yearMonthDuration.
2012fn get_xsd_primitive_type(code: XmlTypeCode) -> XmlTypeCode {
2013    match code {
2014        // Already primitive types - return as-is
2015        XmlTypeCode::String
2016        | XmlTypeCode::Boolean
2017        | XmlTypeCode::Decimal
2018        | XmlTypeCode::Float
2019        | XmlTypeCode::Double
2020        | XmlTypeCode::Duration
2021        | XmlTypeCode::DateTime
2022        | XmlTypeCode::Time
2023        | XmlTypeCode::Date
2024        | XmlTypeCode::GYearMonth
2025        | XmlTypeCode::GYear
2026        | XmlTypeCode::GMonthDay
2027        | XmlTypeCode::GDay
2028        | XmlTypeCode::GMonth
2029        | XmlTypeCode::HexBinary
2030        | XmlTypeCode::Base64Binary
2031        | XmlTypeCode::AnyUri
2032        | XmlTypeCode::QName
2033        | XmlTypeCode::Notation => code,
2034
2035        // XPath 2.0 special cases: dayTimeDuration and yearMonthDuration
2036        // These are their own "primitive" types for comparison purposes
2037        XmlTypeCode::DayTimeDuration => XmlTypeCode::DayTimeDuration,
2038        XmlTypeCode::YearMonthDuration => XmlTypeCode::YearMonthDuration,
2039
2040        // String-derived types → xs:string
2041        XmlTypeCode::NormalizedString
2042        | XmlTypeCode::Token
2043        | XmlTypeCode::Language
2044        | XmlTypeCode::NmToken
2045        | XmlTypeCode::Name
2046        | XmlTypeCode::NCName
2047        | XmlTypeCode::Id
2048        | XmlTypeCode::IdRef
2049        | XmlTypeCode::Entity => XmlTypeCode::String,
2050
2051        // Integer hierarchy → xs:decimal (the primitive for all integers)
2052        XmlTypeCode::Integer
2053        | XmlTypeCode::NonPositiveInteger
2054        | XmlTypeCode::NegativeInteger
2055        | XmlTypeCode::Long
2056        | XmlTypeCode::Int
2057        | XmlTypeCode::Short
2058        | XmlTypeCode::Byte
2059        | XmlTypeCode::NonNegativeInteger
2060        | XmlTypeCode::UnsignedLong
2061        | XmlTypeCode::UnsignedInt
2062        | XmlTypeCode::UnsignedShort
2063        | XmlTypeCode::UnsignedByte
2064        | XmlTypeCode::PositiveInteger => XmlTypeCode::Decimal,
2065
2066        // dateTimeStamp → xs:dateTime
2067        XmlTypeCode::DateTimeStamp => XmlTypeCode::DateTime,
2068
2069        // List types - shouldn't reach here after atomization, but fallback to string
2070        XmlTypeCode::NmTokens | XmlTypeCode::IdRefs | XmlTypeCode::Entities => XmlTypeCode::String,
2071
2072        // Other/unknown types → xs:string as fallback
2073        _ => XmlTypeCode::String,
2074    }
2075}
2076
2077/// Cast an UntypedAtomic value to a primitive type.
2078///
2079/// Uses `cast_to` for types it supports, falls back to `VALIDATOR_REGISTRY`
2080/// for other types (date/time/duration/etc). Does NOT apply facets.
2081fn cast_to_primitive(value: &XmlValue, target_type: XmlTypeCode) -> Result<XmlValue, XPathError> {
2082    // First try cast_to which handles common types
2083    match cast_to(value, target_type) {
2084        Ok(result) => Ok(result),
2085        Err(_) => {
2086            // Fallback to VALIDATOR_REGISTRY for date/time/duration types
2087            let string_val = value.to_string_value();
2088            VALIDATOR_REGISTRY
2089                .validate(target_type, &string_val)
2090                .map_err(|e| XPathError::FORG0001 {
2091                    value: string_val,
2092                    target_type: format!("{:?}: {}", target_type, e),
2093                })
2094        }
2095    }
2096}
2097
2098/// Cast an UntypedAtomic value to a primitive type with namespace context.
2099///
2100/// This handles QName and NOTATION specially by resolving namespace prefixes
2101/// using the in-scope namespace bindings from the XPath context.
2102fn cast_to_primitive_ctx(
2103    context: &XPathContext,
2104    value: &XmlValue,
2105    target_type: XmlTypeCode,
2106) -> Result<XmlValue, XPathError> {
2107    match target_type {
2108        XmlTypeCode::QName | XmlTypeCode::Notation => {
2109            // QName/NOTATION need namespace resolution
2110            cast_to_qname_with_context(context, value, target_type)
2111        }
2112        _ => cast_to_primitive(value, target_type),
2113    }
2114}
2115
2116/// Cast an UntypedAtomic value to QName or NOTATION with namespace resolution.
2117///
2118/// Parses the lexical QName and resolves the prefix using the context's namespace bindings.
2119pub(crate) fn cast_to_qname_with_context(
2120    context: &XPathContext,
2121    value: &XmlValue,
2122    type_code: XmlTypeCode,
2123) -> Result<XmlValue, XPathError> {
2124    use crate::xpath::functions::qname::parse_lexical_qname;
2125
2126    let lexical = value.to_string_value();
2127
2128    // Parse prefix:local or local (reuse existing validated parser)
2129    let (prefix, local_name) = parse_lexical_qname(&lexical)?;
2130
2131    // Resolve namespace using context.namespaces
2132    let namespace_uri = if let Some(ref pfx) = prefix {
2133        let prefix_id = context
2134            .names
2135            .get(pfx)
2136            .ok_or_else(|| XPathError::undefined_prefix(pfx))?;
2137        let ns_id = context
2138            .namespaces
2139            .resolve_prefix(prefix_id)
2140            .ok_or_else(|| XPathError::undefined_prefix(pfx))?;
2141        Some(ns_id)
2142    } else {
2143        // Unprefixed QName in casting context - no namespace per XPath spec
2144        None
2145    };
2146
2147    // Build QualifiedName
2148    let local_id = context.names.add(&local_name);
2149    let prefix_id = prefix.as_ref().map(|p| context.names.add(p));
2150    let qn = QualifiedName::new(namespace_uri, local_id, prefix_id);
2151
2152    Ok(XmlValue::new(
2153        type_code,
2154        XmlValueKind::Atomic(XmlAtomicValue::QName(qn)),
2155    ))
2156}
2157
2158/// Perform magnitude relationship promotion with context-aware casting.
2159///
2160/// Per XPath 2.0 spec (general comparison rules):
2161/// - If both operands are numeric → both cast to xs:double
2162/// - If both operands are xs:untypedAtomic → both cast to xs:string
2163/// - If exactly one operand is xs:untypedAtomic:
2164///   - If other is numeric → cast untyped to xs:double
2165///   - If other is xs:dayTimeDuration → cast untyped to xs:dayTimeDuration
2166///   - If other is xs:yearMonthDuration → cast untyped to xs:yearMonthDuration
2167///   - Otherwise → cast untyped to the **primitive base type** of the other operand
2168///
2169/// Note: This does NOT apply facet validation - facets are for schema validation,
2170/// not XPath general comparisons.
2171pub fn magnitude_relationship_ctx(
2172    context: &XPathContext,
2173    left: &XmlValue,
2174    right: &XmlValue,
2175) -> Result<(XmlValue, XmlValue), XPathError> {
2176    let mut left_result = left.clone();
2177    let mut right_result = right.clone();
2178
2179    if left_result.type_code == XmlTypeCode::UntypedAtomic {
2180        if right.type_code.is_numeric() {
2181            // Numeric → cast to xs:double
2182            let s = left_result.to_string_value();
2183            let d: f64 = s
2184                .trim()
2185                .parse()
2186                .map_err(|_| XPathError::invalid_cast_value(&s, "xs:double"))?;
2187            left_result = XmlValue::double(d);
2188        } else if is_string_like(right.type_code) {
2189            // String-like → cast to xs:string
2190            left_result = XmlValue::string(left_result.to_string_value());
2191        } else if right.type_code != XmlTypeCode::UntypedAtomic {
2192            // Other typed value → cast to primitive base type
2193            let primitive_type =
2194                get_primitive_base_type(context.schema_set, right.schema_type, right.type_code);
2195            left_result = cast_to_primitive_ctx(context, &left_result, primitive_type)?;
2196        }
2197    }
2198
2199    if right_result.type_code == XmlTypeCode::UntypedAtomic {
2200        if left_result.type_code.is_numeric() {
2201            // Numeric → cast to xs:double
2202            let s = right_result.to_string_value();
2203            let d: f64 = s
2204                .trim()
2205                .parse()
2206                .map_err(|_| XPathError::invalid_cast_value(&s, "xs:double"))?;
2207            right_result = XmlValue::double(d);
2208        } else if is_string_like(left_result.type_code) {
2209            // String-like → cast to xs:string
2210            right_result = XmlValue::string(right_result.to_string_value());
2211        } else if left_result.type_code != XmlTypeCode::UntypedAtomic {
2212            // Other typed value → cast to primitive base type
2213            let primitive_type = get_primitive_base_type(
2214                context.schema_set,
2215                left_result.schema_type,
2216                left_result.type_code,
2217            );
2218            right_result = cast_to_primitive_ctx(context, &right_result, primitive_type)?;
2219        }
2220    }
2221
2222    Ok((left_result, right_result))
2223}
2224
2225fn atomize_item<N: DomNavigator>(item: XmlItemRef<'_, N>) -> Result<Option<XmlValue>, XPathError> {
2226    match item {
2227        XmlItemRef::Atomic(value) => Ok(Some(value.clone())),
2228        XmlItemRef::Node(node) => crate::xpath::atomize::atomize_node(node),
2229    }
2230}
2231
2232/// Compare two values for equality (value comparison).
2233///
2234/// This is the core equality comparison used by both value and general comparisons.
2235/// For general comparisons, use `magnitude_relationship` first to promote UntypedAtomic values.
2236pub fn value_eq(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2237    compare_eq(left, right)
2238}
2239
2240/// Compare two values for greater-than (value comparison).
2241pub fn value_gt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2242    compare_gt(left, right)
2243}
2244
2245/// Compare two values for greater-than-or-equal (value comparison).
2246pub fn value_ge(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2247    compare_ge(left, right)
2248}
2249
2250/// Compare two values for less-than (value comparison).
2251pub fn value_lt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2252    compare_lt(left, right)
2253}
2254
2255/// Compare two values for less-than-or-equal (value comparison).
2256pub fn value_le(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2257    compare_le(left, right)
2258}
2259
2260// ============================================================================
2261// General comparisons for single atomized values
2262// ============================================================================
2263
2264/// General equality comparison with magnitude relationship promotion (single values).
2265///
2266/// Promotes UntypedAtomic values before comparison.
2267/// For sequence comparisons, use `general_eq_seq`.
2268pub fn general_eq(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2269    let (l, r) = magnitude_relationship(left, right)?;
2270    compare_eq(&l, &r)
2271}
2272
2273/// General greater-than comparison with magnitude relationship promotion (single values).
2274pub fn general_gt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2275    let (l, r) = magnitude_relationship(left, right)?;
2276    compare_gt(&l, &r)
2277}
2278
2279/// General not-equal comparison with magnitude relationship promotion (single values).
2280pub fn general_ne(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2281    general_eq(left, right).map(|eq| !eq)
2282}
2283
2284/// General greater-than-or-equal comparison with magnitude relationship promotion (single values).
2285pub fn general_ge(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2286    let (l, r) = magnitude_relationship(left, right)?;
2287    compare_ge(&l, &r)
2288}
2289
2290/// General less-than comparison with magnitude relationship promotion (single values).
2291pub fn general_lt(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2292    let (l, r) = magnitude_relationship(left, right)?;
2293    compare_lt(&l, &r)
2294}
2295
2296/// General less-than-or-equal comparison with magnitude relationship promotion (single values).
2297pub fn general_le(left: &XmlValue, right: &XmlValue) -> Result<bool, XPathError> {
2298    let (l, r) = magnitude_relationship(left, right)?;
2299    compare_le(&l, &r)
2300}
2301
2302// ============================================================================
2303// Iterator-based general comparisons (Cartesian product semantics)
2304// ============================================================================
2305
2306pub fn general_eq_iter<I1, I2>(
2307    context: &XPathContext,
2308    left: &I1,
2309    right: &I2,
2310) -> Result<bool, XPathError>
2311where
2312    I1: XmlNodeIterator,
2313    I2: XmlNodeIterator,
2314{
2315    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2316    let mut left_iter = left.clone();
2317
2318    while left_iter.move_next()? {
2319        let left_item = left_iter
2320            .current()
2321            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2322        let left_value = match atomize_item(left_item)? {
2323            Some(v) => v,
2324            None => continue, // nilled → skip
2325        };
2326        let mut right_iter = right_buf.clone();
2327
2328        while right_iter.move_next()? {
2329            let right_item = right_iter
2330                .current()
2331                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2332            let right_value = match atomize_item(right_item)? {
2333                Some(v) => v,
2334                None => continue, // nilled → skip
2335            };
2336            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2337
2338            match value_eq(&l, &r) {
2339                Ok(true) => return Ok(true),
2340                Ok(false) => continue,
2341                Err(err) if is_operator_not_defined(&err) => continue,
2342                Err(err) => return Err(err),
2343            }
2344        }
2345    }
2346
2347    Ok(false)
2348}
2349
2350pub fn general_ne_iter<I1, I2>(
2351    context: &XPathContext,
2352    left: &I1,
2353    right: &I2,
2354) -> Result<bool, XPathError>
2355where
2356    I1: XmlNodeIterator,
2357    I2: XmlNodeIterator,
2358{
2359    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2360    let mut left_iter = left.clone();
2361
2362    while left_iter.move_next()? {
2363        let left_item = left_iter
2364            .current()
2365            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2366        let left_value = match atomize_item(left_item)? {
2367            Some(v) => v,
2368            None => continue, // nilled → skip
2369        };
2370        let mut right_iter = right_buf.clone();
2371
2372        while right_iter.move_next()? {
2373            let right_item = right_iter
2374                .current()
2375                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2376            let right_value = match atomize_item(right_item)? {
2377                Some(v) => v,
2378                None => continue, // nilled → skip
2379            };
2380            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2381
2382            match value_eq(&l, &r) {
2383                Ok(true) => continue,
2384                Ok(false) => return Ok(true),
2385                Err(err) if is_operator_not_defined(&err) => return Ok(true),
2386                Err(err) => return Err(err),
2387            }
2388        }
2389    }
2390
2391    Ok(false)
2392}
2393
2394pub fn general_lt_iter<I1, I2>(
2395    context: &XPathContext,
2396    left: &I1,
2397    right: &I2,
2398) -> Result<bool, XPathError>
2399where
2400    I1: XmlNodeIterator,
2401    I2: XmlNodeIterator,
2402{
2403    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2404    let mut left_iter = left.clone();
2405
2406    while left_iter.move_next()? {
2407        let left_item = left_iter
2408            .current()
2409            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2410        let left_value = match atomize_item(left_item)? {
2411            Some(v) => v,
2412            None => continue, // nilled → skip
2413        };
2414        let mut right_iter = right_buf.clone();
2415
2416        while right_iter.move_next()? {
2417            let right_item = right_iter
2418                .current()
2419                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2420            let right_value = match atomize_item(right_item)? {
2421                Some(v) => v,
2422                None => continue, // nilled → skip
2423            };
2424            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2425
2426            match value_lt(&l, &r) {
2427                Ok(true) => return Ok(true),
2428                Ok(false) => continue,
2429                Err(err) => return Err(err),
2430            }
2431        }
2432    }
2433
2434    Ok(false)
2435}
2436
2437pub fn general_le_iter<I1, I2>(
2438    context: &XPathContext,
2439    left: &I1,
2440    right: &I2,
2441) -> Result<bool, XPathError>
2442where
2443    I1: XmlNodeIterator,
2444    I2: XmlNodeIterator,
2445{
2446    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2447    let mut left_iter = left.clone();
2448
2449    while left_iter.move_next()? {
2450        let left_item = left_iter
2451            .current()
2452            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2453        let left_value = match atomize_item(left_item)? {
2454            Some(v) => v,
2455            None => continue, // nilled → skip
2456        };
2457        let mut right_iter = right_buf.clone();
2458
2459        while right_iter.move_next()? {
2460            let right_item = right_iter
2461                .current()
2462                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2463            let right_value = match atomize_item(right_item)? {
2464                Some(v) => v,
2465                None => continue, // nilled → skip
2466            };
2467            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2468
2469            match value_eq(&l, &r) {
2470                Ok(true) => return Ok(true),
2471                Ok(false) => {}
2472                Err(err) if is_operator_not_defined(&err) => {}
2473                Err(err) => return Err(err),
2474            }
2475
2476            match value_lt(&l, &r) {
2477                Ok(true) => return Ok(true),
2478                Ok(false) => continue,
2479                Err(err) => return Err(err),
2480            }
2481        }
2482    }
2483
2484    Ok(false)
2485}
2486
2487pub fn general_gt_iter<I1, I2>(
2488    context: &XPathContext,
2489    left: &I1,
2490    right: &I2,
2491) -> Result<bool, XPathError>
2492where
2493    I1: XmlNodeIterator,
2494    I2: XmlNodeIterator,
2495{
2496    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2497    let mut left_iter = left.clone();
2498
2499    while left_iter.move_next()? {
2500        let left_item = left_iter
2501            .current()
2502            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2503        let left_value = match atomize_item(left_item)? {
2504            Some(v) => v,
2505            None => continue, // nilled → skip
2506        };
2507        let mut right_iter = right_buf.clone();
2508
2509        while right_iter.move_next()? {
2510            let right_item = right_iter
2511                .current()
2512                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2513            let right_value = match atomize_item(right_item)? {
2514                Some(v) => v,
2515                None => continue, // nilled → skip
2516            };
2517            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2518
2519            match value_gt(&l, &r) {
2520                Ok(true) => return Ok(true),
2521                Ok(false) => continue,
2522                Err(err) => return Err(err),
2523            }
2524        }
2525    }
2526
2527    Ok(false)
2528}
2529
2530pub fn general_ge_iter<I1, I2>(
2531    context: &XPathContext,
2532    left: &I1,
2533    right: &I2,
2534) -> Result<bool, XPathError>
2535where
2536    I1: XmlNodeIterator,
2537    I2: XmlNodeIterator,
2538{
2539    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2540    let mut left_iter = left.clone();
2541
2542    while left_iter.move_next()? {
2543        let left_item = left_iter
2544            .current()
2545            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2546        let left_value = match atomize_item(left_item)? {
2547            Some(v) => v,
2548            None => continue, // nilled → skip
2549        };
2550        let mut right_iter = right_buf.clone();
2551
2552        while right_iter.move_next()? {
2553            let right_item = right_iter
2554                .current()
2555                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2556            let right_value = match atomize_item(right_item)? {
2557                Some(v) => v,
2558                None => continue, // nilled → skip
2559            };
2560            let (l, r) = magnitude_relationship_ctx(context, &left_value, &right_value)?;
2561
2562            match value_eq(&l, &r) {
2563                Ok(true) => return Ok(true),
2564                Ok(false) => {}
2565                Err(err) if is_operator_not_defined(&err) => {}
2566                Err(err) => return Err(err),
2567            }
2568
2569            match value_gt(&l, &r) {
2570                Ok(true) => return Ok(true),
2571                Ok(false) => continue,
2572                Err(err) => return Err(err),
2573            }
2574        }
2575    }
2576
2577    Ok(false)
2578}
2579
2580// ============================================================================
2581// XPath 1.0 comparison helpers
2582// ============================================================================
2583
2584/// XPath 1.0 type coercion for comparisons (spec §3.4).
2585///
2586/// For `=`/`!=`:
2587///   1. If either operand is boolean → convert both to boolean
2588///   2. If either operand is numeric → convert both to number
2589///   3. Otherwise → compare as strings
2590///
2591/// For `<`/`<=`/`>`/`>=`:
2592///   Always convert both to number
2593fn coerce_for_comparison_10(
2594    op: BinaryOpKind,
2595    left: &XmlValue,
2596    right: &XmlValue,
2597) -> (XmlValue, XmlValue) {
2598    let is_equality = matches!(op, BinaryOpKind::GeneralEq | BinaryOpKind::GeneralNe);
2599
2600    if is_equality {
2601        // Rule 1: if either is boolean, convert both to boolean
2602        if left.type_code == XmlTypeCode::Boolean || right.type_code == XmlTypeCode::Boolean {
2603            let l_bool = ebv_atomic(left);
2604            let r_bool = ebv_atomic(right);
2605            return (XmlValue::boolean(l_bool), XmlValue::boolean(r_bool));
2606        }
2607
2608        // Rule 2: if either is numeric, convert both to number
2609        if left.type_code.is_numeric() || right.type_code.is_numeric() {
2610            let l_num = crate::xpath::atomize::to_number(left);
2611            let r_num = crate::xpath::atomize::to_number(right);
2612            return (XmlValue::double(l_num), XmlValue::double(r_num));
2613        }
2614
2615        // Rule 3: otherwise string comparison
2616        let l_str = left.to_string_value();
2617        let r_str = right.to_string_value();
2618        return (XmlValue::string(l_str), XmlValue::string(r_str));
2619    }
2620
2621    // Relational operators: always numeric
2622    let l_num = crate::xpath::atomize::to_number(left);
2623    let r_num = crate::xpath::atomize::to_number(right);
2624    (XmlValue::double(l_num), XmlValue::double(r_num))
2625}
2626
2627/// Effective boolean value of a single atomic value (for XPath 1.0 coercion).
2628fn ebv_atomic(value: &XmlValue) -> bool {
2629    if let Some(b) = value.as_boolean() {
2630        return b;
2631    }
2632    if let Some(s) = value.as_string() {
2633        return !s.is_empty();
2634    }
2635    if let Some(d) = value.as_double() {
2636        return !d.is_nan() && d != 0.0;
2637    }
2638    if value.type_code.is_numeric() {
2639        let d = crate::xpath::atomize::to_number(value);
2640        return !d.is_nan() && d != 0.0;
2641    }
2642    let s = value.to_string_value();
2643    !s.is_empty()
2644}
2645
2646/// XPath 1.0 general equality comparison (iterator-based, Cartesian product).
2647pub fn general_eq_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2648where
2649    I1: XmlNodeIterator,
2650    I2: XmlNodeIterator,
2651{
2652    general_compare_iter_10(BinaryOpKind::GeneralEq, left, right)
2653}
2654
2655/// XPath 1.0 general not-equal comparison (iterator-based, Cartesian product).
2656pub fn general_ne_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2657where
2658    I1: XmlNodeIterator,
2659    I2: XmlNodeIterator,
2660{
2661    general_compare_iter_10(BinaryOpKind::GeneralNe, left, right)
2662}
2663
2664/// XPath 1.0 general less-than comparison (iterator-based, Cartesian product).
2665pub fn general_lt_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2666where
2667    I1: XmlNodeIterator,
2668    I2: XmlNodeIterator,
2669{
2670    general_compare_iter_10(BinaryOpKind::GeneralLt, left, right)
2671}
2672
2673/// XPath 1.0 general less-than-or-equal comparison (iterator-based, Cartesian product).
2674pub fn general_le_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2675where
2676    I1: XmlNodeIterator,
2677    I2: XmlNodeIterator,
2678{
2679    general_compare_iter_10(BinaryOpKind::GeneralLe, left, right)
2680}
2681
2682/// XPath 1.0 general greater-than comparison (iterator-based, Cartesian product).
2683pub fn general_gt_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2684where
2685    I1: XmlNodeIterator,
2686    I2: XmlNodeIterator,
2687{
2688    general_compare_iter_10(BinaryOpKind::GeneralGt, left, right)
2689}
2690
2691/// XPath 1.0 general greater-than-or-equal comparison (iterator-based, Cartesian product).
2692pub fn general_ge_iter_10<I1, I2>(left: &I1, right: &I2) -> Result<bool, XPathError>
2693where
2694    I1: XmlNodeIterator,
2695    I2: XmlNodeIterator,
2696{
2697    general_compare_iter_10(BinaryOpKind::GeneralGe, left, right)
2698}
2699
2700/// Shared implementation for all XPath 1.0 general comparison iterators.
2701fn general_compare_iter_10<I1, I2>(
2702    op: BinaryOpKind,
2703    left: &I1,
2704    right: &I2,
2705) -> Result<bool, XPathError>
2706where
2707    I1: XmlNodeIterator,
2708    I2: XmlNodeIterator,
2709{
2710    let right_buf = BufferedNodeIterator::preload(right.clone())?;
2711    let mut left_iter = left.clone();
2712
2713    while left_iter.move_next()? {
2714        let left_item = left_iter
2715            .current()
2716            .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2717        let left_value = match atomize_item(left_item)? {
2718            Some(v) => v,
2719            None => continue, // nilled → skip
2720        };
2721        let mut right_iter = right_buf.clone();
2722
2723        while right_iter.move_next()? {
2724            let right_item = right_iter
2725                .current()
2726                .ok_or_else(|| XPathError::internal("Iterator current missing"))?;
2727            let right_value = match atomize_item(right_item)? {
2728                Some(v) => v,
2729                None => continue, // nilled → skip
2730            };
2731            let (l, r) = coerce_for_comparison_10(op, &left_value, &right_value);
2732
2733            let satisfied = match op {
2734                BinaryOpKind::GeneralEq => compare_eq(&l, &r).unwrap_or(false),
2735                BinaryOpKind::GeneralNe => !compare_eq(&l, &r).unwrap_or(true),
2736                BinaryOpKind::GeneralLt => compare_lt(&l, &r).unwrap_or(false),
2737                BinaryOpKind::GeneralLe => compare_le(&l, &r).unwrap_or(false),
2738                BinaryOpKind::GeneralGt => compare_gt(&l, &r).unwrap_or(false),
2739                BinaryOpKind::GeneralGe => compare_ge(&l, &r).unwrap_or(false),
2740                _ => unreachable!(),
2741            };
2742
2743            if satisfied {
2744                return Ok(true);
2745            }
2746        }
2747    }
2748
2749    Ok(false)
2750}
2751
2752/// Evaluate a numeric binary operation in XPath 1.0 mode.
2753///
2754/// In XPath 1.0, arithmetic always returns double (never integer or decimal).
2755pub fn eval_numeric_binary_10(
2756    op: BinaryOpKind,
2757    left: &XmlValue,
2758    right: &XmlValue,
2759) -> Result<XmlValue, XPathError> {
2760    let l = crate::xpath::atomize::to_number(left);
2761    let r = crate::xpath::atomize::to_number(right);
2762
2763    let result = match op {
2764        BinaryOpKind::Add => l + r,
2765        BinaryOpKind::Sub => l - r,
2766        BinaryOpKind::Mul => l * r,
2767        BinaryOpKind::Div => {
2768            if r == 0.0 {
2769                if l == 0.0 || l.is_nan() {
2770                    f64::NAN
2771                } else if l > 0.0 {
2772                    f64::INFINITY
2773                } else {
2774                    f64::NEG_INFINITY
2775                }
2776            } else {
2777                l / r
2778            }
2779        }
2780        BinaryOpKind::Mod => {
2781            if r == 0.0 {
2782                f64::NAN
2783            } else {
2784                l % r
2785            }
2786        }
2787        _ => return Err(XPathError::internal("Unsupported arithmetic operator")),
2788    };
2789
2790    Ok(XmlValue::double(result))
2791}
2792
2793// ============================================================================
2794// General comparisons for sequences (Cartesian product semantics)
2795// ============================================================================
2796
2797/// General equality comparison for sequences (Cartesian product).
2798///
2799/// Returns true if ANY pair (left_item, right_item) from the Cartesian product
2800/// of the two sequences satisfies the equality condition.
2801///
2802/// # XPath 2.0 Semantics
2803///
2804/// The general comparison operators (`=`, `!=`, `<`, `<=`, `>`, `>=`) are
2805/// existentially quantified over their operand sequences:
2806/// - `A = B` is true if there exist atomized values `a` in `A` and `b` in `B`
2807///   such that `a eq b` is true (after type promotion).
2808///
2809/// # Arguments
2810///
2811/// * `left` - Left sequence of atomized values
2812/// * `right` - Right sequence of atomized values
2813///
2814/// # Returns
2815///
2816/// `true` if any pair satisfies equality, `false` if no pairs satisfy or
2817/// either sequence is empty.
2818pub fn general_eq_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2819    // Empty sequences: result is false (no pairs exist)
2820    if left.is_empty() || right.is_empty() {
2821        return Ok(false);
2822    }
2823
2824    // Cartesian product: check if ANY pair satisfies the condition
2825    for l in left {
2826        for r in right {
2827            match general_eq(l, r) {
2828                Ok(true) => return Ok(true),
2829                Ok(false) => continue,
2830                Err(_) => continue, // Type errors mean this pair doesn't match
2831            }
2832        }
2833    }
2834
2835    Ok(false)
2836}
2837
2838/// General not-equal comparison for sequences (Cartesian product).
2839///
2840/// Returns true if ANY pair (left_item, right_item) satisfies inequality.
2841pub fn general_ne_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2842    if left.is_empty() || right.is_empty() {
2843        return Ok(false);
2844    }
2845
2846    for l in left {
2847        for r in right {
2848            match general_ne(l, r) {
2849                Ok(true) => return Ok(true),
2850                Ok(false) => continue,
2851                Err(_) => continue,
2852            }
2853        }
2854    }
2855
2856    Ok(false)
2857}
2858
2859/// General less-than comparison for sequences (Cartesian product).
2860///
2861/// Returns true if ANY pair (left_item, right_item) satisfies left < right.
2862pub fn general_lt_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2863    if left.is_empty() || right.is_empty() {
2864        return Ok(false);
2865    }
2866
2867    for l in left {
2868        for r in right {
2869            match general_lt(l, r) {
2870                Ok(true) => return Ok(true),
2871                Ok(false) => continue,
2872                Err(_) => continue,
2873            }
2874        }
2875    }
2876
2877    Ok(false)
2878}
2879
2880/// General less-than-or-equal comparison for sequences (Cartesian product).
2881///
2882/// Returns true if ANY pair (left_item, right_item) satisfies left <= right.
2883pub fn general_le_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2884    if left.is_empty() || right.is_empty() {
2885        return Ok(false);
2886    }
2887
2888    for l in left {
2889        for r in right {
2890            match general_le(l, r) {
2891                Ok(true) => return Ok(true),
2892                Ok(false) => continue,
2893                Err(_) => continue,
2894            }
2895        }
2896    }
2897
2898    Ok(false)
2899}
2900
2901/// General greater-than comparison for sequences (Cartesian product).
2902///
2903/// Returns true if ANY pair (left_item, right_item) satisfies left > right.
2904pub fn general_gt_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2905    if left.is_empty() || right.is_empty() {
2906        return Ok(false);
2907    }
2908
2909    for l in left {
2910        for r in right {
2911            match general_gt(l, r) {
2912                Ok(true) => return Ok(true),
2913                Ok(false) => continue,
2914                Err(_) => continue,
2915            }
2916        }
2917    }
2918
2919    Ok(false)
2920}
2921
2922/// General greater-than-or-equal comparison for sequences (Cartesian product).
2923///
2924/// Returns true if ANY pair (left_item, right_item) satisfies left >= right.
2925pub fn general_ge_seq(left: &[XmlValue], right: &[XmlValue]) -> Result<bool, XPathError> {
2926    if left.is_empty() || right.is_empty() {
2927        return Ok(false);
2928    }
2929
2930    for l in left {
2931        for r in right {
2932            match general_ge(l, r) {
2933                Ok(true) => return Ok(true),
2934                Ok(false) => continue,
2935                Err(_) => continue,
2936            }
2937        }
2938    }
2939
2940    Ok(false)
2941}
2942
2943fn unsupported_operator(op: BinaryOpKind, left: &XmlValue, right: &XmlValue) -> XPathError {
2944    XPathError::internal(format!(
2945        "Operator {:?} not defined for types {:?} and {:?}",
2946        op, left.type_code, right.type_code
2947    ))
2948}
2949
2950#[cfg(test)]
2951mod tests {
2952    use super::*;
2953    use crate::namespace::qname::QualifiedName;
2954    use crate::namespace::NameTable;
2955    use crate::navigator::RoXmlNavigator;
2956    use crate::xpath::context::XPathContext;
2957    use crate::xpath::iterator::{VecNodeIterator, XmlItem};
2958
2959    fn int_value(type_code: XmlTypeCode, value: i64) -> XmlValue {
2960        XmlValue {
2961            type_code,
2962            schema_type: None,
2963            value: XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(value))),
2964        }
2965    }
2966
2967    fn decimal_value(value: &str) -> XmlValue {
2968        XmlValue {
2969            type_code: XmlTypeCode::Decimal,
2970            schema_type: None,
2971            value: XmlValueKind::Atomic(XmlAtomicValue::Decimal(value.parse::<Decimal>().unwrap())),
2972        }
2973    }
2974
2975    fn datetime_value(
2976        type_code: XmlTypeCode,
2977        year: i32,
2978        month: u8,
2979        day: u8,
2980        hour: u8,
2981        minute: u8,
2982        second: Decimal,
2983    ) -> XmlValue {
2984        XmlValue {
2985            type_code,
2986            schema_type: None,
2987            value: XmlValueKind::Atomic(XmlAtomicValue::DateTime(DateTimeValue {
2988                year,
2989                month,
2990                day,
2991                hour,
2992                minute,
2993                second,
2994                timezone: None,
2995            })),
2996        }
2997    }
2998
2999    fn date_value(year: i32, month: u8, day: u8) -> XmlValue {
3000        XmlValue {
3001            type_code: XmlTypeCode::Date,
3002            schema_type: None,
3003            value: XmlValueKind::Atomic(XmlAtomicValue::Date(DateValue {
3004                year,
3005                month,
3006                day,
3007                timezone: None,
3008            })),
3009        }
3010    }
3011
3012    fn time_value(hour: u8, minute: u8, second: Decimal) -> XmlValue {
3013        XmlValue {
3014            type_code: XmlTypeCode::Time,
3015            schema_type: None,
3016            value: XmlValueKind::Atomic(XmlAtomicValue::Time(TimeValue {
3017                hour,
3018                minute,
3019                second,
3020                timezone: None,
3021            })),
3022        }
3023    }
3024
3025    fn time_value_with_tz(
3026        hour: u8,
3027        minute: u8,
3028        second: Decimal,
3029        timezone: TimezoneOffset,
3030    ) -> XmlValue {
3031        XmlValue {
3032            type_code: XmlTypeCode::Time,
3033            schema_type: None,
3034            value: XmlValueKind::Atomic(XmlAtomicValue::Time(TimeValue {
3035                hour,
3036                minute,
3037                second,
3038                timezone: Some(timezone),
3039            })),
3040        }
3041    }
3042
3043    fn year_month_duration_value(years: u32, months: u32) -> XmlValue {
3044        XmlValue {
3045            type_code: XmlTypeCode::YearMonthDuration,
3046            schema_type: None,
3047            value: XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(
3048                YearMonthDurationValue {
3049                    negative: false,
3050                    years,
3051                    months,
3052                },
3053            )),
3054        }
3055    }
3056
3057    fn day_time_duration_value(days: u32, hours: u32, minutes: u32, seconds: Decimal) -> XmlValue {
3058        XmlValue {
3059            type_code: XmlTypeCode::DayTimeDuration,
3060            schema_type: None,
3061            value: XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(DayTimeDurationValue {
3062                negative: false,
3063                days,
3064                hours,
3065                minutes,
3066                seconds,
3067            })),
3068        }
3069    }
3070
3071    fn duration_value(
3072        negative: bool,
3073        years: u32,
3074        months: u32,
3075        days: u32,
3076        hours: u32,
3077        minutes: u32,
3078        seconds: Decimal,
3079    ) -> XmlValue {
3080        XmlValue {
3081            type_code: XmlTypeCode::Duration,
3082            schema_type: None,
3083            value: XmlValueKind::Atomic(XmlAtomicValue::Duration(DurationValue {
3084                negative,
3085                years,
3086                months,
3087                days,
3088                hours,
3089                minutes,
3090                seconds,
3091            })),
3092        }
3093    }
3094
3095    #[test]
3096    fn test_add_byte_unsigned_byte_returns_int() {
3097        let left = int_value(XmlTypeCode::Byte, 1);
3098        let right = int_value(XmlTypeCode::UnsignedByte, 2);
3099        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3100        assert_eq!(result.type_code, XmlTypeCode::Int);
3101        assert_eq!(result.as_integer().unwrap(), &BigInt::from(3));
3102    }
3103
3104    #[test]
3105    fn test_add_int_unsigned_int_returns_unsigned_int() {
3106        let left = int_value(XmlTypeCode::Int, 3);
3107        let right = int_value(XmlTypeCode::UnsignedInt, 4);
3108        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3109        assert_eq!(result.type_code, XmlTypeCode::UnsignedInt);
3110        assert_eq!(result.as_integer().unwrap(), &BigInt::from(7));
3111    }
3112
3113    #[test]
3114    fn test_div_int_int_returns_decimal() {
3115        let left = int_value(XmlTypeCode::Int, 3);
3116        let right = int_value(XmlTypeCode::Int, 2);
3117        let result = eval_binary(BinaryOpKind::Div, &left, &right).unwrap();
3118        assert_eq!(result.type_code, XmlTypeCode::Decimal);
3119        assert_eq!(result.as_decimal().unwrap(), Decimal::new(15, 1));
3120    }
3121
3122    #[test]
3123    fn test_div_short_bounds_rounds_to_xpath_precision() {
3124        let left = int_value(XmlTypeCode::Short, -32768);
3125        let right = int_value(XmlTypeCode::Short, 32767);
3126        let result = eval_binary(BinaryOpKind::Div, &left, &right).unwrap();
3127        assert_eq!(result.type_code, XmlTypeCode::Decimal);
3128        assert_eq!(
3129            result.as_decimal().unwrap().to_string(),
3130            "-1.000030518509475997"
3131        );
3132    }
3133
3134    #[test]
3135    fn test_idiv_double_truncates() {
3136        let left = XmlValue::double(5.9);
3137        let right = XmlValue::double(2.0);
3138        let result = eval_binary(BinaryOpKind::IDiv, &left, &right).unwrap();
3139        assert_eq!(result.type_code, XmlTypeCode::Integer);
3140        assert_eq!(result.as_integer().unwrap(), &BigInt::from(2));
3141    }
3142
3143    #[test]
3144    fn test_unary_minus_unsigned_int_returns_long() {
3145        let value = int_value(XmlTypeCode::UnsignedInt, 5);
3146        let result = eval_unary(UnaryOpKind::Negate, &value).unwrap();
3147        assert_eq!(result.type_code, XmlTypeCode::Long);
3148        assert_eq!(result.as_integer().unwrap(), &BigInt::from(-5));
3149    }
3150
3151    #[test]
3152    fn test_string_comparison() {
3153        let left = XmlValue::string("alpha");
3154        let right = XmlValue::string("beta");
3155        let result = eval_binary(BinaryOpKind::GeneralLt, &left, &right).unwrap();
3156        assert_eq!(result.as_boolean(), Some(true));
3157    }
3158
3159    #[test]
3160    fn test_boolean_eq() {
3161        let left = XmlValue::boolean(true);
3162        let right = XmlValue::boolean(false);
3163        let result = eval_binary(BinaryOpKind::GeneralEq, &left, &right).unwrap();
3164        assert_eq!(result.as_boolean(), Some(false));
3165    }
3166
3167    #[test]
3168    fn test_range_integer_sequence() {
3169        let start = int_value(XmlTypeCode::Integer, 1);
3170        let end = int_value(XmlTypeCode::Integer, 3);
3171        let result = eval_range(&start, &end).unwrap();
3172        let values: Vec<_> = result
3173            .iter()
3174            .map(|v| v.as_integer().unwrap().clone())
3175            .collect();
3176        assert_eq!(
3177            values,
3178            vec![BigInt::from(1), BigInt::from(2), BigInt::from(3)]
3179        );
3180    }
3181
3182    #[test]
3183    fn test_decimal_eq() {
3184        let left = decimal_value("2.5");
3185        let right = decimal_value("2.5");
3186        let result = eval_binary(BinaryOpKind::GeneralEq, &left, &right).unwrap();
3187        assert_eq!(result.as_boolean(), Some(true));
3188    }
3189
3190    #[test]
3191    fn test_datetime_add_year_month_clamps_day() {
3192        let left = datetime_value(XmlTypeCode::DateTime, 2023, 1, 31, 10, 0, Decimal::ZERO);
3193        let right = year_month_duration_value(0, 1);
3194        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3195        assert_eq!(result.type_code, XmlTypeCode::DateTime);
3196        match result.value {
3197            XmlValueKind::Atomic(XmlAtomicValue::DateTime(dt)) => {
3198                assert_eq!((dt.year, dt.month, dt.day), (2023, 2, 28));
3199                assert_eq!((dt.hour, dt.minute), (10, 0));
3200            }
3201            _ => panic!("Expected dateTime result"),
3202        }
3203    }
3204
3205    #[test]
3206    fn test_date_sub_date_returns_day_time_duration() {
3207        let left = date_value(2024, 3, 15);
3208        let right = date_value(2024, 3, 14);
3209        let result = eval_binary(BinaryOpKind::Sub, &left, &right).unwrap();
3210        assert_eq!(result.type_code, XmlTypeCode::DayTimeDuration);
3211        match result.value {
3212            XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(duration)) => {
3213                assert!(!duration.negative);
3214                assert_eq!(duration.days, 1);
3215                assert_eq!(duration.hours, 0);
3216                assert_eq!(duration.minutes, 0);
3217                assert!(duration.seconds.is_zero());
3218            }
3219            _ => panic!("Expected dayTimeDuration result"),
3220        }
3221    }
3222
3223    #[test]
3224    fn test_time_add_day_time_wraps() {
3225        let left = time_value(23, 0, Decimal::ZERO);
3226        let right = day_time_duration_value(0, 2, 0, Decimal::ZERO);
3227        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3228        assert_eq!(result.type_code, XmlTypeCode::Time);
3229        match result.value {
3230            XmlValueKind::Atomic(XmlAtomicValue::Time(time)) => {
3231                assert_eq!((time.hour, time.minute), (1, 0));
3232            }
3233            _ => panic!("Expected time result"),
3234        }
3235    }
3236
3237    #[test]
3238    fn test_time_compare_uses_implicit_timezone() {
3239        let implicit = implicit_timezone_offset();
3240        let left = time_value(10, 0, Decimal::ZERO);
3241        let right = time_value_with_tz(10, 0, Decimal::ZERO, implicit);
3242        let result = eval_binary(BinaryOpKind::GeneralEq, &left, &right).unwrap();
3243        assert_eq!(result.as_boolean(), Some(true));
3244    }
3245
3246    #[test]
3247    fn test_numeric_mul_year_month_duration() {
3248        let left = int_value(XmlTypeCode::Int, 2);
3249        let right = year_month_duration_value(1, 2);
3250        let result = eval_binary(BinaryOpKind::Mul, &left, &right).unwrap();
3251        assert_eq!(result.type_code, XmlTypeCode::YearMonthDuration);
3252        match result.value {
3253            XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(duration)) => {
3254                assert_eq!((duration.years, duration.months), (2, 4));
3255            }
3256            _ => panic!("Expected yearMonthDuration result"),
3257        }
3258    }
3259
3260    #[test]
3261    fn test_day_time_duration_div_duration_returns_decimal() {
3262        let left = day_time_duration_value(0, 3, 0, Decimal::ZERO);
3263        let right = day_time_duration_value(0, 1, 0, Decimal::ZERO);
3264        let result = eval_binary(BinaryOpKind::Div, &left, &right).unwrap();
3265        assert_eq!(result.type_code, XmlTypeCode::Decimal);
3266        assert_eq!(result.as_decimal(), Some(Decimal::from(3)));
3267    }
3268
3269    #[test]
3270    fn test_duration_eq_across_subtypes() {
3271        let left = duration_value(false, 1, 2, 0, 0, 0, Decimal::ZERO);
3272        let right = year_month_duration_value(1, 2);
3273        let result = eval_binary(BinaryOpKind::GeneralEq, &left, &right).unwrap();
3274        assert_eq!(result.as_boolean(), Some(true));
3275    }
3276
3277    #[test]
3278    fn test_datetime_sub_datetime_returns_day_time_duration() {
3279        let left = datetime_value(XmlTypeCode::DateTime, 2024, 3, 15, 12, 0, Decimal::ZERO);
3280        let right = datetime_value(XmlTypeCode::DateTime, 2024, 3, 15, 11, 0, Decimal::ZERO);
3281        let result = eval_binary(BinaryOpKind::Sub, &left, &right).unwrap();
3282        assert_eq!(result.type_code, XmlTypeCode::DayTimeDuration);
3283        match result.value {
3284            XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(duration)) => {
3285                assert!(!duration.negative);
3286                assert_eq!((duration.days, duration.hours, duration.minutes), (0, 1, 0));
3287            }
3288            _ => panic!("Expected dayTimeDuration result"),
3289        }
3290    }
3291
3292    // ========================================================================
3293    // General Comparison Tests
3294    // ========================================================================
3295
3296    #[test]
3297    fn test_magnitude_relationship_untyped_to_numeric() {
3298        // UntypedAtomic compared with integer should promote to double
3299        let left = XmlValue::untyped("42");
3300        let right = int_value(XmlTypeCode::Integer, 42);
3301        let (promoted_left, promoted_right) = magnitude_relationship(&left, &right).unwrap();
3302        assert_eq!(promoted_left.type_code, XmlTypeCode::Double);
3303        assert_eq!(promoted_right.type_code, XmlTypeCode::Integer);
3304    }
3305
3306    #[test]
3307    fn test_magnitude_relationship_untyped_to_string() {
3308        // UntypedAtomic compared with string should become string
3309        let left = XmlValue::untyped("hello");
3310        let right = XmlValue::string("world");
3311        let (promoted_left, promoted_right) = magnitude_relationship(&left, &right).unwrap();
3312        assert_eq!(promoted_left.type_code, XmlTypeCode::String);
3313        assert_eq!(promoted_right.type_code, XmlTypeCode::String);
3314    }
3315
3316    #[test]
3317    fn test_magnitude_relationship_both_untyped() {
3318        // Both UntypedAtomic should both become string
3319        let left = XmlValue::untyped("abc");
3320        let right = XmlValue::untyped("def");
3321        let (promoted_left, promoted_right) = magnitude_relationship(&left, &right).unwrap();
3322        // When both are untyped, they stay as string-like
3323        assert!(is_string_like(promoted_left.type_code));
3324        assert!(is_string_like(promoted_right.type_code));
3325    }
3326
3327    #[test]
3328    fn test_general_eq_with_untyped() {
3329        // "42" = 42 should be true (UntypedAtomic promoted to double)
3330        let left = XmlValue::untyped("42");
3331        let right = int_value(XmlTypeCode::Integer, 42);
3332        assert!(general_eq(&left, &right).unwrap());
3333    }
3334
3335    #[test]
3336    fn test_general_eq_strings() {
3337        let left = XmlValue::string("hello");
3338        let right = XmlValue::string("hello");
3339        assert!(general_eq(&left, &right).unwrap());
3340
3341        let right2 = XmlValue::string("world");
3342        assert!(!general_eq(&left, &right2).unwrap());
3343    }
3344
3345    #[test]
3346    fn test_general_gt_with_untyped() {
3347        // "50" > 42 should be true
3348        let left = XmlValue::untyped("50");
3349        let right = int_value(XmlTypeCode::Integer, 42);
3350        assert!(general_gt(&left, &right).unwrap());
3351    }
3352
3353    #[test]
3354    fn test_general_ne() {
3355        let left = XmlValue::string("a");
3356        let right = XmlValue::string("b");
3357        assert!(general_ne(&left, &right).unwrap());
3358
3359        let same = XmlValue::string("a");
3360        assert!(!general_ne(&left, &same).unwrap());
3361    }
3362
3363    #[test]
3364    fn test_general_comparisons_numeric() {
3365        let five = int_value(XmlTypeCode::Integer, 5);
3366        let ten = int_value(XmlTypeCode::Integer, 10);
3367
3368        assert!(general_lt(&five, &ten).unwrap());
3369        assert!(general_le(&five, &ten).unwrap());
3370        assert!(!general_gt(&five, &ten).unwrap());
3371        assert!(!general_ge(&five, &ten).unwrap());
3372
3373        assert!(general_ge(&five, &five).unwrap());
3374        assert!(general_le(&five, &five).unwrap());
3375    }
3376
3377    #[test]
3378    fn test_value_comparisons() {
3379        let a = XmlValue::string("abc");
3380        let b = XmlValue::string("xyz");
3381
3382        assert!(value_lt(&a, &b).unwrap());
3383        assert!(value_le(&a, &b).unwrap());
3384        assert!(!value_gt(&a, &b).unwrap());
3385        assert!(!value_ge(&a, &b).unwrap());
3386        assert!(!value_eq(&a, &b).unwrap());
3387    }
3388
3389    // ========================================================================
3390    // Sequence General Comparison Tests (Cartesian product)
3391    // ========================================================================
3392
3393    #[test]
3394    fn test_general_eq_seq_finds_match() {
3395        // (1, 2, 3) = (3, 4, 5) should be true because 3 appears in both
3396        let left = vec![
3397            int_value(XmlTypeCode::Integer, 1),
3398            int_value(XmlTypeCode::Integer, 2),
3399            int_value(XmlTypeCode::Integer, 3),
3400        ];
3401        let right = vec![
3402            int_value(XmlTypeCode::Integer, 3),
3403            int_value(XmlTypeCode::Integer, 4),
3404            int_value(XmlTypeCode::Integer, 5),
3405        ];
3406        assert!(general_eq_seq(&left, &right).unwrap());
3407    }
3408
3409    #[test]
3410    fn test_general_eq_seq_no_match() {
3411        // (1, 2) = (3, 4) should be false because no common values
3412        let left = vec![
3413            int_value(XmlTypeCode::Integer, 1),
3414            int_value(XmlTypeCode::Integer, 2),
3415        ];
3416        let right = vec![
3417            int_value(XmlTypeCode::Integer, 3),
3418            int_value(XmlTypeCode::Integer, 4),
3419        ];
3420        assert!(!general_eq_seq(&left, &right).unwrap());
3421    }
3422
3423    #[test]
3424    fn test_general_eq_seq_empty_is_false() {
3425        // Empty sequences always return false for general comparisons
3426        let left: Vec<XmlValue> = vec![];
3427        let right = vec![int_value(XmlTypeCode::Integer, 1)];
3428        assert!(!general_eq_seq(&left, &right).unwrap());
3429        assert!(!general_eq_seq(&right, &left).unwrap());
3430        assert!(!general_eq_seq(&left, &left).unwrap());
3431    }
3432
3433    #[test]
3434    fn test_general_ne_seq() {
3435        // (1, 2) != (2, 3) should be true because 1 != 2, 1 != 3, 2 != 3 all true
3436        let left = vec![
3437            int_value(XmlTypeCode::Integer, 1),
3438            int_value(XmlTypeCode::Integer, 2),
3439        ];
3440        let right = vec![
3441            int_value(XmlTypeCode::Integer, 2),
3442            int_value(XmlTypeCode::Integer, 3),
3443        ];
3444        assert!(general_ne_seq(&left, &right).unwrap());
3445
3446        // (1) != (1) should be false because no pair is not-equal
3447        let same = vec![int_value(XmlTypeCode::Integer, 1)];
3448        assert!(!general_ne_seq(&same, &same).unwrap());
3449    }
3450
3451    #[test]
3452    fn test_general_lt_seq() {
3453        // (1, 2) < (3, 4) should be true because 1 < 3, 1 < 4, 2 < 3, 2 < 4
3454        let left = vec![
3455            int_value(XmlTypeCode::Integer, 1),
3456            int_value(XmlTypeCode::Integer, 2),
3457        ];
3458        let right = vec![
3459            int_value(XmlTypeCode::Integer, 3),
3460            int_value(XmlTypeCode::Integer, 4),
3461        ];
3462        assert!(general_lt_seq(&left, &right).unwrap());
3463
3464        // (3, 4) < (1, 2) should be false
3465        assert!(!general_lt_seq(&right, &left).unwrap());
3466
3467        // (1, 5) < (3, 4) should be true because 1 < 3, 1 < 4
3468        let mixed = vec![
3469            int_value(XmlTypeCode::Integer, 1),
3470            int_value(XmlTypeCode::Integer, 5),
3471        ];
3472        assert!(general_lt_seq(&mixed, &right).unwrap());
3473    }
3474
3475    #[test]
3476    fn test_general_gt_seq() {
3477        // (3, 4) > (1, 2) should be true
3478        let left = vec![
3479            int_value(XmlTypeCode::Integer, 3),
3480            int_value(XmlTypeCode::Integer, 4),
3481        ];
3482        let right = vec![
3483            int_value(XmlTypeCode::Integer, 1),
3484            int_value(XmlTypeCode::Integer, 2),
3485        ];
3486        assert!(general_gt_seq(&left, &right).unwrap());
3487    }
3488
3489    #[test]
3490    fn test_general_le_seq() {
3491        // (1, 2) <= (2, 3) should be true because 1 <= 2, 1 <= 3, 2 <= 2, 2 <= 3
3492        let left = vec![
3493            int_value(XmlTypeCode::Integer, 1),
3494            int_value(XmlTypeCode::Integer, 2),
3495        ];
3496        let right = vec![
3497            int_value(XmlTypeCode::Integer, 2),
3498            int_value(XmlTypeCode::Integer, 3),
3499        ];
3500        assert!(general_le_seq(&left, &right).unwrap());
3501    }
3502
3503    #[test]
3504    fn test_general_ge_seq() {
3505        // (2, 3) >= (1, 2) should be true
3506        let left = vec![
3507            int_value(XmlTypeCode::Integer, 2),
3508            int_value(XmlTypeCode::Integer, 3),
3509        ];
3510        let right = vec![
3511            int_value(XmlTypeCode::Integer, 1),
3512            int_value(XmlTypeCode::Integer, 2),
3513        ];
3514        assert!(general_ge_seq(&left, &right).unwrap());
3515    }
3516
3517    #[test]
3518    fn test_general_seq_with_type_promotion() {
3519        // UntypedAtomic should be promoted: ("42") = (42) should be true
3520        let left = vec![XmlValue::untyped("42")];
3521        let right = vec![int_value(XmlTypeCode::Integer, 42)];
3522        assert!(general_eq_seq(&left, &right).unwrap());
3523    }
3524
3525    #[test]
3526    fn test_general_seq_mixed_types() {
3527        // Mixed types that can't compare just skip those pairs
3528        // (1, "hello") = ("hello", 2) should be true because "hello" = "hello"
3529        let left = vec![
3530            int_value(XmlTypeCode::Integer, 1),
3531            XmlValue::string("hello"),
3532        ];
3533        let right = vec![
3534            XmlValue::string("hello"),
3535            int_value(XmlTypeCode::Integer, 2),
3536        ];
3537        assert!(general_eq_seq(&left, &right).unwrap());
3538    }
3539
3540    #[test]
3541    fn test_compare_ge_prefers_eq_over_ordering() {
3542        // QName ordering isn't defined, but equality is.
3543        let names = NameTable::new();
3544        let local = names.add("a");
3545        let qname = QualifiedName::local(local);
3546        let left = XmlValue::new(
3547            XmlTypeCode::QName,
3548            XmlValueKind::Atomic(XmlAtomicValue::QName(qname)),
3549        );
3550        let right = left.clone();
3551
3552        assert!(compare_ge(&left, &right).unwrap());
3553        assert!(compare_le(&left, &right).unwrap());
3554    }
3555
3556    #[test]
3557    fn test_list_equality() {
3558        let left = XmlValue::new(
3559            XmlTypeCode::NmTokens,
3560            XmlValueKind::List {
3561                item_type: XmlTypeCode::NmToken,
3562                items: vec![
3563                    XmlAtomicValue::String("a".to_string()),
3564                    XmlAtomicValue::String("b".to_string()),
3565                ],
3566            },
3567        );
3568        let right = left.clone();
3569        let different = XmlValue::new(
3570            XmlTypeCode::NmTokens,
3571            XmlValueKind::List {
3572                item_type: XmlTypeCode::NmToken,
3573                items: vec![XmlAtomicValue::String("a".to_string())],
3574            },
3575        );
3576
3577        assert!(compare_eq(&left, &right).unwrap());
3578        assert!(!compare_eq(&left, &different).unwrap());
3579    }
3580
3581    #[test]
3582    fn test_union_unwrap_equality() {
3583        let inner = XmlValue::string("hello");
3584        let left = XmlValue::new(XmlTypeCode::String, XmlValueKind::Union(Box::new(inner)));
3585        let right = XmlValue::string("hello");
3586        assert!(compare_eq(&left, &right).unwrap());
3587    }
3588
3589    #[test]
3590    fn test_general_eq_iter_finds_match() {
3591        let names = NameTable::new();
3592        let context = XPathContext::new(&names);
3593        let left: VecNodeIterator<RoXmlNavigator<'static>> = VecNodeIterator::new(vec![
3594            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
3595            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
3596        ]);
3597        let right: VecNodeIterator<RoXmlNavigator<'static>> =
3598            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(2)))]);
3599
3600        assert!(general_eq_iter(&context, &left, &right).unwrap());
3601    }
3602
3603    #[test]
3604    fn test_general_eq_iter_invalid_cast_errors() {
3605        let names = NameTable::new();
3606        let context = XPathContext::new(&names);
3607        let left: VecNodeIterator<RoXmlNavigator<'static>> =
3608            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::untyped("not-a-number"))]);
3609        let right: VecNodeIterator<RoXmlNavigator<'static>> =
3610            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(1)))]);
3611
3612        let result = general_eq_iter(&context, &left, &right);
3613        assert!(matches!(result, Err(XPathError::FORG0001 { .. })));
3614    }
3615
3616    #[test]
3617    fn test_general_eq_iter_type_mismatch_is_false() {
3618        let names = NameTable::new();
3619        let context = XPathContext::new(&names);
3620        let left: VecNodeIterator<RoXmlNavigator<'static>> =
3621            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::boolean(true))]);
3622        let right: VecNodeIterator<RoXmlNavigator<'static>> =
3623            VecNodeIterator::new(vec![XmlItem::Atomic(date_value(2024, 1, 1))]);
3624
3625        assert!(!general_eq_iter(&context, &left, &right).unwrap());
3626    }
3627
3628    #[test]
3629    fn test_general_gt_iter_type_mismatch_errors() {
3630        let names = NameTable::new();
3631        let context = XPathContext::new(&names);
3632        let left: VecNodeIterator<RoXmlNavigator<'static>> =
3633            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::boolean(true))]);
3634        let right: VecNodeIterator<RoXmlNavigator<'static>> =
3635            VecNodeIterator::new(vec![XmlItem::Atomic(XmlValue::string("false"))]);
3636
3637        let result = general_gt_iter(&context, &left, &right);
3638        assert!(matches!(
3639            result,
3640            Err(XPathError::BinaryOperatorNotDefined { .. })
3641        ));
3642    }
3643
3644    // =========================================================================
3645    // Primitive base type resolution tests (XPath 2.0 spec-aligned)
3646    // =========================================================================
3647
3648    #[test]
3649    fn test_magnitude_relationship_ctx_numeric_to_double() {
3650        // Per XPath spec, UntypedAtomic is promoted to Double when compared with numeric types
3651        let names = NameTable::new();
3652        let context = XPathContext::new(&names);
3653
3654        let untyped = XmlValue::untyped("42");
3655        let typed = XmlValue::integer(BigInt::from(100));
3656
3657        let (left, _right) = magnitude_relationship_ctx(&context, &untyped, &typed).unwrap();
3658
3659        assert_eq!(left.type_code, XmlTypeCode::Double);
3660        assert!((left.as_double().unwrap() - 42.0).abs() < 0.001);
3661    }
3662
3663    #[test]
3664    fn test_magnitude_relationship_ctx_promotes_untyped() {
3665        let names = NameTable::new();
3666        let context = XPathContext::new(&names);
3667
3668        // Test UntypedAtomic promoted to numeric type
3669        let untyped = XmlValue::untyped("2.5");
3670        let typed = XmlValue::double(1.5);
3671
3672        let (left, right) = magnitude_relationship_ctx(&context, &untyped, &typed).unwrap();
3673
3674        assert_eq!(left.type_code, XmlTypeCode::Double);
3675        assert!((left.as_double().unwrap() - 2.5).abs() < 0.001);
3676        assert_eq!(right.type_code, XmlTypeCode::Double);
3677    }
3678
3679    #[test]
3680    fn test_magnitude_relationship_ctx_string_like() {
3681        let names = NameTable::new();
3682        let context = XPathContext::new(&names);
3683
3684        // Test UntypedAtomic with string-like type stays as string
3685        let untyped = XmlValue::untyped("test");
3686        let typed = XmlValue::string("other");
3687
3688        let (left, right) = magnitude_relationship_ctx(&context, &untyped, &typed).unwrap();
3689
3690        assert_eq!(left.type_code, XmlTypeCode::String);
3691        assert_eq!(left.to_string_value(), "test");
3692        assert_eq!(right.type_code, XmlTypeCode::String);
3693    }
3694
3695    #[test]
3696    fn test_get_xsd_primitive_type_string_derived() {
3697        // String-derived types should map to xs:string
3698        assert_eq!(
3699            get_xsd_primitive_type(XmlTypeCode::NormalizedString),
3700            XmlTypeCode::String
3701        );
3702        assert_eq!(
3703            get_xsd_primitive_type(XmlTypeCode::Token),
3704            XmlTypeCode::String
3705        );
3706        assert_eq!(
3707            get_xsd_primitive_type(XmlTypeCode::NCName),
3708            XmlTypeCode::String
3709        );
3710        assert_eq!(get_xsd_primitive_type(XmlTypeCode::Id), XmlTypeCode::String);
3711    }
3712
3713    #[test]
3714    fn test_get_xsd_primitive_type_integer_derived() {
3715        // Integer-derived types should map to xs:decimal
3716        assert_eq!(
3717            get_xsd_primitive_type(XmlTypeCode::Integer),
3718            XmlTypeCode::Decimal
3719        );
3720        assert_eq!(
3721            get_xsd_primitive_type(XmlTypeCode::Long),
3722            XmlTypeCode::Decimal
3723        );
3724        assert_eq!(
3725            get_xsd_primitive_type(XmlTypeCode::Int),
3726            XmlTypeCode::Decimal
3727        );
3728        assert_eq!(
3729            get_xsd_primitive_type(XmlTypeCode::UnsignedInt),
3730            XmlTypeCode::Decimal
3731        );
3732    }
3733
3734    #[test]
3735    fn test_get_xsd_primitive_type_duration_special_cases() {
3736        // dayTimeDuration and yearMonthDuration are their own primitives for XPath
3737        assert_eq!(
3738            get_xsd_primitive_type(XmlTypeCode::DayTimeDuration),
3739            XmlTypeCode::DayTimeDuration
3740        );
3741        assert_eq!(
3742            get_xsd_primitive_type(XmlTypeCode::YearMonthDuration),
3743            XmlTypeCode::YearMonthDuration
3744        );
3745        // But xs:duration is already primitive
3746        assert_eq!(
3747            get_xsd_primitive_type(XmlTypeCode::Duration),
3748            XmlTypeCode::Duration
3749        );
3750    }
3751
3752    #[test]
3753    fn test_get_xsd_primitive_type_date_time() {
3754        // Date/time types are already primitive
3755        assert_eq!(
3756            get_xsd_primitive_type(XmlTypeCode::DateTime),
3757            XmlTypeCode::DateTime
3758        );
3759        assert_eq!(get_xsd_primitive_type(XmlTypeCode::Date), XmlTypeCode::Date);
3760        assert_eq!(get_xsd_primitive_type(XmlTypeCode::Time), XmlTypeCode::Time);
3761        // dateTimeStamp derives from dateTime
3762        assert_eq!(
3763            get_xsd_primitive_type(XmlTypeCode::DateTimeStamp),
3764            XmlTypeCode::DateTime
3765        );
3766    }
3767
3768    #[test]
3769    fn test_get_primitive_base_type_without_schema() {
3770        // Without schema info, should use the type_code's primitive
3771        let primitive = get_primitive_base_type(None, None, XmlTypeCode::Integer);
3772        assert_eq!(primitive, XmlTypeCode::Decimal);
3773
3774        let primitive = get_primitive_base_type(None, None, XmlTypeCode::NCName);
3775        assert_eq!(primitive, XmlTypeCode::String);
3776    }
3777
3778    #[test]
3779    fn test_get_primitive_base_type_with_schema() {
3780        // With schema info, should resolve from the type definition
3781        let schema_set = SchemaSet::new();
3782        let builtin_types = schema_set.builtin_types();
3783
3784        // xs:integer should resolve to xs:decimal
3785        let primitive = get_primitive_base_type(
3786            Some(&schema_set),
3787            Some(builtin_types.integer),
3788            XmlTypeCode::Integer,
3789        );
3790        assert_eq!(primitive, XmlTypeCode::Decimal);
3791
3792        // xs:string should stay as xs:string (already primitive)
3793        let primitive = get_primitive_base_type(
3794            Some(&schema_set),
3795            Some(builtin_types.string),
3796            XmlTypeCode::String,
3797        );
3798        assert_eq!(primitive, XmlTypeCode::String);
3799    }
3800
3801    #[test]
3802    fn test_magnitude_relationship_ctx_to_date() {
3803        // Test that UntypedAtomic is cast to xs:date when compared with date
3804        let names = NameTable::new();
3805        let context = XPathContext::new(&names);
3806
3807        let untyped = XmlValue::untyped("2024-01-15");
3808        let typed = date_value(2024, 6, 1);
3809
3810        let (left, right) = magnitude_relationship_ctx(&context, &untyped, &typed).unwrap();
3811
3812        assert_eq!(left.type_code, XmlTypeCode::Date);
3813        assert_eq!(right.type_code, XmlTypeCode::Date);
3814    }
3815
3816    #[test]
3817    fn test_magnitude_relationship_ctx_to_boolean() {
3818        // Test that UntypedAtomic is cast to xs:boolean when compared with boolean
3819        let names = NameTable::new();
3820        let context = XPathContext::new(&names);
3821
3822        let untyped = XmlValue::untyped("true");
3823        let typed = XmlValue::boolean(false);
3824
3825        let (left, right) = magnitude_relationship_ctx(&context, &untyped, &typed).unwrap();
3826
3827        assert_eq!(left.type_code, XmlTypeCode::Boolean);
3828        assert_eq!(left.as_boolean(), Some(true));
3829        assert_eq!(right.type_code, XmlTypeCode::Boolean);
3830    }
3831
3832    // ========================================================================
3833    // XPath 1.0 comparison tests
3834    // ========================================================================
3835
3836    #[test]
3837    fn test_xpath10_eq_boolean_priority() {
3838        // "1" = true() → both convert to boolean → true (string "1" is truthy)
3839        let left = XmlValue::string("1".to_string());
3840        let right = XmlValue::boolean(true);
3841        let (l, r) = coerce_for_comparison_10(BinaryOpKind::GeneralEq, &left, &right);
3842        assert_eq!(l.type_code, XmlTypeCode::Boolean);
3843        assert_eq!(r.type_code, XmlTypeCode::Boolean);
3844        assert!(compare_eq(&l, &r).unwrap());
3845    }
3846
3847    #[test]
3848    fn test_xpath10_eq_boolean_priority_empty_string() {
3849        // "" = true() → both to boolean → false = true → false
3850        let left = XmlValue::string("".to_string());
3851        let right = XmlValue::boolean(true);
3852        let (l, r) = coerce_for_comparison_10(BinaryOpKind::GeneralEq, &left, &right);
3853        assert_eq!(l.as_boolean(), Some(false));
3854        assert_eq!(r.as_boolean(), Some(true));
3855        assert!(!compare_eq(&l, &r).unwrap());
3856    }
3857
3858    #[test]
3859    fn test_xpath10_relational_numeric() {
3860        // "3" < "10" → 3.0 < 10.0 → true (numeric comparison, not string)
3861        let left = XmlValue::string("3".to_string());
3862        let right = XmlValue::string("10".to_string());
3863        let (l, r) = coerce_for_comparison_10(BinaryOpKind::GeneralLt, &left, &right);
3864        assert_eq!(l.type_code, XmlTypeCode::Double);
3865        assert_eq!(r.type_code, XmlTypeCode::Double);
3866        assert!(compare_lt(&l, &r).unwrap());
3867    }
3868
3869    #[test]
3870    fn test_xpath10_eq_string_comparison() {
3871        // "3" = "10" → string compare → false
3872        let left = XmlValue::string("3".to_string());
3873        let right = XmlValue::string("10".to_string());
3874        let (l, r) = coerce_for_comparison_10(BinaryOpKind::GeneralEq, &left, &right);
3875        assert_eq!(l.type_code, XmlTypeCode::String);
3876        assert_eq!(r.type_code, XmlTypeCode::String);
3877        assert!(!compare_eq(&l, &r).unwrap());
3878    }
3879
3880    #[test]
3881    fn test_xpath10_eq_numeric_priority() {
3882        // "3" = 3.0 → numeric coercion → 3.0 = 3.0 → true
3883        let left = XmlValue::string("3".to_string());
3884        let right = XmlValue::double(3.0);
3885        let (l, r) = coerce_for_comparison_10(BinaryOpKind::GeneralEq, &left, &right);
3886        assert_eq!(l.type_code, XmlTypeCode::Double);
3887        assert_eq!(r.type_code, XmlTypeCode::Double);
3888        assert!(compare_eq(&l, &r).unwrap());
3889    }
3890
3891    #[test]
3892    fn test_xpath10_arithmetic_returns_double() {
3893        // 1 + 2 in XPath 1.0 → 3.0 (double, not integer)
3894        let left = XmlValue::integer(BigInt::from(1));
3895        let right = XmlValue::integer(BigInt::from(2));
3896        let result = eval_numeric_binary_10(BinaryOpKind::Add, &left, &right).unwrap();
3897        assert_eq!(result.type_code, XmlTypeCode::Double);
3898        assert_eq!(result.as_double(), Some(3.0));
3899    }
3900
3901    #[test]
3902    fn test_xpath10_arithmetic_div_by_zero() {
3903        let left = XmlValue::double(1.0);
3904        let right = XmlValue::double(0.0);
3905        let result = eval_numeric_binary_10(BinaryOpKind::Div, &left, &right).unwrap();
3906        assert_eq!(result.as_double(), Some(f64::INFINITY));
3907    }
3908
3909    #[test]
3910    fn test_xpath10_arithmetic_mod() {
3911        let left = XmlValue::integer(BigInt::from(5));
3912        let right = XmlValue::integer(BigInt::from(3));
3913        let result = eval_numeric_binary_10(BinaryOpKind::Mod, &left, &right).unwrap();
3914        assert_eq!(result.type_code, XmlTypeCode::Double);
3915        assert_eq!(result.as_double(), Some(2.0));
3916    }
3917
3918    #[test]
3919    fn test_xpath10_general_eq_iter() {
3920        // Test iterator-based XPath 1.0 comparison: existential over sequences
3921        type Item = XmlItem<RoXmlNavigator<'static>>;
3922        let left_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("hello".to_string()))];
3923        let right_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("hello".to_string()))];
3924        let left_iter = VecNodeIterator::new(left_items);
3925        let right_iter = VecNodeIterator::new(right_items);
3926        assert!(general_eq_iter_10(&left_iter, &right_iter).unwrap());
3927    }
3928
3929    #[test]
3930    fn test_xpath10_general_ne_iter() {
3931        type Item = XmlItem<RoXmlNavigator<'static>>;
3932        let left_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("a".to_string()))];
3933        let right_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("b".to_string()))];
3934        let left_iter = VecNodeIterator::new(left_items);
3935        let right_iter = VecNodeIterator::new(right_items);
3936        assert!(general_ne_iter_10(&left_iter, &right_iter).unwrap());
3937    }
3938
3939    #[test]
3940    fn test_xpath10_general_lt_iter_numeric_coercion() {
3941        // "3" < "10" in XPath 1.0 → numeric → 3.0 < 10.0 → true
3942        type Item = XmlItem<RoXmlNavigator<'static>>;
3943        let left_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("3".to_string()))];
3944        let right_items: Vec<Item> = vec![XmlItem::Atomic(XmlValue::string("10".to_string()))];
3945        let left_iter = VecNodeIterator::new(left_items);
3946        let right_iter = VecNodeIterator::new(right_items);
3947        assert!(general_lt_iter_10(&left_iter, &right_iter).unwrap());
3948    }
3949
3950    // ========================================================================
3951    // UntypedAtomic arithmetic promotion (XPath 2.0 Section 3.5.1)
3952    // ========================================================================
3953
3954    #[test]
3955    fn test_untyped_atomic_add() {
3956        // UntypedAtomic("40") + integer(1) → double(41.0)
3957        let left = XmlValue::untyped("40");
3958        let right = int_value(XmlTypeCode::Integer, 1);
3959        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3960        assert_eq!(result.type_code, XmlTypeCode::Double);
3961        assert_eq!(result.as_double().unwrap(), 41.0);
3962    }
3963
3964    #[test]
3965    fn test_untyped_atomic_both_sides() {
3966        // UntypedAtomic("40") + UntypedAtomic("40") → double(80.0)
3967        let left = XmlValue::untyped("40");
3968        let right = XmlValue::untyped("40");
3969        let result = eval_binary(BinaryOpKind::Add, &left, &right).unwrap();
3970        assert_eq!(result.type_code, XmlTypeCode::Double);
3971        assert_eq!(result.as_double().unwrap(), 80.0);
3972    }
3973
3974    #[test]
3975    fn test_untyped_atomic_div() {
3976        // UntypedAtomic("40") div integer(2) → double(20.0)
3977        let left = XmlValue::untyped("40");
3978        let right = int_value(XmlTypeCode::Integer, 2);
3979        let result = eval_binary(BinaryOpKind::Div, &left, &right).unwrap();
3980        assert_eq!(result.type_code, XmlTypeCode::Double);
3981        assert_eq!(result.as_double().unwrap(), 20.0);
3982    }
3983
3984    #[test]
3985    fn test_untyped_atomic_negate() {
3986        // -(UntypedAtomic("42")) → double(-42.0)
3987        let value = XmlValue::untyped("42");
3988        let result = eval_unary(UnaryOpKind::Negate, &value).unwrap();
3989        assert_eq!(result.type_code, XmlTypeCode::Double);
3990        assert_eq!(result.as_double().unwrap(), -42.0);
3991    }
3992
3993    #[test]
3994    fn test_untyped_atomic_non_numeric_fails() {
3995        // UntypedAtomic("abc") + integer(1) → cast error
3996        let left = XmlValue::untyped("abc");
3997        let right = int_value(XmlTypeCode::Integer, 1);
3998        assert!(eval_binary(BinaryOpKind::Add, &left, &right).is_err());
3999    }
4000}