pub mod aggregate;
pub mod datetime;
pub mod extensible;
pub mod node;
pub mod numeric;
pub mod qname;
pub mod regex;
pub mod registry;
pub mod sequence;
pub mod signature;
pub mod special;
pub mod string;
pub mod uri;
pub use extensible::{
BuiltinCatalog, BuiltinEvaluator, CustomFn, DynamicFunctionSignature, FunctionCatalog,
FunctionEvaluator, FunctionHandle, FunctionSet, XPath10Catalog, XPath10Evaluator,
};
pub use registry::{FunctionEntry, FunctionKey, FunctionRegistry, FUNCTION_REGISTRY};
pub use signature::{FunctionArity, FunctionSignature, FN_2010_NAMESPACE, FN_NAMESPACE};
use num_bigint::BigInt;
use crate::types::value::XmlValue;
use crate::types::XmlTypeCode;
use crate::xpath::atomize;
use crate::xpath::error::XPathError;
use crate::xpath::iterator::XmlItem;
use crate::xpath::DomNavigator;
use super::context::DynamicContext;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum FunctionId {
Concat = 1,
StringJoin,
Substring,
StringLength,
NormalizeSpace,
NormalizeUnicode,
UpperCase,
LowerCase,
Translate,
EncodeForUri,
IriToUri,
EscapeHtmlUri,
Contains,
StartsWith,
EndsWith,
SubstringBefore,
SubstringAfter,
StringToCodepoints,
CodepointsToString,
Compare,
CodepointEqual,
Abs = 100,
Ceiling,
Floor,
Round,
RoundHalfToEven,
Empty = 200,
Exists,
Reverse,
IndexOf,
Remove,
InsertBefore,
Subsequence,
Unordered,
ZeroOrOne,
OneOrMore,
ExactlyOne,
DistinctValues,
DeepEqual,
Count,
Sum = 300,
Avg,
Min,
Max,
Name = 400,
LocalName,
NamespaceUri,
NodeName,
Nilled,
BaseUri,
DocumentUri,
Lang,
Root,
Id,
Collection,
DateTime = 500,
CurrentDateTime,
CurrentDate,
CurrentTime,
ImplicitTimezone,
YearsFromDuration,
MonthsFromDuration,
DaysFromDuration,
HoursFromDuration,
MinutesFromDuration,
SecondsFromDuration,
YearFromDateTime,
MonthFromDateTime,
DayFromDateTime,
HoursFromDateTime,
MinutesFromDateTime,
SecondsFromDateTime,
TimezoneFromDateTime,
YearFromDate,
MonthFromDate,
DayFromDate,
TimezoneFromDate,
HoursFromTime,
MinutesFromTime,
SecondsFromTime,
TimezoneFromTime,
AdjustDateTimeToTimezone,
AdjustDateToTimezone,
AdjustTimeToTimezone,
ResolveQName = 600,
QName,
PrefixFromQName,
LocalNameFromQName,
NamespaceUriFromQName,
NamespaceUriForPrefix,
InScopePrefixes,
ResolveUri = 700,
StaticBaseUri,
Matches = 800,
Replace,
Tokenize,
Position = 900,
Last,
Trace,
Data,
DefaultCollation,
True = 1000,
False,
Not,
Boolean,
String = 1100,
Number,
}
#[derive(Debug, Clone)]
pub enum XPathValue<N: DomNavigator> {
Empty,
Item(XmlItem<N>),
Sequence(Vec<XmlItem<N>>),
}
impl<N: DomNavigator> XPathValue<N> {
pub fn empty() -> Self {
Self::Empty
}
pub fn from_item(item: XmlItem<N>) -> Self {
Self::Item(item)
}
pub fn from_atomic(value: XmlValue) -> Self {
Self::Item(XmlItem::Atomic(value))
}
pub fn from_node(node: N) -> Self {
Self::Item(XmlItem::Node(node))
}
pub fn from_sequence(items: Vec<XmlItem<N>>) -> Self {
match items.len() {
0 => Self::Empty,
1 => Self::Item(items.into_iter().next().unwrap()),
_ => Self::Sequence(items),
}
}
pub fn boolean(b: bool) -> Self {
Self::from_atomic(XmlValue::boolean(b))
}
pub fn string(s: impl Into<String>) -> Self {
Self::from_atomic(XmlValue::string(s))
}
pub fn integer(i: impl Into<num_bigint::BigInt>) -> Self {
Self::from_atomic(XmlValue::integer(i.into()))
}
pub fn decimal(d: rust_decimal::Decimal) -> Self {
Self::from_atomic(XmlValue::decimal(d))
}
pub fn double(d: f64) -> Self {
Self::from_atomic(XmlValue::double(d))
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
pub fn len(&self) -> usize {
match self {
Self::Empty => 0,
Self::Item(_) => 1,
Self::Sequence(items) => items.len(),
}
}
pub fn is_single(&self) -> bool {
matches!(self, Self::Item(_))
}
pub fn into_vec(self) -> Vec<XmlItem<N>> {
match self {
Self::Empty => Vec::new(),
Self::Item(item) => vec![item],
Self::Sequence(items) => items,
}
}
pub fn as_slice(&self) -> &[XmlItem<N>] {
match self {
Self::Empty => &[],
Self::Item(_) => {
&[]
}
Self::Sequence(items) => items,
}
}
pub fn first(&self) -> Option<&XmlItem<N>> {
match self {
Self::Empty => None,
Self::Item(item) => Some(item),
Self::Sequence(items) => items.first(),
}
}
pub fn as_str(&self) -> Option<String> {
match self {
Self::Item(XmlItem::Atomic(v)) => v.as_string().map(|s| s.to_string()),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Item(XmlItem::Atomic(v)) => v.as_boolean(),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Item(XmlItem::Atomic(v)) => v.as_double(),
_ => None,
}
}
pub fn as_integer(&self) -> Option<num_bigint::BigInt> {
match self {
Self::Item(XmlItem::Atomic(v)) => v.as_integer().cloned(),
_ => None,
}
}
}
pub fn atomize_to_string<N: DomNavigator>(value: XPathValue<N>) -> Result<String, XPathError> {
match value {
XPathValue::Empty => Ok(String::new()),
XPathValue::Item(item) => item_to_string(item),
XPathValue::Sequence(items) => {
if items.len() == 1 {
item_to_string(items.into_iter().next().unwrap())
} else {
Err(XPathError::more_than_one_item())
}
}
}
}
pub fn atomize_to_string_required<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<String, XPathError> {
match value {
XPathValue::Empty => Err(XPathError::XPTY0004 {
expected: "xs:string".to_string(),
found: "empty-sequence()".to_string(),
}),
other => atomize_to_string(other),
}
}
pub fn atomize_to_string_strict<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<String, XPathError> {
match value {
XPathValue::Empty => Ok(String::new()),
XPathValue::Item(item) => item_to_string_strict(item),
XPathValue::Sequence(items) => {
if items.len() == 1 {
item_to_string_strict(items.into_iter().next().unwrap())
} else {
Err(XPathError::more_than_one_item())
}
}
}
}
pub fn atomize_to_string_strict_opt<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<Option<String>, XPathError> {
match value {
XPathValue::Empty => Ok(None),
other => atomize_to_string_strict(other).map(Some),
}
}
fn item_to_string_strict<N: DomNavigator>(item: XmlItem<N>) -> Result<String, XPathError> {
match item {
XmlItem::Atomic(value) => match value.type_code {
XmlTypeCode::String
| XmlTypeCode::UntypedAtomic
| XmlTypeCode::AnyUri
| XmlTypeCode::NormalizedString
| XmlTypeCode::Token
| XmlTypeCode::Language
| XmlTypeCode::NmToken
| XmlTypeCode::Name
| XmlTypeCode::NCName
| XmlTypeCode::Id
| XmlTypeCode::IdRef
| XmlTypeCode::Entity => Ok(atomize::string_value(&value)),
_ => Err(XPathError::XPTY0004 {
expected: "xs:string".to_string(),
found: crate::xpath::type_info::type_code_to_name(value.type_code).to_string(),
}),
},
XmlItem::Node(nav) => Ok(nav.value()),
}
}
pub fn atomize_to_string_opt<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<Option<String>, XPathError> {
match value {
XPathValue::Empty => Ok(None),
other => atomize_to_string(other).map(Some),
}
}
fn item_to_string<N: DomNavigator>(item: XmlItem<N>) -> Result<String, XPathError> {
match item {
XmlItem::Atomic(value) => Ok(atomize::string_value(&value)),
XmlItem::Node(nav) => Ok(nav.value()),
}
}
pub fn atomize_to_double<N: DomNavigator>(value: XPathValue<N>) -> Result<f64, XPathError> {
match value {
XPathValue::Empty => Ok(f64::NAN),
XPathValue::Item(item) => item_to_double(item),
XPathValue::Sequence(items) => {
if items.len() == 1 {
item_to_double(items.into_iter().next().unwrap())
} else {
Err(XPathError::more_than_one_item())
}
}
}
}
fn item_to_double<N: DomNavigator>(item: XmlItem<N>) -> Result<f64, XPathError> {
match item {
XmlItem::Atomic(value) => Ok(atomize::to_number(&value)),
XmlItem::Node(nav) => {
let s = nav.value();
Ok(s.trim().parse().unwrap_or(f64::NAN))
}
}
}
pub fn atomize_to_single<N: DomNavigator>(value: XPathValue<N>) -> Result<XmlValue, XPathError> {
match value {
XPathValue::Empty => Err(XPathError::XPTY0004 {
expected: "item()".to_string(),
found: "empty-sequence()".to_string(),
}),
XPathValue::Item(item) => item_to_atomic(item),
XPathValue::Sequence(items) => {
if items.len() == 1 {
item_to_atomic(items.into_iter().next().unwrap())
} else {
Err(XPathError::more_than_one_item())
}
}
}
}
pub fn atomize_to_single_opt<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<Option<XmlValue>, XPathError> {
match value {
XPathValue::Empty => Ok(None),
other => atomize_to_single(other).map(Some),
}
}
fn item_to_atomic<N: DomNavigator>(item: XmlItem<N>) -> Result<XmlValue, XPathError> {
match item {
XmlItem::Atomic(value) => atomize::atomize(&value),
XmlItem::Node(nav) => atomize::atomize_node(&nav)?
.ok_or_else(|| XPathError::type_mismatch("item()", "empty-sequence()")),
}
}
pub fn atomize_sequence<N: DomNavigator>(
value: XPathValue<N>,
) -> Result<Vec<XmlValue>, XPathError> {
match value {
XPathValue::Empty => Ok(Vec::new()),
XPathValue::Item(item) => match item {
XmlItem::Atomic(value) => Ok(vec![atomize::atomize(&value)?]),
XmlItem::Node(nav) => match atomize::atomize_node(&nav)? {
Some(v) => Ok(vec![v]),
None => Ok(Vec::new()),
},
},
XPathValue::Sequence(items) => {
let mut result = Vec::with_capacity(items.len());
for item in items {
match item {
XmlItem::Atomic(value) => result.push(atomize::atomize(&value)?),
XmlItem::Node(nav) => {
if let Some(v) = atomize::atomize_node(&nav)? {
result.push(v);
}
}
}
}
Ok(result)
}
}
}
pub fn materialize<N: DomNavigator>(value: XPathValue<N>) -> Vec<XmlItem<N>> {
value.into_vec()
}
pub fn eval_function<N: DomNavigator>(
id: FunctionId,
context: &mut DynamicContext<'_, N>,
args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
match id {
FunctionId::True => Ok(XPathValue::boolean(true)),
FunctionId::False => Ok(XPathValue::boolean(false)),
FunctionId::Not => eval_not(args),
FunctionId::Position => special::position(context, args),
FunctionId::Last => special::last(context, args),
FunctionId::Trace => special::trace(context, args),
FunctionId::Data => special::data(context, args),
FunctionId::DefaultCollation => special::default_collation(context, args),
FunctionId::Empty => eval_empty(args),
FunctionId::Exists => eval_exists(args),
FunctionId::Count => eval_count(args),
FunctionId::Concat => string::concat(context, args),
FunctionId::StringJoin => string::string_join(context, args),
FunctionId::Substring => string::substring(context, args),
FunctionId::StringLength => string::string_length(context, args),
FunctionId::NormalizeSpace => string::normalize_space(context, args),
FunctionId::NormalizeUnicode => string::normalize_unicode(context, args),
FunctionId::UpperCase => string::upper_case(context, args),
FunctionId::LowerCase => string::lower_case(context, args),
FunctionId::Translate => string::translate(context, args),
FunctionId::EncodeForUri => string::encode_for_uri(context, args),
FunctionId::IriToUri => string::iri_to_uri(context, args),
FunctionId::EscapeHtmlUri => string::escape_html_uri(context, args),
FunctionId::Contains => string::contains(context, args),
FunctionId::StartsWith => string::starts_with(context, args),
FunctionId::EndsWith => string::ends_with(context, args),
FunctionId::SubstringBefore => string::substring_before(context, args),
FunctionId::SubstringAfter => string::substring_after(context, args),
FunctionId::StringToCodepoints => string::string_to_codepoints(context, args),
FunctionId::CodepointsToString => string::codepoints_to_string(context, args),
FunctionId::Compare => string::compare(context, args),
FunctionId::CodepointEqual => string::codepoint_equal(context, args),
FunctionId::Abs => numeric::abs(context, args),
FunctionId::Ceiling => numeric::ceiling(context, args),
FunctionId::Floor => numeric::floor(context, args),
FunctionId::Round => numeric::round(context, args),
FunctionId::RoundHalfToEven => numeric::round_half_to_even(context, args),
FunctionId::Reverse => sequence::reverse(context, args),
FunctionId::ZeroOrOne => sequence::zero_or_one(context, args),
FunctionId::OneOrMore => sequence::one_or_more(context, args),
FunctionId::ExactlyOne => sequence::exactly_one(context, args),
FunctionId::DistinctValues => sequence::distinct_values(context, args),
FunctionId::IndexOf => sequence::index_of(context, args),
FunctionId::Remove => sequence::remove(context, args),
FunctionId::InsertBefore => sequence::insert_before(context, args),
FunctionId::Subsequence => sequence::subsequence(context, args),
FunctionId::Unordered => sequence::unordered(context, args),
FunctionId::DeepEqual => sequence::deep_equal(context, args),
FunctionId::Sum => aggregate::sum(context, args),
FunctionId::Avg => aggregate::avg(context, args),
FunctionId::Min => aggregate::min(context, args),
FunctionId::Max => aggregate::max(context, args),
FunctionId::Name => node::name(context, args),
FunctionId::LocalName => node::local_name(context, args),
FunctionId::NamespaceUri => node::namespace_uri(context, args),
FunctionId::NodeName => node::node_name(context, args),
FunctionId::Nilled => node::nilled(context, args),
FunctionId::BaseUri => node::base_uri(context, args),
FunctionId::DocumentUri => node::document_uri(context, args),
FunctionId::Lang => node::lang(context, args),
FunctionId::Root => node::root(context, args),
FunctionId::Id => node::id(context, args),
FunctionId::Collection => {
if args.len() > 1 {
return Err(XPathError::wrong_number_of_arguments(
"collection",
1,
args.len(),
));
}
Ok(XPathValue::Empty)
}
FunctionId::DateTime => datetime::create_datetime(context, args),
FunctionId::CurrentDateTime => datetime::current_datetime(context, args),
FunctionId::CurrentDate => datetime::current_date(context, args),
FunctionId::CurrentTime => datetime::current_time(context, args),
FunctionId::ImplicitTimezone => datetime::implicit_timezone(context, args),
FunctionId::YearsFromDuration => datetime::years_from_duration(context, args),
FunctionId::MonthsFromDuration => datetime::months_from_duration(context, args),
FunctionId::DaysFromDuration => datetime::days_from_duration(context, args),
FunctionId::HoursFromDuration => datetime::hours_from_duration(context, args),
FunctionId::MinutesFromDuration => datetime::minutes_from_duration(context, args),
FunctionId::SecondsFromDuration => datetime::seconds_from_duration(context, args),
FunctionId::YearFromDateTime => datetime::year_from_datetime(context, args),
FunctionId::MonthFromDateTime => datetime::month_from_datetime(context, args),
FunctionId::DayFromDateTime => datetime::day_from_datetime(context, args),
FunctionId::HoursFromDateTime => datetime::hours_from_datetime(context, args),
FunctionId::MinutesFromDateTime => datetime::minutes_from_datetime(context, args),
FunctionId::SecondsFromDateTime => datetime::seconds_from_datetime(context, args),
FunctionId::TimezoneFromDateTime => datetime::timezone_from_datetime(context, args),
FunctionId::YearFromDate => datetime::year_from_date(context, args),
FunctionId::MonthFromDate => datetime::month_from_date(context, args),
FunctionId::DayFromDate => datetime::day_from_date(context, args),
FunctionId::TimezoneFromDate => datetime::timezone_from_date(context, args),
FunctionId::HoursFromTime => datetime::hours_from_time(context, args),
FunctionId::MinutesFromTime => datetime::minutes_from_time(context, args),
FunctionId::SecondsFromTime => datetime::seconds_from_time(context, args),
FunctionId::TimezoneFromTime => datetime::timezone_from_time(context, args),
FunctionId::AdjustDateTimeToTimezone => {
datetime::adjust_datetime_to_timezone(context, args)
}
FunctionId::AdjustDateToTimezone => datetime::adjust_date_to_timezone(context, args),
FunctionId::AdjustTimeToTimezone => datetime::adjust_time_to_timezone(context, args),
FunctionId::ResolveQName => qname::resolve_qname(context, args),
FunctionId::QName => qname::qname_constructor(context, args),
FunctionId::PrefixFromQName => qname::prefix_from_qname(context, args),
FunctionId::LocalNameFromQName => qname::local_name_from_qname(context, args),
FunctionId::NamespaceUriFromQName => qname::namespace_uri_from_qname(context, args),
FunctionId::NamespaceUriForPrefix => qname::namespace_uri_for_prefix(context, args),
FunctionId::InScopePrefixes => qname::in_scope_prefixes(context, args),
FunctionId::ResolveUri => uri::resolve_uri(context, args),
FunctionId::StaticBaseUri => uri::static_base_uri(context, args),
FunctionId::Matches => regex::matches(context, args),
FunctionId::Replace => regex::replace(context, args),
FunctionId::Tokenize => regex::tokenize(context, args),
FunctionId::String => eval_fn_string(context, args),
FunctionId::Number => eval_fn_number(context, args),
FunctionId::Boolean => eval_boolean(args),
}
}
fn eval_not<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments("not", 1, args.len()));
}
let arg = args.remove(0);
let ebv = effective_boolean_value(&arg)?;
Ok(XPathValue::boolean(!ebv))
}
fn eval_empty<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"empty",
1,
args.len(),
));
}
let arg = args.remove(0);
Ok(XPathValue::boolean(arg.is_empty()))
}
fn eval_exists<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"exists",
1,
args.len(),
));
}
let arg = args.remove(0);
Ok(XPathValue::boolean(!arg.is_empty()))
}
fn eval_count<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"count",
1,
args.len(),
));
}
let arg = args.remove(0);
Ok(XPathValue::integer(arg.len() as i64))
}
fn eval_fn_string<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
match args.len() {
0 => {
let item = context.require_context_item()?.clone();
let s = match item {
XmlItem::Node(nav) => nav.value(),
XmlItem::Atomic(v) => atomize::string_value(&v),
};
Ok(XPathValue::string(s))
}
1 => {
let arg = args.remove(0);
let s = atomize_to_string(arg)?;
Ok(XPathValue::string(s))
}
_ => Err(XPathError::wrong_number_of_arguments(
"string",
1,
args.len(),
)),
}
}
fn eval_fn_number<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
match args.len() {
0 => {
let item = context.require_context_item()?.clone();
let d = match item {
XmlItem::Node(nav) => {
let s = nav.value();
s.trim().parse().unwrap_or(f64::NAN)
}
XmlItem::Atomic(v) => atomize::to_number(&v),
};
Ok(XPathValue::double(d))
}
1 => {
let arg = args.remove(0);
let d = atomize_to_double(arg)?;
Ok(XPathValue::double(d))
}
_ => Err(XPathError::wrong_number_of_arguments(
"number",
1,
args.len(),
)),
}
}
fn eval_boolean<N: DomNavigator>(
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"boolean",
1,
args.len(),
));
}
let arg = args.remove(0);
let ebv = effective_boolean_value(&arg)?;
Ok(XPathValue::boolean(ebv))
}
pub fn effective_boolean_value<N: DomNavigator>(value: &XPathValue<N>) -> Result<bool, XPathError> {
match value {
XPathValue::Empty => Ok(false),
XPathValue::Item(item) => item_boolean_value(item),
XPathValue::Sequence(items) => {
if items.is_empty() {
Ok(false)
} else if let Some(XmlItem::Node(_)) = items.first() {
Ok(true)
} else if items.len() == 1 {
item_boolean_value(&items[0])
} else {
Err(XPathError::FORG0006 {
message:
"Effective boolean value not defined for sequence of multiple atomic values"
.to_string(),
})
}
}
}
}
pub fn effective_boolean_value_10<N: DomNavigator>(
value: &XPathValue<N>,
) -> Result<bool, XPathError> {
match value {
XPathValue::Empty => Ok(false),
XPathValue::Item(item) => item_boolean_value(item),
XPathValue::Sequence(items) => {
if items.is_empty() {
Ok(false)
} else if items.len() == 1 {
item_boolean_value(&items[0])
} else {
Ok(true)
}
}
}
}
fn item_boolean_value<N: DomNavigator>(item: &XmlItem<N>) -> Result<bool, XPathError> {
match item {
XmlItem::Node(_) => Ok(true),
XmlItem::Atomic(value) => {
match value.as_boolean() {
Some(b) => Ok(b),
None => {
if let Some(s) = value.as_string() {
Ok(!s.is_empty())
} else if value.type_code == crate::types::XmlTypeCode::AnyUri {
let s = value.to_string_value();
Ok(!s.is_empty())
} else if let Some(d) = value.as_double() {
Ok(!d.is_nan() && d != 0.0)
} else if let Some(i) = value.as_integer() {
Ok(*i != BigInt::from(0))
} else {
Err(XPathError::FORG0006 {
message: format!(
"Effective boolean value not defined for type {:?}",
value.type_code
),
})
}
}
}
}
}
}
impl<N: DomNavigator> From<bool> for XPathValue<N> {
fn from(b: bool) -> Self {
XPathValue::boolean(b)
}
}
impl<N: DomNavigator> From<i32> for XPathValue<N> {
fn from(i: i32) -> Self {
XPathValue::integer(BigInt::from(i))
}
}
impl<N: DomNavigator> From<i64> for XPathValue<N> {
fn from(i: i64) -> Self {
XPathValue::integer(BigInt::from(i))
}
}
impl<N: DomNavigator> From<f64> for XPathValue<N> {
fn from(d: f64) -> Self {
XPathValue::double(d)
}
}
impl<N: DomNavigator> From<f32> for XPathValue<N> {
fn from(f: f32) -> Self {
XPathValue::double(f as f64)
}
}
impl<N: DomNavigator> From<String> for XPathValue<N> {
fn from(s: String) -> Self {
XPathValue::string(s)
}
}
impl<N: DomNavigator> From<&str> for XPathValue<N> {
fn from(s: &str) -> Self {
XPathValue::string(s)
}
}
impl<N: DomNavigator> From<()> for XPathValue<N> {
fn from(_: ()) -> Self {
XPathValue::empty()
}
}
impl<N: DomNavigator> From<BigInt> for XPathValue<N> {
fn from(i: BigInt) -> Self {
XPathValue::integer(i)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xpath::RoXmlNavigator;
#[test]
fn test_xpath_value_empty() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
assert!(value.is_empty());
assert_eq!(value.len(), 0);
}
#[test]
fn test_xpath_value_single() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(true);
assert!(!value.is_empty());
assert_eq!(value.len(), 1);
assert!(value.is_single());
}
#[test]
fn test_xpath_value_from_sequence() {
let items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(1.into())),
XmlItem::Atomic(XmlValue::integer(2.into())),
];
let value = XPathValue::from_sequence(items);
assert_eq!(value.len(), 2);
assert!(!value.is_single());
}
#[test]
fn test_effective_boolean_value_empty() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
assert!(!effective_boolean_value(&value).unwrap());
}
#[test]
fn test_effective_boolean_value_boolean() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(true);
assert!(effective_boolean_value(&value).unwrap());
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(false);
assert!(!effective_boolean_value(&value).unwrap());
}
#[test]
fn test_effective_boolean_value_string() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("hello");
assert!(effective_boolean_value(&value).unwrap());
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("");
assert!(!effective_boolean_value(&value).unwrap());
}
#[test]
fn test_effective_boolean_value_number() {
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(1.0);
assert!(effective_boolean_value(&value).unwrap());
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(0.0);
assert!(!effective_boolean_value(&value).unwrap());
let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(f64::NAN);
assert!(!effective_boolean_value(&value).unwrap());
}
use crate::namespace::table::NameTable;
use crate::xpath::context::{DynamicContext, XPathContext};
#[test]
fn test_eval_fn_string_integer() {
let names = NameTable::new();
let static_ctx = XPathContext::new(&names);
let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
DynamicContext::new(&static_ctx, 0);
let args = vec![XPathValue::integer(42i64)];
let result = eval_fn_string(&mut ctx, args).unwrap();
assert_eq!(result.as_str(), Some("42".to_string()));
}
#[test]
fn test_eval_fn_string_string() {
let names = NameTable::new();
let static_ctx = XPathContext::new(&names);
let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
DynamicContext::new(&static_ctx, 0);
let args = vec![XPathValue::string("hello")];
let result = eval_fn_string(&mut ctx, args).unwrap();
assert_eq!(result.as_str(), Some("hello".to_string()));
}
#[test]
fn test_eval_fn_number_string() {
let names = NameTable::new();
let static_ctx = XPathContext::new(&names);
let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
DynamicContext::new(&static_ctx, 0);
let args = vec![XPathValue::string("42.5")];
let result = eval_fn_number(&mut ctx, args).unwrap();
assert_eq!(result.as_f64(), Some(42.5));
}
#[test]
fn test_eval_fn_number_invalid() {
let names = NameTable::new();
let static_ctx = XPathContext::new(&names);
let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
DynamicContext::new(&static_ctx, 0);
let args = vec![XPathValue::string("abc")];
let result = eval_fn_number(&mut ctx, args).unwrap();
assert!(result.as_f64().unwrap().is_nan());
}
#[test]
fn test_eval_boolean_false_empty_string() {
let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::string("")];
let result = eval_boolean(args).unwrap();
assert_eq!(result.as_bool(), Some(false));
}
#[test]
fn test_eval_boolean_true_nonempty_string() {
let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::string("x")];
let result = eval_boolean(args).unwrap();
assert_eq!(result.as_bool(), Some(true));
}
#[test]
fn test_eval_boolean_false_zero() {
let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::double(0.0)];
let result = eval_boolean(args).unwrap();
assert_eq!(result.as_bool(), Some(false));
}
}