1use std::borrow::ToOwned;
6use std::collections::HashSet;
7
8use crate::Node;
9
10#[derive(Debug)]
12pub enum Value<N: Node> {
13 Boolean(bool),
14 Number(f64),
16 String(String),
17 Nodeset(Vec<N>),
19}
20
21pub(crate) fn parse_number_from_string(string: &str) -> f64 {
22 string.trim_ascii().parse().unwrap_or(f64::NAN)
31}
32
33fn num_vals<N: Node>(nodes: &[N]) -> Vec<f64> {
35 nodes
36 .iter()
37 .map(|node| parse_number_from_string(&node.text_content()))
38 .collect()
39}
40
41impl<N: Node> PartialEq<Value<N>> for Value<N> {
42 fn eq(&self, other: &Value<N>) -> bool {
43 match (self, other) {
44 (Value::Nodeset(left_nodes), Value::Nodeset(right_nodes)) => {
45 let left_strings: HashSet<String> =
46 left_nodes.iter().map(|node| node.text_content()).collect();
47 let right_strings: HashSet<String> =
48 right_nodes.iter().map(|node| node.text_content()).collect();
49 !left_strings.is_disjoint(&right_strings)
50 },
51 (&Value::Nodeset(ref nodes), &Value::Number(val)) |
52 (&Value::Number(val), &Value::Nodeset(ref nodes)) => {
53 let numbers = num_vals(nodes);
54 numbers.contains(&val)
55 },
56 (&Value::Nodeset(ref nodes), &Value::String(ref string)) |
57 (&Value::String(ref string), &Value::Nodeset(ref nodes)) => nodes
58 .iter()
59 .map(|node| node.text_content())
60 .any(|text_content| &text_content == string),
61 (&Value::Boolean(_), _) | (_, &Value::Boolean(_)) => {
62 self.convert_to_boolean() == other.convert_to_boolean()
63 },
64 (&Value::Number(_), _) | (_, &Value::Number(_)) => {
65 self.convert_to_number() == other.convert_to_number()
66 },
67 _ => self.convert_to_string() == other.convert_to_string(),
68 }
69 }
70}
71
72impl<N: Node> Value<N> {
73 pub fn convert_to_boolean(&self) -> bool {
75 match self {
76 Value::Boolean(boolean) => *boolean,
77 Value::Number(number) => *number != 0.0 && !number.is_nan(),
78 Value::String(string) => !string.is_empty(),
79 Value::Nodeset(nodeset) => !nodeset.is_empty(),
80 }
81 }
82
83 pub fn convert_to_number(&self) -> f64 {
85 match self {
86 Value::Boolean(boolean) => {
87 if *boolean {
88 1.0
89 } else {
90 0.0
91 }
92 },
93 Value::Number(number) => *number,
94 Value::String(string) => parse_number_from_string(string),
95 Value::Nodeset(_) => parse_number_from_string(&self.convert_to_string()),
96 }
97 }
98
99 pub fn convert_to_string(&self) -> String {
101 match self {
102 Value::Boolean(value) => value.to_string(),
103 Value::Number(number) => {
104 if number.is_infinite() {
105 if number.is_sign_negative() {
106 "-Infinity".to_owned()
107 } else {
108 "Infinity".to_owned()
109 }
110 } else if *number == 0.0 {
111 "0".into()
113 } else {
114 number.to_string()
115 }
116 },
117 Value::String(string) => string.to_owned(),
118 Value::Nodeset(nodes) => nodes
119 .document_order_first()
120 .as_ref()
121 .map(Node::text_content)
122 .unwrap_or_default(),
123 }
124 }
125}
126
127macro_rules! from_impl {
128 ($raw:ty, $variant:expr) => {
129 impl<N: Node> From<$raw> for Value<N> {
130 fn from(other: $raw) -> Self {
131 $variant(other)
132 }
133 }
134 };
135}
136
137from_impl!(bool, Value::Boolean);
138from_impl!(f64, Value::Number);
139from_impl!(String, Value::String);
140impl<'a, N: Node> From<&'a str> for Value<N> {
141 fn from(other: &'a str) -> Self {
142 Value::String(other.into())
143 }
144}
145from_impl!(Vec<N>, Value::Nodeset);
146
147macro_rules! partial_eq_impl {
148 ($raw:ty, $variant:pat => $b:expr) => {
149 impl<N: Node> PartialEq<$raw> for Value<N> {
150 fn eq(&self, other: &$raw) -> bool {
151 match *self {
152 $variant => $b == other,
153 _ => false,
154 }
155 }
156 }
157
158 impl<N: Node> PartialEq<Value<N>> for $raw {
159 fn eq(&self, other: &Value<N>) -> bool {
160 match *other {
161 $variant => $b == self,
162 _ => false,
163 }
164 }
165 }
166 };
167}
168
169partial_eq_impl!(bool, Value::Boolean(ref v) => v);
170partial_eq_impl!(f64, Value::Number(ref v) => v);
171partial_eq_impl!(String, Value::String(ref v) => v);
172partial_eq_impl!(&str, Value::String(ref v) => v);
173partial_eq_impl!(Vec<N>, Value::Nodeset(ref v) => v);
174
175pub trait NodesetHelpers<N: Node> {
176 fn document_order_first(&self) -> Option<N>;
180 fn document_order(&self) -> Vec<N>;
181 fn document_order_unique(&self) -> Vec<N>;
182}
183
184impl<N: Node> NodesetHelpers<N> for Vec<N> {
185 fn document_order_first(&self) -> Option<N> {
186 self.iter().min_by(|a, b| a.compare_tree_order(b)).cloned()
187 }
188
189 fn document_order(&self) -> Vec<N> {
190 let mut nodes: Vec<N> = self.clone();
191 if nodes.len() <= 1 {
192 return nodes;
193 }
194
195 nodes.sort_by(|a, b| a.compare_tree_order(b));
196
197 nodes
198 }
199
200 fn document_order_unique(&self) -> Vec<N> {
201 let mut seen = HashSet::new();
202 let unique_nodes: Vec<N> = self
203 .iter()
204 .filter(|node| seen.insert(node.to_opaque()))
205 .cloned()
206 .collect();
207
208 unique_nodes.document_order()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use std::f64;
215
216 use crate::dummy_implementation;
217
218 type Value = super::Value<dummy_implementation::DummyNode>;
219
220 #[test]
221 fn string_value_to_number() {
222 assert_eq!(Value::String("42.123".into()).convert_to_number(), 42.123);
223 assert_eq!(Value::String(" 42\n".into()).convert_to_number(), 42.);
224 assert!(
225 Value::String("totally-invalid".into())
226 .convert_to_number()
227 .is_nan()
228 );
229
230 assert!(
232 Value::String("\u{2004}42".into())
233 .convert_to_number()
234 .is_nan()
235 );
236 }
237
238 #[test]
239 fn number_value_to_string() {
240 assert_eq!(Value::Number(f64::NAN).convert_to_string(), "NaN");
241 assert_eq!(Value::Number(0.).convert_to_string(), "0");
242 assert_eq!(Value::Number(-0.).convert_to_string(), "0");
243 assert_eq!(Value::Number(f64::INFINITY).convert_to_string(), "Infinity");
244 assert_eq!(
245 Value::Number(f64::NEG_INFINITY).convert_to_string(),
246 "-Infinity"
247 );
248 assert_eq!(Value::Number(42.0).convert_to_string(), "42");
249 assert_eq!(Value::Number(-42.0).convert_to_string(), "-42");
250 assert_eq!(Value::Number(0.75).convert_to_string(), "0.75");
251 assert_eq!(Value::Number(-0.75).convert_to_string(), "-0.75");
252 }
253
254 #[test]
255 fn boolean_value_to_string() {
256 assert_eq!(Value::Boolean(false).convert_to_string(), "false");
257 assert_eq!(Value::Boolean(true).convert_to_string(), "true");
258 }
259}