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(_)) => self.boolean() == other.boolean(),
62 (&Value::Number(_), _) | (_, &Value::Number(_)) => self.number() == other.number(),
63 _ => self.string() == other.string(),
64 }
65 }
66}
67
68impl<N: Node> Value<N> {
69 pub(crate) fn boolean(&self) -> bool {
71 match self {
72 Value::Boolean(boolean) => *boolean,
73 Value::Number(number) => *number != 0.0 && !number.is_nan(),
74 Value::String(string) => !string.is_empty(),
75 Value::Nodeset(nodeset) => !nodeset.is_empty(),
76 }
77 }
78
79 pub(crate) fn number(&self) -> f64 {
81 match self {
82 Value::Boolean(boolean) => {
83 if *boolean {
84 1.0
85 } else {
86 0.0
87 }
88 },
89 Value::Number(number) => *number,
90 Value::String(string) => parse_number_from_string(string),
91 Value::Nodeset(_) => parse_number_from_string(&self.string()),
92 }
93 }
94
95 pub(crate) fn string(&self) -> String {
97 match self {
98 Value::Boolean(value) => value.to_string(),
99 Value::Number(number) => {
100 if number.is_infinite() {
101 if number.is_sign_negative() {
102 "-Infinity".to_owned()
103 } else {
104 "Infinity".to_owned()
105 }
106 } else if *number == 0.0 {
107 "0".into()
109 } else {
110 number.to_string()
111 }
112 },
113 Value::String(string) => string.to_owned(),
114 Value::Nodeset(nodes) => nodes
115 .document_order_first()
116 .as_ref()
117 .map(Node::text_content)
118 .unwrap_or_default(),
119 }
120 }
121}
122
123macro_rules! from_impl {
124 ($raw:ty, $variant:expr) => {
125 impl<N: Node> From<$raw> for Value<N> {
126 fn from(other: $raw) -> Self {
127 $variant(other)
128 }
129 }
130 };
131}
132
133from_impl!(bool, Value::Boolean);
134from_impl!(f64, Value::Number);
135from_impl!(String, Value::String);
136impl<'a, N: Node> From<&'a str> for Value<N> {
137 fn from(other: &'a str) -> Self {
138 Value::String(other.into())
139 }
140}
141from_impl!(Vec<N>, Value::Nodeset);
142
143macro_rules! partial_eq_impl {
144 ($raw:ty, $variant:pat => $b:expr) => {
145 impl<N: Node> PartialEq<$raw> for Value<N> {
146 fn eq(&self, other: &$raw) -> bool {
147 match *self {
148 $variant => $b == other,
149 _ => false,
150 }
151 }
152 }
153
154 impl<N: Node> PartialEq<Value<N>> for $raw {
155 fn eq(&self, other: &Value<N>) -> bool {
156 match *other {
157 $variant => $b == self,
158 _ => false,
159 }
160 }
161 }
162 };
163}
164
165partial_eq_impl!(bool, Value::Boolean(ref v) => v);
166partial_eq_impl!(f64, Value::Number(ref v) => v);
167partial_eq_impl!(String, Value::String(ref v) => v);
168partial_eq_impl!(&str, Value::String(ref v) => v);
169partial_eq_impl!(Vec<N>, Value::Nodeset(ref v) => v);
170
171pub trait NodesetHelpers<N: Node> {
172 fn document_order_first(&self) -> Option<N>;
176 fn document_order(&self) -> Vec<N>;
177 fn document_order_unique(&self) -> Vec<N>;
178}
179
180impl<N: Node> NodesetHelpers<N> for Vec<N> {
181 fn document_order_first(&self) -> Option<N> {
182 self.iter().min_by(|a, b| a.compare_tree_order(b)).cloned()
183 }
184
185 fn document_order(&self) -> Vec<N> {
186 let mut nodes: Vec<N> = self.clone();
187 if nodes.len() <= 1 {
188 return nodes;
189 }
190
191 nodes.sort_by(|a, b| a.compare_tree_order(b));
192
193 nodes
194 }
195
196 fn document_order_unique(&self) -> Vec<N> {
197 let mut seen = HashSet::new();
198 let unique_nodes: Vec<N> = self
199 .iter()
200 .filter(|node| seen.insert(node.to_opaque()))
201 .cloned()
202 .collect();
203
204 unique_nodes.document_order()
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::f64;
211
212 use crate::dummy_implementation;
213
214 type Value = super::Value<dummy_implementation::DummyNode>;
215
216 #[test]
217 fn string_value_to_number() {
218 assert_eq!(Value::String("42.123".into()).number(), 42.123);
219 assert_eq!(Value::String(" 42\n".into()).number(), 42.);
220 assert!(Value::String("totally-invalid".into()).number().is_nan());
221
222 assert!(Value::String("\u{2004}42".into()).number().is_nan());
224 }
225
226 #[test]
227 fn number_value_to_string() {
228 assert_eq!(Value::Number(f64::NAN).string(), "NaN");
229 assert_eq!(Value::Number(0.).string(), "0");
230 assert_eq!(Value::Number(-0.).string(), "0");
231 assert_eq!(Value::Number(f64::INFINITY).string(), "Infinity");
232 assert_eq!(Value::Number(f64::NEG_INFINITY).string(), "-Infinity");
233 assert_eq!(Value::Number(42.0).string(), "42");
234 assert_eq!(Value::Number(-42.0).string(), "-42");
235 assert_eq!(Value::Number(0.75).string(), "0.75");
236 assert_eq!(Value::Number(-0.75).string(), "-0.75");
237 }
238
239 #[test]
240 fn boolean_value_to_string() {
241 assert_eq!(Value::Boolean(false).string(), "false");
242 assert_eq!(Value::Boolean(true).string(), "true");
243 }
244}