use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use super::{Value, XPath, XPathError, float};
use crate::tree::{Document, NodeId};
pub trait FromXPath: Sized {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeError {
pub message: String,
}
impl core::fmt::Display for TypeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.message)
}
}
#[cfg(feature = "std")]
impl std::error::Error for TypeError {}
impl FromXPath for String {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError> {
Ok(value.to_str(doc))
}
}
impl FromXPath for f64 {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError> {
let n = value.to_number(doc);
if n.is_nan() {
return Err(TypeError {
message: format!("`{}` is not a number", value.to_str(doc)),
});
}
Ok(n)
}
}
impl FromXPath for i64 {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError> {
let n = f64::from_value(value, doc)?;
#[allow(clippy::float_cmp)]
let fractional = n != float::trunc(n);
if fractional {
return Err(TypeError {
message: format!("`{n}` is not an integer"),
});
}
if n.abs() > 9_007_199_254_740_992.0 {
return Err(TypeError {
message: format!(
"`{n}` is outside the exactly-representable range"
),
});
}
#[allow(clippy::cast_possible_truncation)]
Ok(n as Self)
}
}
impl FromXPath for bool {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError> {
let _ = doc;
Ok(value.to_boolean())
}
}
impl FromXPath for NodeId {
fn from_value(value: &Value, doc: &Document) -> Result<Self, TypeError> {
let _ = doc;
match value.nodes() {
Some([first, ..]) => Ok(*first),
Some([]) => Err(TypeError {
message: String::from("the expression matched no nodes"),
}),
None => Err(TypeError {
message: String::from(
"the expression does not produce a node-set",
),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryError {
Compile(XPathError),
Type(TypeError),
}
impl core::fmt::Display for QueryError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Compile(e) => write!(f, "{e}"),
Self::Type(e) => write!(f, "{e}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for QueryError {}
impl Document {
pub fn xpath_one<T: FromXPath>(&self, expr: &str) -> Result<T, QueryError> {
let compiled = XPath::compile(expr).map_err(QueryError::Compile)?;
let value = compiled.evaluate(self);
T::from_value(&value, self).map_err(QueryError::Type)
}
pub fn xpath_all<T: FromXPath>(
&self,
expr: &str,
) -> Result<Vec<T>, QueryError> {
let compiled = XPath::compile(expr).map_err(QueryError::Compile)?;
let value = compiled.evaluate(self);
let Some(nodes) = value.nodes() else {
return Err(QueryError::Type(TypeError {
message: String::from(
"the expression does not produce a node-set",
),
}));
};
nodes
.iter()
.map(|id| {
let single = Value::NodeSet(alloc::vec![*id]);
T::from_value(&single, self).map_err(QueryError::Type)
})
.collect()
}
}