use num_bigint::BigInt;
use rust_decimal::prelude::ToPrimitive;
use crate::types::value::{XmlAtomicValue, XmlValue};
use crate::types::XmlTypeCode;
use crate::xpath::context::DynamicContext;
use crate::xpath::error::XPathError;
use crate::xpath::iterator::{VecNodeIterator, XmlItem};
use crate::xpath::tree_comparer::TreeComparer;
use crate::xpath::DomNavigator;
use super::{
atomize_sequence, atomize_to_double, atomize_to_single, atomize_to_single_opt,
atomize_to_string_opt, materialize, XPathValue,
};
const DEFAULT_COLLATION: &str = "http://www.w3.org/2005/xpath-functions/collation/codepoint";
fn validate_collation(collation: Option<&str>) -> Result<(), XPathError> {
match collation {
None => Ok(()),
Some(c) if c.is_empty() || c == DEFAULT_COLLATION => Ok(()),
Some(c) => Err(XPathError::unknown_collation(c)),
}
}
pub fn index_of<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() < 2 || args.len() > 3 {
return Err(XPathError::wrong_number_of_arguments(
"index-of",
2,
args.len(),
));
}
let seq = args.remove(0);
let search_arg = args.remove(0);
let seq_values = atomize_sequence(seq)?;
let search_value = match atomize_to_single_opt(search_arg)? {
None => return Ok(XPathValue::Empty),
Some(value) => value,
};
let mut positions = Vec::new();
for (idx, item) in seq_values.iter().enumerate() {
if values_equal(item, &search_value) {
positions.push(XmlItem::Atomic(XmlValue::integer(BigInt::from(idx + 1))));
}
}
Ok(XPathValue::from_sequence(positions))
}
fn values_equal(left: &XmlValue, right: &XmlValue) -> bool {
let left_norm = normalize_for_comparison(left);
let right_norm = normalize_for_comparison(right);
if left_norm.type_code.is_numeric() && right_norm.type_code.is_numeric() {
return numeric_values_equal(&left_norm, &right_norm);
}
left_norm == right_norm
}
fn numeric_values_equal(left: &XmlValue, right: &XmlValue) -> bool {
numeric_values_equal_inner(left, right, false)
}
fn numeric_values_equal_inner(left: &XmlValue, right: &XmlValue, nan_equal: bool) -> bool {
let lt = left.type_code;
let rt = right.type_code;
let is_decimal_or_int =
|tc: XmlTypeCode| tc.is_numeric() && tc != XmlTypeCode::Float && tc != XmlTypeCode::Double;
if lt == XmlTypeCode::Double || rt == XmlTypeCode::Double {
if let (Some(l), Some(r)) = (left.as_double(), right.as_double()) {
if nan_equal && l.is_nan() && r.is_nan() {
return true;
}
if l.is_nan() || r.is_nan() {
return false;
}
return l == r;
}
return false;
}
if lt == XmlTypeCode::Float || rt == XmlTypeCode::Float {
let lf = match &left.value {
crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f),
_ => left.as_decimal().and_then(|d| d.to_f32()),
};
let rf = match &right.value {
crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f),
_ => right.as_decimal().and_then(|d| d.to_f32()),
};
if let (Some(l), Some(r)) = (lf, rf) {
if nan_equal && l.is_nan() && r.is_nan() {
return true;
}
if l.is_nan() || r.is_nan() {
return false;
}
return l == r;
}
return false;
}
if is_decimal_or_int(lt) && is_decimal_or_int(rt) {
if let (Some(l), Some(r)) = (left.as_decimal(), right.as_decimal()) {
return l == r;
}
}
false
}
fn distinct_values_equal(left: &XmlValue, right: &XmlValue) -> bool {
let left_norm = normalize_for_comparison(left);
let right_norm = normalize_for_comparison(right);
if left_norm.type_code.is_numeric() && right_norm.type_code.is_numeric() {
return numeric_values_equal_inner(&left_norm, &right_norm, true);
}
if is_duration_code(left_norm.type_code) && is_duration_code(right_norm.type_code) {
return durations_equal(&left_norm, &right_norm);
}
left_norm == right_norm
}
fn is_duration_code(code: XmlTypeCode) -> bool {
matches!(
code,
XmlTypeCode::Duration | XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration
)
}
fn durations_equal(left: &XmlValue, right: &XmlValue) -> bool {
if left.type_code == right.type_code {
return left == right;
}
is_zero_duration(left) && is_zero_duration(right)
}
fn is_zero_duration(value: &XmlValue) -> bool {
match &value.value {
crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(d)) => {
d.years == 0 && d.months == 0
}
crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(d)) => {
d.days == 0 && d.hours == 0 && d.minutes == 0 && d.seconds.is_zero()
}
_ => false,
}
}
fn normalize_for_comparison(value: &XmlValue) -> XmlValue {
match value.type_code {
XmlTypeCode::UntypedAtomic | XmlTypeCode::AnyUri => {
XmlValue::string(value.to_string_value())
}
_ => value.clone(),
}
}
pub fn reverse<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"reverse",
1,
args.len(),
));
}
let mut items = materialize(args.remove(0));
items.reverse();
Ok(XPathValue::from_sequence(items))
}
pub fn zero_or_one<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"zero-or-one",
1,
args.len(),
));
}
let arg = args.remove(0);
if arg.len() > 1 {
return Err(XPathError::FORG0003);
}
Ok(arg)
}
pub fn one_or_more<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"one-or-more",
1,
args.len(),
));
}
let arg = args.remove(0);
if arg.is_empty() {
return Err(XPathError::FORG0004);
}
Ok(arg)
}
pub fn exactly_one<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"exactly-one",
1,
args.len(),
));
}
let arg = args.remove(0);
if arg.len() != 1 {
return Err(XPathError::FORG0005);
}
Ok(arg)
}
pub fn distinct_values<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.is_empty() || args.len() > 2 {
return Err(XPathError::wrong_number_of_arguments(
"distinct-values",
1,
args.len(),
));
}
let seq = args.remove(0);
let values = atomize_sequence(seq)?;
if values.is_empty() {
return Ok(XPathValue::Empty);
}
let mut distinct: Vec<XmlValue> = Vec::new();
for value in values {
let is_duplicate = distinct
.iter()
.any(|existing| distinct_values_equal(existing, &value));
if !is_duplicate {
distinct.push(value);
}
}
let items: Vec<XmlItem<N>> = distinct.into_iter().map(XmlItem::Atomic).collect();
Ok(XPathValue::from_sequence(items))
}
pub fn remove<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 2 {
return Err(XPathError::wrong_number_of_arguments(
"remove",
2,
args.len(),
));
}
let target = args.remove(0);
let position_arg = args.remove(0);
let position_value = atomize_to_single(position_arg)?;
let position = position_value
.as_integer()
.and_then(|i| i.to_i64())
.ok_or_else(|| XPathError::XPTY0004 {
expected: "xs:integer".to_string(),
found: format!("{:?}", position_value.type_code),
})?;
let mut items = materialize(target);
if position < 1 || position as usize > items.len() {
return Ok(XPathValue::from_sequence(items));
}
items.remove((position - 1) as usize);
Ok(XPathValue::from_sequence(items))
}
pub fn insert_before<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 3 {
return Err(XPathError::wrong_number_of_arguments(
"insert-before",
3,
args.len(),
));
}
let target = args.remove(0);
let position_arg = args.remove(0);
let inserts = args.remove(0);
let position_value = atomize_to_single(position_arg)?;
let position = position_value
.as_integer()
.and_then(|i| i.to_i64())
.ok_or_else(|| XPathError::XPTY0004 {
expected: "xs:integer".to_string(),
found: format!("{:?}", position_value.type_code),
})?;
let mut target_items = materialize(target);
let insert_items = materialize(inserts);
let len = target_items.len();
let adjusted_pos = if position < 1 {
0
} else if position as usize > len {
len
} else {
(position - 1) as usize
};
let mut result = Vec::with_capacity(target_items.len() + insert_items.len());
result.extend(target_items.drain(..adjusted_pos));
result.extend(insert_items);
result.extend(target_items);
Ok(XPathValue::from_sequence(result))
}
pub fn subsequence<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.is_empty() || args.len() > 3 {
return Err(XPathError::wrong_number_of_arguments(
"subsequence",
2,
args.len(),
));
}
let source = args.remove(0);
let starting_loc_arg = args.remove(0);
let length_arg = if !args.is_empty() {
Some(args.remove(0))
} else {
None
};
let starting_loc = atomize_to_double(starting_loc_arg)?;
let length = match length_arg {
Some(arg) => Some(atomize_to_double(arg)?),
None => None,
};
if starting_loc.is_nan() {
return Ok(XPathValue::Empty);
}
if let Some(len) = length {
if len.is_nan() {
return Ok(XPathValue::Empty);
}
}
if starting_loc.is_infinite() && starting_loc.is_sign_positive() {
return Ok(XPathValue::Empty);
}
if let Some(len) = length {
if len.is_infinite() && len.is_sign_negative() {
return Ok(XPathValue::Empty);
}
}
let items = materialize(source);
let start_rounded = round_half_away_from_zero(starting_loc);
let (start_idx, end_idx) = match length {
Some(len) => {
let len_rounded = round_half_away_from_zero(len);
let effective_start = if start_rounded < 1.0 {
1.0
} else {
start_rounded
};
let adjusted_len = if start_rounded < 1.0 {
len_rounded + start_rounded - 1.0
} else {
len_rounded
};
if adjusted_len <= 0.0 {
return Ok(XPathValue::Empty);
}
let start = (effective_start - 1.0).max(0.0) as usize;
let end = (effective_start - 1.0 + adjusted_len).min(items.len() as f64) as usize;
(start, end)
}
None => {
if start_rounded < 1.0 {
(0, items.len())
} else {
let start = (start_rounded - 1.0).max(0.0) as usize;
(start, items.len())
}
}
};
if start_idx >= items.len() {
return Ok(XPathValue::Empty);
}
let result: Vec<XmlItem<N>> = items
.into_iter()
.skip(start_idx)
.take(end_idx.saturating_sub(start_idx))
.collect();
Ok(XPathValue::from_sequence(result))
}
fn round_half_away_from_zero(d: f64) -> f64 {
if d.is_nan() || d.is_infinite() {
return d;
}
if d >= 0.0 {
(d + 0.5).floor()
} else {
(d - 0.5).ceil()
}
}
pub fn unordered<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"unordered",
1,
args.len(),
));
}
Ok(args.remove(0))
}
pub fn deep_equal<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() < 2 || args.len() > 3 {
return Err(XPathError::wrong_number_of_arguments(
"deep-equal",
2,
args.len(),
));
}
if args.len() == 3 {
let collation_arg = args.pop().unwrap();
let collation = atomize_to_string_opt(collation_arg)?;
validate_collation(collation.as_deref())?;
}
let param1 = args.remove(0);
let param2 = args.remove(0);
let items1 = materialize(param1);
let items2 = materialize(param2);
let iter1: VecNodeIterator<N> = VecNodeIterator::new(items1);
let iter2: VecNodeIterator<N> = VecNodeIterator::new(items2);
let comparer = TreeComparer::default();
let result = comparer.deep_equal_iter(&iter1, &iter2)?;
Ok(XPathValue::boolean(result))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::namespace::table::NameTable;
use crate::xpath::context::XPathContext;
use crate::xpath::iterator::XmlItem;
use crate::xpath::RoXmlNavigator;
fn make_context<'a>() -> DynamicContext<'a, RoXmlNavigator<'a>> {
let table = Box::leak(Box::new(NameTable::new()));
let xpath_ctx = Box::leak(Box::new(XPathContext::new(table)));
DynamicContext::new(xpath_ctx, 0)
}
fn integer_seq<N: DomNavigator>(values: &[i64]) -> XPathValue<N> {
let items: Vec<XmlItem<N>> = values
.iter()
.map(|&v| XmlItem::Atomic(XmlValue::integer(BigInt::from(v))))
.collect();
XPathValue::from_sequence(items)
}
fn extract_integers<N: DomNavigator>(value: XPathValue<N>) -> Vec<i64> {
match value {
XPathValue::Empty => vec![],
XPathValue::Item(item) => {
if let XmlItem::Atomic(v) = item {
vec![v.as_integer().and_then(|i| i.to_i64()).unwrap()]
} else {
vec![]
}
}
XPathValue::Sequence(items) => items
.into_iter()
.filter_map(|item| {
if let XmlItem::Atomic(v) = item {
v.as_integer().and_then(|i| i.to_i64())
} else {
None
}
})
.collect(),
}
}
fn extract_bool<N: DomNavigator>(value: XPathValue<N>) -> bool {
match value {
XPathValue::Item(XmlItem::Atomic(v)) => v.as_boolean().unwrap_or(false),
_ => false,
}
}
#[test]
fn test_index_of_multiple_matches() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30, 20]);
let search = XPathValue::integer(20);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2, 4]);
}
#[test]
fn test_index_of_no_match() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30]);
let search = XPathValue::integer(40);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), Vec::<i64>::new());
}
#[test]
fn test_index_of_string_matches() {
let mut ctx = make_context();
let items: Vec<XmlItem<RoXmlNavigator>> = vec!["a", "b", "c", "b"]
.into_iter()
.map(|s| XmlItem::Atomic(XmlValue::string(s)))
.collect();
let seq = XPathValue::from_sequence(items);
let search = XPathValue::string("b");
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2, 4]);
}
#[test]
fn test_index_of_empty_sequence() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let search = XPathValue::integer(1);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_remove_middle() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let pos = XPathValue::integer(2);
let args = vec![seq, pos];
let result = remove(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 3]);
}
#[test]
fn test_remove_out_of_range_low() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let pos = XPathValue::integer(0);
let args = vec![seq, pos];
let result = remove(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_remove_out_of_range_high() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let pos = XPathValue::integer(10);
let args = vec![seq, pos];
let result = remove(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_insert_before_middle() {
let mut ctx = make_context();
let target = integer_seq::<RoXmlNavigator>(&[1, 3]);
let pos = XPathValue::integer(2);
let inserts = XPathValue::integer(2);
let args = vec![target, pos, inserts];
let result = insert_before(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_insert_before_position_less_than_one() {
let mut ctx = make_context();
let target = integer_seq::<RoXmlNavigator>(&[2, 3]);
let pos = XPathValue::integer(0);
let inserts = XPathValue::integer(1);
let args = vec![target, pos, inserts];
let result = insert_before(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_insert_before_position_beyond_end() {
let mut ctx = make_context();
let target = integer_seq::<RoXmlNavigator>(&[1, 2]);
let pos = XPathValue::integer(10);
let inserts = XPathValue::integer(3);
let args = vec![target, pos, inserts];
let result = insert_before(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_subsequence_with_length() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
let start = XPathValue::double(2.0);
let len = XPathValue::double(3.0);
let args = vec![seq, start, len];
let result = subsequence(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2, 3, 4]);
}
#[test]
fn test_subsequence_without_length() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
let start = XPathValue::double(3.0);
let args = vec![seq, start];
let result = subsequence(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![3, 4, 5]);
}
#[test]
fn test_subsequence_negative_start() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let start = XPathValue::double(-1.0);
let len = XPathValue::double(4.0);
let args = vec![seq, start, len];
let result = subsequence(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2]);
}
#[test]
fn test_subsequence_nan_start() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let start = XPathValue::double(f64::NAN);
let len = XPathValue::double(2.0);
let args = vec![seq, start, len];
let result = subsequence(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_subsequence_rounding() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
let start = XPathValue::double(1.5);
let len = XPathValue::double(2.6);
let args = vec![seq, start, len];
let result = subsequence(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2, 3, 4]);
}
#[test]
fn test_unordered_passthrough() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let args = vec![seq];
let result = unordered(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_index_of_integer_matches_double() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30]);
let search = XPathValue::double(20.0);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2]);
}
#[test]
fn test_index_of_double_matches_integer() {
let mut ctx = make_context();
let items: Vec<XmlItem<RoXmlNavigator>> = vec![
XmlItem::Atomic(XmlValue::double(10.0)),
XmlItem::Atomic(XmlValue::double(20.0)),
XmlItem::Atomic(XmlValue::double(30.0)),
];
let seq = XPathValue::from_sequence(items);
let search = XPathValue::integer(20);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![2]);
}
#[test]
fn test_index_of_nan_not_equal() {
let mut ctx = make_context();
let items: Vec<XmlItem<RoXmlNavigator>> = vec![
XmlItem::Atomic(XmlValue::double(f64::NAN)),
XmlItem::Atomic(XmlValue::double(1.0)),
];
let seq = XPathValue::from_sequence(items);
let search = XPathValue::double(f64::NAN);
let args = vec![seq, search];
let result = index_of(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_reverse_sequence() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
let args = vec![seq];
let result = reverse(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![5, 4, 3, 2, 1]);
}
#[test]
fn test_reverse_empty() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq];
let result = reverse(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_reverse_single() {
let mut ctx = make_context();
let seq = XPathValue::integer(42);
let args = vec![seq];
let result = reverse(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![42]);
}
#[test]
fn test_zero_or_one_empty() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq];
let result = zero_or_one(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_zero_or_one_single() {
let mut ctx = make_context();
let seq = XPathValue::integer(42);
let args = vec![seq];
let result = zero_or_one(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![42]);
}
#[test]
fn test_zero_or_one_multiple_fails() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2]);
let args = vec![seq];
let result = zero_or_one(&mut ctx, args);
match result {
Err(e) => assert_eq!(e.error_code(), Some("FORG0003")),
Ok(_) => panic!("Expected FORG0003 error"),
}
}
#[test]
fn test_one_or_more_single() {
let mut ctx = make_context();
let seq = XPathValue::integer(42);
let args = vec![seq];
let result = one_or_more(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![42]);
}
#[test]
fn test_one_or_more_multiple() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let args = vec![seq];
let result = one_or_more(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_one_or_more_empty_fails() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq];
let result = one_or_more(&mut ctx, args);
match result {
Err(e) => assert_eq!(e.error_code(), Some("FORG0004")),
Ok(_) => panic!("Expected FORG0004 error"),
}
}
#[test]
fn test_exactly_one_single() {
let mut ctx = make_context();
let seq = XPathValue::integer(42);
let args = vec![seq];
let result = exactly_one(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![42]);
}
#[test]
fn test_exactly_one_empty_fails() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq];
let result = exactly_one(&mut ctx, args);
match result {
Err(e) => assert_eq!(e.error_code(), Some("FORG0005")),
Ok(_) => panic!("Expected FORG0005 error"),
}
}
#[test]
fn test_exactly_one_multiple_fails() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2]);
let args = vec![seq];
let result = exactly_one(&mut ctx, args);
match result {
Err(e) => assert_eq!(e.error_code(), Some("FORG0005")),
Ok(_) => panic!("Expected FORG0005 error"),
}
}
#[test]
fn test_distinct_values_integers() {
let mut ctx = make_context();
let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 1, 3, 2, 1]);
let args = vec![seq];
let result = distinct_values(&mut ctx, args).unwrap();
assert_eq!(extract_integers(result), vec![1, 2, 3]);
}
#[test]
fn test_distinct_values_empty() {
let mut ctx = make_context();
let seq = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq];
let result = distinct_values(&mut ctx, args).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_distinct_values_mixed_numeric() {
let mut ctx = make_context();
let items: Vec<XmlItem<RoXmlNavigator>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::double(2.0)),
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))), XmlItem::Atomic(XmlValue::double(2.0)), XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
];
let seq = XPathValue::from_sequence(items);
let args = vec![seq];
let result = distinct_values(&mut ctx, args).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_distinct_values_strings() {
let mut ctx = make_context();
let items: Vec<XmlItem<RoXmlNavigator>> = vec!["a", "b", "a", "c", "b"]
.into_iter()
.map(|s| XmlItem::Atomic(XmlValue::string(s)))
.collect();
let seq = XPathValue::from_sequence(items);
let args = vec![seq];
let result = distinct_values(&mut ctx, args).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_deep_equal_same_integers() {
let mut ctx = make_context();
let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let args = vec![seq1, seq2];
let result = deep_equal(&mut ctx, args).unwrap();
assert!(extract_bool(result));
}
#[test]
fn test_deep_equal_different_integers() {
let mut ctx = make_context();
let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 4]);
let args = vec![seq1, seq2];
let result = deep_equal(&mut ctx, args).unwrap();
assert!(!extract_bool(result));
}
#[test]
fn test_deep_equal_nan() {
let mut ctx = make_context();
let seq1: XPathValue<RoXmlNavigator> = XPathValue::double(f64::NAN);
let seq2: XPathValue<RoXmlNavigator> = XPathValue::double(f64::NAN);
let args = vec![seq1, seq2];
let result = deep_equal(&mut ctx, args).unwrap();
assert!(extract_bool(result));
}
#[test]
fn test_deep_equal_empty_sequences() {
let mut ctx = make_context();
let seq1 = XPathValue::<RoXmlNavigator>::Empty;
let seq2 = XPathValue::<RoXmlNavigator>::Empty;
let args = vec![seq1, seq2];
let result = deep_equal(&mut ctx, args).unwrap();
assert!(extract_bool(result));
}
#[test]
fn test_deep_equal_different_lengths() {
let mut ctx = make_context();
let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2]);
let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
let args = vec![seq1, seq2];
let result = deep_equal(&mut ctx, args).unwrap();
assert!(!extract_bool(result));
}
}