1use num_bigint::BigInt;
12use rust_decimal::prelude::ToPrimitive;
13
14use crate::types::value::{XmlAtomicValue, XmlValue};
15use crate::types::XmlTypeCode;
16use crate::xpath::context::DynamicContext;
17use crate::xpath::error::XPathError;
18use crate::xpath::iterator::{VecNodeIterator, XmlItem};
19use crate::xpath::tree_comparer::TreeComparer;
20use crate::xpath::DomNavigator;
21
22use super::{
23 atomize_sequence, atomize_to_double, atomize_to_single, atomize_to_single_opt,
24 atomize_to_string_opt, materialize, XPathValue,
25};
26
27const DEFAULT_COLLATION: &str = "http://www.w3.org/2005/xpath-functions/collation/codepoint";
29
30fn validate_collation(collation: Option<&str>) -> Result<(), XPathError> {
33 match collation {
34 None => Ok(()),
35 Some(c) if c.is_empty() || c == DEFAULT_COLLATION => Ok(()),
36 Some(c) => Err(XPathError::unknown_collation(c)),
37 }
38}
39
40pub fn index_of<N: DomNavigator>(
50 _context: &mut DynamicContext<'_, N>,
51 mut args: Vec<XPathValue<N>>,
52) -> Result<XPathValue<N>, XPathError> {
53 if args.len() < 2 || args.len() > 3 {
54 return Err(XPathError::wrong_number_of_arguments(
55 "index-of",
56 2,
57 args.len(),
58 ));
59 }
60
61 let seq = args.remove(0);
63 let search_arg = args.remove(0);
64 let seq_values = atomize_sequence(seq)?;
68 let search_value = match atomize_to_single_opt(search_arg)? {
69 None => return Ok(XPathValue::Empty),
70 Some(value) => value,
71 };
72
73 let mut positions = Vec::new();
75 for (idx, item) in seq_values.iter().enumerate() {
76 if values_equal(item, &search_value) {
77 positions.push(XmlItem::Atomic(XmlValue::integer(BigInt::from(idx + 1))));
78 }
79 }
80
81 Ok(XPathValue::from_sequence(positions))
82}
83
84fn values_equal(left: &XmlValue, right: &XmlValue) -> bool {
88 let left_norm = normalize_for_comparison(left);
89 let right_norm = normalize_for_comparison(right);
90
91 if left_norm.type_code.is_numeric() && right_norm.type_code.is_numeric() {
93 return numeric_values_equal(&left_norm, &right_norm);
94 }
95
96 left_norm == right_norm
98}
99
100fn numeric_values_equal(left: &XmlValue, right: &XmlValue) -> bool {
104 numeric_values_equal_inner(left, right, false)
105}
106
107fn numeric_values_equal_inner(left: &XmlValue, right: &XmlValue, nan_equal: bool) -> bool {
110 let lt = left.type_code;
111 let rt = right.type_code;
112 let is_decimal_or_int =
113 |tc: XmlTypeCode| tc.is_numeric() && tc != XmlTypeCode::Float && tc != XmlTypeCode::Double;
114
115 if lt == XmlTypeCode::Double || rt == XmlTypeCode::Double {
117 if let (Some(l), Some(r)) = (left.as_double(), right.as_double()) {
118 if nan_equal && l.is_nan() && r.is_nan() {
119 return true;
120 }
121 if l.is_nan() || r.is_nan() {
122 return false;
123 }
124 return l == r;
125 }
126 return false;
127 }
128
129 if lt == XmlTypeCode::Float || rt == XmlTypeCode::Float {
131 let lf = match &left.value {
132 crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f),
133 _ => left.as_decimal().and_then(|d| d.to_f32()),
134 };
135 let rf = match &right.value {
136 crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f),
137 _ => right.as_decimal().and_then(|d| d.to_f32()),
138 };
139 if let (Some(l), Some(r)) = (lf, rf) {
140 if nan_equal && l.is_nan() && r.is_nan() {
141 return true;
142 }
143 if l.is_nan() || r.is_nan() {
144 return false;
145 }
146 return l == r;
147 }
148 return false;
149 }
150
151 if is_decimal_or_int(lt) && is_decimal_or_int(rt) {
153 if let (Some(l), Some(r)) = (left.as_decimal(), right.as_decimal()) {
154 return l == r;
155 }
156 }
157
158 false
159}
160
161fn distinct_values_equal(left: &XmlValue, right: &XmlValue) -> bool {
164 let left_norm = normalize_for_comparison(left);
165 let right_norm = normalize_for_comparison(right);
166
167 if left_norm.type_code.is_numeric() && right_norm.type_code.is_numeric() {
169 return numeric_values_equal_inner(&left_norm, &right_norm, true);
170 }
171
172 if is_duration_code(left_norm.type_code) && is_duration_code(right_norm.type_code) {
174 return durations_equal(&left_norm, &right_norm);
175 }
176
177 left_norm == right_norm
179}
180
181fn is_duration_code(code: XmlTypeCode) -> bool {
182 matches!(
183 code,
184 XmlTypeCode::Duration | XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration
185 )
186}
187
188fn durations_equal(left: &XmlValue, right: &XmlValue) -> bool {
191 if left.type_code == right.type_code {
193 return left == right;
194 }
195 is_zero_duration(left) && is_zero_duration(right)
197}
198
199fn is_zero_duration(value: &XmlValue) -> bool {
201 match &value.value {
202 crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(d)) => {
203 d.years == 0 && d.months == 0
204 }
205 crate::types::value::XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(d)) => {
206 d.days == 0 && d.hours == 0 && d.minutes == 0 && d.seconds.is_zero()
207 }
208 _ => false,
209 }
210}
211
212fn normalize_for_comparison(value: &XmlValue) -> XmlValue {
214 match value.type_code {
215 XmlTypeCode::UntypedAtomic | XmlTypeCode::AnyUri => {
216 XmlValue::string(value.to_string_value())
217 }
218 _ => value.clone(),
219 }
220}
221
222pub fn reverse<N: DomNavigator>(
228 _context: &mut DynamicContext<'_, N>,
229 mut args: Vec<XPathValue<N>>,
230) -> Result<XPathValue<N>, XPathError> {
231 if args.len() != 1 {
232 return Err(XPathError::wrong_number_of_arguments(
233 "reverse",
234 1,
235 args.len(),
236 ));
237 }
238
239 let mut items = materialize(args.remove(0));
240 items.reverse();
241 Ok(XPathValue::from_sequence(items))
242}
243
244pub fn zero_or_one<N: DomNavigator>(
252 _context: &mut DynamicContext<'_, N>,
253 mut args: Vec<XPathValue<N>>,
254) -> Result<XPathValue<N>, XPathError> {
255 if args.len() != 1 {
256 return Err(XPathError::wrong_number_of_arguments(
257 "zero-or-one",
258 1,
259 args.len(),
260 ));
261 }
262
263 let arg = args.remove(0);
264 if arg.len() > 1 {
265 return Err(XPathError::FORG0003);
266 }
267 Ok(arg)
268}
269
270pub fn one_or_more<N: DomNavigator>(
278 _context: &mut DynamicContext<'_, N>,
279 mut args: Vec<XPathValue<N>>,
280) -> Result<XPathValue<N>, XPathError> {
281 if args.len() != 1 {
282 return Err(XPathError::wrong_number_of_arguments(
283 "one-or-more",
284 1,
285 args.len(),
286 ));
287 }
288
289 let arg = args.remove(0);
290 if arg.is_empty() {
291 return Err(XPathError::FORG0004);
292 }
293 Ok(arg)
294}
295
296pub fn exactly_one<N: DomNavigator>(
304 _context: &mut DynamicContext<'_, N>,
305 mut args: Vec<XPathValue<N>>,
306) -> Result<XPathValue<N>, XPathError> {
307 if args.len() != 1 {
308 return Err(XPathError::wrong_number_of_arguments(
309 "exactly-one",
310 1,
311 args.len(),
312 ));
313 }
314
315 let arg = args.remove(0);
316 if arg.len() != 1 {
317 return Err(XPathError::FORG0005);
318 }
319 Ok(arg)
320}
321
322pub fn distinct_values<N: DomNavigator>(
331 _context: &mut DynamicContext<'_, N>,
332 mut args: Vec<XPathValue<N>>,
333) -> Result<XPathValue<N>, XPathError> {
334 if args.is_empty() || args.len() > 2 {
335 return Err(XPathError::wrong_number_of_arguments(
336 "distinct-values",
337 1,
338 args.len(),
339 ));
340 }
341
342 let seq = args.remove(0);
343 let values = atomize_sequence(seq)?;
347
348 if values.is_empty() {
349 return Ok(XPathValue::Empty);
350 }
351
352 let mut distinct: Vec<XmlValue> = Vec::new();
355 for value in values {
356 let is_duplicate = distinct
357 .iter()
358 .any(|existing| distinct_values_equal(existing, &value));
359 if !is_duplicate {
360 distinct.push(value);
361 }
362 }
363
364 let items: Vec<XmlItem<N>> = distinct.into_iter().map(XmlItem::Atomic).collect();
366
367 Ok(XPathValue::from_sequence(items))
368}
369
370pub fn remove<N: DomNavigator>(
380 _context: &mut DynamicContext<'_, N>,
381 mut args: Vec<XPathValue<N>>,
382) -> Result<XPathValue<N>, XPathError> {
383 if args.len() != 2 {
384 return Err(XPathError::wrong_number_of_arguments(
385 "remove",
386 2,
387 args.len(),
388 ));
389 }
390
391 let target = args.remove(0);
392 let position_arg = args.remove(0);
393
394 let position_value = atomize_to_single(position_arg)?;
396 let position = position_value
397 .as_integer()
398 .and_then(|i| i.to_i64())
399 .ok_or_else(|| XPathError::XPTY0004 {
400 expected: "xs:integer".to_string(),
401 found: format!("{:?}", position_value.type_code),
402 })?;
403
404 let mut items = materialize(target);
406
407 if position < 1 || position as usize > items.len() {
409 return Ok(XPathValue::from_sequence(items));
410 }
411
412 items.remove((position - 1) as usize);
414
415 Ok(XPathValue::from_sequence(items))
416}
417
418pub fn insert_before<N: DomNavigator>(
429 _context: &mut DynamicContext<'_, N>,
430 mut args: Vec<XPathValue<N>>,
431) -> Result<XPathValue<N>, XPathError> {
432 if args.len() != 3 {
433 return Err(XPathError::wrong_number_of_arguments(
434 "insert-before",
435 3,
436 args.len(),
437 ));
438 }
439
440 let target = args.remove(0);
441 let position_arg = args.remove(0);
442 let inserts = args.remove(0);
443
444 let position_value = atomize_to_single(position_arg)?;
446 let position = position_value
447 .as_integer()
448 .and_then(|i| i.to_i64())
449 .ok_or_else(|| XPathError::XPTY0004 {
450 expected: "xs:integer".to_string(),
451 found: format!("{:?}", position_value.type_code),
452 })?;
453
454 let mut target_items = materialize(target);
456 let insert_items = materialize(inserts);
457
458 let len = target_items.len();
460 let adjusted_pos = if position < 1 {
461 0
462 } else if position as usize > len {
463 len
464 } else {
465 (position - 1) as usize
466 };
467
468 let mut result = Vec::with_capacity(target_items.len() + insert_items.len());
470 result.extend(target_items.drain(..adjusted_pos));
471 result.extend(insert_items);
472 result.extend(target_items);
473
474 Ok(XPathValue::from_sequence(result))
475}
476
477pub fn subsequence<N: DomNavigator>(
493 _context: &mut DynamicContext<'_, N>,
494 mut args: Vec<XPathValue<N>>,
495) -> Result<XPathValue<N>, XPathError> {
496 if args.is_empty() || args.len() > 3 {
497 return Err(XPathError::wrong_number_of_arguments(
498 "subsequence",
499 2,
500 args.len(),
501 ));
502 }
503
504 let source = args.remove(0);
505 let starting_loc_arg = args.remove(0);
506 let length_arg = if !args.is_empty() {
507 Some(args.remove(0))
508 } else {
509 None
510 };
511
512 let starting_loc = atomize_to_double(starting_loc_arg)?;
514
515 let length = match length_arg {
517 Some(arg) => Some(atomize_to_double(arg)?),
518 None => None,
519 };
520
521 if starting_loc.is_nan() {
523 return Ok(XPathValue::Empty);
524 }
525 if let Some(len) = length {
526 if len.is_nan() {
527 return Ok(XPathValue::Empty);
528 }
529 }
530
531 if starting_loc.is_infinite() && starting_loc.is_sign_positive() {
533 return Ok(XPathValue::Empty);
534 }
535 if let Some(len) = length {
536 if len.is_infinite() && len.is_sign_negative() {
537 return Ok(XPathValue::Empty);
538 }
539 }
540
541 let items = materialize(source);
543
544 let start_rounded = round_half_away_from_zero(starting_loc);
546
547 let (start_idx, end_idx) = match length {
549 Some(len) => {
550 let len_rounded = round_half_away_from_zero(len);
551 let effective_start = if start_rounded < 1.0 {
556 1.0
558 } else {
559 start_rounded
560 };
561
562 let adjusted_len = if start_rounded < 1.0 {
564 len_rounded + start_rounded - 1.0
565 } else {
566 len_rounded
567 };
568
569 if adjusted_len <= 0.0 {
570 return Ok(XPathValue::Empty);
571 }
572
573 let start = (effective_start - 1.0).max(0.0) as usize;
574 let end = (effective_start - 1.0 + adjusted_len).min(items.len() as f64) as usize;
575 (start, end)
576 }
577 None => {
578 if start_rounded < 1.0 {
580 (0, items.len())
581 } else {
582 let start = (start_rounded - 1.0).max(0.0) as usize;
583 (start, items.len())
584 }
585 }
586 };
587
588 if start_idx >= items.len() {
590 return Ok(XPathValue::Empty);
591 }
592
593 let result: Vec<XmlItem<N>> = items
595 .into_iter()
596 .skip(start_idx)
597 .take(end_idx.saturating_sub(start_idx))
598 .collect();
599
600 Ok(XPathValue::from_sequence(result))
601}
602
603fn round_half_away_from_zero(d: f64) -> f64 {
605 if d.is_nan() || d.is_infinite() {
606 return d;
607 }
608 if d >= 0.0 {
609 (d + 0.5).floor()
610 } else {
611 (d - 0.5).ceil()
612 }
613}
614
615pub fn unordered<N: DomNavigator>(
624 _context: &mut DynamicContext<'_, N>,
625 mut args: Vec<XPathValue<N>>,
626) -> Result<XPathValue<N>, XPathError> {
627 if args.len() != 1 {
628 return Err(XPathError::wrong_number_of_arguments(
629 "unordered",
630 1,
631 args.len(),
632 ));
633 }
634
635 Ok(args.remove(0))
637}
638
639pub fn deep_equal<N: DomNavigator>(
649 _context: &mut DynamicContext<'_, N>,
650 mut args: Vec<XPathValue<N>>,
651) -> Result<XPathValue<N>, XPathError> {
652 if args.len() < 2 || args.len() > 3 {
653 return Err(XPathError::wrong_number_of_arguments(
654 "deep-equal",
655 2,
656 args.len(),
657 ));
658 }
659
660 if args.len() == 3 {
662 let collation_arg = args.pop().unwrap();
663 let collation = atomize_to_string_opt(collation_arg)?;
664 validate_collation(collation.as_deref())?;
665 }
666
667 let param1 = args.remove(0);
668 let param2 = args.remove(0);
669
670 let items1 = materialize(param1);
672 let items2 = materialize(param2);
673
674 let iter1: VecNodeIterator<N> = VecNodeIterator::new(items1);
676 let iter2: VecNodeIterator<N> = VecNodeIterator::new(items2);
677
678 let comparer = TreeComparer::default();
680 let result = comparer.deep_equal_iter(&iter1, &iter2)?;
681
682 Ok(XPathValue::boolean(result))
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688 use crate::namespace::table::NameTable;
689 use crate::xpath::context::XPathContext;
690 use crate::xpath::iterator::XmlItem;
691 use crate::xpath::RoXmlNavigator;
692
693 fn make_context<'a>() -> DynamicContext<'a, RoXmlNavigator<'a>> {
694 let table = Box::leak(Box::new(NameTable::new()));
695 let xpath_ctx = Box::leak(Box::new(XPathContext::new(table)));
696 DynamicContext::new(xpath_ctx, 0)
697 }
698
699 fn integer_seq<N: DomNavigator>(values: &[i64]) -> XPathValue<N> {
700 let items: Vec<XmlItem<N>> = values
701 .iter()
702 .map(|&v| XmlItem::Atomic(XmlValue::integer(BigInt::from(v))))
703 .collect();
704 XPathValue::from_sequence(items)
705 }
706
707 fn extract_integers<N: DomNavigator>(value: XPathValue<N>) -> Vec<i64> {
708 match value {
709 XPathValue::Empty => vec![],
710 XPathValue::Item(item) => {
711 if let XmlItem::Atomic(v) = item {
712 vec![v.as_integer().and_then(|i| i.to_i64()).unwrap()]
713 } else {
714 vec![]
715 }
716 }
717 XPathValue::Sequence(items) => items
718 .into_iter()
719 .filter_map(|item| {
720 if let XmlItem::Atomic(v) = item {
721 v.as_integer().and_then(|i| i.to_i64())
722 } else {
723 None
724 }
725 })
726 .collect(),
727 }
728 }
729
730 fn extract_bool<N: DomNavigator>(value: XPathValue<N>) -> bool {
731 match value {
732 XPathValue::Item(XmlItem::Atomic(v)) => v.as_boolean().unwrap_or(false),
733 _ => false,
734 }
735 }
736
737 #[test]
740 fn test_index_of_multiple_matches() {
741 let mut ctx = make_context();
742 let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30, 20]);
743 let search = XPathValue::integer(20);
744 let args = vec![seq, search];
745 let result = index_of(&mut ctx, args).unwrap();
746 assert_eq!(extract_integers(result), vec![2, 4]);
747 }
748
749 #[test]
750 fn test_index_of_no_match() {
751 let mut ctx = make_context();
752 let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30]);
753 let search = XPathValue::integer(40);
754 let args = vec![seq, search];
755 let result = index_of(&mut ctx, args).unwrap();
756 assert_eq!(extract_integers(result), Vec::<i64>::new());
757 }
758
759 #[test]
760 fn test_index_of_string_matches() {
761 let mut ctx = make_context();
762 let items: Vec<XmlItem<RoXmlNavigator>> = vec!["a", "b", "c", "b"]
763 .into_iter()
764 .map(|s| XmlItem::Atomic(XmlValue::string(s)))
765 .collect();
766 let seq = XPathValue::from_sequence(items);
767 let search = XPathValue::string("b");
768 let args = vec![seq, search];
769 let result = index_of(&mut ctx, args).unwrap();
770 assert_eq!(extract_integers(result), vec![2, 4]);
771 }
772
773 #[test]
774 fn test_index_of_empty_sequence() {
775 let mut ctx = make_context();
776 let seq = XPathValue::<RoXmlNavigator>::Empty;
777 let search = XPathValue::integer(1);
778 let args = vec![seq, search];
779 let result = index_of(&mut ctx, args).unwrap();
780 assert!(result.is_empty());
781 }
782
783 #[test]
786 fn test_remove_middle() {
787 let mut ctx = make_context();
788 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
789 let pos = XPathValue::integer(2);
790 let args = vec![seq, pos];
791 let result = remove(&mut ctx, args).unwrap();
792 assert_eq!(extract_integers(result), vec![1, 3]);
793 }
794
795 #[test]
796 fn test_remove_out_of_range_low() {
797 let mut ctx = make_context();
798 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
799 let pos = XPathValue::integer(0);
800 let args = vec![seq, pos];
801 let result = remove(&mut ctx, args).unwrap();
802 assert_eq!(extract_integers(result), vec![1, 2, 3]);
803 }
804
805 #[test]
806 fn test_remove_out_of_range_high() {
807 let mut ctx = make_context();
808 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
809 let pos = XPathValue::integer(10);
810 let args = vec![seq, pos];
811 let result = remove(&mut ctx, args).unwrap();
812 assert_eq!(extract_integers(result), vec![1, 2, 3]);
813 }
814
815 #[test]
818 fn test_insert_before_middle() {
819 let mut ctx = make_context();
820 let target = integer_seq::<RoXmlNavigator>(&[1, 3]);
821 let pos = XPathValue::integer(2);
822 let inserts = XPathValue::integer(2);
823 let args = vec![target, pos, inserts];
824 let result = insert_before(&mut ctx, args).unwrap();
825 assert_eq!(extract_integers(result), vec![1, 2, 3]);
826 }
827
828 #[test]
829 fn test_insert_before_position_less_than_one() {
830 let mut ctx = make_context();
831 let target = integer_seq::<RoXmlNavigator>(&[2, 3]);
832 let pos = XPathValue::integer(0);
833 let inserts = XPathValue::integer(1);
834 let args = vec![target, pos, inserts];
835 let result = insert_before(&mut ctx, args).unwrap();
836 assert_eq!(extract_integers(result), vec![1, 2, 3]);
837 }
838
839 #[test]
840 fn test_insert_before_position_beyond_end() {
841 let mut ctx = make_context();
842 let target = integer_seq::<RoXmlNavigator>(&[1, 2]);
843 let pos = XPathValue::integer(10);
844 let inserts = XPathValue::integer(3);
845 let args = vec![target, pos, inserts];
846 let result = insert_before(&mut ctx, args).unwrap();
847 assert_eq!(extract_integers(result), vec![1, 2, 3]);
848 }
849
850 #[test]
853 fn test_subsequence_with_length() {
854 let mut ctx = make_context();
855 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
856 let start = XPathValue::double(2.0);
857 let len = XPathValue::double(3.0);
858 let args = vec![seq, start, len];
859 let result = subsequence(&mut ctx, args).unwrap();
860 assert_eq!(extract_integers(result), vec![2, 3, 4]);
861 }
862
863 #[test]
864 fn test_subsequence_without_length() {
865 let mut ctx = make_context();
866 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
867 let start = XPathValue::double(3.0);
868 let args = vec![seq, start];
869 let result = subsequence(&mut ctx, args).unwrap();
870 assert_eq!(extract_integers(result), vec![3, 4, 5]);
871 }
872
873 #[test]
874 fn test_subsequence_negative_start() {
875 let mut ctx = make_context();
876 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
877 let start = XPathValue::double(-1.0);
878 let len = XPathValue::double(4.0);
879 let args = vec![seq, start, len];
880 let result = subsequence(&mut ctx, args).unwrap();
881 assert_eq!(extract_integers(result), vec![1, 2]);
883 }
884
885 #[test]
886 fn test_subsequence_nan_start() {
887 let mut ctx = make_context();
888 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
889 let start = XPathValue::double(f64::NAN);
890 let len = XPathValue::double(2.0);
891 let args = vec![seq, start, len];
892 let result = subsequence(&mut ctx, args).unwrap();
893 assert!(result.is_empty());
894 }
895
896 #[test]
897 fn test_subsequence_rounding() {
898 let mut ctx = make_context();
899 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
901 let start = XPathValue::double(1.5);
902 let len = XPathValue::double(2.6);
903 let args = vec![seq, start, len];
904 let result = subsequence(&mut ctx, args).unwrap();
905 assert_eq!(extract_integers(result), vec![2, 3, 4]);
906 }
907
908 #[test]
911 fn test_unordered_passthrough() {
912 let mut ctx = make_context();
913 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
914 let args = vec![seq];
915 let result = unordered(&mut ctx, args).unwrap();
916 assert_eq!(extract_integers(result), vec![1, 2, 3]);
917 }
918
919 #[test]
922 fn test_index_of_integer_matches_double() {
923 let mut ctx = make_context();
924 let seq = integer_seq::<RoXmlNavigator>(&[10, 20, 30]);
926 let search = XPathValue::double(20.0);
928 let args = vec![seq, search];
929 let result = index_of(&mut ctx, args).unwrap();
930 assert_eq!(extract_integers(result), vec![2]);
932 }
933
934 #[test]
935 fn test_index_of_double_matches_integer() {
936 let mut ctx = make_context();
937 let items: Vec<XmlItem<RoXmlNavigator>> = vec![
939 XmlItem::Atomic(XmlValue::double(10.0)),
940 XmlItem::Atomic(XmlValue::double(20.0)),
941 XmlItem::Atomic(XmlValue::double(30.0)),
942 ];
943 let seq = XPathValue::from_sequence(items);
944 let search = XPathValue::integer(20);
946 let args = vec![seq, search];
947 let result = index_of(&mut ctx, args).unwrap();
948 assert_eq!(extract_integers(result), vec![2]);
950 }
951
952 #[test]
953 fn test_index_of_nan_not_equal() {
954 let mut ctx = make_context();
955 let items: Vec<XmlItem<RoXmlNavigator>> = vec![
957 XmlItem::Atomic(XmlValue::double(f64::NAN)),
958 XmlItem::Atomic(XmlValue::double(1.0)),
959 ];
960 let seq = XPathValue::from_sequence(items);
961 let search = XPathValue::double(f64::NAN);
963 let args = vec![seq, search];
964 let result = index_of(&mut ctx, args).unwrap();
965 assert!(result.is_empty());
967 }
968
969 #[test]
972 fn test_reverse_sequence() {
973 let mut ctx = make_context();
974 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3, 4, 5]);
975 let args = vec![seq];
976 let result = reverse(&mut ctx, args).unwrap();
977 assert_eq!(extract_integers(result), vec![5, 4, 3, 2, 1]);
978 }
979
980 #[test]
981 fn test_reverse_empty() {
982 let mut ctx = make_context();
983 let seq = XPathValue::<RoXmlNavigator>::Empty;
984 let args = vec![seq];
985 let result = reverse(&mut ctx, args).unwrap();
986 assert!(result.is_empty());
987 }
988
989 #[test]
990 fn test_reverse_single() {
991 let mut ctx = make_context();
992 let seq = XPathValue::integer(42);
993 let args = vec![seq];
994 let result = reverse(&mut ctx, args).unwrap();
995 assert_eq!(extract_integers(result), vec![42]);
996 }
997
998 #[test]
1001 fn test_zero_or_one_empty() {
1002 let mut ctx = make_context();
1003 let seq = XPathValue::<RoXmlNavigator>::Empty;
1004 let args = vec![seq];
1005 let result = zero_or_one(&mut ctx, args).unwrap();
1006 assert!(result.is_empty());
1007 }
1008
1009 #[test]
1010 fn test_zero_or_one_single() {
1011 let mut ctx = make_context();
1012 let seq = XPathValue::integer(42);
1013 let args = vec![seq];
1014 let result = zero_or_one(&mut ctx, args).unwrap();
1015 assert_eq!(extract_integers(result), vec![42]);
1016 }
1017
1018 #[test]
1019 fn test_zero_or_one_multiple_fails() {
1020 let mut ctx = make_context();
1021 let seq = integer_seq::<RoXmlNavigator>(&[1, 2]);
1022 let args = vec![seq];
1023 let result = zero_or_one(&mut ctx, args);
1024 match result {
1025 Err(e) => assert_eq!(e.error_code(), Some("FORG0003")),
1026 Ok(_) => panic!("Expected FORG0003 error"),
1027 }
1028 }
1029
1030 #[test]
1033 fn test_one_or_more_single() {
1034 let mut ctx = make_context();
1035 let seq = XPathValue::integer(42);
1036 let args = vec![seq];
1037 let result = one_or_more(&mut ctx, args).unwrap();
1038 assert_eq!(extract_integers(result), vec![42]);
1039 }
1040
1041 #[test]
1042 fn test_one_or_more_multiple() {
1043 let mut ctx = make_context();
1044 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
1045 let args = vec![seq];
1046 let result = one_or_more(&mut ctx, args).unwrap();
1047 assert_eq!(extract_integers(result), vec![1, 2, 3]);
1048 }
1049
1050 #[test]
1051 fn test_one_or_more_empty_fails() {
1052 let mut ctx = make_context();
1053 let seq = XPathValue::<RoXmlNavigator>::Empty;
1054 let args = vec![seq];
1055 let result = one_or_more(&mut ctx, args);
1056 match result {
1057 Err(e) => assert_eq!(e.error_code(), Some("FORG0004")),
1058 Ok(_) => panic!("Expected FORG0004 error"),
1059 }
1060 }
1061
1062 #[test]
1065 fn test_exactly_one_single() {
1066 let mut ctx = make_context();
1067 let seq = XPathValue::integer(42);
1068 let args = vec![seq];
1069 let result = exactly_one(&mut ctx, args).unwrap();
1070 assert_eq!(extract_integers(result), vec![42]);
1071 }
1072
1073 #[test]
1074 fn test_exactly_one_empty_fails() {
1075 let mut ctx = make_context();
1076 let seq = XPathValue::<RoXmlNavigator>::Empty;
1077 let args = vec![seq];
1078 let result = exactly_one(&mut ctx, args);
1079 match result {
1080 Err(e) => assert_eq!(e.error_code(), Some("FORG0005")),
1081 Ok(_) => panic!("Expected FORG0005 error"),
1082 }
1083 }
1084
1085 #[test]
1086 fn test_exactly_one_multiple_fails() {
1087 let mut ctx = make_context();
1088 let seq = integer_seq::<RoXmlNavigator>(&[1, 2]);
1089 let args = vec![seq];
1090 let result = exactly_one(&mut ctx, args);
1091 match result {
1092 Err(e) => assert_eq!(e.error_code(), Some("FORG0005")),
1093 Ok(_) => panic!("Expected FORG0005 error"),
1094 }
1095 }
1096
1097 #[test]
1100 fn test_distinct_values_integers() {
1101 let mut ctx = make_context();
1102 let seq = integer_seq::<RoXmlNavigator>(&[1, 2, 1, 3, 2, 1]);
1103 let args = vec![seq];
1104 let result = distinct_values(&mut ctx, args).unwrap();
1105 assert_eq!(extract_integers(result), vec![1, 2, 3]);
1106 }
1107
1108 #[test]
1109 fn test_distinct_values_empty() {
1110 let mut ctx = make_context();
1111 let seq = XPathValue::<RoXmlNavigator>::Empty;
1112 let args = vec![seq];
1113 let result = distinct_values(&mut ctx, args).unwrap();
1114 assert!(result.is_empty());
1115 }
1116
1117 #[test]
1118 fn test_distinct_values_mixed_numeric() {
1119 let mut ctx = make_context();
1120 let items: Vec<XmlItem<RoXmlNavigator>> = vec![
1122 XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
1123 XmlItem::Atomic(XmlValue::double(2.0)),
1124 XmlItem::Atomic(XmlValue::integer(BigInt::from(1))), XmlItem::Atomic(XmlValue::double(2.0)), XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
1127 ];
1128 let seq = XPathValue::from_sequence(items);
1129 let args = vec![seq];
1130 let result = distinct_values(&mut ctx, args).unwrap();
1131 assert_eq!(result.len(), 3);
1133 }
1134
1135 #[test]
1136 fn test_distinct_values_strings() {
1137 let mut ctx = make_context();
1138 let items: Vec<XmlItem<RoXmlNavigator>> = vec!["a", "b", "a", "c", "b"]
1139 .into_iter()
1140 .map(|s| XmlItem::Atomic(XmlValue::string(s)))
1141 .collect();
1142 let seq = XPathValue::from_sequence(items);
1143 let args = vec![seq];
1144 let result = distinct_values(&mut ctx, args).unwrap();
1145 assert_eq!(result.len(), 3);
1147 }
1148
1149 #[test]
1152 fn test_deep_equal_same_integers() {
1153 let mut ctx = make_context();
1154 let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
1155 let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
1156 let args = vec![seq1, seq2];
1157 let result = deep_equal(&mut ctx, args).unwrap();
1158 assert!(extract_bool(result));
1159 }
1160
1161 #[test]
1162 fn test_deep_equal_different_integers() {
1163 let mut ctx = make_context();
1164 let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
1165 let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 4]);
1166 let args = vec![seq1, seq2];
1167 let result = deep_equal(&mut ctx, args).unwrap();
1168 assert!(!extract_bool(result));
1169 }
1170
1171 #[test]
1172 fn test_deep_equal_nan() {
1173 let mut ctx = make_context();
1174 let seq1: XPathValue<RoXmlNavigator> = XPathValue::double(f64::NAN);
1175 let seq2: XPathValue<RoXmlNavigator> = XPathValue::double(f64::NAN);
1176 let args = vec![seq1, seq2];
1177 let result = deep_equal(&mut ctx, args).unwrap();
1178 assert!(extract_bool(result));
1180 }
1181
1182 #[test]
1183 fn test_deep_equal_empty_sequences() {
1184 let mut ctx = make_context();
1185 let seq1 = XPathValue::<RoXmlNavigator>::Empty;
1186 let seq2 = XPathValue::<RoXmlNavigator>::Empty;
1187 let args = vec![seq1, seq2];
1188 let result = deep_equal(&mut ctx, args).unwrap();
1189 assert!(extract_bool(result));
1190 }
1191
1192 #[test]
1193 fn test_deep_equal_different_lengths() {
1194 let mut ctx = make_context();
1195 let seq1 = integer_seq::<RoXmlNavigator>(&[1, 2]);
1196 let seq2 = integer_seq::<RoXmlNavigator>(&[1, 2, 3]);
1197 let args = vec![seq1, seq2];
1198 let result = deep_equal(&mut ctx, args).unwrap();
1199 assert!(!extract_bool(result));
1200 }
1201}