xee_interpreter/sequence/
compare.rs

1use std::cmp::Ordering;
2
3use xot::Xot;
4
5use crate::{error, function, string::Collation};
6
7use super::{core::Sequence, item::Item};
8
9impl Sequence {
10    /// Compare two sequences using XPath deep equal rules.
11    ///
12    /// <https://www.w3.org/TR/xpath-functions-31/#func-deep-equal>
13    pub fn deep_equal(
14        &self,
15        other: &Self,
16        collation: &Collation,
17        default_offset: chrono::FixedOffset,
18        xot: &Xot,
19    ) -> error::Result<bool> {
20        // https://www.w3.org/TR/xpath-functions-31/#func-deep-equal
21        if self.is_empty() && other.is_empty() {
22            return Ok(true);
23        }
24        if self.len() != other.len() {
25            return Ok(false);
26        }
27        for (a, b) in self.iter().zip(other.iter()) {
28            match (a, b) {
29                (Item::Atomic(a), Item::Atomic(b)) => {
30                    if !a.deep_equal(&b, collation, default_offset) {
31                        return Ok(false);
32                    }
33                }
34                (Item::Node(a), Item::Node(b)) => {
35                    if !xot.deep_equal_xpath(a, b, |a, b| collation.compare(a, b).is_eq()) {
36                        return Ok(false);
37                    }
38                }
39                (Item::Function(a), Item::Function(b)) => match (a, b) {
40                    (function::Function::Array(a), function::Function::Array(b)) => {
41                        if !a.deep_equal(b.clone(), collation, default_offset, xot)? {
42                            return Ok(false);
43                        }
44                    }
45                    (function::Function::Map(a), function::Function::Map(b)) => {
46                        if !a.deep_equal(&b, collation, default_offset, xot)? {
47                            return Ok(false);
48                        }
49                    }
50                    (function::Function::Map(_), function::Function::Array(_)) => return Ok(false),
51                    (function::Function::Array(_), function::Function::Map(_)) => return Ok(false),
52                    _ => return Err(error::Error::FOTY0015),
53                },
54                _ => {
55                    return Ok(false);
56                }
57            }
58        }
59        Ok(true)
60    }
61
62    pub(crate) fn fallible_compare(
63        &self,
64        other: &Sequence,
65        collation: &Collation,
66        implicit_offset: chrono::FixedOffset,
67    ) -> error::Result<Ordering> {
68        // we get atoms not by atomizing, but by trying to turn each
69        // item into an atom. If it's not an atom, it's not comparable
70        // by eq, lt, gt, etc.
71        let a_atoms = self.iter().map(|item| item.to_atomic());
72        let mut b_atoms = other.iter().map(|item| item.to_atomic());
73        for a_atom in a_atoms {
74            let b_atom = b_atoms.next();
75            let a_atom = a_atom?;
76            if let Some(b_atom) = b_atom {
77                let b_atom = b_atom?;
78                let ordering = a_atom.fallible_compare(&b_atom, collation, implicit_offset)?;
79                if !ordering.is_eq() {
80                    return Ok(ordering);
81                }
82            } else {
83                return Ok(Ordering::Greater);
84            }
85        }
86        if b_atoms.next().is_some() {
87            Ok(Ordering::Less)
88        } else {
89            Ok(Ordering::Equal)
90        }
91    }
92
93    /// For use in sorting. If the comparison fails, it's always Ordering::Less
94    /// Another pass is required to determine whether the sequence is in order
95    /// or whether the comparison failed.
96    pub(crate) fn compare(
97        &self,
98        other: &Sequence,
99        collation: &Collation,
100        implicit_offset: chrono::FixedOffset,
101    ) -> Ordering {
102        self.fallible_compare(other, collation, implicit_offset)
103            .unwrap_or(Ordering::Less)
104    }
105}