script/xpath/
eval_value.rs1use 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
13pub(crate) enum Value {
15 Boolean(bool),
16 Number(f64),
18 String(String),
19 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
38fn 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
46fn 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 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 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}