1pub mod aggregate;
16pub mod datetime;
17pub mod extensible;
18pub mod node;
19pub mod numeric;
20pub mod qname;
21pub mod regex;
22pub mod registry;
23pub mod sequence;
24pub mod signature;
25pub mod special;
26pub mod string;
27pub mod uri;
28
29pub use extensible::{
30 BuiltinCatalog, BuiltinEvaluator, CustomFn, DynamicFunctionSignature, FunctionCatalog,
31 FunctionEvaluator, FunctionHandle, FunctionSet, XPath10Catalog, XPath10Evaluator,
32};
33pub use registry::{FunctionEntry, FunctionKey, FunctionRegistry, FUNCTION_REGISTRY};
34pub use signature::{FunctionArity, FunctionSignature, FN_2010_NAMESPACE, FN_NAMESPACE};
35
36use num_bigint::BigInt;
37
38use crate::types::value::XmlValue;
39use crate::types::XmlTypeCode;
40use crate::xpath::atomize;
41use crate::xpath::error::XPathError;
42use crate::xpath::iterator::XmlItem;
43use crate::xpath::DomNavigator;
44
45use super::context::DynamicContext;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52#[repr(u16)]
53pub enum FunctionId {
54 Concat = 1,
57 StringJoin,
59 Substring,
61 StringLength,
63 NormalizeSpace,
65 NormalizeUnicode,
67 UpperCase,
69 LowerCase,
71 Translate,
73 EncodeForUri,
75 IriToUri,
77 EscapeHtmlUri,
79 Contains,
81 StartsWith,
83 EndsWith,
85 SubstringBefore,
87 SubstringAfter,
89 StringToCodepoints,
91 CodepointsToString,
93 Compare,
95 CodepointEqual,
97
98 Abs = 100,
101 Ceiling,
103 Floor,
105 Round,
107 RoundHalfToEven,
109
110 Empty = 200,
113 Exists,
115 Reverse,
117 IndexOf,
119 Remove,
121 InsertBefore,
123 Subsequence,
125 Unordered,
127 ZeroOrOne,
129 OneOrMore,
131 ExactlyOne,
133 DistinctValues,
135 DeepEqual,
137 Count,
139
140 Sum = 300,
143 Avg,
145 Min,
147 Max,
149
150 Name = 400,
153 LocalName,
155 NamespaceUri,
157 NodeName,
159 Nilled,
161 BaseUri,
163 DocumentUri,
165 Lang,
167 Root,
169 Id,
171 Collection,
173
174 DateTime = 500,
177 CurrentDateTime,
179 CurrentDate,
181 CurrentTime,
183 ImplicitTimezone,
185 YearsFromDuration,
187 MonthsFromDuration,
189 DaysFromDuration,
191 HoursFromDuration,
193 MinutesFromDuration,
195 SecondsFromDuration,
197 YearFromDateTime,
199 MonthFromDateTime,
201 DayFromDateTime,
203 HoursFromDateTime,
205 MinutesFromDateTime,
207 SecondsFromDateTime,
209 TimezoneFromDateTime,
211 YearFromDate,
213 MonthFromDate,
215 DayFromDate,
217 TimezoneFromDate,
219 HoursFromTime,
221 MinutesFromTime,
223 SecondsFromTime,
225 TimezoneFromTime,
227 AdjustDateTimeToTimezone,
229 AdjustDateToTimezone,
231 AdjustTimeToTimezone,
233
234 ResolveQName = 600,
237 QName,
239 PrefixFromQName,
241 LocalNameFromQName,
243 NamespaceUriFromQName,
245 NamespaceUriForPrefix,
247 InScopePrefixes,
249
250 ResolveUri = 700,
253 StaticBaseUri,
255
256 Matches = 800,
259 Replace,
261 Tokenize,
263
264 Position = 900,
267 Last,
269 Trace,
271 Data,
273 DefaultCollation,
275
276 True = 1000,
279 False,
281 Not,
283 Boolean,
285
286 String = 1100,
289 Number,
291}
292
293#[derive(Debug, Clone)]
298pub enum XPathValue<N: DomNavigator> {
299 Empty,
301 Item(XmlItem<N>),
303 Sequence(Vec<XmlItem<N>>),
305}
306
307impl<N: DomNavigator> XPathValue<N> {
308 pub fn empty() -> Self {
310 Self::Empty
311 }
312
313 pub fn from_item(item: XmlItem<N>) -> Self {
315 Self::Item(item)
316 }
317
318 pub fn from_atomic(value: XmlValue) -> Self {
320 Self::Item(XmlItem::Atomic(value))
321 }
322
323 pub fn from_node(node: N) -> Self {
325 Self::Item(XmlItem::Node(node))
326 }
327
328 pub fn from_sequence(items: Vec<XmlItem<N>>) -> Self {
330 match items.len() {
331 0 => Self::Empty,
332 1 => Self::Item(items.into_iter().next().unwrap()),
333 _ => Self::Sequence(items),
334 }
335 }
336
337 pub fn boolean(b: bool) -> Self {
339 Self::from_atomic(XmlValue::boolean(b))
340 }
341
342 pub fn string(s: impl Into<String>) -> Self {
344 Self::from_atomic(XmlValue::string(s))
345 }
346
347 pub fn integer(i: impl Into<num_bigint::BigInt>) -> Self {
349 Self::from_atomic(XmlValue::integer(i.into()))
350 }
351
352 pub fn decimal(d: rust_decimal::Decimal) -> Self {
354 Self::from_atomic(XmlValue::decimal(d))
355 }
356
357 pub fn double(d: f64) -> Self {
359 Self::from_atomic(XmlValue::double(d))
360 }
361
362 pub fn is_empty(&self) -> bool {
364 matches!(self, Self::Empty)
365 }
366
367 pub fn len(&self) -> usize {
369 match self {
370 Self::Empty => 0,
371 Self::Item(_) => 1,
372 Self::Sequence(items) => items.len(),
373 }
374 }
375
376 pub fn is_single(&self) -> bool {
378 matches!(self, Self::Item(_))
379 }
380
381 pub fn into_vec(self) -> Vec<XmlItem<N>> {
383 match self {
384 Self::Empty => Vec::new(),
385 Self::Item(item) => vec![item],
386 Self::Sequence(items) => items,
387 }
388 }
389
390 pub fn as_slice(&self) -> &[XmlItem<N>] {
392 match self {
393 Self::Empty => &[],
394 Self::Item(_) => {
395 &[]
398 }
399 Self::Sequence(items) => items,
400 }
401 }
402
403 pub fn first(&self) -> Option<&XmlItem<N>> {
405 match self {
406 Self::Empty => None,
407 Self::Item(item) => Some(item),
408 Self::Sequence(items) => items.first(),
409 }
410 }
411
412 pub fn as_str(&self) -> Option<String> {
424 match self {
425 Self::Item(XmlItem::Atomic(v)) => v.as_string().map(|s| s.to_string()),
426 _ => None,
427 }
428 }
429
430 pub fn as_bool(&self) -> Option<bool> {
434 match self {
435 Self::Item(XmlItem::Atomic(v)) => v.as_boolean(),
436 _ => None,
437 }
438 }
439
440 pub fn as_f64(&self) -> Option<f64> {
444 match self {
445 Self::Item(XmlItem::Atomic(v)) => v.as_double(),
446 _ => None,
447 }
448 }
449
450 pub fn as_integer(&self) -> Option<num_bigint::BigInt> {
454 match self {
455 Self::Item(XmlItem::Atomic(v)) => v.as_integer().cloned(),
456 _ => None,
457 }
458 }
459}
460
461pub fn atomize_to_string<N: DomNavigator>(value: XPathValue<N>) -> Result<String, XPathError> {
472 match value {
473 XPathValue::Empty => Ok(String::new()),
474 XPathValue::Item(item) => item_to_string(item),
475 XPathValue::Sequence(items) => {
476 if items.len() == 1 {
477 item_to_string(items.into_iter().next().unwrap())
478 } else {
479 Err(XPathError::more_than_one_item())
480 }
481 }
482 }
483}
484
485pub fn atomize_to_string_required<N: DomNavigator>(
489 value: XPathValue<N>,
490) -> Result<String, XPathError> {
491 match value {
492 XPathValue::Empty => Err(XPathError::XPTY0004 {
493 expected: "xs:string".to_string(),
494 found: "empty-sequence()".to_string(),
495 }),
496 other => atomize_to_string(other),
497 }
498}
499
500pub fn atomize_to_string_strict<N: DomNavigator>(
506 value: XPathValue<N>,
507) -> Result<String, XPathError> {
508 match value {
509 XPathValue::Empty => Ok(String::new()),
510 XPathValue::Item(item) => item_to_string_strict(item),
511 XPathValue::Sequence(items) => {
512 if items.len() == 1 {
513 item_to_string_strict(items.into_iter().next().unwrap())
514 } else {
515 Err(XPathError::more_than_one_item())
516 }
517 }
518 }
519}
520
521pub fn atomize_to_string_strict_opt<N: DomNavigator>(
525 value: XPathValue<N>,
526) -> Result<Option<String>, XPathError> {
527 match value {
528 XPathValue::Empty => Ok(None),
529 other => atomize_to_string_strict(other).map(Some),
530 }
531}
532
533fn item_to_string_strict<N: DomNavigator>(item: XmlItem<N>) -> Result<String, XPathError> {
536 match item {
537 XmlItem::Atomic(value) => match value.type_code {
538 XmlTypeCode::String
539 | XmlTypeCode::UntypedAtomic
540 | XmlTypeCode::AnyUri
541 | XmlTypeCode::NormalizedString
542 | XmlTypeCode::Token
543 | XmlTypeCode::Language
544 | XmlTypeCode::NmToken
545 | XmlTypeCode::Name
546 | XmlTypeCode::NCName
547 | XmlTypeCode::Id
548 | XmlTypeCode::IdRef
549 | XmlTypeCode::Entity => Ok(atomize::string_value(&value)),
550 _ => Err(XPathError::XPTY0004 {
551 expected: "xs:string".to_string(),
552 found: crate::xpath::type_info::type_code_to_name(value.type_code).to_string(),
553 }),
554 },
555 XmlItem::Node(nav) => Ok(nav.value()),
556 }
557}
558
559pub fn atomize_to_string_opt<N: DomNavigator>(
563 value: XPathValue<N>,
564) -> Result<Option<String>, XPathError> {
565 match value {
566 XPathValue::Empty => Ok(None),
567 other => atomize_to_string(other).map(Some),
568 }
569}
570
571fn item_to_string<N: DomNavigator>(item: XmlItem<N>) -> Result<String, XPathError> {
573 match item {
574 XmlItem::Atomic(value) => Ok(atomize::string_value(&value)),
575 XmlItem::Node(nav) => Ok(nav.value()),
576 }
577}
578
579pub fn atomize_to_double<N: DomNavigator>(value: XPathValue<N>) -> Result<f64, XPathError> {
586 match value {
587 XPathValue::Empty => Ok(f64::NAN),
588 XPathValue::Item(item) => item_to_double(item),
589 XPathValue::Sequence(items) => {
590 if items.len() == 1 {
591 item_to_double(items.into_iter().next().unwrap())
592 } else {
593 Err(XPathError::more_than_one_item())
594 }
595 }
596 }
597}
598
599fn item_to_double<N: DomNavigator>(item: XmlItem<N>) -> Result<f64, XPathError> {
601 match item {
602 XmlItem::Atomic(value) => Ok(atomize::to_number(&value)),
603 XmlItem::Node(nav) => {
604 let s = nav.value();
605 Ok(s.trim().parse().unwrap_or(f64::NAN))
606 }
607 }
608}
609
610pub fn atomize_to_single<N: DomNavigator>(value: XPathValue<N>) -> Result<XmlValue, XPathError> {
614 match value {
615 XPathValue::Empty => Err(XPathError::XPTY0004 {
616 expected: "item()".to_string(),
617 found: "empty-sequence()".to_string(),
618 }),
619 XPathValue::Item(item) => item_to_atomic(item),
620 XPathValue::Sequence(items) => {
621 if items.len() == 1 {
622 item_to_atomic(items.into_iter().next().unwrap())
623 } else {
624 Err(XPathError::more_than_one_item())
625 }
626 }
627 }
628}
629
630pub fn atomize_to_single_opt<N: DomNavigator>(
632 value: XPathValue<N>,
633) -> Result<Option<XmlValue>, XPathError> {
634 match value {
635 XPathValue::Empty => Ok(None),
636 other => atomize_to_single(other).map(Some),
637 }
638}
639
640fn item_to_atomic<N: DomNavigator>(item: XmlItem<N>) -> Result<XmlValue, XPathError> {
645 match item {
646 XmlItem::Atomic(value) => atomize::atomize(&value),
647 XmlItem::Node(nav) => atomize::atomize_node(&nav)?
648 .ok_or_else(|| XPathError::type_mismatch("item()", "empty-sequence()")),
649 }
650}
651
652pub fn atomize_sequence<N: DomNavigator>(
656 value: XPathValue<N>,
657) -> Result<Vec<XmlValue>, XPathError> {
658 match value {
659 XPathValue::Empty => Ok(Vec::new()),
660 XPathValue::Item(item) => match item {
661 XmlItem::Atomic(value) => Ok(vec![atomize::atomize(&value)?]),
662 XmlItem::Node(nav) => match atomize::atomize_node(&nav)? {
663 Some(v) => Ok(vec![v]),
664 None => Ok(Vec::new()),
665 },
666 },
667 XPathValue::Sequence(items) => {
668 let mut result = Vec::with_capacity(items.len());
669 for item in items {
670 match item {
671 XmlItem::Atomic(value) => result.push(atomize::atomize(&value)?),
672 XmlItem::Node(nav) => {
673 if let Some(v) = atomize::atomize_node(&nav)? {
674 result.push(v);
675 }
676 }
677 }
678 }
679 Ok(result)
680 }
681 }
682}
683
684pub fn materialize<N: DomNavigator>(value: XPathValue<N>) -> Vec<XmlItem<N>> {
686 value.into_vec()
687}
688
689pub fn eval_function<N: DomNavigator>(
698 id: FunctionId,
699 context: &mut DynamicContext<'_, N>,
700 args: Vec<XPathValue<N>>,
701) -> Result<XPathValue<N>, XPathError> {
702 match id {
703 FunctionId::True => Ok(XPathValue::boolean(true)),
707 FunctionId::False => Ok(XPathValue::boolean(false)),
708 FunctionId::Not => eval_not(args),
709
710 FunctionId::Position => special::position(context, args),
714 FunctionId::Last => special::last(context, args),
715 FunctionId::Trace => special::trace(context, args),
716 FunctionId::Data => special::data(context, args),
717 FunctionId::DefaultCollation => special::default_collation(context, args),
718
719 FunctionId::Empty => eval_empty(args),
723 FunctionId::Exists => eval_exists(args),
724 FunctionId::Count => eval_count(args),
725
726 FunctionId::Concat => string::concat(context, args),
730 FunctionId::StringJoin => string::string_join(context, args),
731 FunctionId::Substring => string::substring(context, args),
732 FunctionId::StringLength => string::string_length(context, args),
733 FunctionId::NormalizeSpace => string::normalize_space(context, args),
734 FunctionId::NormalizeUnicode => string::normalize_unicode(context, args),
735 FunctionId::UpperCase => string::upper_case(context, args),
736 FunctionId::LowerCase => string::lower_case(context, args),
737 FunctionId::Translate => string::translate(context, args),
738 FunctionId::EncodeForUri => string::encode_for_uri(context, args),
739 FunctionId::IriToUri => string::iri_to_uri(context, args),
740 FunctionId::EscapeHtmlUri => string::escape_html_uri(context, args),
741 FunctionId::Contains => string::contains(context, args),
742 FunctionId::StartsWith => string::starts_with(context, args),
743 FunctionId::EndsWith => string::ends_with(context, args),
744 FunctionId::SubstringBefore => string::substring_before(context, args),
745 FunctionId::SubstringAfter => string::substring_after(context, args),
746 FunctionId::StringToCodepoints => string::string_to_codepoints(context, args),
747 FunctionId::CodepointsToString => string::codepoints_to_string(context, args),
748 FunctionId::Compare => string::compare(context, args),
749 FunctionId::CodepointEqual => string::codepoint_equal(context, args),
750
751 FunctionId::Abs => numeric::abs(context, args),
755 FunctionId::Ceiling => numeric::ceiling(context, args),
756 FunctionId::Floor => numeric::floor(context, args),
757 FunctionId::Round => numeric::round(context, args),
758 FunctionId::RoundHalfToEven => numeric::round_half_to_even(context, args),
759
760 FunctionId::Reverse => sequence::reverse(context, args),
764 FunctionId::ZeroOrOne => sequence::zero_or_one(context, args),
765 FunctionId::OneOrMore => sequence::one_or_more(context, args),
766 FunctionId::ExactlyOne => sequence::exactly_one(context, args),
767 FunctionId::DistinctValues => sequence::distinct_values(context, args),
768 FunctionId::IndexOf => sequence::index_of(context, args),
769 FunctionId::Remove => sequence::remove(context, args),
770 FunctionId::InsertBefore => sequence::insert_before(context, args),
771 FunctionId::Subsequence => sequence::subsequence(context, args),
772 FunctionId::Unordered => sequence::unordered(context, args),
773 FunctionId::DeepEqual => sequence::deep_equal(context, args),
774
775 FunctionId::Sum => aggregate::sum(context, args),
779 FunctionId::Avg => aggregate::avg(context, args),
780 FunctionId::Min => aggregate::min(context, args),
781 FunctionId::Max => aggregate::max(context, args),
782
783 FunctionId::Name => node::name(context, args),
787 FunctionId::LocalName => node::local_name(context, args),
788 FunctionId::NamespaceUri => node::namespace_uri(context, args),
789 FunctionId::NodeName => node::node_name(context, args),
790 FunctionId::Nilled => node::nilled(context, args),
791 FunctionId::BaseUri => node::base_uri(context, args),
792 FunctionId::DocumentUri => node::document_uri(context, args),
793 FunctionId::Lang => node::lang(context, args),
794 FunctionId::Root => node::root(context, args),
795 FunctionId::Id => node::id(context, args),
796 FunctionId::Collection => {
797 if args.len() > 1 {
802 return Err(XPathError::wrong_number_of_arguments(
803 "collection",
804 1,
805 args.len(),
806 ));
807 }
808 Ok(XPathValue::Empty)
809 }
810
811 FunctionId::DateTime => datetime::create_datetime(context, args),
815 FunctionId::CurrentDateTime => datetime::current_datetime(context, args),
816 FunctionId::CurrentDate => datetime::current_date(context, args),
817 FunctionId::CurrentTime => datetime::current_time(context, args),
818 FunctionId::ImplicitTimezone => datetime::implicit_timezone(context, args),
819 FunctionId::YearsFromDuration => datetime::years_from_duration(context, args),
821 FunctionId::MonthsFromDuration => datetime::months_from_duration(context, args),
822 FunctionId::DaysFromDuration => datetime::days_from_duration(context, args),
823 FunctionId::HoursFromDuration => datetime::hours_from_duration(context, args),
824 FunctionId::MinutesFromDuration => datetime::minutes_from_duration(context, args),
825 FunctionId::SecondsFromDuration => datetime::seconds_from_duration(context, args),
826 FunctionId::YearFromDateTime => datetime::year_from_datetime(context, args),
828 FunctionId::MonthFromDateTime => datetime::month_from_datetime(context, args),
829 FunctionId::DayFromDateTime => datetime::day_from_datetime(context, args),
830 FunctionId::HoursFromDateTime => datetime::hours_from_datetime(context, args),
831 FunctionId::MinutesFromDateTime => datetime::minutes_from_datetime(context, args),
832 FunctionId::SecondsFromDateTime => datetime::seconds_from_datetime(context, args),
833 FunctionId::TimezoneFromDateTime => datetime::timezone_from_datetime(context, args),
834 FunctionId::YearFromDate => datetime::year_from_date(context, args),
836 FunctionId::MonthFromDate => datetime::month_from_date(context, args),
837 FunctionId::DayFromDate => datetime::day_from_date(context, args),
838 FunctionId::TimezoneFromDate => datetime::timezone_from_date(context, args),
839 FunctionId::HoursFromTime => datetime::hours_from_time(context, args),
841 FunctionId::MinutesFromTime => datetime::minutes_from_time(context, args),
842 FunctionId::SecondsFromTime => datetime::seconds_from_time(context, args),
843 FunctionId::TimezoneFromTime => datetime::timezone_from_time(context, args),
844 FunctionId::AdjustDateTimeToTimezone => {
846 datetime::adjust_datetime_to_timezone(context, args)
847 }
848 FunctionId::AdjustDateToTimezone => datetime::adjust_date_to_timezone(context, args),
849 FunctionId::AdjustTimeToTimezone => datetime::adjust_time_to_timezone(context, args),
850
851 FunctionId::ResolveQName => qname::resolve_qname(context, args),
855 FunctionId::QName => qname::qname_constructor(context, args),
856 FunctionId::PrefixFromQName => qname::prefix_from_qname(context, args),
857 FunctionId::LocalNameFromQName => qname::local_name_from_qname(context, args),
858 FunctionId::NamespaceUriFromQName => qname::namespace_uri_from_qname(context, args),
859 FunctionId::NamespaceUriForPrefix => qname::namespace_uri_for_prefix(context, args),
860 FunctionId::InScopePrefixes => qname::in_scope_prefixes(context, args),
861
862 FunctionId::ResolveUri => uri::resolve_uri(context, args),
866 FunctionId::StaticBaseUri => uri::static_base_uri(context, args),
867
868 FunctionId::Matches => regex::matches(context, args),
872 FunctionId::Replace => regex::replace(context, args),
873 FunctionId::Tokenize => regex::tokenize(context, args),
874
875 FunctionId::String => eval_fn_string(context, args),
879 FunctionId::Number => eval_fn_number(context, args),
880 FunctionId::Boolean => eval_boolean(args),
881 }
882}
883
884fn eval_not<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
887 if args.len() != 1 {
888 return Err(XPathError::wrong_number_of_arguments("not", 1, args.len()));
889 }
890 let arg = args.remove(0);
891 let ebv = effective_boolean_value(&arg)?;
892 Ok(XPathValue::boolean(!ebv))
893}
894
895fn eval_empty<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
896 if args.len() != 1 {
897 return Err(XPathError::wrong_number_of_arguments(
898 "empty",
899 1,
900 args.len(),
901 ));
902 }
903 let arg = args.remove(0);
904 Ok(XPathValue::boolean(arg.is_empty()))
905}
906
907fn eval_exists<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
908 if args.len() != 1 {
909 return Err(XPathError::wrong_number_of_arguments(
910 "exists",
911 1,
912 args.len(),
913 ));
914 }
915 let arg = args.remove(0);
916 Ok(XPathValue::boolean(!arg.is_empty()))
917}
918
919fn eval_count<N: DomNavigator>(mut args: Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError> {
920 if args.len() != 1 {
921 return Err(XPathError::wrong_number_of_arguments(
922 "count",
923 1,
924 args.len(),
925 ));
926 }
927 let arg = args.remove(0);
928 Ok(XPathValue::integer(arg.len() as i64))
929}
930
931fn eval_fn_string<N: DomNavigator>(
932 context: &mut DynamicContext<'_, N>,
933 mut args: Vec<XPathValue<N>>,
934) -> Result<XPathValue<N>, XPathError> {
935 match args.len() {
936 0 => {
937 let item = context.require_context_item()?.clone();
939 let s = match item {
940 XmlItem::Node(nav) => nav.value(),
941 XmlItem::Atomic(v) => atomize::string_value(&v),
942 };
943 Ok(XPathValue::string(s))
944 }
945 1 => {
946 let arg = args.remove(0);
947 let s = atomize_to_string(arg)?;
948 Ok(XPathValue::string(s))
949 }
950 _ => Err(XPathError::wrong_number_of_arguments(
951 "string",
952 1,
953 args.len(),
954 )),
955 }
956}
957
958fn eval_fn_number<N: DomNavigator>(
959 context: &mut DynamicContext<'_, N>,
960 mut args: Vec<XPathValue<N>>,
961) -> Result<XPathValue<N>, XPathError> {
962 match args.len() {
963 0 => {
964 let item = context.require_context_item()?.clone();
966 let d = match item {
967 XmlItem::Node(nav) => {
968 let s = nav.value();
969 s.trim().parse().unwrap_or(f64::NAN)
970 }
971 XmlItem::Atomic(v) => atomize::to_number(&v),
972 };
973 Ok(XPathValue::double(d))
974 }
975 1 => {
976 let arg = args.remove(0);
977 let d = atomize_to_double(arg)?;
978 Ok(XPathValue::double(d))
979 }
980 _ => Err(XPathError::wrong_number_of_arguments(
981 "number",
982 1,
983 args.len(),
984 )),
985 }
986}
987
988fn eval_boolean<N: DomNavigator>(
989 mut args: Vec<XPathValue<N>>,
990) -> Result<XPathValue<N>, XPathError> {
991 if args.len() != 1 {
992 return Err(XPathError::wrong_number_of_arguments(
993 "boolean",
994 1,
995 args.len(),
996 ));
997 }
998 let arg = args.remove(0);
999 let ebv = effective_boolean_value(&arg)?;
1000 Ok(XPathValue::boolean(ebv))
1001}
1002
1003pub fn effective_boolean_value<N: DomNavigator>(value: &XPathValue<N>) -> Result<bool, XPathError> {
1005 match value {
1006 XPathValue::Empty => Ok(false),
1007 XPathValue::Item(item) => item_boolean_value(item),
1008 XPathValue::Sequence(items) => {
1009 if items.is_empty() {
1010 Ok(false)
1011 } else if let Some(XmlItem::Node(_)) = items.first() {
1012 Ok(true)
1014 } else if items.len() == 1 {
1015 item_boolean_value(&items[0])
1016 } else {
1017 Err(XPathError::FORG0006 {
1019 message:
1020 "Effective boolean value not defined for sequence of multiple atomic values"
1021 .to_string(),
1022 })
1023 }
1024 }
1025 }
1026}
1027
1028pub fn effective_boolean_value_10<N: DomNavigator>(
1034 value: &XPathValue<N>,
1035) -> Result<bool, XPathError> {
1036 match value {
1037 XPathValue::Empty => Ok(false),
1038 XPathValue::Item(item) => item_boolean_value(item),
1039 XPathValue::Sequence(items) => {
1040 if items.is_empty() {
1041 Ok(false)
1042 } else if items.len() == 1 {
1043 item_boolean_value(&items[0])
1044 } else {
1045 Ok(true)
1047 }
1048 }
1049 }
1050}
1051
1052fn item_boolean_value<N: DomNavigator>(item: &XmlItem<N>) -> Result<bool, XPathError> {
1053 match item {
1054 XmlItem::Node(_) => Ok(true),
1055 XmlItem::Atomic(value) => {
1056 match value.as_boolean() {
1057 Some(b) => Ok(b),
1058 None => {
1059 if let Some(s) = value.as_string() {
1061 Ok(!s.is_empty())
1062 } else if value.type_code == crate::types::XmlTypeCode::AnyUri {
1063 let s = value.to_string_value();
1065 Ok(!s.is_empty())
1066 } else if let Some(d) = value.as_double() {
1067 Ok(!d.is_nan() && d != 0.0)
1069 } else if let Some(i) = value.as_integer() {
1070 Ok(*i != BigInt::from(0))
1071 } else {
1072 Err(XPathError::FORG0006 {
1075 message: format!(
1076 "Effective boolean value not defined for type {:?}",
1077 value.type_code
1078 ),
1079 })
1080 }
1081 }
1082 }
1083 }
1084 }
1085}
1086
1087impl<N: DomNavigator> From<bool> for XPathValue<N> {
1092 fn from(b: bool) -> Self {
1093 XPathValue::boolean(b)
1094 }
1095}
1096
1097impl<N: DomNavigator> From<i32> for XPathValue<N> {
1098 fn from(i: i32) -> Self {
1099 XPathValue::integer(BigInt::from(i))
1100 }
1101}
1102
1103impl<N: DomNavigator> From<i64> for XPathValue<N> {
1104 fn from(i: i64) -> Self {
1105 XPathValue::integer(BigInt::from(i))
1106 }
1107}
1108
1109impl<N: DomNavigator> From<f64> for XPathValue<N> {
1110 fn from(d: f64) -> Self {
1111 XPathValue::double(d)
1112 }
1113}
1114
1115impl<N: DomNavigator> From<f32> for XPathValue<N> {
1116 fn from(f: f32) -> Self {
1117 XPathValue::double(f as f64)
1118 }
1119}
1120
1121impl<N: DomNavigator> From<String> for XPathValue<N> {
1122 fn from(s: String) -> Self {
1123 XPathValue::string(s)
1124 }
1125}
1126
1127impl<N: DomNavigator> From<&str> for XPathValue<N> {
1128 fn from(s: &str) -> Self {
1129 XPathValue::string(s)
1130 }
1131}
1132
1133impl<N: DomNavigator> From<()> for XPathValue<N> {
1134 fn from(_: ()) -> Self {
1135 XPathValue::empty()
1136 }
1137}
1138
1139impl<N: DomNavigator> From<BigInt> for XPathValue<N> {
1140 fn from(i: BigInt) -> Self {
1141 XPathValue::integer(i)
1142 }
1143}
1144
1145#[cfg(test)]
1146mod tests {
1147 use super::*;
1148 use crate::xpath::RoXmlNavigator;
1149
1150 #[test]
1151 fn test_xpath_value_empty() {
1152 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
1153 assert!(value.is_empty());
1154 assert_eq!(value.len(), 0);
1155 }
1156
1157 #[test]
1158 fn test_xpath_value_single() {
1159 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(true);
1160 assert!(!value.is_empty());
1161 assert_eq!(value.len(), 1);
1162 assert!(value.is_single());
1163 }
1164
1165 #[test]
1166 fn test_xpath_value_from_sequence() {
1167 let items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
1168 XmlItem::Atomic(XmlValue::integer(1.into())),
1169 XmlItem::Atomic(XmlValue::integer(2.into())),
1170 ];
1171 let value = XPathValue::from_sequence(items);
1172 assert_eq!(value.len(), 2);
1173 assert!(!value.is_single());
1174 }
1175
1176 #[test]
1177 fn test_effective_boolean_value_empty() {
1178 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
1179 assert!(!effective_boolean_value(&value).unwrap());
1180 }
1181
1182 #[test]
1183 fn test_effective_boolean_value_boolean() {
1184 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(true);
1185 assert!(effective_boolean_value(&value).unwrap());
1186
1187 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::boolean(false);
1188 assert!(!effective_boolean_value(&value).unwrap());
1189 }
1190
1191 #[test]
1192 fn test_effective_boolean_value_string() {
1193 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("hello");
1194 assert!(effective_boolean_value(&value).unwrap());
1195
1196 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("");
1197 assert!(!effective_boolean_value(&value).unwrap());
1198 }
1199
1200 #[test]
1201 fn test_effective_boolean_value_number() {
1202 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(1.0);
1203 assert!(effective_boolean_value(&value).unwrap());
1204
1205 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(0.0);
1206 assert!(!effective_boolean_value(&value).unwrap());
1207
1208 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(f64::NAN);
1209 assert!(!effective_boolean_value(&value).unwrap());
1210 }
1211
1212 use crate::namespace::table::NameTable;
1217 use crate::xpath::context::{DynamicContext, XPathContext};
1218
1219 #[test]
1220 fn test_eval_fn_string_integer() {
1221 let names = NameTable::new();
1222 let static_ctx = XPathContext::new(&names);
1223 let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
1224 DynamicContext::new(&static_ctx, 0);
1225 let args = vec![XPathValue::integer(42i64)];
1226 let result = eval_fn_string(&mut ctx, args).unwrap();
1227 assert_eq!(result.as_str(), Some("42".to_string()));
1228 }
1229
1230 #[test]
1231 fn test_eval_fn_string_string() {
1232 let names = NameTable::new();
1233 let static_ctx = XPathContext::new(&names);
1234 let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
1235 DynamicContext::new(&static_ctx, 0);
1236 let args = vec![XPathValue::string("hello")];
1237 let result = eval_fn_string(&mut ctx, args).unwrap();
1238 assert_eq!(result.as_str(), Some("hello".to_string()));
1239 }
1240
1241 #[test]
1242 fn test_eval_fn_number_string() {
1243 let names = NameTable::new();
1244 let static_ctx = XPathContext::new(&names);
1245 let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
1246 DynamicContext::new(&static_ctx, 0);
1247 let args = vec![XPathValue::string("42.5")];
1248 let result = eval_fn_number(&mut ctx, args).unwrap();
1249 assert_eq!(result.as_f64(), Some(42.5));
1250 }
1251
1252 #[test]
1253 fn test_eval_fn_number_invalid() {
1254 let names = NameTable::new();
1255 let static_ctx = XPathContext::new(&names);
1256 let mut ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
1257 DynamicContext::new(&static_ctx, 0);
1258 let args = vec![XPathValue::string("abc")];
1259 let result = eval_fn_number(&mut ctx, args).unwrap();
1260 assert!(result.as_f64().unwrap().is_nan());
1261 }
1262
1263 #[test]
1264 fn test_eval_boolean_false_empty_string() {
1265 let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::string("")];
1266 let result = eval_boolean(args).unwrap();
1267 assert_eq!(result.as_bool(), Some(false));
1268 }
1269
1270 #[test]
1271 fn test_eval_boolean_true_nonempty_string() {
1272 let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::string("x")];
1273 let result = eval_boolean(args).unwrap();
1274 assert_eq!(result.as_bool(), Some(true));
1275 }
1276
1277 #[test]
1278 fn test_eval_boolean_false_zero() {
1279 let args: Vec<XPathValue<RoXmlNavigator<'static>>> = vec![XPathValue::double(0.0)];
1280 let result = eval_boolean(args).unwrap();
1281 assert_eq!(result.as_bool(), Some(false));
1282 }
1283}