skyscraper/xpath/
xpath_item_set.rs

1//! An ordered set of [`XpathItem`]s.
2
3use std::{fmt::Display, ops::Index};
4
5use indexmap::{self, IndexSet};
6
7use super::grammar::data_model::{AnyAtomicType, XpathItem};
8
9/// An ordered set of [`XpathItem`]s.
10#[derive(Debug)]
11pub struct XpathItemSet<'tree> {
12    index_set: IndexSet<XpathItem<'tree>>,
13}
14
15impl Display for XpathItemSet<'_> {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "[")?;
18        let values: Vec<String> = self.iter().map(|item| item.to_string()).collect();
19        let values_str = values.join(", ");
20        write!(f, "{}", values_str)?;
21        write!(f, "]")
22    }
23}
24
25impl PartialEq for XpathItemSet<'_> {
26    fn eq(&self, other: &Self) -> bool {
27        self.index_set == other.index_set
28    }
29}
30
31impl PartialOrd for XpathItemSet<'_> {
32    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33        self.index_set.iter().partial_cmp(&other.index_set)
34    }
35}
36
37impl<'a, 'tree> IntoIterator for &'a XpathItemSet<'tree> {
38    type Item = &'a XpathItem<'tree>;
39
40    type IntoIter = indexmap::set::Iter<'a, XpathItem<'tree>>;
41
42    fn into_iter(self) -> Self::IntoIter {
43        self.index_set.iter()
44    }
45}
46
47impl<'tree> IntoIterator for XpathItemSet<'tree> {
48    type Item = XpathItem<'tree>;
49
50    type IntoIter = indexmap::set::IntoIter<XpathItem<'tree>>;
51
52    fn into_iter(self) -> Self::IntoIter {
53        self.index_set.into_iter()
54    }
55}
56
57impl<'tree> FromIterator<XpathItem<'tree>> for XpathItemSet<'tree> {
58    fn from_iter<T: IntoIterator<Item = XpathItem<'tree>>>(iter: T) -> Self {
59        let index_set = IndexSet::from_iter(iter);
60        XpathItemSet { index_set }
61    }
62}
63
64impl<'tree> Extend<XpathItem<'tree>> for XpathItemSet<'tree> {
65    fn extend<T: IntoIterator<Item = XpathItem<'tree>>>(&mut self, iter: T) {
66        self.index_set.extend(iter)
67    }
68}
69
70impl<'tree> XpathItemSet<'tree> {
71    /// Create a new empty [`XpathItemSet`].
72    pub fn new() -> Self {
73        XpathItemSet {
74            index_set: IndexSet::new(),
75        }
76    }
77
78    /// Whether the set is empty.
79    pub fn is_empty(&self) -> bool {
80        self.index_set.is_empty()
81    }
82
83    /// The number of items in the set.
84    pub fn len(&self) -> usize {
85        self.index_set.len()
86    }
87
88    /// Inserts a new item into the set.
89    ///
90    /// Returns true if the item was inserted, false if it was already present.
91    pub fn insertb(&mut self, item: XpathItem<'tree>) -> bool {
92        self.index_set.insert(item)
93    }
94
95    /// Inserts a new item into the set.
96    ///
97    /// Drops the bool returned by [`XpathItemSet::insertb`] so that it can be used in match arms
98    /// without causing incompatible types with [`XpathItemSet::extend`].
99    pub fn insert(&mut self, item: XpathItem<'tree>) {
100        self.insertb(item);
101    }
102
103    /// Return an iterator over the items in the set.
104    pub fn iter(&self) -> indexmap::set::Iter<'_, XpathItem<'tree>> {
105        self.index_set.iter()
106    }
107
108    /// Return the effective boolean value of the result.
109    ///
110    /// https://www.w3.org/TR/2017/REC-xpath-31-20170321/#dt-ebv
111    pub fn boolean(&self) -> bool {
112        // If this is a singleton value, check for the effective boolean value of that value.
113        if self.index_set.len() == 1 {
114            match &self.index_set[0] {
115                XpathItem::Node(_) => true,
116                XpathItem::Function(_) => true,
117                XpathItem::AnyAtomicType(atomic_type) => match atomic_type {
118                    AnyAtomicType::Boolean(b) => *b,
119                    AnyAtomicType::Integer(n) => *n != 0,
120                    AnyAtomicType::Float(n) => *n != 0.0,
121                    AnyAtomicType::Double(n) => *n != 0.0,
122                    AnyAtomicType::String(s) => !s.is_empty(),
123                },
124            }
125        }
126        // Otherwise, the effective boolean value is true if the sequence contains any items.
127        else {
128            !self.index_set.is_empty()
129        }
130    }
131
132    pub(crate) fn sort(&mut self) {
133        self.index_set.sort();
134    }
135}
136
137impl<'tree> From<IndexSet<XpathItem<'tree>>> for XpathItemSet<'tree> {
138    fn from(value: IndexSet<XpathItem<'tree>>) -> Self {
139        XpathItemSet { index_set: value }
140    }
141}
142
143impl<'tree> Index<usize> for XpathItemSet<'tree> {
144    type Output = XpathItem<'tree>;
145
146    fn index(&self, index: usize) -> &Self::Output {
147        self.index_set.index(index)
148    }
149}
150
151/// Create an [XpathItemSet] from a list of values
152#[macro_export]
153macro_rules! xpath_item_set {
154    ($($value:expr,)+) => { $crate::xpath::xpath_item_set::xpath_item_set!($($value),+) };
155    ($($value:expr),*) => {
156        {
157            let set = indexmap::indexset![$($value,)*];
158
159            crate::xpath::XpathItemSet::from(set)
160        }
161    };
162}
163
164#[cfg(test)]
165mod tests {
166    use crate::xpath::grammar::data_model::AnyAtomicType;
167
168    use super::*;
169
170    #[test]
171    fn macro_works_with_one() {
172        // arrange
173        let node1 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("1")));
174
175        // act
176        let item_set = xpath_item_set![node1.clone()];
177
178        // assert
179        let mut expected = XpathItemSet::new();
180        expected.insert(node1);
181
182        assert_eq!(item_set, expected);
183    }
184
185    #[test]
186    fn macro_works_with_multiple() {
187        // arrange
188        let node1 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("1")));
189        let node2 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("2")));
190        let node3 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("3")));
191
192        // act
193        let item_set = xpath_item_set![node1.clone(), node2.clone(), node3.clone()];
194
195        // assert
196        let mut expected = XpathItemSet::new();
197        expected.insert(node1);
198        expected.insert(node2);
199        expected.insert(node3);
200
201        assert_eq!(item_set, expected);
202    }
203}