use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use super::compare::{ieee_eq, xpath_number};
use super::float;
use crate::tree::{Document, NodeId, NodeKind};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
NodeSet(Vec<NodeId>),
String(String),
Number(f64),
Boolean(bool),
}
impl Value {
#[must_use]
pub fn to_boolean(&self) -> bool {
match self {
Self::Boolean(b) => *b,
Self::Number(n) => *n != 0.0 && !n.is_nan(),
Self::String(s) => !s.is_empty(),
Self::NodeSet(n) => !n.is_empty(),
}
}
#[must_use]
pub fn to_number(&self, doc: &Document) -> f64 {
match self {
Self::Number(n) => *n,
Self::Boolean(b) => f64::from(u8::from(*b)),
Self::String(s) => xpath_number(s),
Self::NodeSet(_) => xpath_number(&self.to_str(doc)),
}
}
#[must_use]
pub fn to_str(&self, doc: &Document) -> String {
match self {
Self::String(s) => s.clone(),
Self::Boolean(b) => {
if *b {
"true".to_owned()
} else {
"false".to_owned()
}
}
Self::Number(n) => format_number(*n),
Self::NodeSet(nodes) => nodes
.first()
.map(|id| string_value(doc, *id))
.unwrap_or_default(),
}
}
#[must_use]
pub fn nodes(&self) -> Option<&[NodeId]> {
match self {
Self::NodeSet(n) => Some(n),
_ => None,
}
}
pub(crate) const fn type_name(&self) -> &'static str {
match self {
Self::NodeSet(_) => "node-set",
Self::String(_) => "string",
Self::Number(_) => "number",
Self::Boolean(_) => "boolean",
}
}
}
fn format_number(n: f64) -> String {
if n.is_nan() {
return "NaN".to_owned();
}
if n.is_infinite() {
return if n > 0.0 { "Infinity" } else { "-Infinity" }.to_owned();
}
if ieee_eq(n, float::trunc(n)) && n.abs() < 1e21 {
return format!("{}", n as i64);
}
let rounded: f64 = format!("{n:.14e}").parse().unwrap_or(n);
let mut s = rounded.to_string();
if s.contains('.') {
s = s.trim_end_matches('0').trim_end_matches('.').to_owned();
}
s
}
pub(crate) fn string_value(doc: &Document, id: NodeId) -> String {
match doc.kind(id) {
Some(NodeKind::Attr(a)) => a.value.to_owned(),
Some(NodeKind::Comment(t) | NodeKind::Text(t)) => t.to_owned(),
Some(NodeKind::ProcessingInstruction { data, .. }) => data.to_owned(),
Some(NodeKind::Namespace { uri, .. }) => uri.to_owned(),
_ => {
let mut text = String::new();
for node in doc.descendants_of(id) {
if let Some(NodeKind::Text(t)) = doc.kind(node) {
text.push_str(t);
}
}
text
}
}
}