1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! An ordered set of [`XpathItem`]s.

use std::{fmt::Display, ops::Index};

use indexmap::{self, IndexSet};

use super::grammar::data_model::{AnyAtomicType, XpathItem};

/// An ordered set of [`XpathItem`]s.
#[derive(Debug)]
pub struct XpathItemSet<'tree> {
    index_set: IndexSet<XpathItem<'tree>>,
}

impl Display for XpathItemSet<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[")?;
        let values: Vec<String> = self.iter().map(|item| item.to_string()).collect();
        let values_str = values.join(", ");
        write!(f, "{}", values_str)?;
        write!(f, "]")
    }
}

impl PartialEq for XpathItemSet<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.index_set == other.index_set
    }
}

impl PartialOrd for XpathItemSet<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.index_set.iter().partial_cmp(&other.index_set)
    }
}

impl<'a, 'tree> IntoIterator for &'a XpathItemSet<'tree> {
    type Item = &'a XpathItem<'tree>;

    type IntoIter = indexmap::set::Iter<'a, XpathItem<'tree>>;

    fn into_iter(self) -> Self::IntoIter {
        self.index_set.iter()
    }
}

impl<'tree> IntoIterator for XpathItemSet<'tree> {
    type Item = XpathItem<'tree>;

    type IntoIter = indexmap::set::IntoIter<XpathItem<'tree>>;

    fn into_iter(self) -> Self::IntoIter {
        self.index_set.into_iter()
    }
}

impl<'tree> FromIterator<XpathItem<'tree>> for XpathItemSet<'tree> {
    fn from_iter<T: IntoIterator<Item = XpathItem<'tree>>>(iter: T) -> Self {
        let index_set = IndexSet::from_iter(iter);
        XpathItemSet { index_set }
    }
}

impl<'tree> Extend<XpathItem<'tree>> for XpathItemSet<'tree> {
    fn extend<T: IntoIterator<Item = XpathItem<'tree>>>(&mut self, iter: T) {
        self.index_set.extend(iter)
    }
}

impl<'tree> XpathItemSet<'tree> {
    /// Create a new empty [`XpathItemSet`].
    pub fn new() -> Self {
        XpathItemSet {
            index_set: IndexSet::new(),
        }
    }

    /// Whether the set is empty.
    pub fn is_empty(&self) -> bool {
        self.index_set.is_empty()
    }

    /// The number of items in the set.
    pub fn len(&self) -> usize {
        self.index_set.len()
    }

    /// Inserts a new item into the set.
    ///
    /// Returns true if the item was inserted, false if it was already present.
    pub fn insertb(&mut self, item: XpathItem<'tree>) -> bool {
        self.index_set.insert(item)
    }

    /// Inserts a new item into the set.
    ///
    /// Drops the bool returned by [`XpathItemSet::insertb`] so that it can be used in match arms
    /// without causing incompatible types with [`XpathItemSet::extend`].
    pub fn insert(&mut self, item: XpathItem<'tree>) {
        self.insertb(item);
    }

    /// Return an iterator over the items in the set.
    pub fn iter(&self) -> indexmap::set::Iter<'_, XpathItem<'tree>> {
        self.index_set.iter()
    }

    /// Return the effective boolean value of the result.
    ///
    /// https://www.w3.org/TR/2017/REC-xpath-31-20170321/#dt-ebv
    pub fn boolean(&self) -> bool {
        // If this is a singleton value, check for the effective boolean value of that value.
        if self.index_set.len() == 1 {
            match &self.index_set[0] {
                XpathItem::Node(_) => true,
                XpathItem::Function(_) => true,
                XpathItem::AnyAtomicType(atomic_type) => match atomic_type {
                    AnyAtomicType::Boolean(b) => *b,
                    AnyAtomicType::Integer(n) => *n != 0,
                    AnyAtomicType::Float(n) => *n != 0.0,
                    AnyAtomicType::Double(n) => *n != 0.0,
                    AnyAtomicType::String(s) => !s.is_empty(),
                },
            }
        }
        // Otherwise, the effective boolean value is true if the sequence contains any items.
        else {
            !self.index_set.is_empty()
        }
    }

    pub(crate) fn sort(&mut self) {
        self.index_set.sort();
    }
}

impl<'tree> From<IndexSet<XpathItem<'tree>>> for XpathItemSet<'tree> {
    fn from(value: IndexSet<XpathItem<'tree>>) -> Self {
        XpathItemSet { index_set: value }
    }
}

impl<'tree> Index<usize> for XpathItemSet<'tree> {
    type Output = XpathItem<'tree>;

    fn index(&self, index: usize) -> &Self::Output {
        self.index_set.index(index)
    }
}

/// Create an [XpathItemSet] from a list of values
#[macro_export]
macro_rules! xpath_item_set {
    ($($value:expr,)+) => { $crate::xpath::xpath_item_set::xpath_item_set!($($value),+) };
    ($($value:expr),*) => {
        {
            let set = indexmap::indexset![$($value,)*];

            crate::xpath::XpathItemSet::from(set)
        }
    };
}

#[cfg(test)]
mod tests {
    use crate::xpath::grammar::data_model::AnyAtomicType;

    use super::*;

    #[test]
    fn macro_works_with_one() {
        // arrange
        let node1 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("1")));

        // act
        let item_set = xpath_item_set![node1.clone()];

        // assert
        let mut expected = XpathItemSet::new();
        expected.insert(node1);

        assert_eq!(item_set, expected);
    }

    #[test]
    fn macro_works_with_multiple() {
        // arrange
        let node1 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("1")));
        let node2 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("2")));
        let node3 = XpathItem::AnyAtomicType(AnyAtomicType::String(String::from("3")));

        // act
        let item_set = xpath_item_set![node1.clone(), node2.clone(), node3.clone()];

        // assert
        let mut expected = XpathItemSet::new();
        expected.insert(node1);
        expected.insert(node2);
        expected.insert(node3);

        assert_eq!(item_set, expected);
    }
}