/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::ToOwned; use std::collections::HashSet; use std::{fmt, string}; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::utils::AsVoidPtr; use crate::dom::node::Node; /// The primary types of values that an XPath expression returns as a result. pub(crate) enum Value { Boolean(bool), /// A IEEE-754 double-precision floating point number Number(f64), String(String), /// A collection of not-necessarily-unique nodes Nodeset(Vec>), } impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Value::Boolean(val) => write!(f, "{}", val), Value::Number(val) => write!(f, "{}", val), Value::String(ref val) => write!(f, "{}", val), Value::Nodeset(ref val) => write!(f, "Nodeset({:?})", val), } } } pub(crate) fn str_to_num(s: &str) -> f64 { s.trim().parse().unwrap_or(f64::NAN) } /// Helper for `PartialEq` implementations fn str_vals(nodes: &[DomRoot]) -> HashSet { nodes .iter() .map(|n| n.GetTextContent().unwrap_or_default().to_string()) .collect() } /// Helper for `PartialEq` implementations fn num_vals(nodes: &[DomRoot]) -> Vec { nodes .iter() .map(|n| Value::String(n.GetTextContent().unwrap_or_default().into()).number()) .collect() } impl PartialEq for Value { fn eq(&self, other: &Value) -> bool { match (self, other) { (Value::Nodeset(left_nodes), Value::Nodeset(right_nodes)) => { let left_strings = str_vals(left_nodes); let right_strings = str_vals(right_nodes); !left_strings.is_disjoint(&right_strings) }, (&Value::Nodeset(ref nodes), &Value::Number(val)) | (&Value::Number(val), &Value::Nodeset(ref nodes)) => { let numbers = num_vals(nodes); numbers.iter().any(|n| *n == val) }, (&Value::Nodeset(ref nodes), &Value::String(ref val)) | (&Value::String(ref val), &Value::Nodeset(ref nodes)) => { let strings = str_vals(nodes); strings.contains(val) }, (&Value::Boolean(_), _) | (_, &Value::Boolean(_)) => self.boolean() == other.boolean(), (&Value::Number(_), _) | (_, &Value::Number(_)) => self.number() == other.number(), _ => self.string() == other.string(), } } } impl Value { pub(crate) fn boolean(&self) -> bool { match *self { Value::Boolean(val) => val, Value::Number(n) => n != 0.0 && !n.is_nan(), Value::String(ref s) => !s.is_empty(), Value::Nodeset(ref nodeset) => !nodeset.is_empty(), } } pub(crate) fn into_boolean(self) -> bool { self.boolean() } pub(crate) fn number(&self) -> f64 { match *self { Value::Boolean(val) => { if val { 1.0 } else { 0.0 } }, Value::Number(val) => val, Value::String(ref s) => str_to_num(s), Value::Nodeset(..) => str_to_num(&self.string()), } } pub(crate) fn into_number(self) -> f64 { self.number() } pub(crate) fn string(&self) -> string::String { match *self { Value::Boolean(v) => v.to_string(), Value::Number(n) => { if n.is_infinite() { if n.signum() < 0.0 { "-Infinity".to_owned() } else { "Infinity".to_owned() } } else if n == 0.0 { // catches -0.0 also 0.0.to_string() } else { n.to_string() } }, Value::String(ref val) => val.clone(), Value::Nodeset(ref nodes) => match nodes.document_order_first() { Some(n) => n.GetTextContent().unwrap_or_default().to_string(), None => "".to_owned(), }, } } pub(crate) fn into_string(self) -> string::String { match self { Value::String(val) => val, other => other.string(), } } } macro_rules! from_impl { ($raw:ty, $variant:expr) => { impl From<$raw> for Value { fn from(other: $raw) -> Value { $variant(other) } } }; } from_impl!(bool, Value::Boolean); from_impl!(f64, Value::Number); from_impl!(String, Value::String); impl<'a> From<&'a str> for Value { fn from(other: &'a str) -> Value { Value::String(other.into()) } } from_impl!(Vec>, Value::Nodeset); macro_rules! partial_eq_impl { ($raw:ty, $variant:pat => $b:expr) => { impl PartialEq<$raw> for Value { fn eq(&self, other: &$raw) -> bool { match *self { $variant => $b == other, _ => false, } } } impl PartialEq for $raw { fn eq(&self, other: &Value) -> bool { match *other { $variant => $b == self, _ => false, } } } }; } partial_eq_impl!(bool, Value::Boolean(ref v) => v); partial_eq_impl!(f64, Value::Number(ref v) => v); partial_eq_impl!(String, Value::String(ref v) => v); partial_eq_impl!(&str, Value::String(ref v) => v); partial_eq_impl!(Vec>, Value::Nodeset(ref v) => v); pub(crate) trait NodesetHelpers { /// Returns the node that occurs first in [document order] /// /// [document order]: https://www.w3.org/TR/xpath/#dt-document-order fn document_order_first(&self) -> Option>; fn document_order(&self) -> Vec>; fn document_order_unique(&self) -> Vec>; } impl NodesetHelpers for Vec> { fn document_order_first(&self) -> Option> { self.iter() .min_by(|a, b| { if a == b { std::cmp::Ordering::Equal } else if a.is_before(b) { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater } }) .cloned() } fn document_order(&self) -> Vec> { let mut nodes: Vec> = self.clone(); if nodes.len() == 1 { return nodes; } nodes.sort_by(|a, b| { if a == b { std::cmp::Ordering::Equal } else if a.is_before(b) { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater } }); nodes } fn document_order_unique(&self) -> Vec> { let mut nodes: Vec> = self.document_order(); nodes.dedup_by_key(|n| n.as_void_ptr()); nodes } }