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;
pub enum Value {
Boolean(bool),
Number(f64),
String(String),
Nodeset(Vec<DomRoot<Node>>),
}
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 fn str_to_num(s: &str) -> f64 {
s.trim().parse().unwrap_or(f64::NAN)
}
fn str_vals(nodes: &[DomRoot<Node>]) -> HashSet<String> {
nodes
.iter()
.map(|n| n.GetTextContent().unwrap_or_default().to_string())
.collect()
}
fn num_vals(nodes: &[DomRoot<Node>]) -> Vec<f64> {
nodes
.iter()
.map(|n| Value::String(n.GetTextContent().unwrap_or_default().into()).number())
.collect()
}
impl PartialEq<Value> 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 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 fn into_boolean(self) -> bool {
self.boolean()
}
pub 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 fn into_number(self) -> f64 {
self.number()
}
pub 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 {
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 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<DomRoot<Node>>, 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<Value> 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<DomRoot<Node>>, Value::Nodeset(ref v) => v);
pub trait NodesetHelpers {
fn document_order_first(&self) -> Option<DomRoot<Node>>;
fn document_order(&self) -> Vec<DomRoot<Node>>;
fn document_order_unique(&self) -> Vec<DomRoot<Node>>;
}
impl NodesetHelpers for Vec<DomRoot<Node>> {
fn document_order_first(&self) -> Option<DomRoot<Node>> {
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<DomRoot<Node>> {
let mut nodes: Vec<DomRoot<Node>> = 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<DomRoot<Node>> {
let mut nodes: Vec<DomRoot<Node>> = self.document_order();
nodes.dedup_by_key(|n| n.as_void_ptr());
nodes
}
}