script/xpath/
eval_value.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::ToOwned;
6use std::collections::HashSet;
7use std::{fmt, string};
8
9use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
10use crate::dom::bindings::root::DomRoot;
11use crate::dom::node::Node;
12
13/// The primary types of values that an XPath expression returns as a result.
14pub(crate) enum Value {
15    Boolean(bool),
16    /// A IEEE-754 double-precision floating point number
17    Number(f64),
18    String(String),
19    /// A collection of not-necessarily-unique nodes
20    Nodeset(Vec<DomRoot<Node>>),
21}
22
23impl fmt::Debug for Value {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match *self {
26            Value::Boolean(val) => write!(f, "{}", val),
27            Value::Number(val) => write!(f, "{}", val),
28            Value::String(ref val) => write!(f, "{}", val),
29            Value::Nodeset(ref val) => write!(f, "Nodeset({:?})", val),
30        }
31    }
32}
33
34pub(crate) fn str_to_num(s: &str) -> f64 {
35    s.trim().parse().unwrap_or(f64::NAN)
36}
37
38/// Helper for `PartialEq<Value>` implementations
39fn str_vals(nodes: &[DomRoot<Node>]) -> HashSet<String> {
40    nodes
41        .iter()
42        .map(|n| n.GetTextContent().unwrap_or_default().to_string())
43        .collect()
44}
45
46/// Helper for `PartialEq<Value>` implementations
47fn num_vals(nodes: &[DomRoot<Node>]) -> Vec<f64> {
48    nodes
49        .iter()
50        .map(|n| Value::String(n.GetTextContent().unwrap_or_default().into()).number())
51        .collect()
52}
53
54impl PartialEq<Value> for Value {
55    fn eq(&self, other: &Value) -> bool {
56        match (self, other) {
57            (Value::Nodeset(left_nodes), Value::Nodeset(right_nodes)) => {
58                let left_strings = str_vals(left_nodes);
59                let right_strings = str_vals(right_nodes);
60                !left_strings.is_disjoint(&right_strings)
61            },
62            (&Value::Nodeset(ref nodes), &Value::Number(val)) |
63            (&Value::Number(val), &Value::Nodeset(ref nodes)) => {
64                let numbers = num_vals(nodes);
65                numbers.contains(&val)
66            },
67            (&Value::Nodeset(ref nodes), &Value::String(ref val)) |
68            (&Value::String(ref val), &Value::Nodeset(ref nodes)) => {
69                let strings = str_vals(nodes);
70                strings.contains(val)
71            },
72            (&Value::Boolean(_), _) | (_, &Value::Boolean(_)) => self.boolean() == other.boolean(),
73            (&Value::Number(_), _) | (_, &Value::Number(_)) => self.number() == other.number(),
74            _ => self.string() == other.string(),
75        }
76    }
77}
78
79impl Value {
80    pub(crate) fn boolean(&self) -> bool {
81        match *self {
82            Value::Boolean(val) => val,
83            Value::Number(n) => n != 0.0 && !n.is_nan(),
84            Value::String(ref s) => !s.is_empty(),
85            Value::Nodeset(ref nodeset) => !nodeset.is_empty(),
86        }
87    }
88
89    pub(crate) fn into_boolean(self) -> bool {
90        self.boolean()
91    }
92
93    pub(crate) fn number(&self) -> f64 {
94        match *self {
95            Value::Boolean(val) => {
96                if val {
97                    1.0
98                } else {
99                    0.0
100                }
101            },
102            Value::Number(val) => val,
103            Value::String(ref s) => str_to_num(s),
104            Value::Nodeset(..) => str_to_num(&self.string()),
105        }
106    }
107
108    pub(crate) fn into_number(self) -> f64 {
109        self.number()
110    }
111
112    pub(crate) fn string(&self) -> string::String {
113        match *self {
114            Value::Boolean(v) => v.to_string(),
115            Value::Number(n) => {
116                if n.is_infinite() {
117                    if n.signum() < 0.0 {
118                        "-Infinity".to_owned()
119                    } else {
120                        "Infinity".to_owned()
121                    }
122                } else if n == 0.0 {
123                    // catches -0.0 also
124                    0.0.to_string()
125                } else {
126                    n.to_string()
127                }
128            },
129            Value::String(ref val) => val.clone(),
130            Value::Nodeset(ref nodes) => match nodes.document_order_first() {
131                Some(n) => n.GetTextContent().unwrap_or_default().to_string(),
132                None => "".to_owned(),
133            },
134        }
135    }
136
137    pub(crate) fn into_string(self) -> string::String {
138        match self {
139            Value::String(val) => val,
140            other => other.string(),
141        }
142    }
143}
144
145macro_rules! from_impl {
146    ($raw:ty, $variant:expr) => {
147        impl From<$raw> for Value {
148            fn from(other: $raw) -> Value {
149                $variant(other)
150            }
151        }
152    };
153}
154
155from_impl!(bool, Value::Boolean);
156from_impl!(f64, Value::Number);
157from_impl!(String, Value::String);
158impl<'a> From<&'a str> for Value {
159    fn from(other: &'a str) -> Value {
160        Value::String(other.into())
161    }
162}
163from_impl!(Vec<DomRoot<Node>>, Value::Nodeset);
164
165macro_rules! partial_eq_impl {
166    ($raw:ty, $variant:pat => $b:expr) => {
167        impl PartialEq<$raw> for Value {
168            fn eq(&self, other: &$raw) -> bool {
169                match *self {
170                    $variant => $b == other,
171                    _ => false,
172                }
173            }
174        }
175
176        impl PartialEq<Value> for $raw {
177            fn eq(&self, other: &Value) -> bool {
178                match *other {
179                    $variant => $b == self,
180                    _ => false,
181                }
182            }
183        }
184    };
185}
186
187partial_eq_impl!(bool, Value::Boolean(ref v) => v);
188partial_eq_impl!(f64, Value::Number(ref v) => v);
189partial_eq_impl!(String, Value::String(ref v) => v);
190partial_eq_impl!(&str, Value::String(ref v) => v);
191partial_eq_impl!(Vec<DomRoot<Node>>, Value::Nodeset(ref v) => v);
192
193pub(crate) trait NodesetHelpers {
194    /// Returns the node that occurs first in [document order]
195    ///
196    /// [document order]: https://www.w3.org/TR/xpath/#dt-document-order
197    fn document_order_first(&self) -> Option<DomRoot<Node>>;
198    fn document_order(&self) -> Vec<DomRoot<Node>>;
199    fn document_order_unique(&self) -> Vec<DomRoot<Node>>;
200}
201
202impl NodesetHelpers for Vec<DomRoot<Node>> {
203    fn document_order_first(&self) -> Option<DomRoot<Node>> {
204        self.iter()
205            .min_by(|a, b| {
206                if a == b {
207                    std::cmp::Ordering::Equal
208                } else if a.is_before(b) {
209                    std::cmp::Ordering::Less
210                } else {
211                    std::cmp::Ordering::Greater
212                }
213            })
214            .cloned()
215    }
216    fn document_order(&self) -> Vec<DomRoot<Node>> {
217        let mut nodes: Vec<DomRoot<Node>> = self.clone();
218        if nodes.len() <= 1 {
219            return nodes;
220        }
221
222        nodes.sort_by(|a, b| {
223            if a == b {
224                std::cmp::Ordering::Equal
225            } else if a.is_before(b) {
226                std::cmp::Ordering::Less
227            } else {
228                std::cmp::Ordering::Greater
229            }
230        });
231
232        nodes
233    }
234    fn document_order_unique(&self) -> Vec<DomRoot<Node>> {
235        let mut seen = HashSet::new();
236        let unique_nodes: Vec<DomRoot<Node>> = self
237            .iter()
238            .filter(|node| seen.insert(node.to_opaque()))
239            .cloned()
240            .collect();
241
242        unique_nodes.document_order()
243    }
244}