1use std::cell::{Cell, RefCell};
2use std::collections::HashSet;
3use std::fmt::Write as _;
4
5use sup_xml_tree::dom::Node;
6
7use super::ast::*;
8use super::index::{DocIndexLike, NodeId, XPathNodeKind};
9use crate::error::{ErrorDomain, ErrorLevel, XmlError};
10
11type Result<T> = std::result::Result<T, XmlError>;
12
13pub fn xpath_err(msg: impl Into<String>) -> XmlError {
14 XmlError::new(ErrorDomain::XPath, ErrorLevel::Error, msg)
15}
16
17pub const DEFAULT_MAX_EVAL_STEPS: u64 = 20_000_000;
36
37thread_local! {
38 static EVAL_STEPS_BUDGET: Cell<u64> = const { Cell::new(DEFAULT_MAX_EVAL_STEPS) };
45 static EVAL_STEPS_REMAINING: Cell<u64> = const { Cell::new(DEFAULT_MAX_EVAL_STEPS) };
51}
52
53pub fn set_eval_budget(max_steps: u64) {
59 EVAL_STEPS_BUDGET.with(|c| c.set(max_steps));
60}
61
62pub fn reset_eval_budget() {
69 let budget = EVAL_STEPS_BUDGET.with(|c| c.get());
70 EVAL_STEPS_REMAINING.with(|c| c.set(budget));
71}
72
73thread_local! {
74 static STABLE_NOW: Cell<Option<i64>> = const { Cell::new(None) };
82}
83
84pub fn refresh_stable_now() {
89 let now = std::time::SystemTime::now()
90 .duration_since(std::time::UNIX_EPOCH)
91 .map(|d| d.as_secs() as i64)
92 .unwrap_or(0);
93 STABLE_NOW.with(|c| c.set(Some(now)));
94}
95
96fn stable_now() -> i64 {
99 STABLE_NOW.with(|c| {
100 if let Some(n) = c.get() {
101 return n;
102 }
103 let now = std::time::SystemTime::now()
104 .duration_since(std::time::UNIX_EPOCH)
105 .map(|d| d.as_secs() as i64)
106 .unwrap_or(0);
107 c.set(Some(now));
108 now
109 })
110}
111
112thread_local! {
113 pub static DEFAULT_COLLATION: RefCell<Option<String>> = const { RefCell::new(None) };
121}
122
123pub fn with_default_collation<R>(uri: Option<String>, f: impl FnOnce() -> R) -> R {
128 let prev = DEFAULT_COLLATION.with(|c| c.borrow().clone());
129 DEFAULT_COLLATION.with(|c| *c.borrow_mut() = uri);
130 struct Guard(Option<String>);
131 impl Drop for Guard {
132 fn drop(&mut self) {
133 let p = self.0.take();
134 DEFAULT_COLLATION.with(|c| *c.borrow_mut() = p);
135 }
136 }
137 let _g = Guard(prev);
138 f()
139}
140
141fn effective_collation(explicit: Option<String>) -> Option<String> {
147 explicit.or_else(|| DEFAULT_COLLATION.with(|c| c.borrow().clone()))
148}
149
150thread_local! {
151 static XPATH_1_0_COMPAT: Cell<bool> = const { Cell::new(false) };
158}
159
160pub fn with_xpath_1_0_compat<R>(f: impl FnOnce() -> R) -> R {
163 let prev = XPATH_1_0_COMPAT.with(|c| c.replace(true));
164 struct Guard(bool);
165 impl Drop for Guard {
166 fn drop(&mut self) { XPATH_1_0_COMPAT.with(|c| c.set(self.0)); }
167 }
168 let _g = Guard(prev);
169 f()
170}
171
172pub fn in_xpath_1_0_compat() -> bool {
178 XPATH_1_0_COMPAT.with(|c| c.get())
179}
180
181thread_local! {
182 static CONTEXT_ITEM: RefCell<Option<Value>> = const { RefCell::new(None) };
191 static FOCUS_UNDEFINED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
199}
200
201pub fn with_focus_undefined<R>(undefined: bool, f: impl FnOnce() -> R) -> R {
205 let prev = FOCUS_UNDEFINED.with(|c| c.replace(undefined));
206 let r = f();
207 FOCUS_UNDEFINED.with(|c| c.set(prev));
208 r
209}
210
211pub fn focus_is_undefined() -> bool {
214 FOCUS_UNDEFINED.with(|c| c.get())
215}
216
217pub fn with_atomic_context_item<R>(item: Option<Value>, f: impl FnOnce() -> R) -> R {
223 with_context_item(item, f)
224}
225
226fn with_context_item<R>(item: Option<Value>, f: impl FnOnce() -> R) -> R {
227 let prev = CONTEXT_ITEM.with(|c| c.replace(item));
228 let r = f();
229 CONTEXT_ITEM.with(|c| { c.replace(prev); });
230 r
231}
232
233pub fn current_context_item() -> Option<Value> {
235 CONTEXT_ITEM.with(|c| c.borrow().clone())
236}
237
238fn is_bare_dot_path(path: &crate::xpath::ast::LocationPath) -> bool {
245 use crate::xpath::ast::{Axis, LocationPath, NodeTest};
246 let steps = match path {
247 LocationPath::Relative(s) => s,
248 LocationPath::Absolute(_) => return false,
249 };
250 if steps.len() != 1 { return false; }
251 let s = &steps[0];
252 s.axis == Axis::Self_
253 && matches!(s.node_test, NodeTest::AnyNode)
254 && s.predicates.is_empty()
255 && s.filter.is_none()
256}
257
258#[inline]
262fn charge_eval_step() -> Result<()> {
263 EVAL_STEPS_REMAINING.with(|c| {
264 let r = c.get();
265 if r == 0 {
266 let budget = EVAL_STEPS_BUDGET.with(|b| b.get());
267 return Err(xpath_err(format!(
268 "XPath evaluation step budget exceeded ({budget}); \
269 expression too expensive for the configured limit"
270 )));
271 }
272 c.set(r - 1);
273 Ok(())
274 })
275}
276
277pub type ForeignNodePtr = *const Node<'static>;
287
288#[derive(Debug, Clone, Copy, PartialEq)]
307pub enum Numeric {
308 Integer(i64),
309 Decimal(rust_decimal::Decimal),
310 Double(f64),
311 Float(f64),
312}
313
314impl Numeric {
315 #[inline]
322 pub fn as_f64(self) -> f64 {
323 use rust_decimal::prelude::ToPrimitive;
324 match self {
325 Numeric::Integer(i) => i as f64,
326 Numeric::Decimal(d) => d.to_f64().unwrap_or(f64::NAN),
327 Numeric::Double(n) | Numeric::Float(n) => n,
328 }
329 }
330
331 #[inline]
338 pub fn integer_from_f64(n: f64) -> Numeric {
339 const I64_LIMIT: f64 = 9_223_372_036_854_775_808.0; if n.is_finite() && n >= -I64_LIMIT && n < I64_LIMIT {
341 Numeric::Integer(n as i64)
342 } else if let Some(d) = rust_decimal::Decimal::from_f64_retain(n) {
343 Numeric::Decimal(d)
344 } else {
345 Numeric::Double(n)
346 }
347 }
348
349 #[inline]
356 pub fn of_kind(kind: &str, n: f64) -> Numeric {
357 match kind {
358 "integer" => Numeric::integer_from_f64(n),
359 "decimal" => match rust_decimal::Decimal::from_f64_retain(n) {
360 Some(d) => Numeric::Decimal(d),
361 None => Numeric::Double(n),
362 },
363 "float" => Numeric::Float(n as f32 as f64),
368 _ => Numeric::Double(n),
369 }
370 }
371
372 #[inline]
377 fn rank(self) -> u8 {
378 match self {
379 Numeric::Integer(_) => 0,
380 Numeric::Decimal(_) => 1,
381 Numeric::Float(_) => 2,
382 Numeric::Double(_) => 3,
383 }
384 }
385
386 #[inline]
391 fn from_rank(rank: u8, n: f64) -> Numeric {
392 match rank {
393 0 => Numeric::integer_from_f64(n),
394 1 => match rust_decimal::Decimal::from_f64_retain(n) {
395 Some(d) => Numeric::Decimal(d),
396 None => Numeric::Double(n),
397 },
398 2 => Numeric::Float(n as f32 as f64),
400 _ => Numeric::Double(n),
401 }
402 }
403
404 #[inline]
408 pub fn kind(self) -> &'static str {
409 match self {
410 Numeric::Integer(_) => "integer",
411 Numeric::Decimal(_) => "decimal",
412 Numeric::Double(_) => "double",
413 Numeric::Float(_) => "float",
414 }
415 }
416}
417
418impl From<f64> for Numeric {
419 #[inline]
423 fn from(n: f64) -> Numeric { Numeric::Double(n) }
424}
425
426#[derive(Debug, Clone)]
427pub enum Value {
428 NodeSet(Vec<NodeId>),
429 ForeignNodeSet(Vec<ForeignNodePtr>),
436 String(String),
437 Number(Numeric),
438 Boolean(bool),
439 Typed(Box<TypedAtomic>),
453 Sequence(Vec<Value>),
465 IntRange { lo: i64, hi: i64 },
480 Map(Box<Vec<(Value, Value)>>),
485 Array(Box<Vec<Value>>),
488 Function(Box<FunctionItem>),
491}
492
493#[derive(Debug, Clone)]
495pub enum FunctionItem {
496 Inline {
500 params: Vec<String>,
501 sig: Box<crate::xpath::ast::FunctionSig>,
503 body: crate::xpath::ast::Expr,
504 closure: Vec<(String, Value)>,
505 },
506 Named {
513 name: String, ns: String, arity: usize,
514 sig: Option<Box<crate::xpath::ast::FunctionSig>>,
519 },
520 Partial { base: Box<FunctionItem>, bound: Vec<Option<Value>> },
523}
524
525impl FunctionItem {
526 pub fn arity(&self) -> usize {
528 match self {
529 FunctionItem::Inline { params, .. } => params.len(),
530 FunctionItem::Named { arity, .. } => *arity,
531 FunctionItem::Partial { bound, .. } =>
532 bound.iter().filter(|b| b.is_none()).count(),
533 }
534 }
535
536 pub fn declared_sig(&self) -> Option<&crate::xpath::ast::FunctionSig> {
540 match self {
541 FunctionItem::Named { sig, .. } => sig.as_deref(),
542 FunctionItem::Inline { sig, .. } => Some(sig),
543 _ => None,
544 }
545 }
546}
547
548#[derive(Debug, Clone)]
554pub struct TypedAtomic {
555 pub kind: &'static str,
561 pub lexical: String,
563 pub numeric: Option<f64>,
566 pub boolean: Option<bool>,
568 pub user_type: Option<Box<(String, String)>>,
574}
575
576impl TypedAtomic {
577 pub fn builtin(kind: &'static str, lexical: String, numeric: Option<f64>, boolean: Option<bool>) -> Self {
579 TypedAtomic { kind, lexical, numeric, boolean, user_type: None }
580 }
581}
582
583pub fn typed_str(kind: &'static str, lexical: String) -> Value {
590 Value::Typed(Box::new(TypedAtomic::builtin(kind, lexical, None, None)))
591}
592
593pub fn parent_atomic_type(t: &str) -> Option<&'static str> {
598 Some(match t {
599 "long" => "integer",
601 "int" => "long",
602 "short" => "int",
603 "byte" => "short",
604 "unsignedLong" => "nonNegativeInteger",
605 "unsignedInt" => "unsignedLong",
606 "unsignedShort" => "unsignedInt",
607 "unsignedByte" => "unsignedShort",
608 "nonNegativeInteger" => "integer",
609 "nonPositiveInteger" => "integer",
610 "positiveInteger" => "nonNegativeInteger",
611 "negativeInteger" => "nonPositiveInteger",
612 "integer" => "decimal",
613 "normalizedString" => "string",
615 "token" => "normalizedString",
616 "language" => "token",
617 "Name" => "token",
618 "NCName" => "Name",
619 "ID" => "NCName",
620 "IDREF" => "NCName",
621 "ENTITY" => "NCName",
622 "NMTOKEN" => "token",
623 "dayTimeDuration" => "duration",
625 "yearMonthDuration" => "duration",
626 "decimal" | "double" | "float" | "boolean" | "string"
628 | "date" | "dateTime" | "time" | "duration"
629 | "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay"
630 | "hexBinary" | "base64Binary" | "anyURI" | "QName"
631 | "NOTATION" | "untypedAtomic"
632 | "IDREFS" | "ENTITIES" | "NMTOKENS"
633 => "anyAtomicType",
634 "anyAtomicType" => "anySimpleType",
635 "anySimpleType" => "anyType",
636 _ => return None,
637 })
638}
639
640pub fn xsd_is_subtype_of(t: &str, target: &str) -> bool {
643 if t == target { return true; }
644 let mut cur = t;
645 while let Some(p) = parent_atomic_type(cur) {
646 if p == target { return true; }
647 cur = p;
648 }
649 false
650}
651
652impl Value {
653 pub fn untyped(self) -> Self {
661 match self {
662 Value::Typed(t) => {
663 if let Some(n) = t.numeric { Value::Number(Numeric::Double(n)) }
664 else if let Some(b) = t.boolean { Value::Boolean(b) }
665 else {
666 Value::String(t.lexical)
669 }
670 }
671 Value::Sequence(mut items) => {
672 if items.len() == 1 {
673 return items.remove(0).untyped();
674 }
675 Value::Sequence(items)
680 }
681 other => other,
682 }
683 }
684}
685
686pub trait XPathBindings {
701 fn resolve_prefix(&self, _prefix: &str) -> Option<String> { None }
704 fn call_function(
709 &self, _ns_uri: &str, _name: &str, _args: Vec<Value>,
710 ) -> Option<Result<Value>> { None }
711 fn call_function_in(
720 &self, ns_uri: &str, name: &str, args: Vec<Value>,
721 _xpath_context_node: NodeId,
722 ) -> Option<Result<Value>> {
723 self.call_function(ns_uri, name, args)
724 }
725 fn function_available_in(&self, _ns_uri: &str, _name: &str, _arity: usize) -> bool {
730 false
731 }
732 fn function_signature_in(
738 &self, _ns_uri: &str, _name: &str, _arity: usize,
739 ) -> Option<crate::xpath::ast::FunctionSig> {
740 None
741 }
742 fn castable_as_user_type(
748 &self, _ns_uri: &str, _local: &str, _value: &str, _source_kind: Option<&str>,
749 ) -> Option<bool> {
750 None
751 }
752 fn cast_to_user_type(&self, _ns_uri: &str, _local: &str, _value: &str)
756 -> Option<Result<Value>> {
757 None
758 }
759 fn instance_of_user_type(
764 &self, _target_ns: &str, _target_local: &str, _value_type: Option<(&str, &str)>,
765 ) -> Option<bool> {
766 None
767 }
768 fn schema_type_exists(&self, _ns: &str, _local: &str) -> bool {
774 false
775 }
776 fn node_schema_type(&self, _node_id: NodeId) -> Option<(String, String)> {
784 None
785 }
786 fn node_typed_value(&self, _node_id: NodeId, _lexical: &str) -> Option<Value> {
794 None
795 }
796 fn variable(&self, _name: &str) -> Option<Value> { None }
799
800 fn xpath_version_2_or_later(&self) -> bool { false }
806
807 fn load_document(
815 &self, _uri: &str, _base_uri: Option<&str>,
816 ) -> Option<Result<Vec<ForeignNodePtr>>> { None }
817
818 fn apply_foreign_path(
827 &self,
828 _nodes: &[ForeignNodePtr],
829 _predicates: &[Expr],
830 _steps: &[Step],
831 ) -> Option<Result<Vec<ForeignNodePtr>>> { None }
832
833 fn static_base_uri(&self) -> Option<String> { None }
840
841 fn node_base_uri(&self, _id: NodeId) -> Option<String> { None }
849
850 fn foreign_string_value(&self, _p: ForeignNodePtr) -> String { String::new() }
857
858 fn load_dynamic_document(
873 &self, _uri: &str,
874 ) -> Option<std::result::Result<NodeId, crate::error::XmlError>> {
875 None
876 }
877
878 fn regex_dialect(&self) -> crate::regex::Dialect {
885 crate::regex::Dialect::Xpath
886 }
887}
888
889pub struct NoBindings;
892impl XPathBindings for NoBindings {}
893
894fn implicit_prefix(prefix: &str) -> Option<&'static str> {
898 match prefix {
899 "xml" => Some("http://www.w3.org/XML/1998/namespace"),
900 "xmlns" => Some("http://www.w3.org/2000/xmlns/"),
901 "xs" => Some("http://www.w3.org/2001/XMLSchema"),
910 "xsi" => Some("http://www.w3.org/2001/XMLSchema-instance"),
911 _ => None,
912 }
913}
914
915fn resolve_prefix_or_implicit(bindings: &dyn XPathBindings, prefix: &str) -> Option<String> {
920 bindings
921 .resolve_prefix(prefix)
922 .or_else(|| implicit_prefix(prefix).map(str::to_string))
923}
924
925fn named_function_namespace(name: &str, bindings: &dyn XPathBindings) -> String {
930 match name.split_once(':') {
931 Some((prefix, _)) => resolve_prefix_or_implicit(bindings, prefix).unwrap_or_default(),
932 None => FN_NAMESPACE.to_string(),
933 }
934}
935
936static NO_BINDINGS: NoBindings = NoBindings;
937
938#[derive(Debug, Clone, Copy)]
950pub struct StaticContext {
951 pub xpath_2_0: bool,
954 pub xpath_3_0: bool,
958 pub libxml2_compatible: bool,
961 pub current_node: Option<NodeId>,
974}
975impl Default for StaticContext {
982 fn default() -> Self {
984 StaticContext { xpath_2_0: false, xpath_3_0: false, libxml2_compatible: false, current_node: None }
985 }
986}
987
988impl StaticContext {
989 pub fn num_style(&self) -> NumStyle {
991 NumStyle::from_context(self.libxml2_compatible, self.xpath_2_0)
992 }
993}
994
995pub static DEFAULT_STATIC_CTX: StaticContext = StaticContext {
998 xpath_2_0: false,
999 xpath_3_0: false,
1000 libxml2_compatible: false,
1001 current_node: None,
1002};
1003
1004pub struct EvalCtx<'b> {
1005 pub context_node: NodeId,
1006 pub pos: usize,
1007 pub size: usize,
1008 pub bindings: &'b dyn XPathBindings,
1009 pub static_ctx: &'b StaticContext,
1013}
1014
1015impl EvalCtx<'static> {
1016 pub fn root() -> Self {
1020 Self {
1021 context_node: 0,
1022 pos: 1,
1023 size: 1,
1024 bindings: &NO_BINDINGS,
1025 static_ctx: &DEFAULT_STATIC_CTX,
1026 }
1027 }
1028}
1029
1030pub fn validate_prefixes(expr: &Expr, bindings: &dyn XPathBindings) -> Result<()> {
1042 match expr {
1043 Expr::Or(l, r) | Expr::And(l, r) | Expr::Eq(l, r) | Expr::Ne(l, r)
1044 | Expr::Lt(l, r) | Expr::Gt(l, r) | Expr::Le(l, r) | Expr::Ge(l, r)
1045 | Expr::ValueEq(l, r) | Expr::ValueNe(l, r)
1046 | Expr::ValueLt(l, r) | Expr::ValueGt(l, r)
1047 | Expr::ValueLe(l, r) | Expr::ValueGe(l, r)
1048 | Expr::Add(l, r) | Expr::Sub(l, r) | Expr::Mul(l, r) | Expr::Div(l, r)
1049 | Expr::Mod(l, r) | Expr::Union(l, r)
1050 | Expr::SimpleMap(l, r)
1051 | Expr::NodeBefore(l, r) | Expr::NodeAfter(l, r) | Expr::NodeIs(l, r) => {
1052 validate_prefixes(l, bindings)?;
1053 validate_prefixes(r, bindings)
1054 }
1055 Expr::Neg(e) => validate_prefixes(e, bindings),
1056 Expr::Path(p) => match p {
1057 LocationPath::Absolute(steps) | LocationPath::Relative(steps) => {
1058 validate_steps(steps, bindings)
1059 }
1060 },
1061 Expr::FilterPath { primary, predicates, steps } => {
1062 validate_prefixes(primary, bindings)?;
1063 for p in predicates { validate_prefixes(p, bindings)?; }
1064 validate_steps(steps, bindings)
1065 }
1066 Expr::FunctionCall(_, args) => {
1067 for a in args { validate_prefixes(a, bindings)?; }
1068 Ok(())
1069 }
1070 Expr::Variable(_) | Expr::Literal(_)
1071 | Expr::Integer(_) | Expr::Decimal(_) | Expr::Double(_) => Ok(()),
1072 Expr::IfThenElse { cond, then_branch, else_branch } => {
1073 validate_prefixes(cond, bindings)?;
1074 validate_prefixes(then_branch, bindings)?;
1075 validate_prefixes(else_branch, bindings)
1076 }
1077 Expr::For { bindings: binds, body }
1078 | Expr::Let { bindings: binds, body } => {
1079 for (_, e) in binds { validate_prefixes(e, bindings)?; }
1080 validate_prefixes(body, bindings)
1081 }
1082 Expr::Range(a, b) => {
1083 validate_prefixes(a, bindings)?;
1084 validate_prefixes(b, bindings)
1085 }
1086 Expr::Sequence(items) => {
1087 for e in items { validate_prefixes(e, bindings)?; }
1088 Ok(())
1089 }
1090 Expr::Quantified { bindings: binds, test, .. } => {
1091 for (_, e) in binds { validate_prefixes(e, bindings)?; }
1092 validate_prefixes(test, bindings)
1093 }
1094 Expr::IDiv(a, b) | Expr::Intersect(a, b) | Expr::Except(a, b) => {
1095 validate_prefixes(a, bindings)?;
1096 validate_prefixes(b, bindings)
1097 }
1098 Expr::InstanceOf(a, _) | Expr::CastAs(a, _)
1099 | Expr::CastableAs(a, _) | Expr::TreatAs(a, _) => validate_prefixes(a, bindings),
1100 Expr::TryCatch { body, catches } => {
1101 validate_prefixes(body, bindings)?;
1102 for c in catches { validate_prefixes(&c.body, bindings)?; }
1103 Ok(())
1104 }
1105 Expr::WithDefaultCollation(_, inner) => validate_prefixes(inner, bindings),
1106 Expr::BackwardsCompat(inner) => validate_prefixes(inner, bindings),
1107 Expr::MapConstructor(entries) => {
1108 for (k, v) in entries {
1109 validate_prefixes(k, bindings)?;
1110 validate_prefixes(v, bindings)?;
1111 }
1112 Ok(())
1113 }
1114 Expr::ArrayConstructor { members, .. } => {
1115 for m in members { validate_prefixes(m, bindings)?; }
1116 Ok(())
1117 }
1118 Expr::Lookup(base, key) => {
1119 validate_prefixes(base, bindings)?;
1120 validate_lookup_key_prefixes(key, bindings)
1121 }
1122 Expr::UnaryLookup(key) => validate_lookup_key_prefixes(key, bindings),
1123 Expr::InlineFunction { body, .. } => validate_prefixes(body, bindings),
1124 Expr::DynamicCall { func, args } => {
1125 validate_prefixes(func, bindings)?;
1126 for a in args { validate_prefixes(a, bindings)?; }
1127 Ok(())
1128 }
1129 Expr::NamedFunctionRef { .. } | Expr::Placeholder | Expr::ContextItem => Ok(()),
1130 }
1131}
1132
1133fn validate_lookup_key_prefixes(
1134 key: &crate::xpath::ast::LookupKey, bindings: &dyn XPathBindings,
1135) -> Result<()> {
1136 if let crate::xpath::ast::LookupKey::Expr(e) = key {
1137 validate_prefixes(e, bindings)?;
1138 }
1139 Ok(())
1140}
1141
1142fn validate_steps(steps: &[Step], bindings: &dyn XPathBindings) -> Result<()> {
1143 for step in steps {
1144 if let NodeTest::QName(prefix, _) | NodeTest::PrefixWildcard(prefix) = &step.node_test
1145 && resolve_prefix_or_implicit(bindings, prefix).is_none()
1146 {
1147 return Err(xpath_err(format!(
1148 "Undefined namespace prefix: {prefix}"
1149 )));
1150 }
1151 for pred in &step.predicates {
1152 validate_prefixes(pred, bindings)?;
1153 }
1154 }
1155 Ok(())
1156}
1157
1158pub fn eval_to_bool<I: DocIndexLike>(
1165 expr: &Expr,
1166 idx: &I,
1167 context_node: NodeId,
1168 bindings: &dyn XPathBindings,
1169) -> Result<bool> {
1170 let static_ctx = StaticContext {
1171 xpath_2_0: bindings.xpath_version_2_or_later(),
1172 xpath_3_0: false,
1173 libxml2_compatible: false,
1174 current_node: Some(context_node),
1175 };
1176 let ctx = EvalCtx {
1177 context_node,
1178 pos: 1,
1179 size: 1,
1180 bindings,
1181 static_ctx: &static_ctx,
1182 };
1183 let v = eval_expr(expr, &ctx, idx)?;
1184 Ok(value_to_bool(&v, idx))
1185}
1186
1187pub fn eval_expr<I: DocIndexLike>(expr: &Expr, ctx: &EvalCtx<'_>, idx: &I) -> Result<Value> {
1188 charge_eval_step()?;
1192 match expr {
1193 Expr::ContextItem => Ok(current_context_item()
1197 .unwrap_or_else(|| Value::NodeSet(vec![ctx.context_node]))),
1198 Expr::Or(l, r) => {
1199 if value_to_bool(&eval_expr(l, ctx, idx)?, idx) {
1200 return Ok(Value::Boolean(true));
1201 }
1202 Ok(Value::Boolean(value_to_bool(&eval_expr(r, ctx, idx)?, idx)))
1203 }
1204 Expr::And(l, r) => {
1205 if !value_to_bool(&eval_expr(l, ctx, idx)?, idx) {
1206 return Ok(Value::Boolean(false));
1207 }
1208 Ok(Value::Boolean(value_to_bool(&eval_expr(r, ctx, idx)?, idx)))
1209 }
1210 Expr::Eq(l, r) => {
1211 let lv = eval_expr(l, ctx, idx)?;
1212 let rv = eval_expr(r, ctx, idx)?;
1213 reject_string_vs_numeric_cmp_2_0(&lv, &rv, ctx, "=")?;
1214 Ok(Value::Boolean(values_eq(&lv, &rv, idx, ctx.bindings)))
1215 }
1216 Expr::Ne(l, r) => {
1217 let lv = eval_expr(l, ctx, idx)?;
1218 let rv = eval_expr(r, ctx, idx)?;
1219 reject_string_vs_numeric_cmp_2_0(&lv, &rv, ctx, "!=")?;
1220 Ok(Value::Boolean(values_ne(&lv, &rv, idx, ctx.bindings)))
1221 }
1222 Expr::Lt(l, r) => cmp_op(l, r, ctx, idx, |a, b| a < b),
1223 Expr::Gt(l, r) => cmp_op(l, r, ctx, idx, |a, b| a > b),
1224 Expr::Le(l, r) => cmp_op(l, r, ctx, idx, |a, b| a <= b),
1225 Expr::Ge(l, r) => cmp_op(l, r, ctx, idx, |a, b| a >= b),
1226 Expr::ValueEq(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Eq),
1230 Expr::ValueNe(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Ne),
1231 Expr::ValueLt(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Lt),
1232 Expr::ValueGt(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Gt),
1233 Expr::ValueLe(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Le),
1234 Expr::ValueGe(l, r) => value_compare(l, r, ctx, idx, ValueCmp::Ge),
1235 Expr::Add(l, r) => {
1236 let lv = eval_expr(l, ctx, idx)?;
1239 let rv = eval_expr(r, ctx, idx)?;
1240 if let Some(v) = date_arith_add(&lv, &rv) { return Ok(v); }
1241 if arith_empty_2_0(&lv, &rv, ctx) { return Ok(Value::NodeSet(Vec::new())); }
1242 reject_string_arith_2_0(&lv, &rv, ctx, "+")?;
1243 Ok(compat_numeric_op(&lv, &rv, idx, ctx.bindings, NumericOp::Add))
1244 }
1245 Expr::Sub(l, r) => {
1246 let lv = eval_expr(l, ctx, idx)?;
1247 let rv = eval_expr(r, ctx, idx)?;
1248 if let Some(v) = date_arith_sub(&lv, &rv) { return Ok(v); }
1249 if arith_empty_2_0(&lv, &rv, ctx) { return Ok(Value::NodeSet(Vec::new())); }
1250 reject_string_arith_2_0(&lv, &rv, ctx, "-")?;
1251 Ok(compat_numeric_op(&lv, &rv, idx, ctx.bindings, NumericOp::Sub))
1252 }
1253 Expr::Mul(l, r) => {
1254 let lv = eval_expr(l, ctx, idx)?;
1255 let rv = eval_expr(r, ctx, idx)?;
1256 if let Some(v) = duration_mul(&lv, &rv, idx, ctx.bindings) { return Ok(v); }
1262 if arith_empty_2_0(&lv, &rv, ctx) { return Ok(Value::NodeSet(Vec::new())); }
1263 reject_string_arith_2_0(&lv, &rv, ctx, "*")?;
1264 Ok(compat_numeric_op(&lv, &rv, idx, ctx.bindings, NumericOp::Mul))
1265 }
1266 Expr::Div(l, r) => {
1267 let lv = eval_expr(l, ctx, idx)?;
1268 let rv = eval_expr(r, ctx, idx)?;
1269 if let Some(v) = duration_div(&lv, &rv, idx, ctx.bindings) { return Ok(v); }
1272 if arith_empty_2_0(&lv, &rv, ctx) { return Ok(Value::NodeSet(Vec::new())); }
1273 if integer_decimal_zero_divisor(&lv, &rv, idx, ctx.bindings) {
1274 return Err(xpath_err("division by zero").with_xpath_code("FOAR0001"));
1275 }
1276 reject_string_arith_2_0(&lv, &rv, ctx, "div")?;
1277 Ok(compat_numeric_op(&lv, &rv, idx, ctx.bindings, NumericOp::Div))
1278 }
1279 Expr::Mod(l, r) => {
1280 let lv = eval_expr(l, ctx, idx)?;
1281 let rv = eval_expr(r, ctx, idx)?;
1282 if arith_empty_2_0(&lv, &rv, ctx) { return Ok(Value::NodeSet(Vec::new())); }
1283 if integer_decimal_zero_divisor(&lv, &rv, idx, ctx.bindings) {
1284 return Err(xpath_err("modulo by zero").with_xpath_code("FOAR0001"));
1285 }
1286 Ok(compat_numeric_op(&lv, &rv, idx, ctx.bindings, NumericOp::Mod))
1287 }
1288 Expr::Neg(inner) => {
1289 let v = eval_expr(inner, ctx, idx)?;
1296 if in_xpath_1_0_compat() {
1297 let n = -value_to_number_with(&v, idx, ctx.bindings);
1298 return Ok(Value::Number(Numeric::Double(n)));
1299 }
1300 if let Value::Number(Numeric::Integer(i)) = v {
1308 return Ok(match i.checked_neg() {
1309 Some(n) => Value::Number(Numeric::Integer(n)),
1310 None => Value::Number(Numeric::Decimal(-rust_decimal::Decimal::from(i))),
1311 });
1312 }
1313 if matches!(numeric_kind_of(&v), Some("integer")) {
1314 let f = -value_to_number_with(&v, idx, ctx.bindings);
1315 return Ok(preserve_numeric_kind(&v, f));
1316 }
1317 if let Some(d) = exact_decimal(&v) {
1318 return Ok(Value::Number(Numeric::Decimal(-d)));
1319 }
1320 let n = -value_to_number_with(&v, idx, ctx.bindings);
1321 Ok(preserve_numeric_kind(&v, n))
1322 }
1323 Expr::Union(l, r) => {
1324 let lv = eval_expr(l, ctx, idx)?;
1325 let rv = eval_expr(r, ctx, idx)?;
1326 match (lv, rv) {
1327 (Value::NodeSet(mut a), Value::NodeSet(b)) => {
1328 a.extend(b);
1329 dedup_sort(&mut a);
1330 Ok(Value::NodeSet(a))
1331 }
1332 (Value::ForeignNodeSet(mut a), Value::ForeignNodeSet(b)) => {
1333 a.extend(b);
1334 dedup_foreign(&mut a);
1335 Ok(Value::ForeignNodeSet(a))
1336 }
1337 (Value::NodeSet(_), Value::ForeignNodeSet(_))
1342 | (Value::ForeignNodeSet(_), Value::NodeSet(_)) => Err(xpath_err(
1343 "mixed primary/foreign node-set union is not supported",
1344 )),
1345 _ => Err(xpath_err("union operator requires node-sets on both sides")),
1346 }
1347 }
1348 Expr::Path(path) => {
1349 if is_bare_dot_path(path) {
1356 if let Some(v) = current_context_item() {
1357 return Ok(v);
1358 }
1359 }
1360 let nodes = eval_path(path, ctx.context_node, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible, ctx.static_ctx.current_node)?;
1361 Ok(Value::NodeSet(nodes))
1362 }
1363 Expr::FilterPath { primary, predicates, steps } => {
1364 let pv = eval_expr(primary, ctx, idx)?;
1365 match pv {
1366 Value::NodeSet(ns) => {
1367 let mut nodes =
1368 apply_predicates_cur(ns, predicates, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible, ctx.static_ctx.current_node)?;
1369 for step in steps {
1370 nodes = eval_step_on_nodes_cur(nodes, step, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible, ctx.static_ctx.current_node)?;
1371 }
1372 Ok(Value::NodeSet(nodes))
1373 }
1374 Value::ForeignNodeSet(ns) => {
1375 match ctx.bindings.apply_foreign_path(&ns, predicates, steps) {
1380 Some(r) => r.map(Value::ForeignNodeSet),
1381 None => Err(xpath_err(
1382 "foreign-doc path traversal not supported by bindings",
1383 )),
1384 }
1385 }
1386 Value::Sequence(items) if steps.is_empty() => {
1394 let kept = filter_sequence_by_predicates(
1395 items, predicates, ctx, idx,
1396 )?;
1397 if kept.len() == 1 {
1398 Ok(kept.into_iter().next().unwrap())
1399 } else {
1400 Ok(Value::Sequence(kept))
1401 }
1402 }
1403 other if steps.is_empty() => {
1406 let kept = filter_sequence_by_predicates(
1407 vec![other], predicates, ctx, idx,
1408 )?;
1409 match kept.len() {
1410 0 => Ok(Value::NodeSet(Vec::new())),
1411 1 => Ok(kept.into_iter().next().unwrap()),
1412 _ => Ok(Value::Sequence(kept)),
1413 }
1414 }
1415 Value::Sequence(items) => {
1420 let kept = filter_sequence_by_predicates(
1421 items, predicates, ctx, idx,
1422 )?;
1423 let mut nodes: Vec<NodeId> = Vec::new();
1424 for it in kept {
1425 if let Value::NodeSet(ns) = it {
1426 nodes.extend(ns);
1427 }
1428 }
1429 nodes.sort_unstable();
1430 nodes.dedup();
1431 for step in steps {
1432 nodes = eval_step_on_nodes_cur(nodes, step, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible, ctx.static_ctx.current_node)?;
1433 }
1434 Ok(Value::NodeSet(nodes))
1435 }
1436 _ => Err(xpath_err("predicate applied to non-node-set")),
1437 }
1438 }
1439 Expr::FunctionCall(name, args) => eval_function(name, args, ctx, idx),
1440 Expr::Variable(name) => {
1441 match ctx.bindings.variable(name) {
1444 Some(v) => Ok(v),
1445 None => Err(xpath_err(format!("undefined XPath variable: ${name}"))),
1446 }
1447 }
1448 Expr::Literal(s) => Ok(Value::String(s.clone())),
1449 Expr::Integer(i) => Ok(Value::Number(if ctx.static_ctx.xpath_2_0 {
1453 Numeric::Integer(*i)
1454 } else {
1455 Numeric::Double(*i as f64)
1456 })),
1457 Expr::Decimal(n) => Ok(Value::Number(if ctx.static_ctx.xpath_2_0 {
1458 Numeric::Decimal(*n)
1459 } else {
1460 use rust_decimal::prelude::ToPrimitive;
1463 Numeric::Double(n.to_f64().unwrap_or(f64::NAN))
1464 })),
1465 Expr::Double(n) => Ok(Value::Number(Numeric::Double(*n))),
1469 Expr::IfThenElse { cond, then_branch, else_branch } => {
1470 let truth = value_to_bool(&eval_expr(cond, ctx, idx)?, idx);
1472 let branch = if truth { then_branch } else { else_branch };
1473 eval_expr(branch, ctx, idx)
1474 }
1475 Expr::IDiv(l, r) => {
1476 let a = value_to_number(&eval_expr(l, ctx, idx)?, idx);
1480 let b = value_to_number(&eval_expr(r, ctx, idx)?, idx);
1481 if b == 0.0 || b.is_nan() || a.is_nan() {
1482 return Err(xpath_err("idiv: division by zero or NaN")
1483 .with_xpath_code("FOAR0001"));
1484 }
1485 Ok(integer_result((a / b).trunc() as i64, ctx.bindings))
1487 }
1488 Expr::Intersect(l, r) => {
1489 let lns = node_set_of(eval_expr(l, ctx, idx)?);
1493 let rns = node_set_of(eval_expr(r, ctx, idx)?);
1494 let rset: std::collections::BTreeSet<NodeId> = rns.into_iter().collect();
1495 let mut out: Vec<NodeId> = lns.into_iter().filter(|n| rset.contains(n)).collect();
1496 out.sort_unstable();
1497 out.dedup();
1498 Ok(Value::NodeSet(out))
1499 }
1500 Expr::Except(l, r) => {
1501 let lns = node_set_of(eval_expr(l, ctx, idx)?);
1503 let rns = node_set_of(eval_expr(r, ctx, idx)?);
1504 let rset: std::collections::BTreeSet<NodeId> = rns.into_iter().collect();
1505 let mut out: Vec<NodeId> = lns.into_iter().filter(|n| !rset.contains(n)).collect();
1506 out.sort_unstable();
1507 out.dedup();
1508 Ok(Value::NodeSet(out))
1509 }
1510 Expr::InstanceOf(inner, st) => {
1511 let v = eval_expr(inner, ctx, idx)?;
1512 let st = resolve_kind_test_namespaces(st, ctx.bindings);
1513 if let crate::xpath::ast::ItemType::Atomic(name) = &st.item {
1517 let target = match name.split_once(':') {
1523 Some((prefix, local)) => resolve_prefix_or_implicit(ctx.bindings, prefix)
1524 .map(|uri| (uri, local)),
1525 None if atomic_kind_static(name).is_none() =>
1526 Some((String::new(), name.as_str())),
1527 None => None,
1528 };
1529 if let Some((uri, local)) = target {
1530 let value_type = match &v {
1531 Value::Typed(t) => t.user_type.as_deref()
1532 .map(|(ns, l)| (ns.as_str(), l.as_str())),
1533 _ => None,
1534 };
1535 if let Some(matches_type) =
1538 ctx.bindings.instance_of_user_type(&uri, local, value_type)
1539 {
1540 let ok = match sequence_len(&v) {
1541 0 => matches!(st.occurrence,
1542 crate::xpath::ast::Occurrence::Optional
1543 | crate::xpath::ast::Occurrence::ZeroOrMore),
1544 1 => matches_type,
1545 _ => false,
1546 };
1547 return Ok(Value::Boolean(ok));
1548 }
1549 const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema";
1556 if uri != XSD_NS && !ctx.bindings.schema_type_exists(&uri, local) {
1557 return Err(xpath_err(format!(
1558 "instance of: type {name} is not a known schema type \
1559 (XPST0051)")).with_xpath_code("XPST0051"));
1560 }
1561 }
1562 }
1563 Ok(Value::Boolean(value_matches_sequence_type(&v, &st, idx)))
1564 }
1565 Expr::CastAs(inner, st) => {
1566 if let crate::xpath::ast::ItemType::Atomic(name) = &st.item {
1574 match name.as_str() {
1575 "anyType" | "anySimpleType" | "untyped" =>
1576 return Err(xpath_err(format!(
1577 "cast as: target type xs:{name} is not an atomic \
1578 type (XPST0051)"))),
1579 "anyAtomicType" | "NOTATION" =>
1580 return Err(xpath_err(format!(
1581 "cast as: xs:{name} is not a permitted target \
1582 type (XPST0080)"))),
1583 _ => {}
1584 }
1585 }
1586 let v = eval_expr(inner, ctx, idx)?;
1587 cast_value_to_atomic(&v, st, idx)
1588 }
1589 Expr::TreatAs(inner, st) => {
1590 let v = eval_expr(inner, ctx, idx)?;
1594 let st = resolve_kind_test_namespaces(st, ctx.bindings);
1595 if !value_matches_sequence_type(&v, &st, idx) {
1596 return Err(xpath_err(format!(
1597 "treat as failed: value doesn't match {st:?}"
1598 )));
1599 }
1600 Ok(v)
1601 }
1602 Expr::WithDefaultCollation(uri, inner) => {
1603 with_default_collation(Some(uri.clone()), ||
1604 eval_expr(inner, ctx, idx))
1605 }
1606 Expr::BackwardsCompat(inner) => {
1607 with_xpath_1_0_compat(|| eval_expr(inner, ctx, idx))
1608 }
1609 Expr::MapConstructor(entries) => {
1610 let mut out: Vec<(Value, Value)> = Vec::with_capacity(entries.len());
1611 for (ke, ve) in entries {
1612 let key = eval_expr(ke, ctx, idx)?;
1613 let key = first_atomic_key(&key, idx);
1616 let val = eval_expr(ve, ctx, idx)?;
1617 if out.iter().any(|(k, _)| map_key_eq(k, &key, idx)) {
1618 return Err(xpath_err(
1619 "duplicate key in map constructor (XQDY0137)"));
1620 }
1621 out.push((key, val));
1622 }
1623 Ok(Value::Map(Box::new(out)))
1624 }
1625 Expr::ArrayConstructor { members, square } => {
1626 if *square {
1627 let mut out = Vec::with_capacity(members.len());
1630 for m in members { out.push(eval_expr(m, ctx, idx)?); }
1631 Ok(Value::Array(Box::new(out)))
1632 } else {
1633 let v = match members.first() {
1636 Some(e) => eval_expr(e, ctx, idx)?,
1637 None => return Ok(Value::Array(Box::new(Vec::new()))),
1638 };
1639 Ok(Value::Array(Box::new(items_of(&v))))
1640 }
1641 }
1642 Expr::Lookup(base, key) => {
1643 let b = eval_expr(base, ctx, idx)?;
1644 eval_lookup(&b, key, ctx, idx)
1645 }
1646 Expr::UnaryLookup(key) => {
1647 let ctx_item = current_context_item()
1649 .unwrap_or(Value::NodeSet(vec![ctx.context_node]));
1650 eval_lookup(&ctx_item, key, ctx, idx)
1651 }
1652 Expr::InlineFunction { params, sig, body } => {
1653 let mut refs = Vec::new();
1656 collect_var_refs(body, &mut refs);
1657 let mut closure = Vec::new();
1658 for name in refs {
1659 if params.iter().any(|p| p == &name) { continue; }
1660 if let Some(v) = ctx.bindings.variable(&name) {
1661 closure.push((name, v));
1662 }
1663 }
1664 Ok(Value::Function(Box::new(FunctionItem::Inline {
1665 params: params.clone(),
1666 sig: sig.clone(),
1667 body: (**body).clone(),
1668 closure,
1669 })))
1670 }
1671 Expr::NamedFunctionRef { name, arity } => {
1672 let ns = named_function_namespace(name, ctx.bindings);
1673 let local = name.rsplit(':').next().unwrap_or(name);
1674 let sig = ctx.bindings
1675 .function_signature_in(&ns, local, *arity)
1676 .map(Box::new);
1677 Ok(Value::Function(Box::new(FunctionItem::Named {
1678 name: name.clone(), ns, arity: *arity, sig,
1679 })))
1680 }
1681 Expr::DynamicCall { func, args } => {
1682 let f = eval_expr(func, ctx, idx)?;
1683 if args.len() == 1 && !matches!(args[0], Expr::Placeholder) {
1687 match &f {
1688 Value::Map(m) => {
1689 let key = first_atomic_key(&eval_expr(&args[0], ctx, idx)?, idx);
1690 return Ok(m.iter().find(|(k, _)| map_key_eq(k, &key, idx))
1691 .map(|(_, v)| v.clone())
1692 .unwrap_or(Value::NodeSet(Vec::new())));
1693 }
1694 Value::Array(a) => {
1695 let n = value_to_number(&eval_expr(&args[0], ctx, idx)?, idx) as i64;
1696 if n < 1 || n as usize > a.len() {
1697 return Err(xpath_err(format!(
1698 "array index {n} is out of bounds (FOAY0001)"))
1699 .with_xpath_code("FOAY0001"));
1700 }
1701 return Ok(a[(n - 1) as usize].clone());
1702 }
1703 _ => {}
1704 }
1705 }
1706 let fi = match &f {
1707 Value::Function(fi) => (**fi).clone(),
1708 _ => return Err(xpath_err(
1709 "dynamic call target is not a function item (XPTY0004)")),
1710 };
1711 if args.iter().any(|a| matches!(a, Expr::Placeholder)) {
1713 let mut bound = Vec::with_capacity(args.len());
1714 for a in args {
1715 bound.push(match a {
1716 Expr::Placeholder => None,
1717 e => Some(eval_expr(e, ctx, idx)?),
1718 });
1719 }
1720 return Ok(Value::Function(Box::new(
1721 FunctionItem::Partial { base: Box::new(fi), bound })));
1722 }
1723 let argv: Vec<Value> = args.iter()
1724 .map(|a| eval_expr(a, ctx, idx)).collect::<Result<_>>()?;
1725 call_function_item(&fi, argv, ctx, idx)
1726 }
1727 Expr::Placeholder => Err(xpath_err(
1728 "'?' placeholder is only valid as a function-call argument")),
1729 Expr::CastableAs(inner, st) => {
1730 let v = eval_expr(inner, ctx, idx)?;
1738 if let crate::xpath::ast::ItemType::Atomic(name) = &st.item {
1742 if let Some((prefix, local)) = name.split_once(':') {
1743 if let Some(uri) = resolve_prefix_or_implicit(ctx.bindings, prefix) {
1744 match sequence_len(&v) {
1749 0 => return Ok(Value::Boolean(matches!(st.occurrence,
1750 crate::xpath::ast::Occurrence::Optional
1751 | crate::xpath::ast::Occurrence::ZeroOrMore))),
1752 1 => {}
1753 _ => return Ok(Value::Boolean(false)),
1754 }
1755 let source_kind = match &v {
1759 Value::Typed(t) => Some(t.kind),
1760 Value::Number(n) => Some(n.kind()),
1761 Value::Boolean(_) => Some("boolean"),
1762 _ => None,
1763 };
1764 let s = value_to_string(&v, idx);
1765 if let Some(ok) =
1766 ctx.bindings.castable_as_user_type(&uri, local, &s, source_kind)
1767 {
1768 return Ok(Value::Boolean(ok));
1769 }
1770 }
1771 }
1772 }
1773 Ok(Value::Boolean(cast_value_to_atomic(&v, st, idx).is_ok()))
1774 }
1775 Expr::TryCatch { body, catches } => {
1776 match eval_expr(body, ctx, idx) {
1783 Ok(v) => Ok(v),
1784 Err(e) => {
1785 let code_local = e.xpath_code.clone()
1789 .unwrap_or_else(|| "FOER0000".to_string());
1790 let err_uri = "http://www.w3.org/2005/xqt-errors";
1791 for c in catches {
1792 if !xpath_catch_matches(&c.matchers, err_uri, &code_local, ctx.bindings) {
1793 continue;
1794 }
1795 let scoped = build_err_scope(ctx.bindings, err_uri, &code_local, &e.message);
1796 let inner_ctx = EvalCtx {
1797 context_node: ctx.context_node,
1798 pos: ctx.pos, size: ctx.size,
1799 bindings: &scoped,
1800 static_ctx: ctx.static_ctx,
1801 };
1802 return eval_expr(&c.body, &inner_ctx, idx);
1803 }
1804 Err(e)
1805 }
1806 }
1807 }
1808 Expr::NodeBefore(l, r) | Expr::NodeAfter(l, r) => {
1809 let after = matches!(expr, Expr::NodeAfter(_, _));
1815 let lv = eval_expr(l, ctx, idx)?;
1816 let rv = eval_expr(r, ctx, idx)?;
1817 let pick = |v: &Value| -> Option<NodeId> {
1818 match v {
1819 Value::NodeSet(ns) if ns.len() == 1 => Some(ns[0]),
1820 _ => None,
1821 }
1822 };
1823 let (Some(a), Some(b)) = (pick(&lv), pick(&rv)) else {
1824 return Ok(Value::NodeSet(Vec::new()));
1825 };
1826 Ok(Value::Boolean(if after { a > b } else { a < b }))
1827 }
1828 Expr::NodeIs(l, r) => {
1829 let lv = eval_expr(l, ctx, idx)?;
1835 let rv = eval_expr(r, ctx, idx)?;
1836 let pick = |v: &Value| -> Option<NodeId> {
1837 match v {
1838 Value::NodeSet(ns) if ns.len() == 1 => Some(ns[0]),
1839 _ => None,
1840 }
1841 };
1842 let pick_foreign = |v: &Value| -> Option<ForeignNodePtr> {
1843 match v {
1844 Value::ForeignNodeSet(fs) if fs.len() == 1 => Some(fs[0]),
1845 _ => None,
1846 }
1847 };
1848 if let (Some(a), Some(b)) = (pick(&lv), pick(&rv)) {
1849 return Ok(Value::Boolean(a == b));
1850 }
1851 if let (Some(a), Some(b)) = (pick_foreign(&lv), pick_foreign(&rv)) {
1852 return Ok(Value::Boolean(a == b));
1853 }
1854 Ok(Value::NodeSet(Vec::new()))
1855 }
1856 Expr::SimpleMap(lhs, rhs) => {
1857 let lv = eval_expr(lhs, ctx, idx)?;
1865 let total = sequence_len(&lv);
1866 let mut out_nodes: Vec<NodeId> = Vec::new();
1867 let mut out_seq: Vec<Value> = Vec::new();
1868 let mut any_atomic = false;
1869 for (i, item) in iter_items(&lv).enumerate() {
1870 let (cx, cit) = match &item {
1875 Value::NodeSet(ns) if ns.len() == 1 => (ns[0], None),
1876 _ => (ctx.context_node, Some(item.clone())),
1877 };
1878 let inner = EvalCtx {
1879 context_node: cx, pos: i + 1, size: total,
1880 bindings: ctx.bindings, static_ctx: ctx.static_ctx,
1881 };
1882 let v = with_context_item(cit, || eval_expr(rhs, &inner, idx))?;
1883 match v {
1884 Value::NodeSet(ns) => out_nodes.extend(ns),
1885 Value::ForeignNodeSet(_) => {}
1886 Value::Sequence(s) => { any_atomic = true; out_seq.extend(s); }
1887 other => { any_atomic = true; out_seq.push(other); }
1888 }
1889 }
1890 if !any_atomic {
1891 return Ok(Value::NodeSet(out_nodes));
1892 }
1893 for id in out_nodes {
1896 out_seq.push(Value::NodeSet(vec![id]));
1897 }
1898 if out_seq.len() == 1 {
1899 Ok(out_seq.into_iter().next().unwrap())
1900 } else {
1901 Ok(Value::Sequence(out_seq))
1902 }
1903 }
1904 Expr::Range(lo, hi) => {
1905 let m_v = eval_expr(lo, ctx, idx)?;
1915 let n_v = eval_expr(hi, ctx, idx)?;
1916 let m = value_to_number(&m_v, idx).round() as i64;
1917 let n = value_to_number(&n_v, idx).round() as i64;
1918 if m > n {
1919 return Ok(Value::NodeSet(Vec::new()));
1920 }
1921 Ok(Value::IntRange { lo: m, hi: n })
1922 }
1923 Expr::Sequence(items) => {
1924 let mut evaluated: Vec<Value> = Vec::with_capacity(items.len());
1939 for item in items {
1940 let v = eval_expr(item, ctx, idx)?;
1941 match v {
1943 Value::Sequence(inner) => evaluated.extend(inner),
1944 other => evaluated.push(other),
1945 }
1946 }
1947 if evaluated.is_empty() {
1948 return Ok(Value::NodeSet(Vec::new()));
1949 }
1950 if evaluated.len() == 1 {
1951 return Ok(evaluated.into_iter().next().unwrap());
1952 }
1953 let any_node = evaluated.iter().any(|v|
1962 matches!(v, Value::NodeSet(_) | Value::ForeignNodeSet(_)));
1963 let any_lazy = evaluated.iter().any(|v|
1964 matches!(v, Value::Typed(_) | Value::IntRange { .. }));
1965 if !any_node || any_lazy {
1966 return Ok(Value::Sequence(evaluated));
1967 }
1968 let mut nodes: Vec<NodeId> = Vec::new();
1971 let mut atoms: Vec<String> = Vec::new();
1972 for v in evaluated {
1973 match v {
1974 Value::NodeSet(ns) => nodes.extend(ns),
1975 Value::ForeignNodeSet(_) => {}
1976 Value::String(s) => atoms.push(s),
1977 Value::Number(n) => atoms.push(value_to_string(&Value::Number(n), idx)),
1978 Value::Boolean(b) => atoms.push((if b { "true" } else { "false" }).to_string()),
1979 Value::Typed(t) => atoms.push(t.lexical),
1980 Value::Sequence(_) => unreachable!(), Value::IntRange { .. } => unreachable!(), Value::Map(_) | Value::Array(_) | Value::Function(_) => {}
1985 }
1986 }
1987 if atoms.is_empty() {
1988 Ok(Value::NodeSet(nodes))
1989 } else {
1990 for n in &nodes { atoms.push(idx.string_value(*n)); }
1991 match idx.allocate_rtf_text_nodes(atoms.clone()) {
1992 Some(ids) => Ok(Value::NodeSet(ids)),
1993 None => Ok(Value::String(atoms.join(""))),
1994 }
1995 }
1996 }
1997 Expr::Quantified { kind, bindings, test } => {
1998 use crate::xpath::ast::QuantifierKind::*;
2003 let mut found_match = false;
2004 let mut all_match = true;
2005 let mut seen_any = false;
2006 eval_quantified_recursive(
2007 bindings, test, 0, ctx, idx,
2008 &mut |result| {
2009 seen_any = true;
2010 if result { found_match = true; }
2011 else { all_match = false; }
2012 },
2013 )?;
2014 Ok(Value::Boolean(match kind {
2015 Some => found_match,
2016 Every => if !seen_any { true } else { all_match },
2017 }))
2018 }
2019 Expr::For { bindings, body } => {
2020 let mut out_items: Vec<Value> = Vec::new();
2028 eval_for_recursive_typed(bindings, body, 0, ctx, idx, &mut out_items)?;
2029 if out_items.is_empty() {
2030 return Ok(Value::NodeSet(Vec::new()));
2031 }
2032 let all_nodes = out_items.iter().all(|v| matches!(v, Value::NodeSet(_)));
2037 if all_nodes {
2038 let mut nodes: Vec<NodeId> = Vec::new();
2039 for v in out_items {
2040 if let Value::NodeSet(ns) = v { nodes.extend(ns); }
2041 }
2042 return Ok(Value::NodeSet(nodes));
2043 }
2044 Ok(Value::Sequence(out_items))
2047 }
2048 Expr::Let { bindings, body } => eval_let(bindings, body, 0, ctx, idx),
2049 }
2050}
2051
2052fn eval_let<I: DocIndexLike>(
2057 bindings: &[(String, Expr)],
2058 body: &Expr,
2059 depth: usize,
2060 ctx: &EvalCtx<'_>,
2061 idx: &I,
2062) -> Result<Value> {
2063 if depth == bindings.len() {
2064 return eval_expr(body, ctx, idx);
2065 }
2066 let (name, bound_expr) = &bindings[depth];
2067 let value = eval_expr(bound_expr, ctx, idx)?;
2068 let scoped = ScopedBindings { parent: ctx.bindings, name, value };
2069 let inner = EvalCtx {
2070 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2071 bindings: &scoped, static_ctx: ctx.static_ctx,
2072 };
2073 eval_let(bindings, body, depth + 1, &inner, idx)
2074}
2075
2076fn eval_for_recursive_typed<I: DocIndexLike>(
2077 bindings: &[(String, Expr)],
2078 body: &Expr,
2079 depth: usize,
2080 ctx: &EvalCtx<'_>,
2081 idx: &I,
2082 out_items: &mut Vec<Value>,
2083) -> Result<()> {
2084 if depth == bindings.len() {
2085 match eval_expr(body, ctx, idx)? {
2086 Value::Sequence(items) => out_items.extend(items),
2087 other => out_items.push(other),
2088 }
2089 return Ok(());
2090 }
2091 let (name, in_expr) = &bindings[depth];
2092 let seq = eval_expr(in_expr, ctx, idx)?;
2093 match seq {
2094 Value::NodeSet(ns) => {
2095 for id in ns {
2096 let scoped = ScopedBindings {
2097 parent: ctx.bindings, name, value: Value::NodeSet(vec![id]),
2098 };
2099 let inner_ctx = EvalCtx {
2100 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2101 bindings: &scoped, static_ctx: ctx.static_ctx,
2102 };
2103 eval_for_recursive_typed(bindings, body, depth + 1, &inner_ctx, idx, out_items)?;
2104 }
2105 }
2106 Value::ForeignNodeSet(ns) => {
2107 for p in ns {
2108 let scoped = ScopedBindings {
2109 parent: ctx.bindings, name, value: Value::ForeignNodeSet(vec![p]),
2110 };
2111 let inner_ctx = EvalCtx {
2112 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2113 bindings: &scoped, static_ctx: ctx.static_ctx,
2114 };
2115 eval_for_recursive_typed(bindings, body, depth + 1, &inner_ctx, idx, out_items)?;
2116 }
2117 }
2118 Value::Sequence(items) => {
2119 for v in items {
2120 let scoped = ScopedBindings {
2121 parent: ctx.bindings, name, value: v,
2122 };
2123 let inner_ctx = EvalCtx {
2124 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2125 bindings: &scoped, static_ctx: ctx.static_ctx,
2126 };
2127 eval_for_recursive_typed(bindings, body, depth + 1, &inner_ctx, idx, out_items)?;
2128 }
2129 }
2130 Value::IntRange { lo, hi } => {
2131 for i in lo..=hi {
2132 let scoped = ScopedBindings {
2133 parent: ctx.bindings, name, value: Value::Number(Numeric::Double(i as f64)),
2134 };
2135 let inner_ctx = EvalCtx {
2136 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2137 bindings: &scoped, static_ctx: ctx.static_ctx,
2138 };
2139 eval_for_recursive_typed(bindings, body, depth + 1, &inner_ctx, idx, out_items)?;
2140 }
2141 }
2142 atomic => {
2143 let scoped = ScopedBindings {
2144 parent: ctx.bindings, name, value: atomic,
2145 };
2146 let inner_ctx = EvalCtx {
2147 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2148 bindings: &scoped, static_ctx: ctx.static_ctx,
2149 };
2150 eval_for_recursive_typed(bindings, body, depth + 1, &inner_ctx, idx, out_items)?;
2151 }
2152 }
2153 Ok(())
2154}
2155
2156fn eval_quantified_recursive<I: DocIndexLike>(
2162 bindings: &[(String, Expr)],
2163 test: &Expr,
2164 depth: usize,
2165 ctx: &EvalCtx<'_>,
2166 idx: &I,
2167 report: &mut dyn FnMut(bool),
2168) -> Result<()> {
2169 if depth == bindings.len() {
2170 let t = eval_expr(test, ctx, idx)?;
2171 report(value_to_bool(&t, idx));
2172 return Ok(());
2173 }
2174 let (name, in_expr) = &bindings[depth];
2175 let seq = eval_expr(in_expr, ctx, idx)?;
2176 match seq {
2177 Value::NodeSet(ns) => {
2178 for id in ns {
2179 let scoped = ScopedBindings { parent: ctx.bindings, name, value: Value::NodeSet(vec![id]) };
2180 let inner = EvalCtx {
2181 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2182 bindings: &scoped, static_ctx: ctx.static_ctx,
2183 };
2184 eval_quantified_recursive(bindings, test, depth + 1, &inner, idx, report)?;
2185 }
2186 }
2187 Value::ForeignNodeSet(ns) => {
2188 for p in ns {
2189 let scoped = ScopedBindings { parent: ctx.bindings, name, value: Value::ForeignNodeSet(vec![p]) };
2190 let inner = EvalCtx {
2191 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2192 bindings: &scoped, static_ctx: ctx.static_ctx,
2193 };
2194 eval_quantified_recursive(bindings, test, depth + 1, &inner, idx, report)?;
2195 }
2196 }
2197 Value::IntRange { lo, hi } => {
2198 for i in lo..=hi {
2199 let scoped = ScopedBindings { parent: ctx.bindings, name, value: Value::Number(Numeric::Double(i as f64)) };
2200 let inner = EvalCtx {
2201 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2202 bindings: &scoped, static_ctx: ctx.static_ctx,
2203 };
2204 eval_quantified_recursive(bindings, test, depth + 1, &inner, idx, report)?;
2205 }
2206 }
2207 Value::Sequence(items) => {
2208 for item in items.iter().flat_map(iter_items) {
2213 let scoped = ScopedBindings { parent: ctx.bindings, name, value: item };
2214 let inner = EvalCtx {
2215 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2216 bindings: &scoped, static_ctx: ctx.static_ctx,
2217 };
2218 eval_quantified_recursive(bindings, test, depth + 1, &inner, idx, report)?;
2219 }
2220 }
2221 atomic => {
2222 let scoped = ScopedBindings { parent: ctx.bindings, name, value: atomic };
2223 let inner = EvalCtx {
2224 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2225 bindings: &scoped, static_ctx: ctx.static_ctx,
2226 };
2227 eval_quantified_recursive(bindings, test, depth + 1, &inner, idx, report)?;
2228 }
2229 }
2230 Ok(())
2231}
2232
2233struct ClosureBindings<'p> {
2242 vars: std::collections::HashMap<String, Value>,
2243 base: &'p dyn XPathBindings,
2244}
2245
2246impl<'p> XPathBindings for ClosureBindings<'p> {
2247 fn variable(&self, name: &str) -> Option<Value> {
2248 self.vars.get(name).cloned().or_else(|| self.base.variable(name))
2249 }
2250 fn resolve_prefix(&self, p: &str) -> Option<String> { self.base.resolve_prefix(p) }
2251 fn call_function(&self, ns: &str, n: &str, a: Vec<Value>) -> Option<Result<Value>> {
2252 self.base.call_function(ns, n, a)
2253 }
2254 fn call_function_in(&self, ns: &str, n: &str, a: Vec<Value>, cn: NodeId) -> Option<Result<Value>> {
2255 self.base.call_function_in(ns, n, a, cn)
2256 }
2257 fn function_available_in(&self, ns: &str, n: &str, a: usize) -> bool {
2258 self.base.function_available_in(ns, n, a)
2259 }
2260 fn function_signature_in(&self, ns: &str, n: &str, a: usize)
2261 -> Option<crate::xpath::ast::FunctionSig> {
2262 self.base.function_signature_in(ns, n, a)
2263 }
2264 fn castable_as_user_type(&self, ns: &str, l: &str, v: &str, sk: Option<&str>) -> Option<bool> {
2265 self.base.castable_as_user_type(ns, l, v, sk)
2266 }
2267 fn instance_of_user_type(&self, ns: &str, l: &str, vt: Option<(&str, &str)>) -> Option<bool> {
2268 self.base.instance_of_user_type(ns, l, vt)
2269 }
2270 fn schema_type_exists(&self, ns: &str, l: &str) -> bool {
2271 self.base.schema_type_exists(ns, l)
2272 }
2273 fn cast_to_user_type(&self, ns: &str, l: &str, v: &str) -> Option<Result<Value>> {
2274 self.base.cast_to_user_type(ns, l, v)
2275 }
2276 fn node_schema_type(&self, id: NodeId) -> Option<(String, String)> {
2277 self.base.node_schema_type(id)
2278 }
2279 fn node_typed_value(&self, id: NodeId, lexical: &str) -> Option<Value> {
2280 self.base.node_typed_value(id, lexical)
2281 }
2282 fn xpath_version_2_or_later(&self) -> bool { self.base.xpath_version_2_or_later() }
2283 fn load_document(&self, u: &str, b: Option<&str>) -> Option<Result<Vec<ForeignNodePtr>>> {
2284 self.base.load_document(u, b)
2285 }
2286 fn apply_foreign_path(&self, n: &[ForeignNodePtr], p: &[Expr], s: &[Step])
2287 -> Option<Result<Vec<ForeignNodePtr>>> { self.base.apply_foreign_path(n, p, s) }
2288 fn static_base_uri(&self) -> Option<String> { self.base.static_base_uri() }
2289 fn node_base_uri(&self, id: NodeId) -> Option<String> { self.base.node_base_uri(id) }
2290 fn foreign_string_value(&self, p: ForeignNodePtr) -> String { self.base.foreign_string_value(p) }
2291 fn load_dynamic_document(&self, u: &str)
2292 -> Option<std::result::Result<NodeId, crate::error::XmlError>> {
2293 self.base.load_dynamic_document(u)
2294 }
2295 fn regex_dialect(&self) -> crate::regex::Dialect { self.base.regex_dialect() }
2296}
2297
2298fn collect_var_refs(e: &Expr, out: &mut Vec<String>) {
2304 use crate::xpath::ast::Expr as E;
2305 let push = |n: &str, out: &mut Vec<String>| {
2306 if !out.iter().any(|x| x == n) { out.push(n.to_string()); }
2307 };
2308 match e {
2309 E::Variable(n) => push(n, out),
2310 E::Or(a, b) | E::And(a, b) | E::Eq(a, b) | E::Ne(a, b)
2311 | E::Lt(a, b) | E::Gt(a, b) | E::Le(a, b) | E::Ge(a, b)
2312 | E::ValueEq(a, b) | E::ValueNe(a, b) | E::ValueLt(a, b)
2313 | E::ValueGt(a, b) | E::ValueLe(a, b) | E::ValueGe(a, b)
2314 | E::Add(a, b) | E::Sub(a, b) | E::Mul(a, b) | E::Div(a, b)
2315 | E::Mod(a, b) | E::IDiv(a, b) | E::Union(a, b)
2316 | E::Intersect(a, b) | E::Except(a, b) | E::Range(a, b)
2317 | E::SimpleMap(a, b) | E::NodeBefore(a, b) | E::NodeAfter(a, b)
2318 | E::NodeIs(a, b) => {
2319 collect_var_refs(a, out); collect_var_refs(b, out);
2320 }
2321 E::Neg(x) | E::InstanceOf(x, _) | E::CastAs(x, _)
2322 | E::CastableAs(x, _) | E::TreatAs(x, _)
2323 | E::WithDefaultCollation(_, x) | E::BackwardsCompat(x) => collect_var_refs(x, out),
2324 E::IfThenElse { cond, then_branch, else_branch } => {
2325 collect_var_refs(cond, out);
2326 collect_var_refs(then_branch, out);
2327 collect_var_refs(else_branch, out);
2328 }
2329 E::For { bindings, body } | E::Let { bindings, body }
2330 | E::Quantified { bindings, test: body, .. } => {
2331 for (_, ex) in bindings { collect_var_refs(ex, out); }
2332 collect_var_refs(body, out);
2333 }
2334 E::Sequence(items) => for x in items { collect_var_refs(x, out); },
2335 E::FunctionCall(_, args) => for a in args { collect_var_refs(a, out); },
2336 E::DynamicCall { func, args } => {
2337 collect_var_refs(func, out);
2338 for a in args { collect_var_refs(a, out); }
2339 }
2340 E::FilterPath { primary, predicates, steps } => {
2341 collect_var_refs(primary, out);
2342 for pr in predicates { collect_var_refs(pr, out); }
2343 for s in steps { for pr in &s.predicates { collect_var_refs(pr, out); } }
2344 }
2345 E::Path(p) => collect_path_var_refs(p, out),
2346 E::TryCatch { body, catches } => {
2347 collect_var_refs(body, out);
2348 for c in catches { collect_var_refs(&c.body, out); }
2349 }
2350 E::MapConstructor(es) => for (k, v) in es { collect_var_refs(k, out); collect_var_refs(v, out); },
2351 E::ArrayConstructor { members, .. } => for m in members { collect_var_refs(m, out); },
2352 E::Lookup(b, key) => {
2353 collect_var_refs(b, out);
2354 if let crate::xpath::ast::LookupKey::Expr(x) = key { collect_var_refs(x, out); }
2355 }
2356 E::UnaryLookup(key) =>
2357 if let crate::xpath::ast::LookupKey::Expr(x) = key { collect_var_refs(x, out); },
2358 E::InlineFunction { body, .. } => collect_var_refs(body, out),
2359 E::Literal(_) | E::Integer(_) | E::Decimal(_) | E::Double(_)
2360 | E::NamedFunctionRef { .. } | E::Placeholder | E::ContextItem => {}
2361 }
2362}
2363
2364fn collect_path_var_refs(p: &crate::xpath::ast::LocationPath, out: &mut Vec<String>) {
2365 use crate::xpath::ast::LocationPath;
2366 let steps = match p { LocationPath::Absolute(s) | LocationPath::Relative(s) => s };
2367 for s in steps {
2368 for pr in &s.predicates { collect_var_refs(pr, out); }
2369 }
2370}
2371
2372fn call_function_item<I: DocIndexLike>(
2374 fi: &FunctionItem, args: Vec<Value>, ctx: &EvalCtx<'_>, idx: &I,
2375) -> Result<Value> {
2376 match fi {
2377 FunctionItem::Inline { params, body, closure, .. } => {
2378 if args.len() != params.len() {
2379 return Err(xpath_err(format!(
2380 "inline function expects {} argument(s), got {}",
2381 params.len(), args.len())));
2382 }
2383 let mut vars: std::collections::HashMap<String, Value> =
2384 closure.iter().cloned().collect();
2385 for (p, a) in params.iter().zip(args) { vars.insert(p.clone(), a); }
2386 let cb = ClosureBindings { vars, base: ctx.bindings };
2387 let inner = EvalCtx {
2388 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2389 bindings: &cb, static_ctx: ctx.static_ctx,
2390 };
2391 eval_expr(body, &inner, idx)
2392 }
2393 FunctionItem::Named { name, ns, arity, .. } => {
2394 if args.len() != *arity {
2395 return Err(xpath_err(format!(
2396 "function {name}#{arity} called with {} argument(s)", args.len())));
2397 }
2398 if !ns.is_empty() && ns.as_str() != FN_NAMESPACE {
2406 let local = name.rsplit(':').next().unwrap_or(name);
2407 if let Some(r) =
2408 ctx.bindings.call_function_in(ns, local, args.clone(), ctx.context_node)
2409 {
2410 return r;
2411 }
2412 if let Some(r) = super::exslt::dispatch(ns, local, args.clone(), idx) {
2413 return r;
2414 }
2415 }
2416 let mut vars = std::collections::HashMap::new();
2419 let mut arg_exprs = Vec::with_capacity(args.len());
2420 for (i, a) in args.into_iter().enumerate() {
2421 let vn = format!("\u{1}fnarg{i}");
2422 vars.insert(vn.clone(), a);
2423 arg_exprs.push(Expr::Variable(vn));
2424 }
2425 let cb = ClosureBindings { vars, base: ctx.bindings };
2426 let inner = EvalCtx {
2427 context_node: ctx.context_node, pos: ctx.pos, size: ctx.size,
2428 bindings: &cb, static_ctx: ctx.static_ctx,
2429 };
2430 eval_function(name, &arg_exprs, &inner, idx)
2431 }
2432 FunctionItem::Partial { base, bound } => {
2433 let mut supplied = args.into_iter();
2434 let mut full = Vec::with_capacity(bound.len());
2435 for b in bound {
2436 match b {
2437 Some(v) => full.push(v.clone()),
2438 None => full.push(supplied.next().ok_or_else(||
2439 xpath_err("too few arguments for partial application"))?),
2440 }
2441 }
2442 call_function_item(base, full, ctx, idx)
2443 }
2444 }
2445}
2446
2447struct ScopedBindings<'p> {
2448 parent: &'p dyn XPathBindings,
2449 name: &'p str,
2450 value: Value,
2451}
2452
2453impl<'p> XPathBindings for ScopedBindings<'p> {
2454 fn resolve_prefix(&self, prefix: &str) -> Option<String> {
2455 self.parent.resolve_prefix(prefix)
2456 }
2457 fn variable(&self, name: &str) -> Option<Value> {
2458 if name == self.name { Some(self.value.clone()) } else { self.parent.variable(name) }
2459 }
2460 fn call_function(
2461 &self, ns_uri: &str, name: &str, args: Vec<Value>,
2462 ) -> Option<std::result::Result<Value, crate::error::XmlError>> {
2463 self.parent.call_function(ns_uri, name, args)
2464 }
2465 fn call_function_in(
2466 &self, ns_uri: &str, name: &str, args: Vec<Value>,
2467 xpath_context_node: NodeId,
2468 ) -> Option<std::result::Result<Value, crate::error::XmlError>> {
2469 self.parent.call_function_in(ns_uri, name, args, xpath_context_node)
2470 }
2471 fn function_available_in(&self, ns: &str, n: &str, a: usize) -> bool {
2472 self.parent.function_available_in(ns, n, a)
2473 }
2474 fn function_signature_in(&self, ns: &str, n: &str, a: usize)
2475 -> Option<crate::xpath::ast::FunctionSig> {
2476 self.parent.function_signature_in(ns, n, a)
2477 }
2478 fn castable_as_user_type(&self, ns: &str, l: &str, v: &str, sk: Option<&str>) -> Option<bool> {
2479 self.parent.castable_as_user_type(ns, l, v, sk)
2480 }
2481 fn instance_of_user_type(&self, ns: &str, l: &str, vt: Option<(&str, &str)>) -> Option<bool> {
2482 self.parent.instance_of_user_type(ns, l, vt)
2483 }
2484 fn schema_type_exists(&self, ns: &str, l: &str) -> bool {
2485 self.parent.schema_type_exists(ns, l)
2486 }
2487 fn cast_to_user_type(&self, ns: &str, l: &str, v: &str) -> Option<Result<Value>> {
2488 self.parent.cast_to_user_type(ns, l, v)
2489 }
2490 fn node_schema_type(&self, id: NodeId) -> Option<(String, String)> {
2491 self.parent.node_schema_type(id)
2492 }
2493 fn node_typed_value(&self, id: NodeId, lexical: &str) -> Option<Value> {
2494 self.parent.node_typed_value(id, lexical)
2495 }
2496 fn foreign_string_value(
2497 &self, p: crate::xpath::eval::ForeignNodePtr,
2498 ) -> String {
2499 self.parent.foreign_string_value(p)
2500 }
2501}
2502
2503struct ErrBindings<'p> {
2507 parent: &'p dyn XPathBindings,
2508 err_uri: String,
2509 code_local: String,
2510 description: String,
2511}
2512
2513impl<'p> XPathBindings for ErrBindings<'p> {
2514 fn resolve_prefix(&self, prefix: &str) -> Option<String> {
2515 self.parent.resolve_prefix(prefix)
2516 .or_else(|| (prefix == "err").then(|| self.err_uri.clone()))
2517 }
2518 fn variable(&self, name: &str) -> Option<Value> {
2519 let key_lex = format!("err:{}", "code");
2523 let key_clark = format!("{{{}}}{}", self.err_uri, "code");
2524 let local = if name == "err:code" || name == key_clark {
2525 Some("code")
2526 } else if name == "err:description"
2527 || name == format!("{{{}}}description", self.err_uri).as_str() {
2528 Some("description")
2529 } else if name == "err:value"
2530 || name == format!("{{{}}}value", self.err_uri).as_str() {
2531 Some("value")
2532 } else if name == "err:module"
2533 || name == format!("{{{}}}module", self.err_uri).as_str() {
2534 Some("module")
2535 } else if name == "err:line-number"
2536 || name == format!("{{{}}}line-number", self.err_uri).as_str() {
2537 Some("line-number")
2538 } else if name == "err:column-number"
2539 || name == format!("{{{}}}column-number", self.err_uri).as_str() {
2540 Some("column-number")
2541 } else {
2542 None
2543 };
2544 let _ = key_lex;
2548 match local {
2549 Some("code") => Some(Value::String(format!("err:{}", self.code_local))),
2550 Some("description") => Some(Value::String(self.description.clone())),
2551 Some("value") => Some(Value::NodeSet(Vec::new())),
2552 Some("module") => Some(Value::NodeSet(Vec::new())),
2553 Some("line-number") => Some(Value::NodeSet(Vec::new())),
2554 Some("column-number") => Some(Value::NodeSet(Vec::new())),
2555 _ => self.parent.variable(name),
2556 }
2557 }
2558 fn call_function(
2559 &self, ns_uri: &str, name: &str, args: Vec<Value>,
2560 ) -> Option<std::result::Result<Value, crate::error::XmlError>> {
2561 self.parent.call_function(ns_uri, name, args)
2562 }
2563 fn call_function_in(
2564 &self, ns_uri: &str, name: &str, args: Vec<Value>,
2565 xpath_context_node: NodeId,
2566 ) -> Option<std::result::Result<Value, crate::error::XmlError>> {
2567 self.parent.call_function_in(ns_uri, name, args, xpath_context_node)
2568 }
2569 fn function_available_in(&self, ns: &str, n: &str, a: usize) -> bool {
2570 self.parent.function_available_in(ns, n, a)
2571 }
2572 fn function_signature_in(&self, ns: &str, n: &str, a: usize)
2573 -> Option<crate::xpath::ast::FunctionSig> {
2574 self.parent.function_signature_in(ns, n, a)
2575 }
2576 fn castable_as_user_type(&self, ns: &str, l: &str, v: &str, sk: Option<&str>) -> Option<bool> {
2577 self.parent.castable_as_user_type(ns, l, v, sk)
2578 }
2579 fn instance_of_user_type(&self, ns: &str, l: &str, vt: Option<(&str, &str)>) -> Option<bool> {
2580 self.parent.instance_of_user_type(ns, l, vt)
2581 }
2582 fn schema_type_exists(&self, ns: &str, l: &str) -> bool {
2583 self.parent.schema_type_exists(ns, l)
2584 }
2585 fn cast_to_user_type(&self, ns: &str, l: &str, v: &str) -> Option<Result<Value>> {
2586 self.parent.cast_to_user_type(ns, l, v)
2587 }
2588 fn node_schema_type(&self, id: NodeId) -> Option<(String, String)> {
2589 self.parent.node_schema_type(id)
2590 }
2591 fn node_typed_value(&self, id: NodeId, lexical: &str) -> Option<Value> {
2592 self.parent.node_typed_value(id, lexical)
2593 }
2594 fn foreign_string_value(
2595 &self, p: crate::xpath::eval::ForeignNodePtr,
2596 ) -> String {
2597 self.parent.foreign_string_value(p)
2598 }
2599}
2600
2601fn build_err_scope<'p>(
2602 parent: &'p dyn XPathBindings,
2603 err_uri: &str, code_local: &str, description: &str,
2604) -> ErrBindings<'p> {
2605 ErrBindings {
2606 parent,
2607 err_uri: err_uri.to_string(),
2608 code_local: code_local.to_string(),
2609 description: description.to_string(),
2610 }
2611}
2612
2613fn xpath_catch_matches(
2614 matchers: &[crate::xpath::ast::CatchNameTest],
2615 err_uri: &str, err_local: &str,
2616 bindings: &dyn XPathBindings,
2617) -> bool {
2618 use crate::xpath::ast::CatchNameTest::*;
2619 matchers.iter().any(|m| match m {
2620 Any => true,
2621 LocalNameOnly(local) => err_local == local,
2622 PrefixWildcard(prefix) => bindings.resolve_prefix(prefix)
2623 .as_deref() == Some(err_uri),
2624 QName { prefix, local } => {
2625 let want_uri = match prefix {
2626 Some(p) => bindings.resolve_prefix(p).unwrap_or_default(),
2627 None => String::new(),
2628 };
2629 want_uri == err_uri && local == err_local
2630 }
2631 })
2632}
2633
2634fn eval_path<I: DocIndexLike>(
2637 path: &LocationPath, ctx_node: NodeId, idx: &I,
2638 bindings: &dyn XPathBindings, compat: bool, current_node: Option<NodeId>,
2639) -> Result<Vec<NodeId>> {
2640 let (initial, steps) = match path {
2641 LocationPath::Absolute(steps) => {
2648 if focus_is_undefined() {
2653 return Err(xpath_err(
2654 "absolute path requires a context item (XPDY0002)"
2655 ).with_xpath_code("XPDY0002"));
2656 }
2657 let mut root = ctx_node;
2658 while let Some(p) = idx.parent(root) {
2659 root = p;
2660 }
2661 (vec![root], steps.as_slice())
2662 },
2663 LocationPath::Relative(steps) => (vec![ctx_node], steps.as_slice()),
2664 };
2665
2666 let mut current = initial;
2667 for step in steps {
2668 current = eval_step_on_nodes_cur(current, step, idx, bindings, compat, current_node)?;
2669 }
2670 Ok(current)
2671}
2672
2673pub fn eval_step_on_nodes<I: DocIndexLike>(
2680 nodes: Vec<NodeId>, step: &Step, idx: &I,
2681 bindings: &dyn XPathBindings, compat: bool,
2682) -> Result<Vec<NodeId>> {
2683 eval_step_on_nodes_cur(nodes, step, idx, bindings, compat, None)
2687}
2688
2689pub fn eval_step_on_nodes_cur<I: DocIndexLike>(
2692 nodes: Vec<NodeId>, step: &Step, idx: &I,
2693 bindings: &dyn XPathBindings, compat: bool, current_node: Option<NodeId>,
2694) -> Result<Vec<NodeId>> {
2695 let sc = StaticContext {
2704 xpath_2_0: bindings.xpath_version_2_or_later(),
2705 xpath_3_0: false,
2706 libxml2_compatible: compat,
2707 current_node,
2708 };
2709 if let Some(filter) = &step.filter {
2710 let mut out: Vec<NodeId> = Vec::new();
2711 let mut all_native_nodes = true;
2712 for node in nodes {
2713 charge_eval_step()?;
2714 let ctx = EvalCtx {
2715 context_node: node, pos: 1, size: 1, bindings,
2716 static_ctx: &sc,
2717 };
2718 let v = with_focus_undefined(false, || eval_expr(filter, &ctx, idx))?;
2724 match v {
2725 Value::NodeSet(ns) => out.extend(ns),
2726 Value::ForeignNodeSet(_) => { }
2728 Value::Sequence(items) => {
2729 let mut atoms: Vec<String> = Vec::new();
2730 for item in items {
2731 match item {
2732 Value::NodeSet(ns) => out.extend(ns),
2733 Value::ForeignNodeSet(_) => {}
2734 atomic => atoms.push(value_to_string_with(&atomic, idx, bindings)),
2735 }
2736 }
2737 if !atoms.is_empty() {
2738 all_native_nodes = false;
2739 if let Some(ids) = idx.allocate_rtf_text_nodes(atoms) {
2740 out.extend(ids);
2741 }
2742 }
2743 }
2744 atomic => {
2745 all_native_nodes = false;
2746 let s = value_to_string_with(&atomic, idx, bindings);
2747 if let Some(ids) = idx.allocate_rtf_text_nodes(vec![s]) {
2748 out.extend(ids);
2749 }
2750 }
2751 }
2752 }
2753 if all_native_nodes && bindings.xpath_version_2_or_later() {
2759 dedup_sort(&mut out);
2760 }
2761 return apply_predicates_cur(out, &step.predicates, idx, bindings, compat, current_node);
2763 }
2764 let mut candidates: Vec<NodeId> = Vec::new();
2765 for node in nodes {
2766 charge_eval_step()?;
2769 let mut cands = axis_nodes(&step.axis, node, idx);
2770 cands.retain(|&n| node_matches(n, &step.node_test, &step.axis, idx, bindings, compat));
2771 let filtered = apply_predicates_cur(cands, &step.predicates, idx, bindings, compat, current_node)?;
2777 candidates.extend(filtered);
2778 }
2779 dedup_sort(&mut candidates);
2780 Ok(candidates)
2781}
2782
2783pub fn apply_predicates<I: DocIndexLike>(
2788 nodes: Vec<NodeId>, predicates: &[Expr], idx: &I,
2789 bindings: &dyn XPathBindings, compat: bool,
2790) -> Result<Vec<NodeId>> {
2791 apply_predicates_cur(nodes, predicates, idx, bindings, compat, None)
2792}
2793
2794pub fn apply_predicates_cur<I: DocIndexLike>(
2797 nodes: Vec<NodeId>, predicates: &[Expr], idx: &I,
2798 bindings: &dyn XPathBindings, compat: bool, current_node: Option<NodeId>,
2799) -> Result<Vec<NodeId>> {
2800 let sc = StaticContext {
2801 xpath_2_0: bindings.xpath_version_2_or_later(),
2802 xpath_3_0: false,
2803 libxml2_compatible: compat,
2804 current_node,
2805 };
2806 let mut result = nodes;
2807 for pred in predicates {
2808 if let Some(n) = positional_index(pred) {
2815 charge_eval_step()?;
2816 result = match n.checked_sub(1).and_then(|i| result.get(i).copied()) {
2817 Some(node) => vec![node],
2818 None => Vec::new(),
2819 };
2820 continue;
2821 }
2822 let size = result.len();
2823 let mut next = Vec::new();
2824 for (i, node) in result.into_iter().enumerate() {
2825 charge_eval_step()?;
2828 let ctx = EvalCtx {
2829 context_node: node, pos: i + 1, size, bindings,
2830 static_ctx: &sc,
2831 };
2832 let v = eval_expr(pred, &ctx, idx)?;
2833 let keep = match &v {
2840 Value::Number(n) => (i + 1) as f64 == n.as_f64(),
2841 Value::Typed(t) => match t.numeric {
2842 Some(n) => (i + 1) as f64 == n,
2843 None => value_to_bool(&v, idx),
2844 },
2845 Value::NodeSet(ns) if ns.len() == 1
2846 && matches!(idx.kind(ns[0]),
2847 crate::xpath::XPathNodeKind::Text)
2848 && idx.parent(ns[0]).is_none()
2849 => {
2850 let s = idx.string_value(ns[0]);
2851 match s.trim().parse::<f64>() {
2852 Ok(n) if n.fract() == 0.0 => (i + 1) as f64 == n,
2853 _ => value_to_bool(&v, idx),
2854 }
2855 }
2856 other => value_to_bool(other, idx),
2857 };
2858 if keep {
2859 next.push(node);
2860 }
2861 }
2862 result = next;
2863 }
2864 Ok(result)
2865}
2866
2867fn positional_index(pred: &Expr) -> Option<usize> {
2873 fn lit_pos_int(e: &Expr) -> Option<usize> {
2874 match e {
2875 Expr::Integer(i) if *i >= 1 => Some(*i as usize),
2876 Expr::Decimal(n) => {
2880 use rust_decimal::prelude::ToPrimitive;
2881 let whole = n.trunc() == *n;
2882 let positive = *n >= rust_decimal::Decimal::ONE;
2883 let fits = n.to_usize();
2884 match (whole, positive, fits) {
2885 (true, true, Some(u)) => Some(u),
2886 _ => None,
2887 }
2888 }
2889 _ => None,
2890 }
2891 }
2892 fn is_position_call(e: &Expr) -> bool {
2893 matches!(e, Expr::FunctionCall(name, args) if name == "position" && args.is_empty())
2894 }
2895 if let Some(n) = lit_pos_int(pred) { return Some(n); }
2897 if let Expr::Eq(l, r) = pred {
2899 if is_position_call(l) { return lit_pos_int(r); }
2900 if is_position_call(r) { return lit_pos_int(l); }
2901 }
2902 None
2903}
2904
2905fn axis_nodes<I: DocIndexLike>(axis: &Axis, node: NodeId, idx: &I) -> Vec<NodeId> {
2908 match axis {
2909 Axis::Self_ => vec![node],
2910 Axis::Child => idx.children(node).to_vec(),
2911 Axis::Parent => idx.parent(node).into_iter().collect(),
2912 Axis::Attribute => idx.attr_range(node).collect(),
2913 Axis::Ancestor => ancestors(node, idx),
2914 Axis::AncestorOrSelf => {
2915 let mut a = vec![node];
2916 a.extend(ancestors(node, idx));
2917 a
2918 }
2919 Axis::Descendant => descendants(node, idx, false),
2920 Axis::DescendantOrSelf => descendants(node, idx, true),
2921 Axis::FollowingSibling => following_siblings(node, idx),
2922 Axis::PrecedingSibling => preceding_siblings(node, idx),
2923 Axis::Following => following(node, idx),
2924 Axis::Preceding => preceding(node, idx),
2925 Axis::Namespace => idx.ns_range(node).collect(),
2930 }
2931}
2932
2933fn ancestors<I: DocIndexLike>(node: NodeId, idx: &I) -> Vec<NodeId> {
2934 let mut result = Vec::new();
2935 let mut cur = idx.parent(node);
2936 while let Some(p) = cur {
2937 result.push(p);
2938 cur = idx.parent(p);
2939 }
2940 result
2941}
2942
2943fn is_valid_lexical_qname(s: &str) -> bool {
2953 fn is_ncname(s: &str) -> bool {
2954 let mut chars = s.chars();
2955 let first = match chars.next() { Some(c) => c, None => return false };
2956 let start_ok = first == '_' || first.is_ascii_alphabetic()
2957 || (first as u32 >= 0x80 && crate::charsets::is_name_start_char(first));
2958 if !start_ok || first == ':' { return false; }
2959 for c in chars {
2960 if c == ':' { return false; }
2961 let ok = c == '_' || c == '-' || c == '.'
2962 || c.is_ascii_alphanumeric()
2963 || (c as u32 >= 0x80 && crate::charsets::is_name_char_unicode(c));
2964 if !ok { return false; }
2965 }
2966 true
2967 }
2968 match s.split_once(':') {
2969 None => is_ncname(s),
2970 Some((p, l)) => is_ncname(p) && is_ncname(l),
2971 }
2972}
2973
2974fn doc_root_of<I: DocIndexLike>(node: NodeId, idx: &I) -> NodeId {
2975 let mut cur = node;
2976 while let Some(p) = idx.parent(cur) { cur = p; }
2977 cur
2978}
2979
2980fn descendants<I: DocIndexLike>(node: NodeId, idx: &I, include_self: bool) -> Vec<NodeId> {
2981 let mut result = Vec::new();
2982 if include_self {
2983 result.push(node);
2984 }
2985 collect_desc(node, idx, &mut result);
2986 result
2987}
2988
2989fn collect_desc<I: DocIndexLike>(node: NodeId, idx: &I, out: &mut Vec<NodeId>) {
2990 for &child in idx.children(node) {
2991 out.push(child);
2992 collect_desc(child, idx, out);
2993 }
2994}
2995
2996fn following_siblings<I: DocIndexLike>(node: NodeId, idx: &I) -> Vec<NodeId> {
2997 let parent = match idx.parent(node) {
2998 Some(p) => p,
2999 None => return Vec::new(),
3000 };
3001 let siblings = idx.children(parent);
3002 siblings.iter().position(|&n| n == node)
3005 .map(|p| siblings[p + 1..].to_vec())
3006 .unwrap_or_default()
3007}
3008
3009fn preceding_siblings<I: DocIndexLike>(node: NodeId, idx: &I) -> Vec<NodeId> {
3010 let parent = match idx.parent(node) {
3011 Some(p) => p,
3012 None => return Vec::new(),
3013 };
3014 let siblings = idx.children(parent);
3015 let pos = siblings.iter().position(|&n| n == node).unwrap_or(0);
3016 siblings[..pos].iter().rev().copied().collect()
3018}
3019
3020fn following<I: DocIndexLike>(node: NodeId, idx: &I) -> Vec<NodeId> {
3021 let desc_set: HashSet<NodeId> = descendants(node, idx, true).into_iter().collect();
3023 let mut result = Vec::new();
3024 let mut cur = node;
3025 while let Some(parent) = idx.parent(cur) {
3026 let siblings = idx.children(parent);
3027 let start = siblings.iter().position(|&n| n == cur).map_or(0, |p| p + 1);
3032 for &sib in &siblings[start..] {
3033 if !desc_set.contains(&sib) {
3034 result.push(sib);
3035 collect_desc(sib, idx, &mut result);
3036 }
3037 }
3038 cur = parent;
3039 }
3040 dedup_sort(&mut result);
3041 result
3042}
3043
3044fn preceding<I: DocIndexLike>(node: NodeId, idx: &I) -> Vec<NodeId> {
3045 let ancestor_set: HashSet<NodeId> = ancestors(node, idx).into_iter().collect();
3046 let mut result = Vec::new();
3047 let mut cur = node;
3048 while let Some(parent) = idx.parent(cur) {
3049 let siblings = idx.children(parent);
3050 let pos = siblings.iter().position(|&n| n == cur).unwrap_or(0);
3051 for &sib in siblings[..pos].iter().rev() {
3052 if !ancestor_set.contains(&sib) {
3053 let mut d = Vec::new();
3055 collect_desc(sib, idx, &mut d);
3056 for id in d.into_iter().rev() {
3057 result.push(id);
3058 }
3059 result.push(sib);
3060 }
3061 }
3062 cur = parent;
3063 }
3064 result
3065}
3066
3067fn principal_kind(axis: &Axis) -> XPathNodeKind {
3075 match axis {
3076 Axis::Attribute => XPathNodeKind::Attribute,
3077 Axis::Namespace => XPathNodeKind::Namespace,
3078 _ => XPathNodeKind::Element,
3079 }
3080}
3081
3082pub fn node_matches_child<I: DocIndexLike>(
3088 node: NodeId, test: &NodeTest, idx: &I, bindings: &dyn XPathBindings,
3089) -> bool {
3090 node_matches(node, test, &Axis::Child, idx, bindings, false)
3091}
3092
3093fn node_matches<I: DocIndexLike>(
3094 node: NodeId, test: &NodeTest, axis: &Axis, idx: &I,
3095 bindings: &dyn XPathBindings, libxml2_compatible: bool,
3096) -> bool {
3097 match test {
3098 NodeTest::AnyNode => true,
3099 NodeTest::Text => matches!(idx.kind(node), XPathNodeKind::Text | XPathNodeKind::CData),
3100 NodeTest::Comment => matches!(idx.kind(node), XPathNodeKind::Comment),
3101 NodeTest::PI(None) => matches!(idx.kind(node), XPathNodeKind::PI),
3102 NodeTest::PI(Some(target)) => {
3103 (matches!(idx.kind(node), XPathNodeKind::PI) && idx.pi_target(node) == *target)
3104 }
3105 NodeTest::Document(inner) => {
3106 if !matches!(idx.kind(node), XPathNodeKind::Document) {
3107 return false;
3108 }
3109 match inner {
3110 None => true,
3111 Some(t) => idx.children(node).iter().any(|c|
3114 idx.kind(*c) == XPathNodeKind::Element
3115 && node_matches(*c, t, &Axis::Child, idx, bindings, libxml2_compatible)),
3116 }
3117 }
3118 NodeTest::Wildcard => idx.kind(node) == principal_kind(axis),
3119 NodeTest::PrefixWildcard(prefix) => {
3120 if idx.kind(node) != principal_kind(axis) {
3121 return false;
3122 }
3123 if let Some(uri) = resolve_prefix_or_implicit(bindings, prefix) {
3129 idx.namespace_uri(node) == uri
3130 } else {
3131 let name = idx.node_name(node);
3132 name.starts_with(&format!("{prefix}:"))
3133 }
3134 }
3135 NodeTest::LocalNameOnly(local) => {
3139 if idx.kind(node) != principal_kind(axis) {
3140 return false;
3141 }
3142 idx.local_name(node) == local
3143 }
3144 NodeTest::LocalName(local) => {
3145 if idx.kind(node) != principal_kind(axis) {
3157 return false;
3158 }
3159 if idx.local_name(node) != local {
3160 return false;
3161 }
3162 if libxml2_compatible || matches!(axis, Axis::Namespace) {
3163 return true;
3164 }
3165 idx.namespace_uri(node).is_empty()
3166 }
3167 NodeTest::DefaultNamespaceName { uri, local } => {
3174 if idx.kind(node) != principal_kind(axis) {
3175 return false;
3176 }
3177 if !matches!(axis,
3178 Axis::Child | Axis::Descendant | Axis::DescendantOrSelf
3179 | Axis::Self_ | Axis::Parent | Axis::Ancestor
3180 | Axis::AncestorOrSelf | Axis::FollowingSibling
3181 | Axis::PrecedingSibling | Axis::Following | Axis::Preceding)
3182 {
3183 return false;
3184 }
3185 idx.local_name(node) == local.as_str()
3186 && idx.namespace_uri(node) == uri.as_str()
3187 }
3188 NodeTest::QName(prefix, local) => {
3189 if idx.kind(node) != principal_kind(axis) {
3190 return false;
3191 }
3192 if let Some(uri) = resolve_prefix_or_implicit(bindings, prefix) {
3197 return idx.local_name(node) == local.as_str()
3198 && idx.namespace_uri(node) == uri;
3199 }
3200 let expected_qname = format!("{prefix}:{local}");
3208 if idx.node_name(node) == expected_qname {
3209 return true;
3210 }
3211 idx.local_name(node) == local.as_str()
3212 && idx.namespace_prefix(node) == Some(prefix.as_str())
3213 }
3214 }
3215}
3216
3217pub fn value_to_bool<I: DocIndexLike>(v: &Value, idx: &I) -> bool {
3220 match v {
3221 Value::Boolean(b) => *b,
3222 Value::Number(n) => { let n = n.as_f64(); n != 0.0 && !n.is_nan() }
3223 Value::String(s) => !s.is_empty(),
3224 Value::NodeSet(ns) => !ns.is_empty(),
3225 Value::ForeignNodeSet(ns) => !ns.is_empty(),
3226 Value::Typed(t) => {
3227 if let Some(b) = t.boolean { return b; }
3228 if let Some(n) = t.numeric { return n != 0.0 && !n.is_nan(); }
3229 !t.lexical.is_empty()
3230 }
3231 Value::Sequence(items) => match items.first() {
3236 None => false,
3237 Some(v) => value_to_bool(v, idx),
3238 }
3239 Value::IntRange { lo, hi } if lo == hi => *lo != 0,
3245 Value::IntRange { .. } => true,
3246 Value::Map(_) | Value::Array(_) | Value::Function(_) => true,
3250 }
3251}
3252
3253pub fn value_to_number<I: DocIndexLike>(v: &Value, idx: &I) -> f64 {
3254 value_to_number_with(v, idx, &NO_BINDINGS)
3255}
3256
3257pub fn value_to_number_with<I: DocIndexLike>(
3258 v: &Value, idx: &I, bindings: &dyn XPathBindings,
3259) -> f64 {
3260 match v {
3261 Value::Number(n) => n.as_f64(),
3262 Value::Boolean(b) => if *b { 1.0 } else { 0.0 },
3263 Value::String(s) => s.trim().parse().unwrap_or(f64::NAN),
3264 Value::NodeSet(ns) => {
3265 if ns.is_empty() {
3266 return f64::NAN;
3267 }
3268 let s = idx.string_value(ns[0]);
3269 s.trim().parse().unwrap_or(f64::NAN)
3270 }
3271 Value::ForeignNodeSet(ns) => {
3272 if ns.is_empty() {
3273 return f64::NAN;
3274 }
3275 bindings.foreign_string_value(ns[0]).trim().parse().unwrap_or(f64::NAN)
3276 }
3277 Value::Typed(t) => {
3278 if let Some(n) = t.numeric { return n; }
3279 if let Some(b) = t.boolean { return if b { 1.0 } else { 0.0 }; }
3280 t.lexical.trim().parse().unwrap_or(f64::NAN)
3281 }
3282 Value::Sequence(items) => match items.first() {
3285 Some(v) => value_to_number_with(v, idx, bindings),
3286 None => f64::NAN,
3287 }
3288 Value::IntRange { lo, .. } => *lo as f64,
3290 Value::Map(_) | Value::Array(_) | Value::Function(_) => f64::NAN,
3292 }
3293}
3294
3295fn value_is_function(v: &Value) -> bool {
3299 matches!(v, Value::Function(_))
3300}
3301
3302fn round_decimal_half_to_pos_inf(d: rust_decimal::Decimal, precision: i32) -> rust_decimal::Decimal {
3307 use rust_decimal::Decimal;
3308 let half = Decimal::new(5, 1); if precision >= 0 {
3310 let shift = Decimal::from(10i64.pow((precision as u32).min(18)));
3311 (d * shift + half).floor() / shift
3312 } else {
3313 let shift = Decimal::from(10i64.pow(((-precision) as u32).min(18)));
3314 (d / shift + half).floor() * shift
3315 }
3316}
3317
3318fn round_decimal_half_to_even(d: rust_decimal::Decimal, precision: i32) -> rust_decimal::Decimal {
3322 use rust_decimal::{Decimal, RoundingStrategy};
3323 if precision >= 0 {
3324 d.round_dp_with_strategy((precision as u32).min(28), RoundingStrategy::MidpointNearestEven)
3325 } else {
3326 let shift = Decimal::from(10i64.pow(((-precision) as u32).min(18)));
3327 (d / shift).round_dp_with_strategy(0, RoundingStrategy::MidpointNearestEven) * shift
3328 }
3329}
3330
3331fn value_seq_has_function(v: &Value) -> bool {
3333 match v {
3334 Value::Function(_) => true,
3335 Value::Sequence(items) => items.iter().any(value_seq_has_function),
3336 _ => false,
3337 }
3338}
3339
3340pub fn value_to_string<I: DocIndexLike>(v: &Value, idx: &I) -> String {
3341 value_to_string_with(v, idx, &NO_BINDINGS)
3342}
3343
3344pub fn value_to_string_with<I: DocIndexLike>(
3345 v: &Value, idx: &I, bindings: &dyn XPathBindings,
3346) -> String {
3347 value_to_string_with_compat(v, idx, bindings, false)
3348}
3349
3350#[derive(Clone, Copy, PartialEq, Eq)]
3361pub enum NumStyle { Xpath10, Libxml2, Xpath20 }
3362
3363impl NumStyle {
3364 pub fn from_context(compat: bool, xpath_2_or_later: bool) -> NumStyle {
3368 if compat { NumStyle::Libxml2 }
3369 else if xpath_2_or_later { NumStyle::Xpath20 }
3370 else { NumStyle::Xpath10 }
3371 }
3372}
3373
3374pub fn format_numeric_styled(n: Numeric, style: NumStyle) -> String {
3380 if let Numeric::Decimal(d) = n {
3385 return format_decimal_canonical(d);
3386 }
3387 let f = n.as_f64();
3388 if matches!(n, Numeric::Float(_)) && matches!(style, NumStyle::Xpath20) {
3393 return canonical_float_lex(f, &Value::Number(n));
3394 }
3395 match style {
3396 NumStyle::Libxml2 => format_number_libxml2(f),
3397 NumStyle::Xpath20 if matches!(n, Numeric::Double(_)) =>
3398 format_number_xpath20(f),
3399 _ => format_number(f),
3400 }
3401}
3402
3403fn format_decimal_canonical(d: rust_decimal::Decimal) -> String {
3409 if d.is_zero() { return "0".into(); }
3410 let s = d.to_string();
3411 let trimmed = match s.split_once('.') {
3412 Some((w, f)) => {
3413 let f = f.trim_end_matches('0');
3414 if f.is_empty() { w.to_string() } else { format!("{w}.{f}") }
3415 }
3416 None => s,
3417 };
3418 if trimmed == "-0" { "0".into() } else { trimmed }
3419}
3420
3421pub fn value_to_string_with_compat<I: DocIndexLike>(
3425 v: &Value, idx: &I, bindings: &dyn XPathBindings, compat: bool,
3426) -> String {
3427 let style = NumStyle::from_context(compat, bindings.xpath_version_2_or_later());
3428 value_to_string_styled_with(v, idx, bindings, style)
3429}
3430
3431pub fn value_to_string_styled<I: DocIndexLike>(
3437 v: &Value, idx: &I, style: NumStyle,
3438) -> String {
3439 value_to_string_styled_with(v, idx, &NO_BINDINGS, style)
3440}
3441
3442pub fn value_to_string_styled_with<I: DocIndexLike>(
3443 v: &Value, idx: &I, bindings: &dyn XPathBindings, style: NumStyle,
3444) -> String {
3445 match v {
3446 Value::String(s) => s.clone(),
3447 Value::Boolean(b) => (if *b { "true" } else { "false" }).to_string(),
3448 Value::Number(n) => format_numeric_styled(*n, style),
3449 Value::NodeSet(ns) => {
3450 if ns.is_empty() {
3451 String::new()
3452 } else {
3453 idx.string_value(ns[0])
3454 }
3455 }
3456 Value::ForeignNodeSet(ns) => {
3457 if ns.is_empty() {
3458 String::new()
3459 } else {
3460 bindings.foreign_string_value(ns[0])
3461 }
3462 }
3463 Value::Typed(t) => t.lexical.clone(),
3466 Value::Sequence(items) => match items.first() {
3472 Some(v) => value_to_string_styled_with(v, idx, bindings, style),
3473 None => String::new(),
3474 }
3475 Value::IntRange { lo, .. } => lo.to_string(),
3477 Value::Map(_) | Value::Array(_) | Value::Function(_) => String::new(),
3479 }
3480}
3481
3482
3483fn preserve_numeric_kind(arg: &Value, result: f64) -> Value {
3490 match numeric_kind_of(arg) {
3491 Some(kind) => Value::Number(Numeric::of_kind(kind, result)),
3492 None => Value::Number(Numeric::Double(result)),
3493 }
3494}
3495
3496fn format_number_xpath20(n: f64) -> String {
3502 if n.is_nan() { return "NaN".to_string(); }
3503 if n.is_infinite() { return if n > 0.0 { "INF" } else { "-INF" }.to_string(); }
3504 if n == 0.0 {
3505 return if n.is_sign_negative() { "-0".to_string() } else { "0".to_string() };
3506 }
3507 let abs = n.abs();
3508 if (1e-6..1e6).contains(&abs) {
3509 return format_number(n); }
3511 let s = format!("{n:e}");
3512 let (mantissa, exp) = s.split_once('e').unwrap_or((s.as_str(), "0"));
3513 let mantissa = if mantissa.contains('.') { mantissa.to_string() }
3514 else { format!("{mantissa}.0") };
3515 format!("{mantissa}E{exp}")
3516}
3517
3518fn format_number(n: f64) -> String {
3521 if n.is_nan() {
3522 "NaN".to_string()
3523 } else if n.is_infinite() {
3524 if n > 0.0 { "Infinity".to_string() } else { "-Infinity".to_string() }
3525 } else if n == 0.0 && n.is_sign_negative() {
3526 "-0".to_string()
3531 } else if n.fract() == 0.0 && n.abs() < 1e15 {
3532 format!("{}", n as i64)
3533 } else {
3534 format!("{n}")
3535 }
3536}
3537
3538fn format_number_libxml2(n: f64) -> String {
3545 if n.is_nan() {
3546 return "NaN".to_string();
3547 }
3548 if n.is_infinite() {
3549 return if n > 0.0 { "Infinity".into() } else { "-Infinity".into() };
3550 }
3551 if n.fract() == 0.0 && n.abs() < 1e15 {
3552 return format!("{}", n as i64);
3553 }
3554 let abs = n.abs();
3555 if abs >= 1e15 || (abs > 0.0 && abs < 1e-5) {
3556 let s = format!("{:.14e}", n);
3559 let Some(idx) = s.find('e') else { return s; };
3560 let (mantissa, exp_with_e) = s.split_at(idx);
3561 let exp = &exp_with_e[1..];
3562 let mantissa = trim_mantissa(mantissa);
3566 if exp.starts_with('-') {
3567 format!("{}e{}", mantissa, exp)
3568 } else {
3569 format!("{}e+{}", mantissa, exp)
3570 }
3571 } else {
3572 format!("{n}")
3573 }
3574}
3575
3576fn trim_mantissa(s: &str) -> String {
3577 let Some(dot_idx) = s.find('.') else { return s.to_string(); };
3581 let trimmed = s.trim_end_matches('0');
3582 let trimmed = trimmed.trim_end_matches('.');
3583 if trimmed.len() <= dot_idx { format!("{}.0", &s[..dot_idx]) } else { trimmed.to_string() }
3584}
3585
3586fn intrange_to_sequence(v: &Value) -> Option<Value> {
3598 if let Value::IntRange { lo, hi } = v {
3599 let items: Vec<Value> = (*lo..=*hi).map(|i| Value::Number(Numeric::Double(i as f64))).collect();
3600 return Some(Value::Sequence(items));
3601 }
3602 None
3603}
3604
3605fn values_ne<I: DocIndexLike>(
3606 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings,
3607) -> bool {
3608 if let Some(lv) = intrange_to_sequence(l) {
3613 return values_ne(&lv, r, idx, bindings);
3614 }
3615 if let Some(rv) = intrange_to_sequence(r) {
3616 return values_ne(l, &rv, idx, bindings);
3617 }
3618 match (l, r) {
3619 (Value::Map(_) | Value::Array(_) | Value::Function(_), _) | (_, Value::Map(_) | Value::Array(_) | Value::Function(_)) => false,
3623 (Value::NodeSet(ls), Value::NodeSet(rs)) => {
3624 let l_vals: HashSet<String> = ls.iter().map(|&id| idx.string_value(id)).collect();
3630 let r_vals: HashSet<String> = rs.iter().map(|&id| idx.string_value(id)).collect();
3631 if l_vals.is_empty() || r_vals.is_empty() { return false; }
3632 l_vals.union(&r_vals).count() > 1
3633 }
3634 (Value::ForeignNodeSet(ls), Value::ForeignNodeSet(rs)) => {
3635 let l_vals: HashSet<String> = ls.iter()
3636 .map(|&p| bindings.foreign_string_value(p)).collect();
3637 let r_vals: HashSet<String> = rs.iter()
3638 .map(|&p| bindings.foreign_string_value(p)).collect();
3639 if l_vals.is_empty() || r_vals.is_empty() { return false; }
3640 l_vals.union(&r_vals).count() > 1
3641 }
3642 (Value::NodeSet(ns), Value::ForeignNodeSet(fs))
3643 | (Value::ForeignNodeSet(fs), Value::NodeSet(ns)) => {
3644 let l_vals: HashSet<String> = ns.iter().map(|&id| idx.string_value(id)).collect();
3645 let r_vals: HashSet<String> = fs.iter()
3646 .map(|&p| bindings.foreign_string_value(p)).collect();
3647 if l_vals.is_empty() || r_vals.is_empty() { return false; }
3648 l_vals.union(&r_vals).count() > 1
3649 }
3650 (Value::NodeSet(ns), other) | (other, Value::NodeSet(ns)) => {
3651 match other {
3652 Value::Boolean(b) => value_to_bool(&Value::NodeSet(ns.clone()), idx) != *b,
3655 Value::Number(n) => ns.iter().any(|&id| {
3656 idx.string_value(id).trim().parse::<f64>().ok() != Some(n.as_f64())
3657 }),
3658 Value::String(s) => ns.iter().any(|&id| idx.string_value(id) != *s),
3659 Value::Typed(t) => {
3663 if let Some(n) = t.numeric {
3664 ns.iter().any(|&id| idx.string_value(id).trim().parse::<f64>().ok() != Some(n))
3665 } else if let Some(b) = t.boolean {
3666 !ns.is_empty() != b
3667 } else {
3668 ns.iter().any(|&id| idx.string_value(id) != t.lexical)
3669 }
3670 }
3671 Value::Sequence(items) => items.iter().any(|v| {
3672 values_ne(&Value::NodeSet(ns.clone()), v, idx, bindings)
3673 }),
3674 Value::NodeSet(_) | Value::ForeignNodeSet(_) | Value::IntRange { .. }
3675 | Value::Map(_) | Value::Array(_) | Value::Function(_) => unreachable!(),
3676 }
3677 }
3678 (Value::ForeignNodeSet(fs), other) | (other, Value::ForeignNodeSet(fs)) => {
3679 match other {
3680 Value::Boolean(b) => !fs.is_empty() != *b,
3681 Value::Number(n) => fs.iter().any(|&p| {
3682 bindings.foreign_string_value(p).trim().parse::<f64>().ok() != Some(n.as_f64())
3683 }),
3684 Value::String(s) => fs.iter().any(|&p| bindings.foreign_string_value(p) != *s),
3685 Value::Typed(t) => {
3686 if let Some(n) = t.numeric {
3687 fs.iter().any(|&p| bindings.foreign_string_value(p)
3688 .trim().parse::<f64>().ok() != Some(n))
3689 } else if let Some(b) = t.boolean {
3690 !fs.is_empty() != b
3691 } else {
3692 fs.iter().any(|&p| bindings.foreign_string_value(p) != t.lexical)
3693 }
3694 }
3695 Value::Sequence(items) => items.iter().any(|v| {
3696 values_ne(&Value::ForeignNodeSet(fs.clone()), v, idx, bindings)
3697 }),
3698 Value::NodeSet(_) | Value::ForeignNodeSet(_) | Value::IntRange { .. }
3699 | Value::Map(_) | Value::Array(_) | Value::Function(_) => unreachable!(),
3700 }
3701 }
3702 (Value::Sequence(items), other) | (other, Value::Sequence(items)) => {
3705 items.iter().any(|v| values_ne(v, other, idx, bindings))
3706 }
3707 _ => !values_eq(l, r, idx, bindings),
3709 }
3710}
3711
3712fn values_eq<I: DocIndexLike>(
3713 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings,
3714) -> bool {
3715 if let Some(lv) = intrange_to_sequence(l) {
3716 return values_eq(&lv, r, idx, bindings);
3717 }
3718 if let Some(rv) = intrange_to_sequence(r) {
3719 return values_eq(l, &rv, idx, bindings);
3720 }
3721 let ci = is_ascii_ci_collation(
3727 DEFAULT_COLLATION.with(|c| c.borrow().clone()).as_deref());
3728 let str_eq = |a: &str, b: &str| if ci {
3729 ascii_ci_fold(a) == ascii_ci_fold(b)
3730 } else { a == b };
3731 match (l, r) {
3732 (Value::Map(_) | Value::Array(_) | Value::Function(_), _) | (_, Value::Map(_) | Value::Array(_) | Value::Function(_)) => false,
3735 (Value::NodeSet(ls), Value::NodeSet(rs)) => {
3736 let (small, large) = if ls.len() <= rs.len() { (ls, rs) } else { (rs, ls) };
3743 let mut set: HashSet<String> = HashSet::with_capacity(small.len());
3744 for &a in small {
3745 set.insert(idx.string_value(a));
3746 }
3747 for &b in large {
3748 if set.contains(&idx.string_value(b)) {
3749 return true;
3750 }
3751 }
3752 false
3753 }
3754 (Value::ForeignNodeSet(ls), Value::ForeignNodeSet(rs)) => {
3755 let (small, large) = if ls.len() <= rs.len() { (ls, rs) } else { (rs, ls) };
3756 let mut set: HashSet<String> = HashSet::with_capacity(small.len());
3757 for &a in small {
3758 set.insert(bindings.foreign_string_value(a));
3759 }
3760 for &b in large {
3761 if set.contains(&bindings.foreign_string_value(b)) {
3762 return true;
3763 }
3764 }
3765 false
3766 }
3767 (Value::NodeSet(ns), Value::ForeignNodeSet(fs))
3771 | (Value::ForeignNodeSet(fs), Value::NodeSet(ns)) => {
3772 let mut set: HashSet<String> = HashSet::with_capacity(ns.len());
3775 for &id in ns {
3776 set.insert(idx.string_value(id));
3777 }
3778 for &p in fs {
3779 if set.contains(&bindings.foreign_string_value(p)) {
3780 return true;
3781 }
3782 }
3783 false
3784 }
3785 (Value::NodeSet(ns), other) | (other, Value::NodeSet(ns)) => {
3786 match other {
3787 Value::Boolean(b) => value_to_bool(&Value::NodeSet(ns.clone()), idx) == *b,
3788 Value::Number(n) => ns.iter().any(|&id| {
3789 idx.string_value(id).trim().parse::<f64>().ok() == Some(n.as_f64())
3790 }),
3791 Value::String(s) => ns.iter().any(|&id| idx.string_value(id) == *s),
3792 Value::Typed(t) => {
3793 if let Some(n) = t.numeric {
3794 ns.iter().any(|&id| idx.string_value(id).trim().parse::<f64>().ok() == Some(n))
3795 } else if let Some(b) = t.boolean {
3796 !ns.is_empty() == b
3797 } else {
3798 ns.iter().any(|&id| idx.string_value(id) == t.lexical)
3799 }
3800 }
3801 Value::Sequence(items) => items.iter().any(|v| {
3802 values_eq(&Value::NodeSet(ns.clone()), v, idx, bindings)
3803 }),
3804 Value::NodeSet(_) | Value::ForeignNodeSet(_) | Value::IntRange { .. }
3805 | Value::Map(_) | Value::Array(_) | Value::Function(_) => unreachable!(),
3806 }
3807 }
3808 (Value::ForeignNodeSet(fs), other) | (other, Value::ForeignNodeSet(fs)) => {
3809 match other {
3810 Value::Boolean(b) => !fs.is_empty() == *b,
3811 Value::Number(n) => fs.iter().any(|&p| {
3812 bindings.foreign_string_value(p).trim().parse::<f64>().ok() == Some(n.as_f64())
3813 }),
3814 Value::String(s) => fs.iter().any(|&p| bindings.foreign_string_value(p) == *s),
3815 Value::Typed(t) => {
3816 if let Some(n) = t.numeric {
3817 fs.iter().any(|&p| bindings.foreign_string_value(p)
3818 .trim().parse::<f64>().ok() == Some(n))
3819 } else if let Some(b) = t.boolean {
3820 !fs.is_empty() == b
3821 } else {
3822 fs.iter().any(|&p| bindings.foreign_string_value(p) == t.lexical)
3823 }
3824 }
3825 Value::Sequence(items) => items.iter().any(|v| {
3826 values_eq(&Value::ForeignNodeSet(fs.clone()), v, idx, bindings)
3827 }),
3828 Value::NodeSet(_) | Value::ForeignNodeSet(_) | Value::IntRange { .. }
3829 | Value::Map(_) | Value::Array(_) | Value::Function(_) => unreachable!(),
3830 }
3831 }
3832 (Value::Sequence(items), other) | (other, Value::Sequence(items)) => {
3835 items.iter().any(|v| values_eq(v, other, idx, bindings))
3836 }
3837 (Value::Boolean(a), b) => *a == value_to_bool(b, idx),
3838 (a, Value::Boolean(b)) => value_to_bool(a, idx) == *b,
3839 (Value::Number(a), Value::Number(b)) => a.as_f64() == b.as_f64(),
3844 (Value::Number(a), Value::String(b)) => a.as_f64() == b.trim().parse::<f64>().unwrap_or(f64::NAN),
3845 (Value::String(a), Value::Number(b)) => a.trim().parse::<f64>().unwrap_or(f64::NAN) == b.as_f64(),
3846 (Value::String(a), Value::String(b)) => str_eq(a, b),
3847 (Value::Typed(t), Value::Typed(u)) => {
3852 match (t.numeric, u.numeric) {
3853 (Some(a), Some(b)) => a == b,
3854 _ => {
3855 let date_eq = matches!(t.kind, "date" | "dateTime" | "time")
3862 && matches!(u.kind, "date" | "dateTime" | "time")
3863 && t.kind == u.kind;
3864 if date_eq {
3865 if let (Some(a), Some(b)) = (
3866 dt_to_utc_seconds(&t.lexical, t.kind),
3867 dt_to_utc_seconds(&u.lexical, u.kind),
3868 ) {
3869 return a == b;
3870 }
3871 }
3872 if t.kind == "dayTimeDuration" && u.kind == "dayTimeDuration" {
3876 if let (Some(a), Some(b)) = (
3877 parse_day_time_duration_secs(&t.lexical),
3878 parse_day_time_duration_secs(&u.lexical),
3879 ) {
3880 return a == b;
3881 }
3882 }
3883 if matches!((t.kind, u.kind),
3888 ("duration", "duration")
3889 | ("duration", "dayTimeDuration") | ("dayTimeDuration", "duration")
3890 | ("duration", "yearMonthDuration") | ("yearMonthDuration", "duration"))
3891 {
3892 if let (Some(a), Some(b)) = (
3893 parse_duration_split(&t.lexical),
3894 parse_duration_split(&u.lexical),
3895 ) {
3896 return a == b;
3897 }
3898 }
3899 str_eq(&t.lexical, &u.lexical)
3900 }
3901 }
3902 }
3903 (Value::Typed(t), Value::Number(n)) | (Value::Number(n), Value::Typed(t)) => {
3904 t.numeric.map(|a| a == n.as_f64())
3905 .unwrap_or_else(|| t.lexical.trim().parse::<f64>().ok() == Some(n.as_f64()))
3906 }
3907 (Value::Typed(t), Value::String(s)) | (Value::String(s), Value::Typed(t)) => {
3908 str_eq(&t.lexical, s)
3909 }
3910 (Value::IntRange { .. }, _) | (_, Value::IntRange { .. }) =>
3912 unreachable!("IntRange normalised at values_eq entry"),
3913 }
3914}
3915
3916pub fn value_equality_key(v: &Value) -> Option<String> {
3925 let t = match v { Value::Typed(t) => t, _ => return None };
3926 match t.kind {
3927 "date" | "dateTime" | "time" =>
3928 dt_to_utc_seconds(&t.lexical, t.kind).map(|s| format!("{}#{s}", t.kind)),
3929 "dayTimeDuration" =>
3930 parse_day_time_duration_secs(&t.lexical).map(|s| format!("dtd#{s}")),
3931 "yearMonthDuration" =>
3932 parse_year_month_duration_months(&t.lexical).map(|m| format!("ymd#{m}")),
3933 _ => None,
3934 }
3935}
3936
3937fn dt_to_utc_seconds(s: &str, kind: &str) -> Option<i64> {
3942 let dk = match kind {
3943 "date" => DateKind::Date,
3944 "dateTime" => DateKind::DateTime,
3945 "time" => DateKind::Time,
3946 _ => return None,
3947 };
3948 let (y, mo, d, h, mi, sec, _frac, tz) = parse_xsd_date_time(s, dk)?;
3949 let (yy, mm, dd) = if matches!(dk, DateKind::Time) {
3952 (1970, 1, 1)
3953 } else { (y, mo, d) };
3954 let days = ymd_to_days(yy, mm as u32, dd as u32);
3955 let secs_in_day = (h as i64) * 3600 + (mi as i64) * 60 + sec as i64;
3956 let local_total = days * 86_400 + secs_in_day;
3957 let tz_offset_secs = tz.map(|m| m as i64 * 60).unwrap_or(0);
3960 Some(local_total - tz_offset_secs)
3961}
3962
3963fn is_date_like_kind(k: &str) -> bool {
3968 matches!(k, "date" | "dateTime" | "time"
3969 | "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay")
3970}
3971
3972fn is_duration_kind(k: &str) -> bool {
3974 matches!(k, "duration" | "dayTimeDuration" | "yearMonthDuration")
3975}
3976
3977fn parse_day_time_duration_secs(s: &str) -> Option<i64> {
3982 let s = s.trim();
3983 let (sign, body) = if let Some(rest) = s.strip_prefix('-') {
3984 (-1i64, rest)
3985 } else { (1i64, s) };
3986 let body = body.strip_prefix('P')?;
3987 let (day_part, time_part) = match body.find('T') {
3988 Some(i) => (&body[..i], &body[i + 1..]),
3989 None => (body, ""),
3990 };
3991 let parse_comp = |part: &str, marker: char| -> Option<i64> {
3992 let i = match part.find(marker) { Some(i) => i, None => return Some(0) };
3993 let start = part[..i].rfind(|c: char| !c.is_ascii_digit() && c != '.')
3994 .map(|n| n + 1).unwrap_or(0);
3995 Some(part[start..i].parse::<i64>().unwrap_or(0))
3996 };
3997 let days = parse_comp(day_part, 'D')?;
3998 let hours = parse_comp(time_part, 'H')?;
3999 let mins = parse_comp(time_part, 'M')?;
4000 let secs = parse_comp(time_part, 'S')?;
4001 Some(sign * (days * 86_400 + hours * 3600 + mins * 60 + secs))
4002}
4003
4004fn duration_seq_total(items: &[Value]) -> Option<(&'static str, i64, i64)> {
4010 let kind = match items.first()? {
4011 Value::Typed(t) if matches!(t.kind, "dayTimeDuration" | "yearMonthDuration") => t.kind,
4012 _ => return None,
4013 };
4014 let mut total: i64 = 0;
4015 for v in items {
4016 let t = match v {
4017 Value::Typed(t) if t.kind == kind => t,
4018 _ => return None,
4019 };
4020 total += if kind == "dayTimeDuration" {
4021 parse_day_time_duration_secs(&t.lexical)?
4022 } else {
4023 parse_year_month_duration_months(&t.lexical)?
4024 };
4025 }
4026 Some((kind, total, items.len() as i64))
4027}
4028
4029fn duration_value(kind: &'static str, units: i64) -> Value {
4032 let lexical = if kind == "dayTimeDuration" {
4033 format_day_time_duration_secs(units)
4034 } else {
4035 format_year_month_duration_months(units)
4036 };
4037 Value::Typed(Box::new(TypedAtomic { kind, lexical, numeric: None, boolean: None, user_type: None }))
4038}
4039
4040fn format_day_time_duration_secs(mut total: i64) -> String {
4043 let mut out = String::with_capacity(16);
4044 if total < 0 { out.push('-'); total = -total; }
4045 out.push('P');
4046 let days = total / 86_400;
4047 let rem = total % 86_400;
4048 if days > 0 { out.push_str(&days.to_string()); out.push('D'); }
4049 if rem > 0 || days == 0 {
4050 out.push('T');
4051 let h = rem / 3600;
4052 let m = (rem % 3600) / 60;
4053 let s = rem % 60;
4054 if h > 0 { out.push_str(&h.to_string()); out.push('H'); }
4055 if m > 0 { out.push_str(&m.to_string()); out.push('M'); }
4056 if s > 0 || (h == 0 && m == 0) { out.push_str(&s.to_string()); out.push('S'); }
4057 }
4058 out
4059}
4060
4061fn parse_xsd_date_only(s: &str) -> Option<(i32, u32, u32, Option<i16>)> {
4064 let s = s.trim();
4065 let (sign, body) = if let Some(rest) = s.strip_prefix('-') {
4066 (-1i32, rest)
4067 } else { (1i32, s) };
4068 let parts: Vec<&str> = body.splitn(3, '-').collect();
4069 if parts.len() != 3 { return None; }
4070 let y: i32 = parts[0].parse().ok()?;
4071 let m: u32 = parts[1].parse().ok()?;
4072 let day_tail = parts[2];
4074 let (d_str, tz_tail) = if day_tail.len() >= 2 {
4075 (&day_tail[..2], &day_tail[2..])
4076 } else { return None; };
4077 let d: u32 = d_str.parse().ok()?;
4078 let signed_year = sign * y;
4079 if !(1..=12).contains(&m) { return None; }
4085 let max_day = days_in_month(signed_year, m);
4086 if !(1..=max_day).contains(&d) { return None; }
4087 let tz = parse_tz_suffix(tz_tail);
4088 Some((signed_year, m, d, tz))
4089}
4090
4091fn ymd_to_days(y: i32, m: u32, d: u32) -> i64 {
4096 let y = if m <= 2 { y - 1 } else { y } as i64;
4098 let era = if y >= 0 { y } else { y - 399 } / 400;
4099 let yoe = (y - era * 400) as u64;
4100 let m = m as u64;
4101 let d = d as u64;
4102 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
4103 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
4104 era * 146097 + doe as i64 - 719468
4105}
4106
4107fn coerce_to_double(v: &Value) -> Option<f64> {
4117 match v {
4118 Value::Number(n) => Some(n.as_f64()),
4119 Value::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
4120 Value::Typed(t) => t.numeric.or_else(|| t.lexical.trim().parse().ok()),
4121 Value::String(s) => s.trim().parse().ok(),
4122 Value::NodeSet(ns) if ns.len() == 1 => {
4126 let _ = ns;
4131 None
4132 }
4133 _ => None,
4134 }
4135}
4136
4137fn parse_duration_split(s: &str) -> Option<(i64, i64)> {
4145 let s = s.trim();
4146 let (sign, body) = if let Some(rest) = s.strip_prefix('-') {
4147 (-1i64, rest)
4148 } else { (1i64, s) };
4149 let body = body.strip_prefix('P')?;
4150 let (date_part, time_part) = match body.find('T') {
4151 Some(i) => (&body[..i], &body[i + 1..]),
4152 None => (body, ""),
4153 };
4154 let mut years: i64 = 0;
4155 let mut months: i64 = 0;
4156 let mut days: i64 = 0;
4157 let mut cur = String::new();
4158 for c in date_part.chars() {
4159 if c.is_ascii_digit() { cur.push(c); }
4160 else {
4161 let n: i64 = cur.parse().ok()?;
4162 cur.clear();
4163 match c {
4164 'Y' => years = n,
4165 'M' => months = n,
4166 'D' => days = n,
4167 _ => return None,
4168 }
4169 }
4170 }
4171 if !cur.is_empty() { return None; }
4172 let mut hours: i64 = 0;
4173 let mut mins: i64 = 0;
4174 let mut secs: i64 = 0;
4175 cur.clear();
4176 let mut frac: f64 = 0.0;
4177 let mut in_frac = false;
4178 let mut frac_str = String::new();
4179 for c in time_part.chars() {
4180 if c.is_ascii_digit() {
4181 if in_frac { frac_str.push(c); }
4182 else { cur.push(c); }
4183 } else if c == '.' {
4184 in_frac = true;
4185 } else {
4186 let n: i64 = cur.parse().ok()?;
4187 cur.clear();
4188 if !frac_str.is_empty() {
4189 let denom = 10f64.powi(frac_str.len() as i32);
4190 frac = frac_str.parse::<f64>().unwrap_or(0.0) / denom;
4191 frac_str.clear();
4192 }
4193 in_frac = false;
4194 match c {
4195 'H' => hours = n,
4196 'M' => mins = n,
4197 'S' => secs = n + frac.round() as i64,
4198 _ => return None,
4199 }
4200 }
4201 }
4202 if !cur.is_empty() { return None; }
4203 Some((sign * (years * 12 + months),
4204 sign * (days * 86_400 + hours * 3600 + mins * 60 + secs)))
4205}
4206
4207fn parse_year_month_duration_months(s: &str) -> Option<i64> {
4208 let (neg, rest) = match s.strip_prefix('-') {
4209 Some(r) => (true, r),
4210 None => (false, s),
4211 };
4212 let rest = rest.strip_prefix('P')?;
4213 let mut years: i64 = 0;
4214 let mut months: i64 = 0;
4215 let mut cur = String::new();
4216 let mut any_field = false;
4217 for c in rest.chars() {
4218 if c.is_ascii_digit() {
4219 cur.push(c);
4220 } else if c == 'Y' {
4221 years = cur.parse().ok()?;
4222 cur.clear();
4223 any_field = true;
4224 } else if c == 'M' {
4225 months = cur.parse().ok()?;
4226 cur.clear();
4227 any_field = true;
4228 } else {
4229 return None;
4232 }
4233 }
4234 if !any_field || !cur.is_empty() { return None; }
4235 let total = years * 12 + months;
4236 Some(if neg { -total } else { total })
4237}
4238
4239fn format_year_month_duration_months(months: i64) -> String {
4243 if months == 0 { return "P0M".into(); }
4244 let neg = months < 0;
4245 let abs = months.unsigned_abs() as u64;
4246 let years = abs / 12;
4247 let months = abs % 12;
4248 let mut out = String::new();
4249 if neg { out.push('-'); }
4250 out.push('P');
4251 if years > 0 { out.push_str(&format!("{years}Y")); }
4252 if months > 0 { out.push_str(&format!("{months}M")); }
4253 if out.ends_with('P') {
4254 out.push_str("0M");
4257 }
4258 out
4259}
4260
4261fn round_months_half_up(x: f64) -> i64 {
4272 (x + 0.5).floor() as i64
4273}
4274
4275fn duration_mul<I: DocIndexLike>(
4276 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings,
4277) -> Option<Value> {
4278 let (dur, num) = match (l, r) {
4279 (Value::Typed(d), other) if is_duration_kind(d.kind) => (d, other),
4280 (other, Value::Typed(d)) if is_duration_kind(d.kind) => (d, other),
4281 _ => return None,
4282 };
4283 let factor = coerce_to_double(num)
4284 .unwrap_or_else(|| value_to_number_with(num, idx, bindings));
4285 if !factor.is_finite() { return None; }
4286 if dur.kind == "yearMonthDuration" {
4287 let months = parse_year_month_duration_months(&dur.lexical)?;
4288 let scaled = round_months_half_up(months as f64 * factor);
4289 return Some(Value::Typed(Box::new(TypedAtomic {
4290 kind: "yearMonthDuration",
4291 lexical: format_year_month_duration_months(scaled),
4292 numeric: None, boolean: None, user_type: None,
4293 })));
4294 }
4295 let us = parse_day_time_duration_micros(&dur.lexical)?;
4298 let scaled = (us as f64 * factor).round() as i64;
4299 Some(Value::Typed(Box::new(TypedAtomic {
4300 kind: "dayTimeDuration",
4301 lexical: canonical_day_time_duration_lex(&format_day_time_duration_micros(scaled)),
4302 numeric: None, boolean: None, user_type: None,
4303 })))
4304}
4305
4306fn duration_div<I: DocIndexLike>(
4310 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings,
4311) -> Option<Value> {
4312 match (l, r) {
4313 (Value::Typed(a), Value::Typed(b))
4314 if is_duration_kind(a.kind) && is_duration_kind(b.kind) =>
4315 {
4316 let (av, bv): (f64, f64) = if a.kind == "yearMonthDuration" {
4318 let am = parse_year_month_duration_months(&a.lexical)?;
4319 let bm = parse_year_month_duration_months(&b.lexical)?;
4320 (am as f64, bm as f64)
4321 } else {
4322 let av = parse_day_time_duration_micros(&a.lexical)?;
4323 let bv = parse_day_time_duration_micros(&b.lexical)?;
4324 (av as f64, bv as f64)
4325 };
4326 if bv == 0.0 { return None; }
4327 Some(Value::Number(Numeric::Double(av / bv)))
4328 }
4329 (Value::Typed(d), other) if is_duration_kind(d.kind) => {
4330 let factor = coerce_to_double(other)
4331 .unwrap_or_else(|| value_to_number_with(other, idx, bindings));
4332 if !factor.is_finite() || factor == 0.0 { return None; }
4333 if d.kind == "yearMonthDuration" {
4334 let months = parse_year_month_duration_months(&d.lexical)?;
4335 let scaled = round_months_half_up(months as f64 / factor);
4336 return Some(Value::Typed(Box::new(TypedAtomic {
4337 kind: "yearMonthDuration",
4338 lexical: format_year_month_duration_months(scaled),
4339 numeric: None, boolean: None, user_type: None,
4340 })));
4341 }
4342 let us = parse_day_time_duration_micros(&d.lexical)?;
4343 let scaled = (us as f64 / factor).round() as i64;
4344 Some(Value::Typed(Box::new(TypedAtomic {
4345 kind: "dayTimeDuration",
4346 lexical: canonical_day_time_duration_lex(&format_day_time_duration_micros(scaled)),
4347 numeric: None, boolean: None, user_type: None,
4348 })))
4349 }
4350 _ => None,
4351 }
4352}
4353
4354fn add_months_to_ymd(y: i32, m: u32, d: u32, months: i64) -> (i32, u32, u32) {
4359 let total_months = (y as i64) * 12 + (m as i64) - 1 + months;
4360 let ny = total_months.div_euclid(12) as i32;
4361 let nm = total_months.rem_euclid(12) as u32 + 1;
4362 let last = days_in_month(ny, nm);
4363 let nd = d.min(last);
4364 (ny, nm, nd)
4365}
4366
4367fn days_in_month(y: i32, m: u32) -> u32 {
4368 match m {
4369 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4370 4 | 6 | 9 | 11 => 30,
4371 2 => if is_leap_year(y) { 29 } else { 28 },
4372 _ => 0,
4373 }
4374}
4375
4376fn is_leap_year(y: i32) -> bool {
4377 (y % 4 == 0 && y % 100 != 0) || y % 400 == 0
4378}
4379
4380fn add_one_day(year: i32, month: u8, day: u8) -> (i32, u8, u8) {
4384 if (day as u32) < days_in_month(year, month as u32) {
4385 (year, month, day + 1)
4386 } else if month < 12 {
4387 (year, month + 1, 1)
4388 } else {
4389 (year + 1, 1, 1)
4390 }
4391}
4392
4393fn duration_combine(a: &TypedAtomic, b: &TypedAtomic, subtract: bool) -> Option<Value> {
4400 let mk = |kind: &'static str, lexical: String| {
4401 Value::Typed(Box::new(TypedAtomic { kind, lexical, numeric: None, boolean: None, user_type: None }))
4402 };
4403 match (a.kind, b.kind) {
4404 ("yearMonthDuration", "yearMonthDuration") => {
4405 let lm = parse_year_month_duration_months(&a.lexical)?;
4406 let rm = parse_year_month_duration_months(&b.lexical)?;
4407 let m = if subtract { lm.checked_sub(rm) } else { lm.checked_add(rm) }?;
4410 Some(mk("yearMonthDuration", format_year_month_duration_months(m)))
4411 }
4412 ("dayTimeDuration", "dayTimeDuration") => {
4413 let lu = parse_day_time_duration_micros(&a.lexical)?;
4414 let ru = parse_day_time_duration_micros(&b.lexical)?;
4415 let u = if subtract { lu.checked_sub(ru) } else { lu.checked_add(ru) }?;
4416 let lex = canonical_day_time_duration_lex(
4417 &format_day_time_duration_micros(i64::try_from(u).ok()?));
4418 Some(mk("dayTimeDuration", lex))
4419 }
4420 _ => None,
4421 }
4422}
4423
4424fn date_arith_add(l: &Value, r: &Value) -> Option<Value> {
4425 let (date, dur, date_kind) = match (l, r) {
4426 (Value::Typed(a), Value::Typed(b))
4427 if is_date_like_kind(a.kind) && is_duration_kind(b.kind)
4428 => (a, b, a.kind),
4429 (Value::Typed(a), Value::Typed(b))
4430 if is_duration_kind(a.kind) && is_date_like_kind(b.kind)
4431 => (b, a, b.kind),
4432 (Value::Typed(a), Value::Typed(b))
4434 if is_duration_kind(a.kind) && is_duration_kind(b.kind) =>
4435 {
4436 return duration_combine(a, b, false);
4437 }
4438 _ => return None,
4439 };
4440 match date_kind {
4441 "date" => {
4442 let (y, m, d, _tz) = parse_xsd_date_only(&date.lexical)?;
4443 if dur.kind == "yearMonthDuration" {
4446 let months = parse_year_month_duration_months(&dur.lexical)?;
4447 let (ny, nm, nd) = add_months_to_ymd(y, m, d, months);
4448 let lex = format!("{:04}-{:02}-{:02}", ny, nm, nd);
4449 return Some(Value::Typed(Box::new(TypedAtomic {
4450 kind: "date", lexical: lex, numeric: None, boolean: None, user_type: None,
4451 })));
4452 }
4453 let sec = parse_day_time_duration_secs(&dur.lexical)?;
4454 let day_delta = sec / 86_400;
4456 let new_days = ymd_to_days(y, m, d) + day_delta;
4457 let (ny, nm, nd) = days_to_ymd(new_days);
4458 let lex = format!("{:04}-{:02}-{:02}", ny, nm, nd);
4459 Some(Value::Typed(Box::new(TypedAtomic {
4460 kind: "date", lexical: lex, numeric: None, boolean: None, user_type: None,
4461 })))
4462 }
4463 "time" => {
4464 if dur.kind == "yearMonthDuration" { return None; }
4471 let dur_us = parse_day_time_duration_micros(&dur.lexical)?;
4472 let (h, m, s, time_frac, tz) = parse_xsd_time(&date.lexical)?;
4473 let day_us = ((h as i128) * 3600 + (m as i128) * 60 + s as i128)
4474 * 1_000_000 + time_frac as i128;
4475 let total_us = (day_us + dur_us).rem_euclid(86_400i128 * 1_000_000);
4476 let total = (total_us / 1_000_000) as i64;
4477 let frac = (total_us % 1_000_000) as u32;
4478 let nh = (total / 3600) as u8;
4479 let nm = ((total / 60) % 60) as u8;
4480 let ns = (total % 60) as u8;
4481 let lex = if frac == 0 {
4482 let mut l = format!("{:02}:{:02}:{:02}", nh, nm, ns);
4483 if let Some(tz_m) = tz { l.push_str(&format_tz_suffix(tz_m)); }
4484 l
4485 } else {
4486 let mut l = format!("{:02}:{:02}:{:02}.{:06}", nh, nm, ns, frac);
4487 while l.ends_with('0') { l.pop(); }
4488 if let Some(tz_m) = tz { l.push_str(&format_tz_suffix(tz_m)); }
4489 l
4490 };
4491 Some(Value::Typed(Box::new(TypedAtomic {
4492 kind: "time", lexical: lex, numeric: None, boolean: None, user_type: None,
4493 })))
4494 }
4495 "dateTime" => {
4496 if dur.kind == "yearMonthDuration" {
4500 let months = parse_year_month_duration_months(&dur.lexical)?;
4501 let (y, mo, d, h, mi, s, frac, tz) =
4502 parse_xsd_date_time(&date.lexical, DateKind::DateTime)?;
4503 let (ny, nm, nd) = add_months_to_ymd(y, mo as u32, d as u32, months);
4504 let lex = format_datetime_lexical(ny, nm as u8, nd as u8, h, mi, s, frac, tz);
4505 return Some(Value::Typed(Box::new(TypedAtomic {
4506 kind: "dateTime", lexical: lex, numeric: None, boolean: None, user_type: None,
4507 })));
4508 }
4509 let dur_us = parse_day_time_duration_micros(&dur.lexical)?;
4515 let (y, mo, d, h, mi, s, frac, tz) =
4516 parse_xsd_date_time(&date.lexical, DateKind::DateTime)?;
4517 let day_us = ((h as i128) * 3600 + (mi as i128) * 60 + s as i128)
4518 * 1_000_000 + (frac as i128);
4519 let total = day_us + dur_us;
4520 let us_per_day = 86_400i128 * 1_000_000;
4521 let day_delta = total.div_euclid(us_per_day);
4522 let remain = total.rem_euclid(us_per_day);
4523 let new_days = ymd_to_days(y, mo as u32, d as u32) + day_delta as i64;
4524 let (ny, nm, nd) = days_to_ymd(new_days);
4525 let remain_secs = (remain / 1_000_000) as i64;
4526 let new_frac = (remain % 1_000_000) as u32;
4527 let nh = (remain_secs / 3600) as u8;
4528 let nmi = ((remain_secs / 60) % 60) as u8;
4529 let ns = (remain_secs % 60) as u8;
4530 let lex = format_datetime_lexical(ny, nm as u8, nd as u8, nh, nmi, ns, new_frac, tz);
4531 Some(Value::Typed(Box::new(TypedAtomic {
4532 kind: "dateTime", lexical: lex, numeric: None, boolean: None, user_type: None,
4533 })))
4534 }
4535 _ => None,
4536 }
4537}
4538
4539fn format_datetime_lexical(
4544 y: i32, mo: u8, d: u8, h: u8, mi: u8, s: u8, frac_us: u32, tz: Option<i16>,
4545) -> String {
4546 let mut out = format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
4547 y, mo, d, h, mi, s);
4548 if frac_us != 0 {
4549 let mut frac = format!(".{:06}", frac_us);
4550 while frac.ends_with('0') { frac.pop(); }
4551 out.push_str(&frac);
4552 }
4553 if let Some(tz) = tz {
4554 out.push_str(&format_tz_suffix(tz));
4555 }
4556 out
4557}
4558
4559fn format_tz_suffix(minutes: i16) -> String {
4562 if minutes == 0 { return "Z".into(); }
4563 let sign = if minutes < 0 { '-' } else { '+' };
4564 let abs = minutes.unsigned_abs() as i32;
4565 format!("{sign}{:02}:{:02}", abs / 60, abs % 60)
4566}
4567
4568fn date_arith_sub(l: &Value, r: &Value) -> Option<Value> {
4571 match (l, r) {
4572 (Value::Typed(a), Value::Typed(b))
4574 if a.kind == "date" && b.kind == "date" =>
4575 {
4576 let (ay, am, ad, _) = parse_xsd_date_only(&a.lexical)?;
4577 let (by, bm, bd, _) = parse_xsd_date_only(&b.lexical)?;
4578 let diff_days = ymd_to_days(ay, am, ad) - ymd_to_days(by, bm, bd);
4579 let lex = format_day_time_duration_secs(diff_days * 86_400);
4580 Some(Value::Typed(Box::new(TypedAtomic {
4581 kind: "dayTimeDuration", lexical: lex,
4582 numeric: None, boolean: None, user_type: None,
4583 })))
4584 }
4585 (Value::Typed(a), Value::Typed(b))
4587 if a.kind == "date" && is_duration_kind(b.kind) =>
4588 {
4589 let (y, m, d, _) = parse_xsd_date_only(&a.lexical)?;
4590 let sec = parse_day_time_duration_secs(&b.lexical)?;
4591 let day_delta = sec / 86_400;
4592 let new_days = ymd_to_days(y, m, d) - day_delta;
4593 let (ny, nm, nd) = days_to_ymd(new_days);
4594 let lex = format!("{:04}-{:02}-{:02}", ny, nm, nd);
4595 Some(Value::Typed(Box::new(TypedAtomic {
4596 kind: "date", lexical: lex, numeric: None, boolean: None, user_type: None,
4597 })))
4598 }
4599 (Value::Typed(a), Value::Typed(b))
4603 if matches!(a.kind, "dateTime" | "time") && is_duration_kind(b.kind) =>
4604 {
4605 let negated = TypedAtomic {
4606 kind: b.kind,
4607 lexical: negate_duration_lex(&b.lexical),
4608 numeric: None,
4609 boolean: None, user_type: None,
4610 };
4611 return date_arith_add(l, &Value::Typed(Box::new(negated)));
4612 }
4613 (Value::Typed(a), Value::Typed(b))
4617 if matches!(a.kind, "dateTime" | "time")
4618 && matches!(b.kind, "dateTime" | "time") && a.kind == b.kind =>
4619 {
4620 let dk = if a.kind == "dateTime" { DateKind::DateTime } else { DateKind::Time };
4623 let a_us = date_value_to_utc_micros(&a.lexical, dk)?;
4624 let b_us = date_value_to_utc_micros(&b.lexical, dk)?;
4625 let diff_us = (a_us - b_us) as i64;
4626 let lex = canonical_day_time_duration_lex(
4627 &format_day_time_duration_micros(diff_us)
4628 );
4629 Some(Value::Typed(Box::new(TypedAtomic {
4630 kind: "dayTimeDuration", lexical: lex,
4631 numeric: None, boolean: None, user_type: None,
4632 })))
4633 }
4634 (Value::Typed(a), Value::Typed(b))
4636 if is_duration_kind(a.kind) && is_duration_kind(b.kind) =>
4637 {
4638 duration_combine(a, b, true)
4639 }
4640 _ => None,
4641 }
4642}
4643
4644#[derive(Clone, Copy)]
4648#[allow(dead_code)]
4649enum NumericOp { Add, Sub, Mul, Div, Mod, IDiv }
4650
4651fn numeric_promote_kind(a: Option<&str>, b: Option<&str>) -> Option<&'static str> {
4657 fn rank(k: &str) -> Option<u8> {
4658 Some(match k {
4659 "integer" | "long" | "int" | "short" | "byte"
4660 | "unsignedLong" | "unsignedInt" | "unsignedShort" | "unsignedByte"
4661 | "nonNegativeInteger" | "nonPositiveInteger"
4662 | "positiveInteger" | "negativeInteger" => 0, "decimal" => 1,
4664 "float" => 2,
4665 "double" => 3,
4666 _ => return None,
4667 })
4668 }
4669 let ra = a.and_then(rank);
4670 let rb = b.and_then(rank);
4671 let r = ra.max(rb)?;
4672 Some(match r {
4673 0 => "integer",
4674 1 => "decimal",
4675 2 => "float",
4676 _ => "double",
4677 })
4678}
4679
4680fn integer_result(n: i64, bindings: &dyn XPathBindings) -> Value {
4687 if bindings.xpath_version_2_or_later() {
4688 Value::Number(Numeric::Integer(n))
4689 } else {
4690 Value::Number(Numeric::Double(n as f64))
4691 }
4692}
4693
4694fn numeric_kind_of(v: &Value) -> Option<&'static str> {
4698 match v {
4699 Value::Number(n) => Some(n.kind()),
4700 Value::Typed(t) if t.kind == "untypedAtomic" => Some("double"),
4705 Value::Typed(t) if t.numeric.is_some() => Some(t.kind),
4706 Value::IntRange { .. } => Some("integer"),
4707 _ => None,
4708 }
4709}
4710
4711fn integer_decimal_zero_divisor<I: DocIndexLike>(
4717 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings,
4718) -> bool {
4719 if in_xpath_1_0_compat() { return false; }
4720 if value_to_number_with(r, idx, bindings) != 0.0 { return false; }
4721 let is_int_dec = |k| matches!(k, Some("integer") | Some("decimal"));
4722 is_int_dec(numeric_kind_of(l)) && is_int_dec(numeric_kind_of(r))
4723}
4724
4725fn value_atomizes_empty(v: &Value) -> bool {
4736 match v {
4737 Value::NodeSet(n) => n.is_empty(),
4738 Value::ForeignNodeSet(n) => n.is_empty(),
4739 Value::Sequence(s) => s.is_empty(),
4740 _ => false,
4741 }
4742}
4743
4744fn arith_empty_2_0(l: &Value, r: &Value, ctx: &EvalCtx) -> bool {
4748 ctx.static_ctx.xpath_2_0
4749 && !in_xpath_1_0_compat()
4750 && (value_atomizes_empty(l) || value_atomizes_empty(r))
4751}
4752
4753fn compat_numeric_op<I: DocIndexLike>(
4754 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings, op: NumericOp,
4755) -> Value {
4756 if in_xpath_1_0_compat() {
4757 let a = value_to_number_with(l, idx, bindings);
4758 let b = value_to_number_with(r, idx, bindings);
4759 let result = match op {
4760 NumericOp::Add => a + b,
4761 NumericOp::Sub => a - b,
4762 NumericOp::Mul => a * b,
4763 NumericOp::Div => a / b,
4764 NumericOp::Mod => a % b,
4765 NumericOp::IDiv => (a / b).trunc(),
4766 };
4767 return Value::Number(Numeric::Double(result));
4768 }
4769 typed_numeric_op(l, r, idx, bindings, op)
4770}
4771
4772fn reject_string_vs_numeric_cmp_2_0(
4781 l: &Value, r: &Value, ctx: &EvalCtx, op: &str,
4782) -> Result<()> {
4783 if !ctx.static_ctx.xpath_2_0 || in_xpath_1_0_compat() {
4784 return Ok(());
4785 }
4786 let is_str = |v: &Value| matches!(v, Value::String(_));
4787 let is_num = |v: &Value| matches!(v, Value::Number(_))
4788 || matches!(v, Value::Typed(t) if t.numeric.is_some()
4789 && !matches!(t.kind, "untypedAtomic"));
4790 if (is_str(l) && is_num(r)) || (is_num(l) && is_str(r)) {
4791 return Err(xpath_err(format!(
4792 "general comparison '{op}' between an xs:string and a \
4793 numeric value (XPTY0004)"
4794 )).with_xpath_code("XPTY0004"));
4795 }
4796 Ok(())
4797}
4798
4799fn reject_string_arith_2_0(
4800 l: &Value, r: &Value, ctx: &EvalCtx, op: &str,
4801) -> Result<()> {
4802 if !ctx.static_ctx.xpath_2_0 || in_xpath_1_0_compat() {
4803 return Ok(());
4804 }
4805 for v in [l, r] {
4806 if let Value::String(s) = v {
4807 return Err(xpath_err(format!(
4808 "arithmetic '{op}' on an xs:string operand '{s}' (XPTY0004)"
4809 )).with_xpath_code("XPTY0004"));
4810 }
4811 }
4812 Ok(())
4813}
4814
4815fn exact_decimal(v: &Value) -> Option<rust_decimal::Decimal> {
4823 match v {
4824 Value::Number(Numeric::Integer(i)) => Some(rust_decimal::Decimal::from(*i)),
4825 Value::Number(Numeric::Decimal(d)) => Some(*d),
4826 Value::Typed(t) if matches!(t.kind,
4827 "integer" | "decimal"
4828 | "int" | "long" | "short" | "byte"
4829 | "unsignedInt" | "unsignedLong" | "unsignedShort" | "unsignedByte"
4830 | "nonNegativeInteger" | "nonPositiveInteger"
4831 | "positiveInteger" | "negativeInteger"
4832 ) => t.lexical.parse().ok(),
4833 _ => None,
4834 }
4835}
4836
4837fn typed_numeric_op<I: DocIndexLike>(
4838 l: &Value, r: &Value, idx: &I, bindings: &dyn XPathBindings, op: NumericOp,
4839) -> Value {
4840 if let (Some(da), Some(db)) = (exact_decimal(l), exact_decimal(r)) {
4845 use rust_decimal::prelude::ToPrimitive;
4846 let zero = db.is_zero();
4847 let exact = match op {
4848 NumericOp::Add => da.checked_add(db),
4849 NumericOp::Sub => da.checked_sub(db),
4850 NumericOp::Mul => da.checked_mul(db),
4851 NumericOp::Div if zero => None,
4852 NumericOp::Div => da.checked_div(db),
4853 NumericOp::Mod if zero => None,
4854 NumericOp::Mod => da.checked_rem(db),
4855 NumericOp::IDiv if zero => None,
4856 NumericOp::IDiv => da.checked_div(db).map(|q| q.trunc()),
4857 };
4858 if let Some(d) = exact {
4859 let both_integer = matches!(
4864 (l, r),
4865 (Value::Number(Numeric::Integer(_)), Value::Number(Numeric::Integer(_)))
4866 );
4867 return match op {
4868 NumericOp::IDiv => match d.to_i64() {
4869 Some(i) => Value::Number(Numeric::Integer(i)),
4870 None => Value::Number(Numeric::Decimal(d)),
4871 },
4872 NumericOp::Div => Value::Number(Numeric::Decimal(d)),
4873 _ if both_integer => match d.to_i64() {
4874 Some(i) => Value::Number(Numeric::Integer(i)),
4875 None => Value::Number(Numeric::Decimal(d)),
4876 },
4877 _ => Value::Number(Numeric::Decimal(d)),
4878 };
4879 }
4880 }
4883 let a = value_to_number_with(l, idx, bindings);
4884 let b = value_to_number_with(r, idx, bindings);
4885 let result = match op {
4886 NumericOp::Add => a + b,
4887 NumericOp::Sub => a - b,
4888 NumericOp::Mul => a * b,
4889 NumericOp::Div => a / b,
4890 NumericOp::Mod => a % b,
4891 NumericOp::IDiv => (a / b).trunc(),
4892 };
4893 if let (Value::Number(la), Value::Number(rb)) = (l, r) {
4898 let rank = match op {
4899 NumericOp::IDiv => 0,
4900 NumericOp::Div => la.rank().max(rb.rank()).max(1),
4901 _ => la.rank().max(rb.rank()),
4902 };
4903 return Value::Number(Numeric::from_rank(rank, result));
4904 }
4905 match numeric_promote_kind(numeric_kind_of(l), numeric_kind_of(r)) {
4911 Some(mut kind) => {
4912 if matches!(op, NumericOp::IDiv) {
4913 kind = "integer";
4914 } else if matches!(op, NumericOp::Div) && kind == "integer" {
4915 kind = "decimal";
4916 }
4917 Value::Number(Numeric::of_kind(kind, result))
4918 }
4919 None => Value::Number(Numeric::Double(result)),
4920 }
4921}
4922
4923#[allow(dead_code)]
4924fn arith<I: DocIndexLike>(
4925 l: &Expr,
4926 r: &Expr,
4927 ctx: &EvalCtx,
4928 idx: &I,
4929 op: impl Fn(f64, f64) -> f64,
4930) -> Result<Value> {
4931 let lv = eval_expr(l, ctx, idx)?;
4932 let rv = eval_expr(r, ctx, idx)?;
4933 Ok(Value::Number(Numeric::Double(op(
4934 value_to_number_with(&lv, idx, ctx.bindings),
4935 value_to_number_with(&rv, idx, ctx.bindings),
4936 ))))
4937}
4938
4939fn cmp_op<I: DocIndexLike>(
4940 l: &Expr,
4941 r: &Expr,
4942 ctx: &EvalCtx,
4943 idx: &I,
4944 op: impl Fn(f64, f64) -> bool,
4945) -> Result<Value> {
4946 let lv = eval_expr(l, ctx, idx)?;
4947 let rv = eval_expr(r, ctx, idx)?;
4948 reject_string_vs_numeric_cmp_2_0(&lv, &rv, ctx, "<=>")?;
4949 let node_to_number = |id: NodeId| -> f64 {
4954 idx.string_value(id).trim().parse::<f64>().unwrap_or(f64::NAN)
4955 };
4956 let foreign_to_number = |p: ForeignNodePtr| -> f64 {
4957 ctx.bindings.foreign_string_value(p)
4958 .trim().parse::<f64>().unwrap_or(f64::NAN)
4959 };
4960 match (&lv, &rv) {
4961 (Value::NodeSet(ns), other) | (other, Value::NodeSet(ns))
4962 if !matches!(other, Value::NodeSet(_) | Value::ForeignNodeSet(_)) =>
4963 {
4964 let n_other = value_to_number_with(other, idx, ctx.bindings);
4965 let any = ns.iter().any(|&id| {
4966 let node_n = node_to_number(id);
4967 if matches!(lv, Value::NodeSet(_)) { op(node_n, n_other) }
4970 else { op(n_other, node_n) }
4971 });
4972 return Ok(Value::Boolean(any));
4973 }
4974 (Value::ForeignNodeSet(fs), other) | (other, Value::ForeignNodeSet(fs))
4975 if !matches!(other, Value::NodeSet(_) | Value::ForeignNodeSet(_)) =>
4976 {
4977 let n_other = value_to_number_with(other, idx, ctx.bindings);
4978 let any = fs.iter().any(|&p| {
4979 let node_n = foreign_to_number(p);
4980 if matches!(lv, Value::ForeignNodeSet(_)) { op(node_n, n_other) }
4981 else { op(n_other, node_n) }
4982 });
4983 return Ok(Value::Boolean(any));
4984 }
4985 (Value::NodeSet(ls), Value::NodeSet(rs)) => {
4989 let l_nums: Vec<f64> = ls.iter().map(|&id| node_to_number(id)).collect();
4990 let r_nums: Vec<f64> = rs.iter().map(|&id| node_to_number(id)).collect();
4991 let any = l_nums.iter().any(|&a| r_nums.iter().any(|&b| op(a, b)));
4992 return Ok(Value::Boolean(any));
4993 }
4994 _ => {}
4995 }
4996 if let (Value::Typed(t), Value::Typed(u)) = (&lv, &rv) {
4999 if t.kind == u.kind {
5000 if matches!(t.kind, "date" | "dateTime" | "time") {
5001 if let (Some(a), Some(b)) = (
5002 dt_to_utc_seconds(&t.lexical, t.kind),
5003 dt_to_utc_seconds(&u.lexical, u.kind),
5004 ) {
5005 return Ok(Value::Boolean(op(a as f64, b as f64)));
5006 }
5007 }
5008 if t.kind == "dayTimeDuration" {
5009 if let (Some(a), Some(b)) = (
5010 parse_day_time_duration_secs(&t.lexical),
5011 parse_day_time_duration_secs(&u.lexical),
5012 ) {
5013 return Ok(Value::Boolean(op(a as f64, b as f64)));
5014 }
5015 }
5016 if t.kind == "yearMonthDuration" {
5017 if let (Some(a), Some(b)) = (
5018 parse_year_month_duration_months(&t.lexical),
5019 parse_year_month_duration_months(&u.lexical),
5020 ) {
5021 return Ok(Value::Boolean(op(a as f64, b as f64)));
5022 }
5023 }
5024 }
5028 }
5029 let ln = value_to_number_with(&lv, idx, ctx.bindings);
5030 let rn = value_to_number_with(&rv, idx, ctx.bindings);
5031 Ok(Value::Boolean(op(ln, rn)))
5032}
5033
5034fn dyn_evaluate<I: DocIndexLike>(
5042 args: &[Value], ctx: &EvalCtx<'_>, idx: &I,
5043) -> Result<Value> {
5044 if args.len() != 1 {
5045 return Err(xpath_err("dyn:evaluate takes 1 argument"));
5046 }
5047 let src = value_to_string(&args[0], idx);
5048 let opts = super::XPathOptions {
5049 libxml2_compatible: ctx.static_ctx.libxml2_compatible,
5050 ..super::XPathOptions::default()
5051 };
5052 let expr = match super::parse_xpath_with(&src, &opts) {
5053 Ok(e) => e,
5054 Err(_) => return Ok(Value::NodeSet(Vec::new())),
5055 };
5056 match eval_expr(&expr, ctx, idx) {
5057 Ok(v) => Ok(v),
5058 Err(_) => Ok(Value::NodeSet(Vec::new())),
5059 }
5060}
5061
5062fn eval_map_function<I: DocIndexLike>(
5068 local: &str, args: &[Value], ctx: &EvalCtx<'_>, idx: &I,
5069) -> Result<Value> {
5070 let as_map = |v: &Value| -> Result<Vec<(Value, Value)>> {
5071 match v {
5072 Value::Map(m) => Ok((**m).clone()),
5073 _ => Err(xpath_err(format!("map:{local}: expected a map argument"))),
5074 }
5075 };
5076 let empty = || Value::NodeSet(Vec::new());
5077 match local {
5078 "size" => Ok(Value::Number(Numeric::Integer(as_map(&args[0])?.len() as i64))),
5079 "contains" => {
5080 let m = as_map(&args[0])?;
5081 let k = first_atomic_key(&args[1], idx);
5082 Ok(Value::Boolean(m.iter().any(|(mk, _)| map_key_eq(mk, &k, idx))))
5083 }
5084 "get" => {
5085 let m = as_map(&args[0])?;
5086 let k = first_atomic_key(&args[1], idx);
5087 Ok(m.into_iter().find(|(mk, _)| map_key_eq(mk, &k, idx))
5088 .map(|(_, v)| v).unwrap_or_else(empty))
5089 }
5090 "keys" => Ok(seq_from_items(
5091 as_map(&args[0])?.into_iter().map(|(k, _)| k).collect())),
5092 "put" => {
5093 let mut m = as_map(&args[0])?;
5094 let k = first_atomic_key(&args[1], idx);
5095 m.retain(|(mk, _)| !map_key_eq(mk, &k, idx));
5096 m.push((k, args[2].clone()));
5097 Ok(Value::Map(Box::new(m)))
5098 }
5099 "remove" => {
5100 let mut m = as_map(&args[0])?;
5101 let ks = items_of(&args[1]);
5102 m.retain(|(mk, _)| !ks.iter().any(|k| map_key_eq(mk, k, idx)));
5103 Ok(Value::Map(Box::new(m)))
5104 }
5105 "entry" => Ok(Value::Map(Box::new(vec![
5106 (first_atomic_key(&args[0], idx), args[1].clone())]))),
5107 "merge" => {
5108 let use_last = match args.get(1) {
5111 Some(Value::Map(opts)) => opts.iter()
5112 .find(|(k, _)| value_to_string(k, idx) == "duplicates")
5113 .map(|(_, v)| value_to_string(v, idx) == "use-last")
5114 .unwrap_or(false),
5115 _ => false,
5116 };
5117 let mut out: Vec<(Value, Value)> = Vec::new();
5118 for item in items_of(&args[0]) {
5119 if let Value::Map(m) = item {
5120 for (k, v) in m.iter() {
5121 match out.iter().position(|(ok, _)| map_key_eq(ok, k, idx)) {
5122 Some(p) if use_last => out[p].1 = v.clone(),
5123 Some(_) => {} None => out.push((k.clone(), v.clone())),
5125 }
5126 }
5127 }
5128 }
5129 Ok(Value::Map(Box::new(out)))
5130 }
5131 "for-each" => {
5134 let m = as_map(&args[0])?;
5135 let f = value_as_function(&args[1])?;
5136 let mut out = Vec::new();
5137 for (k, v) in m {
5138 out.push(call_function_item(f, vec![k, v], ctx, idx)?);
5139 }
5140 Ok(seq_from_items(out))
5141 }
5142 "find" => {
5145 let key = first_atomic_key(&args[1], idx);
5146 let mut found = Vec::new();
5147 fn search<I: DocIndexLike>(
5148 v: &Value, key: &Value, idx: &I, out: &mut Vec<Value>,
5149 ) {
5150 for item in items_of(v) {
5151 match item {
5152 Value::Map(m) => {
5153 for (k, val) in m.iter() {
5154 if map_key_eq(k, key, idx) { out.push(val.clone()); }
5155 search(val, key, idx, out);
5156 }
5157 }
5158 Value::Array(a) => for member in a.iter() {
5159 search(member, key, idx, out);
5160 },
5161 _ => {}
5162 }
5163 }
5164 }
5165 search(&args[0], &key, idx, &mut found);
5166 Ok(Value::Array(Box::new(found)))
5167 }
5168 _ => Err(xpath_err(format!(
5169 "map:{local} is not supported in this build"))),
5170 }
5171}
5172
5173fn eval_array_function<I: DocIndexLike>(
5175 local: &str, args: &[Value], ctx: &EvalCtx<'_>, idx: &I,
5176) -> Result<Value> {
5177 let as_array = |v: &Value| -> Result<Vec<Value>> {
5178 match v {
5179 Value::Array(a) => Ok((**a).clone()),
5180 _ => Err(xpath_err(format!("array:{local}: expected an array argument"))),
5181 }
5182 };
5183 match local {
5184 "size" => Ok(Value::Number(Numeric::Integer(as_array(&args[0])?.len() as i64))),
5185 "get" => {
5186 let a = as_array(&args[0])?;
5187 let pos = value_to_number(&args[1], idx);
5188 if pos.fract() == 0.0 && pos >= 1.0 && (pos as usize) <= a.len() {
5189 Ok(a[pos as usize - 1].clone())
5190 } else {
5191 Err(xpath_err("array:get: index out of bounds (FOAY0001)"))
5192 }
5193 }
5194 "append" => {
5195 let mut a = as_array(&args[0])?;
5196 a.push(args[1].clone());
5197 Ok(Value::Array(Box::new(a)))
5198 }
5199 "head" => {
5200 let a = as_array(&args[0])?;
5201 a.into_iter().next().ok_or_else(|| xpath_err("array:head: empty array (FOAY0001)"))
5202 }
5203 "tail" => {
5204 let mut a = as_array(&args[0])?;
5205 if a.is_empty() { return Err(xpath_err("array:tail: empty array (FOAY0001)")); }
5206 a.remove(0);
5207 Ok(Value::Array(Box::new(a)))
5208 }
5209 "reverse" => {
5210 let mut a = as_array(&args[0])?;
5211 a.reverse();
5212 Ok(Value::Array(Box::new(a)))
5213 }
5214 "subarray" => {
5215 let a = as_array(&args[0])?;
5216 let start = value_to_number(&args[1], idx).round() as i64;
5217 let len = match args.get(2) {
5218 Some(v) => value_to_number(v, idx).round() as i64,
5219 None => a.len() as i64 - start + 1,
5220 };
5221 let s = (start - 1).max(0) as usize;
5222 let e = ((start - 1 + len).max(0) as usize).min(a.len());
5223 Ok(Value::Array(Box::new(a.get(s..e).map(<[_]>::to_vec).unwrap_or_default())))
5224 }
5225 "join" => {
5226 let mut out = Vec::new();
5227 for item in items_of(&args[0]) {
5228 if let Value::Array(a) = item { out.extend((*a).clone()); }
5229 }
5230 Ok(Value::Array(Box::new(out)))
5231 }
5232 "flatten" => {
5233 fn flat(v: &Value, out: &mut Vec<Value>) {
5234 for item in items_of(v) {
5235 match item {
5236 Value::Array(a) => for m in a.iter() { flat(m, out); },
5237 other => out.push(other),
5238 }
5239 }
5240 }
5241 let mut out = Vec::new();
5242 flat(&args[0], &mut out);
5243 Ok(seq_from_items(out))
5244 }
5245 "for-each" => {
5248 let a = as_array(&args[0])?;
5249 let f = value_as_function(&args[1])?;
5250 let mut out = Vec::with_capacity(a.len());
5251 for m in a {
5252 out.push(call_function_item(f, vec![m], ctx, idx)?);
5253 }
5254 Ok(Value::Array(Box::new(out)))
5255 }
5256 "filter" => {
5259 let a = as_array(&args[0])?;
5260 let f = value_as_function(&args[1])?;
5261 let mut out = Vec::new();
5262 for m in a {
5263 let keep = call_function_item(f, vec![m.clone()], ctx, idx)?;
5264 if value_to_bool(&keep, idx) { out.push(m); }
5265 }
5266 Ok(Value::Array(Box::new(out)))
5267 }
5268 "fold-left" => {
5269 let a = as_array(&args[0])?;
5270 let f = value_as_function(&args[2])?;
5271 let mut acc = args[1].clone();
5272 for m in a {
5273 acc = call_function_item(f, vec![acc, m], ctx, idx)?;
5274 }
5275 Ok(acc)
5276 }
5277 "fold-right" => {
5278 let a = as_array(&args[0])?;
5279 let f = value_as_function(&args[2])?;
5280 let mut acc = args[1].clone();
5281 for m in a.into_iter().rev() {
5282 acc = call_function_item(f, vec![m, acc], ctx, idx)?;
5283 }
5284 Ok(acc)
5285 }
5286 "for-each-pair" => {
5289 let a = as_array(&args[0])?;
5290 let b = as_array(&args[1])?;
5291 let f = value_as_function(&args[2])?;
5292 let mut out = Vec::new();
5293 for (x, y) in a.into_iter().zip(b) {
5294 out.push(call_function_item(f, vec![x, y], ctx, idx)?);
5295 }
5296 Ok(Value::Array(Box::new(out)))
5297 }
5298 "sort" => {
5301 let a = as_array(&args[0])?;
5302 let keyfn = match args.get(2) {
5303 Some(v) => Some(value_as_function(v)?),
5304 None => None,
5305 };
5306 let mut keyed: Vec<(Value, Value)> = Vec::with_capacity(a.len());
5307 for m in a {
5308 let k = match keyfn {
5309 Some(f) => call_function_item(f, vec![m.clone()], ctx, idx)?,
5310 None => m.clone(),
5311 };
5312 keyed.push((k, m));
5313 }
5314 keyed.sort_by(|(ka, _), (kb, _)| {
5315 compare_atomic_for_sort(ka, kb, idx, ctx.bindings)
5316 });
5317 Ok(Value::Array(Box::new(keyed.into_iter().map(|(_, m)| m).collect())))
5318 }
5319 "put" => {
5324 let mut a = as_array(&args[0])?;
5325 let pos = value_to_number(&args[1], idx);
5326 if pos.fract() == 0.0 && pos >= 1.0 && (pos as usize) <= a.len() {
5327 a[pos as usize - 1] = args[2].clone();
5328 Ok(Value::Array(Box::new(a)))
5329 } else {
5330 Err(xpath_err("array:put: position out of bounds (FOAY0001)"))
5331 }
5332 }
5333 "insert-before" => {
5337 let mut a = as_array(&args[0])?;
5338 let pos = value_to_number(&args[1], idx);
5339 if pos.fract() == 0.0 && pos >= 1.0 && (pos as usize) <= a.len() + 1 {
5340 a.insert(pos as usize - 1, args[2].clone());
5341 Ok(Value::Array(Box::new(a)))
5342 } else {
5343 Err(xpath_err("array:insert-before: position out of bounds (FOAY0001)"))
5344 }
5345 }
5346 "remove" => {
5350 let a = as_array(&args[0])?;
5351 let mut drop: std::collections::HashSet<usize> = std::collections::HashSet::new();
5352 for p in items_of(&args[1]) {
5353 let n = value_to_number(&p, idx);
5354 if n.fract() != 0.0 || n < 1.0 || (n as usize) > a.len() {
5355 return Err(xpath_err("array:remove: position out of bounds (FOAY0001)"));
5356 }
5357 drop.insert(n as usize);
5358 }
5359 let out: Vec<Value> = a.into_iter().enumerate()
5360 .filter(|(i, _)| !drop.contains(&(i + 1)))
5361 .map(|(_, m)| m)
5362 .collect();
5363 Ok(Value::Array(Box::new(out)))
5364 }
5365 _ => Err(xpath_err(format!(
5369 "array:{local} is not supported in this build"))),
5370 }
5371}
5372
5373fn value_as_function(v: &Value) -> Result<&FunctionItem> {
5375 match v {
5376 Value::Function(fi) => Ok(fi),
5377 _ => Err(xpath_err("expected a function item (XPTY0004)")),
5378 }
5379}
5380
5381fn compare_atomic_for_sort<I: DocIndexLike>(
5385 a: &Value, b: &Value, idx: &I, bindings: &dyn XPathBindings,
5386) -> std::cmp::Ordering {
5387 use std::cmp::Ordering;
5388 let numeric = |v: &Value| -> Option<f64> {
5389 match v {
5390 Value::Number(_) => Some(value_to_number(v, idx)),
5391 Value::Typed(t) if matches!(t.kind,
5392 "integer" | "decimal" | "double" | "float" | "long" | "int"
5393 | "short" | "byte" | "numeric") => Some(value_to_number(v, idx)),
5394 _ => None,
5395 }
5396 };
5397 match (numeric(a), numeric(b)) {
5398 (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal),
5399 _ => value_to_string_with(a, idx, bindings)
5400 .cmp(&value_to_string_with(b, idx, bindings)),
5401 }
5402}
5403
5404fn eval_hof_function<I: DocIndexLike>(
5408 local: &str, args: &[Value], ctx: &EvalCtx<'_>, idx: &I,
5409) -> Option<Result<Value>> {
5410 let f = |i: usize| value_as_function(&args[i]);
5411 let res = match local {
5412 "for-each" if args.len() == 2 => (|| {
5415 let func = f(1)?;
5416 let mut out = Vec::new();
5417 for item in iter_items(&args[0]) {
5418 out.push(call_function_item(func, vec![item], ctx, idx)?);
5419 }
5420 Ok(seq_from_items(out))
5421 })(),
5422 "filter" if args.len() == 2 => (|| {
5425 let func = f(1)?;
5426 let mut out = Vec::new();
5427 for item in iter_items(&args[0]) {
5428 let keep = call_function_item(func, vec![item.clone()], ctx, idx)?;
5429 if value_to_bool(&keep, idx) { out.push(item); }
5430 }
5431 Ok(seq_from_items(out))
5432 })(),
5433 "fold-left" if args.len() == 3 => (|| {
5434 let func = f(2)?;
5435 let mut acc = args[1].clone();
5436 for item in iter_items(&args[0]) {
5437 acc = call_function_item(func, vec![acc, item], ctx, idx)?;
5438 }
5439 Ok(acc)
5440 })(),
5441 "fold-right" if args.len() == 3 => (|| {
5442 let func = f(2)?;
5443 let items: Vec<Value> = iter_items(&args[0]).collect();
5444 let mut acc = args[1].clone();
5445 for item in items.into_iter().rev() {
5446 acc = call_function_item(func, vec![item, acc], ctx, idx)?;
5447 }
5448 Ok(acc)
5449 })(),
5450 "for-each-pair" if args.len() == 3 => (|| {
5453 let func = f(2)?;
5454 let mut out = Vec::new();
5455 for (x, y) in iter_items(&args[0]).zip(iter_items(&args[1])) {
5456 out.push(call_function_item(func, vec![x, y], ctx, idx)?);
5457 }
5458 Ok(seq_from_items(out))
5459 })(),
5460 "sort" if (1..=3).contains(&args.len()) => (|| {
5462 let keyfn = match args.get(2) {
5463 Some(v) => Some(value_as_function(v)?),
5464 None => None,
5465 };
5466 let mut keyed: Vec<(Value, Value)> = Vec::new();
5467 for item in iter_items(&args[0]) {
5468 let k = match keyfn {
5469 Some(func) => call_function_item(func, vec![item.clone()], ctx, idx)?,
5470 None => item.clone(),
5471 };
5472 keyed.push((k, item));
5473 }
5474 keyed.sort_by(|(ka, _), (kb, _)|
5475 compare_atomic_for_sort(ka, kb, idx, ctx.bindings));
5476 Ok(seq_from_items(keyed.into_iter().map(|(_, v)| v).collect()))
5477 })(),
5478 "apply" if args.len() == 2 => (|| {
5481 let func = f(0)?;
5482 let call_args = match &args[1] {
5483 Value::Array(a) => (**a).clone(),
5484 _ => return Err(xpath_err("fn:apply: second argument must be an array")),
5485 };
5486 call_function_item(func, call_args, ctx, idx)
5487 })(),
5488 "function-arity" if args.len() == 1 =>
5490 f(0).map(|func| Value::Number(Numeric::Integer(func.arity() as i64))),
5491 "function-name" if args.len() == 1 => f(0).map(|func| match func {
5495 FunctionItem::Named { name, ns, .. } => {
5496 let local = name.rsplit(':').next().unwrap_or(name);
5497 let lexical = if ns.is_empty() {
5498 local.to_string()
5499 } else {
5500 format!("{{{ns}}}{local}")
5501 };
5502 Value::Typed(Box::new(TypedAtomic {
5503 kind: "QName", lexical, numeric: None, boolean: None, user_type: None,
5504 }))
5505 }
5506 _ => Value::NodeSet(Vec::new()),
5507 }),
5508 "function-lookup" if args.len() == 2 => {
5512 let qname = value_to_string(&args[0], idx);
5513 let arity = value_to_number(&args[1], idx) as usize;
5514 let (ns, local) = if let Some(rest) = qname.strip_prefix('{') {
5518 rest.split_once('}')
5519 .map(|(u, l)| (u.to_string(), l.to_string()))
5520 .unwrap_or_else(|| (String::new(), qname.clone()))
5521 } else if let Some((prefix, l)) = qname.split_once(':') {
5522 (resolve_prefix_or_implicit(ctx.bindings, prefix).unwrap_or_default(),
5523 l.to_string())
5524 } else {
5525 (FN_NAMESPACE.to_string(), qname.clone())
5526 };
5527 let available = if ns.is_empty() || ns.as_str() == FN_NAMESPACE {
5528 xpath_function_available(&local, ctx)
5529 } else {
5530 ctx.bindings.function_available_in(&ns, &local, arity)
5531 };
5532 if available {
5533 let sig = ctx.bindings.function_signature_in(&ns, &local, arity).map(Box::new);
5534 Ok(Value::Function(Box::new(FunctionItem::Named { name: local, ns, arity, sig })))
5535 } else {
5536 Ok(Value::NodeSet(Vec::new()))
5537 }
5538 }
5539 _ => return None,
5540 };
5541 Some(res)
5542}
5543
5544fn eval_json_function<I: DocIndexLike>(
5548 local: &str, args: &[Value], _ctx: &EvalCtx<'_>, idx: &I,
5549) -> Option<Result<Value>> {
5550 let opt_str = |opts: Option<&Value>, key: &str| -> Option<String> {
5552 match opts {
5553 Some(Value::Map(m)) => m.iter()
5554 .find(|(k, _)| value_to_string(k, idx) == key)
5555 .map(|(_, v)| value_to_string(v, idx)),
5556 _ => None,
5557 }
5558 };
5559 let opt_bool = |opts: Option<&Value>, key: &str| -> Option<bool> {
5560 match opts {
5561 Some(Value::Map(m)) => m.iter()
5562 .find(|(k, _)| value_to_string(k, idx) == key)
5563 .map(|(_, v)| value_to_bool(v, idx)),
5564 _ => None,
5565 }
5566 };
5567 let res = match local {
5568 "parse-json" if (1..=2).contains(&args.len()) => (|| {
5571 if sequence_len(&args[0]) == 0 {
5573 return Ok(Value::Sequence(Vec::new()));
5574 }
5575 let text = value_to_string(&args[0], idx);
5576 let opts = args.get(1);
5577 let dup = opt_str(opts, "duplicates")
5578 .unwrap_or_else(|| "use-first".to_string());
5579 let escape = opt_bool(opts, "escape").unwrap_or(false);
5580 let liberal = opt_bool(opts, "liberal").unwrap_or(false);
5581 parse_json_value(&text, &dup, escape, liberal)
5582 })(),
5583 "xml-to-json" if (1..=2).contains(&args.len()) => (|| {
5586 let root = match &args[0] {
5587 Value::NodeSet(ns) if ns.len() == 1 => ns[0],
5588 Value::NodeSet(ns) if ns.is_empty() =>
5589 return Ok(Value::Sequence(Vec::new())),
5590 _ => return Err(xpath_err(
5591 "xml-to-json: argument must be a single document or element node")),
5592 };
5593 let elem = match idx.kind(root) {
5596 XPathNodeKind::Document => {
5597 let mut elems = idx.children(root).iter().copied()
5598 .filter(|&c| matches!(idx.kind(c), XPathNodeKind::Element));
5599 let first = elems.next().ok_or_else(||
5600 xpath_err("xml-to-json: empty document (FOJS0006)"))?;
5601 if elems.next().is_some() {
5602 return Err(xpath_err(
5603 "xml-to-json: more than one top-level element (FOJS0006)"));
5604 }
5605 first
5606 }
5607 XPathNodeKind::Element => root,
5608 _ => return Err(xpath_err(
5609 "xml-to-json: argument must be a document or element node")),
5610 };
5611 let mut out = String::new();
5612 xml_node_to_json(elem, idx, false, &mut out)?;
5613 Ok(Value::String(out))
5614 })(),
5615 "serialize" if (1..=2).contains(&args.len()) => (|| {
5620 let method = opt_str(args.get(1), "method").unwrap_or_default();
5621 if method == "json" {
5622 let mut out = String::new();
5623 value_to_json(&args[0], idx, &mut out)?;
5624 Ok(Value::String(out))
5625 } else {
5626 Ok(Value::String(value_to_string(&args[0], idx)))
5627 }
5628 })(),
5629 _ => return None,
5630 };
5631 Some(res)
5632}
5633
5634fn value_to_json<I: DocIndexLike>(v: &Value, idx: &I, out: &mut String) -> Result<()> {
5639 match v {
5640 Value::Map(m) => {
5641 out.push('{');
5642 for (i, (k, val)) in m.iter().enumerate() {
5643 if i > 0 { out.push(','); }
5644 out.push('"');
5645 json_escape_into(&value_to_string(k, idx), out);
5646 out.push_str("\":");
5647 value_to_json(val, idx, out)?;
5648 }
5649 out.push('}');
5650 }
5651 Value::Array(a) => {
5652 out.push('[');
5653 for (i, val) in a.iter().enumerate() {
5654 if i > 0 { out.push(','); }
5655 value_to_json(val, idx, out)?;
5656 }
5657 out.push(']');
5658 }
5659 Value::Boolean(b) => out.push_str(if *b { "true" } else { "false" }),
5660 Value::Number(_) => out.push_str(&value_to_string(v, idx)),
5661 Value::Typed(t) if t.numeric.is_some() => out.push_str(&value_to_string(v, idx)),
5662 Value::NodeSet(ns) if ns.is_empty() => out.push_str("null"),
5663 Value::Sequence(items) if items.is_empty() => out.push_str("null"),
5664 other => {
5665 out.push('"');
5666 json_escape_into(&value_to_string(other, idx), out);
5667 out.push('"');
5668 }
5669 }
5670 Ok(())
5671}
5672
5673pub enum JsonEvent {
5679 StartObject,
5680 EndObject,
5681 StartArray,
5682 EndArray,
5683 Key(String),
5685 Str(String),
5686 Number(String),
5687 Bool(bool),
5688 Null,
5689}
5690
5691pub fn parse_json_events(
5697 json: &str, escape: bool, liberal: bool, sink: &mut dyn FnMut(JsonEvent),
5698) -> Result<()> {
5699 let chars: Vec<char> = json.chars().collect();
5700 let mut p = JsonParser { chars: &chars, pos: 0, escape, liberal };
5701 p.skip_ws();
5702 p.parse_value(sink)?;
5703 p.skip_ws();
5704 if p.pos != p.chars.len() {
5705 return Err(xpath_err("parse-json: trailing content after JSON value (FOJS0001)"));
5706 }
5707 Ok(())
5708}
5709
5710pub fn parse_json_value(text: &str, duplicates: &str, escape: bool, liberal: bool)
5716 -> Result<Value>
5717{
5718 enum Frame { Obj(Vec<(Value, Value)>, Option<String>), Arr(Vec<Value>) }
5721 let mut stack: Vec<Frame> = Vec::new();
5722 let mut result: Option<Value> = None;
5723 let mut dup_err: Option<crate::error::XmlError> = None;
5724
5725 let mut attach = |stack: &mut Vec<Frame>, result: &mut Option<Value>, v: Value| {
5726 match stack.last_mut() {
5727 Some(Frame::Arr(a)) => a.push(v),
5728 Some(Frame::Obj(entries, pending)) => {
5729 let key = pending.take().unwrap_or_default();
5730 match entries.iter().position(|(k, _)| value_as_str(k) == key) {
5731 Some(i) => match duplicates {
5732 "reject" => {
5733 dup_err.get_or_insert_with(|| xpath_err(format!(
5734 "parse-json: duplicate key {key:?} (FOJS0003)")));
5735 }
5736 "use-last" => entries[i].1 = v,
5737 _ => {}
5738 },
5739 None => entries.push((Value::String(key), v)),
5740 }
5741 }
5742 None => *result = Some(v),
5743 }
5744 };
5745
5746 parse_json_events(text, escape, liberal, &mut |ev| match ev {
5747 JsonEvent::StartObject => stack.push(Frame::Obj(Vec::new(), None)),
5748 JsonEvent::StartArray => stack.push(Frame::Arr(Vec::new())),
5749 JsonEvent::Key(k) => {
5750 if let Some(Frame::Obj(_, pending)) = stack.last_mut() { *pending = Some(k); }
5751 }
5752 JsonEvent::EndObject => {
5753 if let Some(Frame::Obj(entries, _)) = stack.pop() {
5754 attach(&mut stack, &mut result, Value::Map(Box::new(entries)));
5755 }
5756 }
5757 JsonEvent::EndArray => {
5758 if let Some(Frame::Arr(members)) = stack.pop() {
5759 attach(&mut stack, &mut result, Value::Array(Box::new(members)));
5760 }
5761 }
5762 JsonEvent::Str(s) => attach(&mut stack, &mut result, Value::String(s)),
5763 JsonEvent::Number(n) => attach(&mut stack, &mut result,
5764 Value::Number(Numeric::Double(n.parse::<f64>().unwrap_or(f64::NAN)))),
5765 JsonEvent::Bool(b) => attach(&mut stack, &mut result, Value::Boolean(b)),
5766 JsonEvent::Null => attach(&mut stack, &mut result, Value::Sequence(Vec::new())),
5767 })?;
5768 if let Some(e) = dup_err { return Err(e); }
5769 Ok(result.unwrap_or_else(|| Value::Sequence(Vec::new())))
5770}
5771
5772struct JsonParser<'a> {
5773 chars: &'a [char],
5774 pos: usize,
5775 escape: bool,
5776 liberal: bool,
5777}
5778
5779impl<'a> JsonParser<'a> {
5780 fn peek(&self) -> Option<char> { self.chars.get(self.pos).copied() }
5781
5782 fn skip_ws(&mut self) {
5783 while let Some(c) = self.peek() {
5784 if c == ' ' || c == '\t' || c == '\n' || c == '\r' { self.pos += 1; }
5785 else { break; }
5786 }
5787 }
5788
5789 fn parse_value(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5790 match self.peek() {
5791 Some('{') => self.parse_object(sink),
5792 Some('[') => self.parse_array(sink),
5793 Some('"') => { let s = self.parse_string()?; sink(JsonEvent::Str(s)); Ok(()) }
5794 Some('t') | Some('f') => self.parse_bool(sink),
5795 Some('n') => self.parse_null(sink),
5796 Some(c) if c == '-' || c.is_ascii_digit() => self.parse_number(sink),
5797 _ => Err(xpath_err("parse-json: unexpected character (FOJS0001)")),
5798 }
5799 }
5800
5801 fn parse_object(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5802 self.pos += 1; sink(JsonEvent::StartObject);
5804 self.skip_ws();
5805 if self.peek() == Some('}') { self.pos += 1; sink(JsonEvent::EndObject); return Ok(()); }
5806 loop {
5807 self.skip_ws();
5808 if self.peek() != Some('"') {
5809 return Err(xpath_err("parse-json: expected object key (FOJS0001)"));
5810 }
5811 let key = self.parse_string()?;
5812 sink(JsonEvent::Key(key));
5813 self.skip_ws();
5814 if self.peek() != Some(':') {
5815 return Err(xpath_err("parse-json: expected ':' in object (FOJS0001)"));
5816 }
5817 self.pos += 1;
5818 self.skip_ws();
5819 self.parse_value(sink)?;
5820 self.skip_ws();
5821 match self.peek() {
5822 Some(',') => { self.pos += 1; continue; }
5823 Some('}') => { self.pos += 1; break; }
5824 _ => return Err(xpath_err("parse-json: expected ',' or '}' (FOJS0001)")),
5825 }
5826 }
5827 sink(JsonEvent::EndObject);
5828 Ok(())
5829 }
5830
5831 fn parse_array(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5832 self.pos += 1; sink(JsonEvent::StartArray);
5834 self.skip_ws();
5835 if self.peek() == Some(']') { self.pos += 1; sink(JsonEvent::EndArray); return Ok(()); }
5836 loop {
5837 self.skip_ws();
5838 self.parse_value(sink)?;
5839 self.skip_ws();
5840 match self.peek() {
5841 Some(',') => { self.pos += 1; continue; }
5842 Some(']') => { self.pos += 1; break; }
5843 _ => return Err(xpath_err("parse-json: expected ',' or ']' (FOJS0001)")),
5844 }
5845 }
5846 sink(JsonEvent::EndArray);
5847 Ok(())
5848 }
5849
5850 fn parse_string(&mut self) -> Result<String> {
5851 self.pos += 1; let mut s = String::new();
5853 loop {
5854 match self.peek() {
5855 None => return Err(xpath_err("parse-json: unterminated string (FOJS0001)")),
5856 Some('"') => { self.pos += 1; break; }
5857 Some('\\') => {
5858 self.pos += 1;
5859 let esc = self.peek().ok_or_else(||
5860 xpath_err("parse-json: dangling escape (FOJS0001)"))?;
5861 self.pos += 1;
5862 if self.escape && esc != 'u' {
5865 s.push('\\'); s.push(esc); continue;
5866 }
5867 match esc {
5868 '"' => s.push('"'),
5869 '\\' => s.push('\\'),
5870 '/' => s.push('/'),
5871 'b' => s.push('\u{0008}'),
5872 'f' => s.push('\u{000C}'),
5873 'n' => s.push('\n'),
5874 'r' => s.push('\r'),
5875 't' => s.push('\t'),
5876 'u' => {
5877 let cp = self.parse_hex4()?;
5878 if (0xD800..=0xDBFF).contains(&cp) {
5880 if self.peek() == Some('\\') {
5881 self.pos += 1;
5882 if self.peek() == Some('u') {
5883 self.pos += 1;
5884 let lo = self.parse_hex4()?;
5885 let c = 0x10000
5886 + ((cp - 0xD800) << 10)
5887 + (lo - 0xDC00);
5888 if self.escape {
5889 s.push_str(&format!("\\u{cp:04X}\\u{lo:04X}"));
5890 } else if let Some(ch) = char::from_u32(c) {
5891 s.push(ch);
5892 }
5893 continue;
5894 }
5895 }
5896 return Err(xpath_err(
5897 "parse-json: invalid surrogate (FOJS0001)"));
5898 }
5899 if self.escape {
5900 s.push_str(&format!("\\u{cp:04X}"));
5901 } else if let Some(ch) = char::from_u32(cp) {
5902 s.push(ch);
5903 }
5904 }
5905 _ => return Err(xpath_err(format!(
5906 "parse-json: invalid escape \\{esc} (FOJS0001)"))),
5907 }
5908 }
5909 Some(c) => {
5910 if (c as u32) < 0x20 && !self.liberal {
5913 return Err(xpath_err(
5914 "parse-json: unescaped control character (FOJS0001)"));
5915 }
5916 s.push(c);
5917 self.pos += 1;
5918 }
5919 }
5920 }
5921 Ok(s)
5922 }
5923
5924 fn parse_hex4(&mut self) -> Result<u32> {
5925 let mut v = 0u32;
5926 for _ in 0..4 {
5927 let c = self.peek().ok_or_else(||
5928 xpath_err("parse-json: short \\u escape (FOJS0001)"))?;
5929 let d = c.to_digit(16).ok_or_else(||
5930 xpath_err("parse-json: invalid hex in \\u escape (FOJS0001)"))?;
5931 v = v * 16 + d;
5932 self.pos += 1;
5933 }
5934 Ok(v)
5935 }
5936
5937 fn parse_bool(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5938 if self.matches_literal("true") { sink(JsonEvent::Bool(true)); Ok(()) }
5939 else if self.matches_literal("false") { sink(JsonEvent::Bool(false)); Ok(()) }
5940 else { Err(xpath_err("parse-json: invalid literal (FOJS0001)")) }
5941 }
5942
5943 fn parse_null(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5944 if self.matches_literal("null") { sink(JsonEvent::Null); Ok(()) }
5945 else { Err(xpath_err("parse-json: invalid literal (FOJS0001)")) }
5946 }
5947
5948 fn matches_literal(&mut self, lit: &str) -> bool {
5949 let end = self.pos + lit.len();
5950 if end <= self.chars.len()
5951 && self.chars[self.pos..end].iter().copied().eq(lit.chars())
5952 {
5953 self.pos = end;
5954 true
5955 } else {
5956 false
5957 }
5958 }
5959
5960 fn parse_number(&mut self, sink: &mut dyn FnMut(JsonEvent)) -> Result<()> {
5961 let start = self.pos;
5962 let digits = |p: &mut Self| {
5963 let s = p.pos;
5964 while p.peek().is_some_and(|c| c.is_ascii_digit()) { p.pos += 1; }
5965 p.pos > s
5966 };
5967 let bad = || xpath_err("parse-json: invalid number (FOJS0001)");
5968 if self.liberal {
5969 if matches!(self.peek(), Some('-') | Some('+')) { self.pos += 1; }
5972 while self.peek().is_some_and(|c|
5973 c.is_ascii_digit() || matches!(c, '.' | 'e' | 'E' | '+' | '-')) {
5974 self.pos += 1;
5975 }
5976 } else {
5977 if self.peek() == Some('-') { self.pos += 1; }
5980 match self.peek() {
5981 Some('0') => self.pos += 1, Some(c) if c.is_ascii_digit() => { digits(self); }
5983 _ => return Err(bad()),
5984 }
5985 if self.peek() == Some('.') {
5986 self.pos += 1;
5987 if !digits(self) { return Err(bad()); }
5988 }
5989 if matches!(self.peek(), Some('e') | Some('E')) {
5990 self.pos += 1;
5991 if matches!(self.peek(), Some('+') | Some('-')) { self.pos += 1; }
5992 if !digits(self) { return Err(bad()); }
5993 }
5994 }
5995 let lex: String = self.chars[start..self.pos].iter().collect();
5996 match lex.parse::<f64>() {
5997 Ok(n) if n.is_finite() => { sink(JsonEvent::Number(lex)); Ok(()) }
5998 _ => Err(xpath_err(format!("parse-json: invalid number {lex:?} (FOJS0001)"))),
5999 }
6000 }
6001}
6002
6003pub fn json_option_bool<I: DocIndexLike>(
6007 opts: Option<&Value>, key: &str, idx: &I,
6008) -> Result<Option<bool>> {
6009 fn single_bool(v: &Value) -> Option<bool> {
6010 match v {
6011 Value::Boolean(b) => Some(*b),
6012 Value::Typed(t) if t.kind == "boolean" => t.boolean,
6013 Value::Sequence(items) if items.len() == 1 => single_bool(&items[0]),
6014 _ => None,
6015 }
6016 }
6017 let Some(Value::Map(m)) = opts else { return Ok(None) };
6018 match m.iter().find(|(k, _)| value_to_string(k, idx) == key) {
6019 None => Ok(None),
6020 Some((_, v)) => single_bool(v).map(Some).ok_or_else(||
6021 xpath_err(format!("JSON option {key:?} must be a single boolean (FOJS0005)"))),
6022 }
6023}
6024
6025fn value_as_str(v: &Value) -> String {
6027 match v {
6028 Value::String(s) => s.clone(),
6029 Value::Typed(t) => t.lexical.clone(),
6030 _ => String::new(),
6031 }
6032}
6033
6034const FN_NAMESPACE: &str = "http://www.w3.org/2005/xpath-functions";
6037
6038fn xml_node_to_json<I: DocIndexLike>(
6045 elem: NodeId, idx: &I, in_map: bool, out: &mut String,
6046) -> Result<()> {
6047 let err = |m: &str| xpath_err(format!("xml-to-json: {m} (FOJS0006)"));
6048 if idx.namespace_uri(elem) != FN_NAMESPACE {
6049 return Err(err("element is not in the JSON namespace"));
6050 }
6051 let local = idx.local_name(elem).to_string();
6052 for a in idx.attr_range(elem) {
6056 if !idx.namespace_uri(a).is_empty() { continue; }
6057 match idx.local_name(a) {
6058 "key" | "escaped" | "escaped-key" => {}
6059 other => return Err(err(&format!("unexpected attribute {other:?}"))),
6060 }
6061 }
6062 let has_key = attr_value(elem, "key", idx).is_some();
6063 if in_map && !has_key { return Err(err("map entry is missing its key")); }
6064 if !in_map && has_key { return Err(err("key attribute outside a map")); }
6065
6066 let elem_children: Vec<NodeId> = idx.children(elem).iter().copied()
6067 .filter(|&c| matches!(idx.kind(c), XPathNodeKind::Element)).collect();
6068 let text = direct_text(elem, idx);
6069 let text_is_blank = text.trim().is_empty();
6070
6071 if in_map {
6072 let key = attr_value(elem, "key", idx).unwrap_or_default();
6074 out.push('"');
6075 if json_bool_attr(elem, "escaped-key", idx)?.unwrap_or(false) {
6076 validate_json_escapes(&key)?;
6077 out.push_str(&key);
6078 } else {
6079 json_escape_into(&key, out);
6080 }
6081 out.push_str("\":");
6082 }
6083
6084 match local.as_str() {
6085 "null" => {
6086 if !elem_children.is_empty() || !text_is_blank {
6087 return Err(err("null must be empty"));
6088 }
6089 out.push_str("null");
6090 }
6091 "boolean" => {
6092 if !elem_children.is_empty() { return Err(err("boolean has element content")); }
6093 match text.trim() {
6094 "true" | "1" => out.push_str("true"),
6095 "false" | "0" => out.push_str("false"),
6096 _ => return Err(err("boolean value is not true/false")),
6097 }
6098 }
6099 "number" => {
6100 if !elem_children.is_empty() { return Err(err("number has element content")); }
6101 let n = text.trim();
6102 match n.parse::<f64>() {
6103 Ok(f) if f.is_finite() => out.push_str(n),
6104 _ => return Err(err("number is not a valid JSON number")),
6105 }
6106 }
6107 "string" => {
6108 if !elem_children.is_empty() { return Err(err("string has element content")); }
6109 out.push('"');
6110 if json_bool_attr(elem, "escaped", idx)?.unwrap_or(false) {
6111 validate_json_escapes(&text)?;
6114 out.push_str(&text);
6115 } else {
6116 json_escape_into(&text, out);
6117 }
6118 out.push('"');
6119 }
6120 "array" => {
6121 if !text_is_blank { return Err(err("array has text content")); }
6122 out.push('[');
6123 for (i, &c) in elem_children.iter().enumerate() {
6124 if i > 0 { out.push(','); }
6125 xml_node_to_json(c, idx, false, out)?;
6126 }
6127 out.push(']');
6128 }
6129 "map" => {
6130 if !text_is_blank { return Err(err("map has text content")); }
6131 out.push('{');
6132 let mut seen: Vec<String> = Vec::new();
6133 for (i, &c) in elem_children.iter().enumerate() {
6134 if idx.namespace_uri(c) == FN_NAMESPACE {
6135 if let Some(k) = attr_value(c, "key", idx) {
6136 if seen.contains(&k) {
6137 return Err(err("duplicate key in map"));
6138 }
6139 seen.push(k);
6140 }
6141 }
6142 if i > 0 { out.push(','); }
6143 xml_node_to_json(c, idx, true, out)?;
6144 }
6145 out.push('}');
6146 }
6147 other => return Err(err(&format!("unexpected element {other:?}"))),
6148 }
6149 Ok(())
6150}
6151
6152fn direct_text<I: DocIndexLike>(elem: NodeId, idx: &I) -> String {
6155 let mut s = String::new();
6156 for &c in idx.children(elem) {
6157 if matches!(idx.kind(c), XPathNodeKind::Text | XPathNodeKind::CData) {
6158 s.push_str(&idx.string_value(c));
6159 }
6160 }
6161 s
6162}
6163
6164fn json_bool_attr<I: DocIndexLike>(elem: NodeId, name: &str, idx: &I) -> Result<Option<bool>> {
6167 match attr_value(elem, name, idx) {
6168 None => Ok(None),
6169 Some(v) => match v.trim() {
6170 "true" | "1" => Ok(Some(true)),
6171 "false" | "0" => Ok(Some(false)),
6172 _ => Err(xpath_err(format!(
6173 "xml-to-json: {name} is not a boolean (FOJS0006)"))),
6174 },
6175 }
6176}
6177
6178fn validate_json_escapes(s: &str) -> Result<()> {
6181 let chars: Vec<char> = s.chars().collect();
6182 let mut i = 0;
6183 while i < chars.len() {
6184 if chars[i] == '\\' {
6185 let Some(&next) = chars.get(i + 1) else {
6186 return Err(xpath_err("xml-to-json: dangling backslash (FOJS0007)"));
6187 };
6188 match next {
6189 '"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' => i += 2,
6190 'u' => {
6191 let hex = chars.get(i + 2..i + 6);
6192 if !hex.is_some_and(|h| h.iter().all(|c| c.is_ascii_hexdigit())) {
6193 return Err(xpath_err(
6194 "xml-to-json: invalid \\u escape (FOJS0007)"));
6195 }
6196 i += 6;
6197 }
6198 _ => return Err(xpath_err(format!(
6199 "xml-to-json: invalid escape \\{next} (FOJS0007)"))),
6200 }
6201 } else {
6202 i += 1;
6203 }
6204 }
6205 Ok(())
6206}
6207
6208fn attr_value<I: DocIndexLike>(elem: NodeId, name: &str, idx: &I) -> Option<String> {
6210 idx.attr_range(elem)
6211 .find(|&a| idx.namespace_uri(a).is_empty() && idx.local_name(a) == name)
6212 .map(|a| idx.string_value(a))
6213}
6214
6215fn json_escape_into(s: &str, out: &mut String) {
6217 for c in s.chars() {
6218 match c {
6219 '"' => out.push_str("\\\""),
6220 '\\' => out.push_str("\\\\"),
6221 '\u{0008}' => out.push_str("\\b"),
6222 '\u{000C}' => out.push_str("\\f"),
6223 '\n' => out.push_str("\\n"),
6224 '\r' => out.push_str("\\r"),
6225 '\t' => out.push_str("\\t"),
6226 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04X}", c as u32)),
6227 c => out.push(c),
6228 }
6229 }
6230}
6231
6232fn eval_function<I: DocIndexLike>(name: &str, args: &[Expr], ctx: &EvalCtx<'_>, idx: &I) -> Result<Value> {
6233 let (call_uri, call_local) = match name.split_once(':') {
6242 Some((prefix, local)) => match resolve_prefix_or_implicit(ctx.bindings, prefix) {
6243 Some(uri) => (uri, local.to_string()),
6244 None => return Err(xpath_err(format!(
6245 "Undefined namespace prefix in function name: {prefix}"
6246 ))),
6247 },
6248 None => (String::new(), name.to_string()),
6249 };
6250 let mut arg_vals: Option<Vec<Value>> = None;
6255 let take_args = |vals: &mut Option<Vec<Value>>, args: &[Expr]| -> Result<Vec<Value>> {
6256 if let Some(v) = vals.take() { return Ok(v); }
6257 args.iter().map(|e| eval_expr(e, ctx, idx)).collect()
6258 };
6259 {
6260 let vs = take_args(&mut arg_vals, args)?;
6261 let res = ctx.bindings.call_function_in(&call_uri, &call_local, vs.clone(), ctx.context_node);
6265 if let Some(r) = res { return r; }
6266 if let Some(r) = super::exslt::dispatch(&call_uri, &call_local, vs.clone(), idx) {
6270 return r;
6271 }
6272 if call_uri == super::exslt::DYN_NS && call_local == "evaluate" {
6278 return dyn_evaluate(&vs, ctx, idx);
6279 }
6280 if call_uri == "http://www.w3.org/2001/XMLSchema" {
6288 return Ok(xs_constructor(&call_local, &vs, idx, ctx.bindings)?);
6289 }
6290 if call_uri == "http://www.w3.org/2005/xpath-functions/map" {
6292 return eval_map_function(&call_local, &vs, ctx, idx);
6293 }
6294 if call_uri == "http://www.w3.org/2005/xpath-functions/array" {
6295 return eval_array_function(&call_local, &vs, ctx, idx);
6296 }
6297 if call_uri.is_empty()
6300 || call_uri == "http://www.w3.org/2005/xpath-functions"
6301 {
6302 if let Some(r) = eval_hof_function(&call_local, &vs, ctx, idx) {
6303 return r;
6304 }
6305 if let Some(r) = eval_json_function(&call_local, &vs, ctx, idx) {
6307 return r;
6308 }
6309 }
6310 arg_vals = Some(vs);
6311 }
6312 if !call_uri.is_empty()
6319 && call_uri != "http://www.w3.org/2005/xpath-functions"
6320 {
6321 return Err(xpath_err(format!(
6322 "Unregistered XPath function: {{{call_uri}}}{call_local}"
6323 )));
6324 }
6325 let _ = arg_vals; macro_rules! arg {
6331 ($n:expr) => {
6332 eval_expr(&args[$n], ctx, idx)?
6333 };
6334 }
6335 macro_rules! arg_str {
6336 ($n:expr) => {
6337 value_to_string_with(&arg!($n), idx, ctx.bindings)
6338 };
6339 }
6340 macro_rules! arg_num {
6341 ($n:expr) => {
6342 value_to_number_with(&arg!($n), idx, ctx.bindings)
6343 };
6344 }
6345 macro_rules! check_args {
6346 ($n:expr) => {
6347 if args.len() != $n {
6348 return Err(xpath_err(format!("{}() requires {} argument(s)", name, $n))
6349 .with_xpath_code("XPTY0004"));
6350 }
6351 };
6352 }
6353
6354 let name = call_local.as_str();
6358 match name {
6359 "true" => { check_args!(0); Ok(Value::Boolean(true)) }
6361 "false" => { check_args!(0); Ok(Value::Boolean(false)) }
6362 "not" => {
6363 check_args!(1);
6364 Ok(Value::Boolean(!value_to_bool(&arg!(0), idx)))
6365 }
6366 "boolean" => {
6367 check_args!(1);
6368 Ok(Value::Boolean(value_to_bool(&arg!(0), idx)))
6369 }
6370 "lang" => {
6371 if args.is_empty() || args.len() > 2 {
6378 return Err(xpath_err("lang() requires 1 or 2 arguments"));
6379 }
6380 let want = value_to_string(&arg!(0), idx);
6381 let want_lc = want.to_ascii_lowercase();
6382 let start = if args.len() == 2 {
6383 match arg!(1) {
6384 Value::NodeSet(ns) => ns.first().copied(),
6385 _ => return Err(xpath_err(
6386 "lang() second argument must be a node")),
6387 }
6388 } else {
6389 Some(ctx.context_node)
6390 };
6391 let mut current = start;
6392 while let Some(node) = current {
6393 for attr in idx.attr_range(node) {
6394 if !(idx.local_name(attr) == "lang" && idx.namespace_uri(attr) == "http://www.w3.org/XML/1998/namespace") { continue; }
6395 let val_lc = idx.string_value(attr).to_ascii_lowercase();
6396 let ok = val_lc == want_lc
6397 || (val_lc.len() > want_lc.len()
6398 && val_lc.starts_with(&want_lc)
6399 && val_lc.as_bytes()[want_lc.len()] == b'-');
6400 return Ok(Value::Boolean(ok));
6401 }
6402 current = idx.parent(node);
6403 }
6404 Ok(Value::Boolean(false))
6405 }
6406
6407 "last" => { check_args!(0); Ok(integer_result(ctx.size as i64, ctx.bindings)) }
6409 "position" => { check_args!(0); Ok(integer_result(ctx.pos as i64, ctx.bindings)) }
6410 "count" => {
6411 check_args!(1);
6412 Ok(integer_result(sequence_len(&arg!(0)) as i64, ctx.bindings))
6420 }
6421 "id" => {
6422 if args.is_empty() || args.len() > 2 {
6423 return Err(xpath_err("id() requires 1 or 2 arguments"));
6424 }
6425 let v = arg!(0);
6440 let search_root = if args.len() == 2 {
6441 match arg!(1) {
6442 Value::NodeSet(ns) => match ns.first().copied() {
6443 Some(n) => doc_root_of(n, idx),
6444 None => return Ok(Value::NodeSet(Vec::new())),
6445 },
6446 _ => return Err(xpath_err(
6447 "id() second argument must be a node")),
6448 }
6449 } else {
6450 doc_root_of(ctx.context_node, idx)
6451 };
6452 let tokens: Vec<String> = match &v {
6453 Value::NodeSet(ns) => {
6454 let mut out: Vec<String> = Vec::new();
6455 for &n in ns {
6456 out.extend(
6457 idx.string_value(n)
6458 .split_whitespace()
6459 .map(str::to_string),
6460 );
6461 }
6462 out
6463 }
6464 _ => value_to_string(&v, idx)
6465 .split_whitespace()
6466 .map(str::to_string)
6467 .collect(),
6468 };
6469 let mut hits: Vec<NodeId> = Vec::new();
6470 for node in descendants(search_root, idx, true) {
6471 if !matches!(idx.kind(node), XPathNodeKind::Element) { continue; }
6472 for attr in idx.attr_range(node) {
6473 if !idx.is_id_attribute(attr) { continue; }
6474 let av = idx.string_value(attr);
6475 if tokens.iter().any(|t| *t == av) {
6476 hits.push(node);
6477 break;
6478 }
6479 }
6480 }
6481 dedup_sort(&mut hits);
6482 Ok(Value::NodeSet(hits))
6483 }
6484 "idref" => {
6485 if args.is_empty() || args.len() > 2 {
6486 return Err(xpath_err("idref() requires 1 or 2 arguments"));
6487 }
6488 let v = arg!(0);
6495 let search_root = if args.len() == 2 {
6496 match arg!(1) {
6497 Value::NodeSet(ns) => match ns.first().copied() {
6498 Some(n) => doc_root_of(n, idx),
6499 None => return Ok(Value::NodeSet(Vec::new())),
6500 },
6501 _ => return Err(xpath_err(
6502 "idref() second argument must be a node")),
6503 }
6504 } else {
6505 doc_root_of(ctx.context_node, idx)
6506 };
6507 let tokens: Vec<String> = match &v {
6508 Value::NodeSet(ns) => ns.iter()
6509 .flat_map(|&n| idx.string_value(n)
6510 .split_whitespace().map(str::to_string).collect::<Vec<_>>())
6511 .collect(),
6512 _ => value_to_string(&v, idx)
6513 .split_whitespace().map(str::to_string).collect(),
6514 };
6515 let mut hits: Vec<NodeId> = Vec::new();
6516 for node in descendants(search_root, idx, true) {
6517 if !matches!(idx.kind(node), XPathNodeKind::Element) { continue; }
6518 for attr in idx.attr_range(node) {
6519 if !idx.is_idref_attribute(attr) { continue; }
6520 let av = idx.string_value(attr);
6524 if av.split_whitespace().any(|w| tokens.iter().any(|t| t == w)) {
6525 hits.push(attr);
6526 }
6527 }
6528 }
6529 dedup_sort(&mut hits);
6530 Ok(Value::NodeSet(hits))
6531 }
6532 "local-name" => {
6533 let id = if args.is_empty() {
6534 if ctx.static_ctx.xpath_2_0 && matches!(current_context_item(),
6537 Some(v) if !matches!(v,
6538 Value::NodeSet(_) | Value::ForeignNodeSet(_)))
6539 {
6540 return Err(xpath_err(
6541 "local-name(): context item is not a node (XPTY0004)"
6542 ).with_xpath_code("XPTY0004"));
6543 }
6544 Some(ctx.context_node)
6545 } else {
6546 match arg!(0) {
6550 Value::NodeSet(ns) => ns.first().copied(),
6551 _ => return Err(xpath_err("local-name() requires a node-set or no argument")),
6552 }
6553 };
6554 Ok(Value::String(id.map(|i| idx.local_name(i).to_string()).unwrap_or_default()))
6555 }
6556 "name" => {
6557 let id = if args.is_empty() {
6561 if ctx.static_ctx.xpath_2_0 && matches!(current_context_item(),
6562 Some(v) if !matches!(v,
6563 Value::NodeSet(_) | Value::ForeignNodeSet(_)))
6564 {
6565 return Err(xpath_err(
6566 "name(): context item is not a node (XPTY0004)"
6567 ).with_xpath_code("XPTY0004"));
6568 }
6569 Some(ctx.context_node)
6570 } else {
6571 match arg!(0) {
6572 Value::NodeSet(ns) => ns.first().copied(),
6573 _ => return Err(xpath_err("name() requires a node-set or no argument")),
6574 }
6575 };
6576 Ok(Value::String(id.map(|i| idx.node_name(i).to_string()).unwrap_or_default()))
6577 }
6578 "namespace-uri" => {
6579 let id = if args.is_empty() {
6580 if ctx.static_ctx.xpath_2_0 && matches!(current_context_item(),
6581 Some(v) if !matches!(v,
6582 Value::NodeSet(_) | Value::ForeignNodeSet(_)))
6583 {
6584 return Err(xpath_err(
6585 "namespace-uri(): context item is not a node (XPTY0004)"
6586 ).with_xpath_code("XPTY0004"));
6587 }
6588 Some(ctx.context_node)
6589 } else {
6590 match arg!(0) {
6591 Value::NodeSet(ns) => ns.first().copied(),
6592 _ => return Err(xpath_err("namespace-uri() requires a node-set or no argument")),
6593 }
6594 };
6595 Ok(Value::String(id.map(|i| idx.namespace_uri(i).to_string()).unwrap_or_default()))
6596 }
6597
6598 "string" => {
6600 if args.is_empty() {
6601 Ok(Value::String(idx.string_value(ctx.context_node)))
6602 } else {
6603 check_args!(1);
6604 let a = arg!(0);
6605 if value_is_function(&a) {
6606 return Err(xpath_err(
6607 "string(): a function item has no string value (FOTY0014)")
6608 .with_xpath_code("FOTY0014"));
6609 }
6610 Ok(Value::String(value_to_string_with_compat(
6611 &a, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible)))
6612 }
6613 }
6614 "concat" => {
6615 if args.len() < 2 {
6616 return Err(xpath_err("concat() requires at least 2 arguments"));
6617 }
6618 let mut s = String::new();
6619 for a in args {
6620 let v = eval_expr(a, ctx, idx)?;
6621 s.push_str(&value_to_string_with_compat(
6622 &v, idx, ctx.bindings, ctx.static_ctx.libxml2_compatible));
6623 }
6624 Ok(Value::String(s))
6625 }
6626 "starts-with" => {
6627 if args.len() < 2 || args.len() > 3 {
6628 return Err(xpath_err("starts-with() requires 2 or 3 arguments"));
6629 }
6630 let s = arg_str!(0);
6631 let pre = arg_str!(1);
6632 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
6633 Ok(Value::Boolean(collation_starts_with(&s, &pre, coll.as_deref())))
6634 }
6635 "contains" => {
6636 if args.len() < 2 || args.len() > 3 {
6637 return Err(xpath_err("contains() requires 2 or 3 arguments"));
6638 }
6639 let s = arg_str!(0);
6640 let sub = arg_str!(1);
6641 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
6642 Ok(Value::Boolean(collation_contains(&s, &sub, coll.as_deref())))
6643 }
6644 "substring-before" => {
6645 if args.len() < 2 || args.len() > 3 {
6646 return Err(xpath_err("substring-before() requires 2 or 3 arguments"));
6647 }
6648 let s = arg_str!(0);
6649 let sep = arg_str!(1);
6650 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
6651 let find_pos = if is_ascii_ci_collation(coll.as_deref()) {
6656 ascii_ci_fold(&s).find(&ascii_ci_fold(&sep))
6657 } else {
6658 s.find(sep.as_str())
6659 };
6660 Ok(Value::String(match find_pos {
6661 Some(pos) => s[..pos].to_string(),
6662 None => String::new(),
6663 }))
6664 }
6665 "substring-after" => {
6666 if args.len() < 2 || args.len() > 3 {
6667 return Err(xpath_err("substring-after() requires 2 or 3 arguments"));
6668 }
6669 let s = arg_str!(0);
6670 let sep = arg_str!(1);
6671 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
6672 let find_pos = if is_ascii_ci_collation(coll.as_deref()) {
6673 ascii_ci_fold(&s).find(&ascii_ci_fold(&sep))
6674 } else {
6675 s.find(sep.as_str())
6676 };
6677 Ok(Value::String(match find_pos {
6678 Some(pos) => s[pos + sep.len()..].to_string(),
6679 None => String::new(),
6680 }))
6681 }
6682 "substring" => {
6683 if args.len() < 2 || args.len() > 3 {
6684 return Err(xpath_err("substring() requires 2 or 3 arguments"));
6685 }
6686 let s = arg_str!(0);
6687 let chars: Vec<char> = s.chars().collect();
6688 let len = chars.len();
6689 let len_f = len as f64;
6695 let start_1based = arg_num!(1).round();
6696 let end_1based = if args.len() == 3 {
6697 let cnt = arg_num!(2).round();
6699 let e = start_1based + cnt;
6700 if e.is_nan() { 0.0 } else { e }
6701 } else {
6702 len_f + 1.0
6703 };
6704 let s_f = (start_1based - 1.0).max(0.0).min(len_f);
6706 let e_f = (end_1based - 1.0).max(0.0).min(len_f);
6707 let (s_idx, e_idx) = if s_f <= e_f {
6708 (s_f as usize, e_f as usize)
6709 } else {
6710 (0, 0)
6711 };
6712 let result: String = chars[s_idx..e_idx].iter().collect();
6713 Ok(Value::String(result))
6714 }
6715 "string-length" => {
6716 let s = if args.is_empty() {
6717 idx.string_value(ctx.context_node)
6718 } else {
6719 check_args!(1);
6720 arg_str!(0)
6721 };
6722 Ok(integer_result(s.chars().count() as i64, ctx.bindings))
6723 }
6724 "normalize-space" => {
6725 let s = if args.is_empty() {
6726 idx.string_value(ctx.context_node)
6727 } else {
6728 check_args!(1);
6729 arg_str!(0)
6730 };
6731 let normalized = s.split_whitespace().collect::<Vec<_>>().join(" ");
6732 Ok(Value::String(normalized))
6733 }
6734 "translate" => {
6735 check_args!(3);
6736 let s = arg_str!(0);
6737 let from = arg_str!(1);
6738 let to: Vec<char> = arg_str!(2).chars().collect();
6739 let mut map: std::collections::HashMap<char, Option<char>> =
6746 std::collections::HashMap::new();
6747 for (i, f) in from.chars().enumerate() {
6748 map.entry(f).or_insert_with(|| to.get(i).copied());
6749 }
6750 let result: String = s
6751 .chars()
6752 .filter_map(|c| match map.get(&c) {
6753 Some(Some(t)) => Some(*t),
6754 Some(None) => None,
6755 None => Some(c),
6756 })
6757 .collect();
6758 Ok(Value::String(result))
6759 }
6760
6761 "number" => {
6763 let v = if args.is_empty() {
6764 Value::String(idx.string_value(ctx.context_node))
6765 } else {
6766 check_args!(1);
6767 arg!(0)
6768 };
6769 if value_is_function(&v)
6773 || matches!(&v, Value::Sequence(items) if items.iter().any(value_is_function))
6774 {
6775 return Err(xpath_err(
6776 "number(): a function item cannot be atomized (FOTY0013)")
6777 .with_xpath_code("FOTY0013"));
6778 }
6779 if ctx.static_ctx.libxml2_compatible {
6784 if let Value::String(ref s) = v {
6785 if s.trim() == "-" { return Ok(Value::Number(Numeric::Double(-0.0))); }
6786 }
6787 }
6788 Ok(Value::Number(Numeric::Double(value_to_number(&v, idx))))
6792 }
6793 "sum" => {
6794 if args.is_empty() || args.len() > 2 {
6799 return Err(xpath_err(format!(
6800 "sum() requires 1 or 2 arguments (got {})", args.len()
6801 )));
6802 }
6803 let zero_val: Value = if args.len() == 2 { arg!(1) } else { Value::Number(Numeric::Double(0.0)) };
6807 match arg!(0).untyped() {
6808 Value::NodeSet(ns) => {
6809 if ns.is_empty() {
6810 return Ok(zero_val);
6811 }
6812 let total: f64 = ns.iter().map(|&id|
6813 idx.string_value(id).trim().parse::<f64>().unwrap_or(f64::NAN)
6814 ).sum();
6815 Ok(Value::Number(Numeric::Double(total)))
6816 }
6817 Value::Number(n) => Ok(Value::Number(n)),
6818 Value::String(s) => Ok(Value::Number(Numeric::Double(s.trim().parse::<f64>().unwrap_or(f64::NAN)))),
6819 Value::Boolean(b) => Ok(Value::Number(Numeric::Double(if b { 1.0 } else { 0.0 }))),
6820 Value::Sequence(items) => {
6824 if items.is_empty() {
6825 return Ok(zero_val);
6826 }
6827 if let Some((kind, total, _)) = duration_seq_total(&items) {
6831 return Ok(duration_value(kind, total));
6832 }
6833 for v in &items {
6842 if let Value::String(s) = v {
6843 if s.trim().parse::<f64>().is_err() {
6844 return Err(xpath_err(format!(
6845 "sum(): non-numeric item '{s}' \
6846 (FORG0006)"
6847 )).with_xpath_code("FORG0006"));
6848 }
6849 }
6850 }
6851 let total: f64 = items.iter()
6852 .map(|v| value_to_number_with(v, idx, ctx.bindings))
6853 .sum();
6854 let kind = items.iter().fold(Some("integer"), |acc, v| {
6860 match (acc, numeric_kind_of(v)) {
6861 (Some(a), Some(b)) => numeric_promote_kind(Some(a), Some(b)),
6862 _ => None,
6863 }
6864 });
6865 Ok(Value::Number(match kind {
6866 Some(k) => Numeric::of_kind(k, total),
6867 None => Numeric::Double(total),
6868 }))
6869 }
6870 Value::ForeignNodeSet(_) => Err(xpath_err(
6871 "sum() over foreign node-sets not supported")),
6872 Value::Typed(_) => unreachable!(),
6873 Value::IntRange { lo, hi } => {
6876 let total = ((hi - lo + 1) as f64) * ((lo + hi) as f64) * 0.5;
6877 Ok(Value::Number(Numeric::of_kind("integer", total)))
6878 }
6879 Value::Map(_) | Value::Array(_) | Value::Function(_) => Ok(zero_val),
6882 }
6883 }
6884 "floor" => {
6885 check_args!(1);
6886 let a = arg!(0);
6887 if ctx.static_ctx.xpath_2_0 {
6888 if let Value::String(s) = &a {
6889 if s.trim().parse::<f64>().is_err() {
6890 return Err(xpath_err(format!(
6891 "floor(): argument '{s}' is not a number (XPTY0004)"
6892 )).with_xpath_code("XPTY0004"));
6893 }
6894 }
6895 }
6896 Ok(preserve_numeric_kind(&a, value_to_number(&a, idx).floor()))
6897 }
6898 "ceiling" => {
6899 check_args!(1);
6900 let a = arg!(0);
6901 if ctx.static_ctx.xpath_2_0 {
6902 if let Value::String(s) = &a {
6903 if s.trim().parse::<f64>().is_err() {
6904 return Err(xpath_err(format!(
6905 "ceiling(): argument '{s}' is not a number (XPTY0004)"
6906 )).with_xpath_code("XPTY0004"));
6907 }
6908 }
6909 }
6910 Ok(preserve_numeric_kind(&a, value_to_number(&a, idx).ceil()))
6911 }
6912 "round" => {
6913 if args.is_empty() || args.len() > 2 {
6917 return Err(xpath_err("round() requires 1 or 2 argument(s)"));
6918 }
6919 if args.len() == 2 && !ctx.static_ctx.xpath_3_0 {
6920 return Err(xpath_err(
6921 "round(): the 2-argument form requires XPath 3.0 (XPST0017)")
6922 .with_xpath_code("XPST0017"));
6923 }
6924 let a = arg!(0);
6925 if ctx.static_ctx.xpath_2_0 {
6930 if let Value::String(s) = &a {
6931 if s.trim().parse::<f64>().is_err() {
6932 return Err(xpath_err(format!(
6933 "round(): argument '{s}' is not a number (XPTY0004)"
6934 )).with_xpath_code("XPTY0004"));
6935 }
6936 }
6937 }
6938 let precision = if args.len() == 2 {
6939 value_to_number(&arg!(1), idx) as i32
6940 } else { 0 };
6941 use rust_decimal::prelude::ToPrimitive;
6946 match &a {
6947 Value::Number(Numeric::Decimal(d)) =>
6948 Ok(Value::Number(Numeric::Decimal(round_decimal_half_to_pos_inf(*d, precision)))),
6949 Value::Number(Numeric::Integer(i)) => {
6950 let d = round_decimal_half_to_pos_inf(rust_decimal::Decimal::from(*i), precision);
6951 Ok(Value::Number(match d.to_i64() {
6952 Some(v) => Numeric::Integer(v),
6953 None => Numeric::Decimal(d),
6954 }))
6955 }
6956 _ => {
6957 let n = value_to_number(&a, idx);
6958 let r = if !n.is_finite() {
6959 n
6960 } else {
6961 let scale = 10f64.powi(precision);
6962 let scaled = n * scale;
6963 let rs = if scaled.is_sign_negative() && scaled >= -0.5 { -0.0 }
6964 else { (scaled + 0.5).floor() };
6965 rs / scale
6966 };
6967 Ok(preserve_numeric_kind(&a, r))
6968 }
6969 }
6970 }
6971
6972 "document" => {
6977 if args.is_empty() || args.len() > 2 {
6978 return Err(xpath_err("document() requires 1 or 2 arguments"));
6979 }
6980 let uri = value_to_string(&arg!(0), idx);
6981 match ctx.bindings.load_document(&uri, None) {
6986 Some(r) => r.map(Value::ForeignNodeSet)
6987 .map_err(|e| e.or_xpath_code("FODC0002")),
6988 None => Err(xpath_err(
6989 "document(): bindings don't support external document loading",
6990 ).with_xpath_code("FODC0002")),
6991 }
6992 }
6993
6994 "ends-with" => {
7003 if args.len() < 2 || args.len() > 3 {
7004 return Err(xpath_err("ends-with() requires 2 or 3 arguments"));
7005 }
7006 let s = arg_str!(0);
7007 let suf = arg_str!(1);
7008 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
7009 Ok(Value::Boolean(collation_ends_with(&s, &suf, coll.as_deref())))
7010 }
7011 "exists" => {
7012 check_args!(1);
7013 let v = arg!(0);
7014 Ok(Value::Boolean(match v {
7015 Value::NodeSet(ns) => !ns.is_empty(),
7020 Value::ForeignNodeSet(ns) => !ns.is_empty(),
7021 Value::Sequence(items) => !items.is_empty(),
7022 Value::IntRange { lo, hi } => hi >= lo,
7023 _ => true,
7024 }))
7025 }
7026 "empty" => {
7027 check_args!(1);
7028 let v = arg!(0);
7029 Ok(Value::Boolean(match v {
7030 Value::NodeSet(ns) => ns.is_empty(),
7031 Value::ForeignNodeSet(ns) => ns.is_empty(),
7032 Value::Sequence(items) => items.is_empty(),
7033 Value::IntRange { lo, hi } => hi < lo,
7034 _ => false,
7035 }))
7036 }
7037 "data" => {
7038 check_args!(1);
7045 let v = arg!(0);
7046 match v {
7047 Value::NodeSet(ns) => {
7048 let items: Vec<Value> = ns.iter()
7049 .map(|&id| {
7050 let sv = idx.string_value(id);
7051 ctx.bindings.node_typed_value(id, &sv)
7055 .unwrap_or(Value::String(sv))
7056 })
7057 .collect();
7058 match items.len() {
7059 0 => Ok(Value::NodeSet(Vec::new())),
7060 1 => Ok(items.into_iter().next().unwrap()),
7061 _ => Ok(Value::Sequence(items)),
7062 }
7063 }
7064 Value::ForeignNodeSet(ns) => {
7065 let items: Vec<Value> = ns.iter()
7066 .map(|&p| Value::String(ctx.bindings.foreign_string_value(p)))
7067 .collect();
7068 match items.len() {
7069 0 => Ok(Value::NodeSet(Vec::new())),
7070 1 => Ok(items.into_iter().next().unwrap()),
7071 _ => Ok(Value::Sequence(items)),
7072 }
7073 }
7074 Value::Function(_) => Err(xpath_err(
7077 "data(): a function item cannot be atomized (FOTY0013)")
7078 .with_xpath_code("FOTY0013")),
7079 Value::Sequence(items) if items.iter().any(value_is_function) =>
7080 Err(xpath_err(
7081 "data(): a function item cannot be atomized (FOTY0013)")
7082 .with_xpath_code("FOTY0013")),
7083 other => Ok(other),
7084 }
7085 }
7086 "current-dateTime" => {
7087 check_args!(0);
7088 let now = stable_now();
7089 Ok(Value::Typed(Box::new(TypedAtomic {
7090 kind: "dateTime",
7091 lexical: format_datetime_utc(now),
7092 numeric: None,
7093 boolean: None, user_type: None,
7094 })))
7095 }
7096 "current-date" => {
7097 check_args!(0);
7098 let now = stable_now();
7099 Ok(Value::Typed(Box::new(TypedAtomic {
7100 kind: "date",
7101 lexical: format_date_utc(now),
7102 numeric: None,
7103 boolean: None, user_type: None,
7104 })))
7105 }
7106 "current-time" => {
7107 check_args!(0);
7108 let now = stable_now();
7109 Ok(Value::Typed(Box::new(TypedAtomic {
7110 kind: "time",
7111 lexical: format_time_utc(now),
7112 numeric: None,
7113 boolean: None, user_type: None,
7114 })))
7115 }
7116 "adjust-dateTime-to-timezone"
7123 | "adjust-date-to-timezone"
7124 | "adjust-time-to-timezone" => {
7125 if args.is_empty() || args.len() > 2 {
7126 return Err(xpath_err(format!(
7127 "{name}() requires 1 or 2 arguments (got {})", args.len()
7128 )));
7129 }
7130 let kind = match name {
7131 "adjust-dateTime-to-timezone" => "dateTime",
7132 "adjust-date-to-timezone" => "date",
7133 "adjust-time-to-timezone" => "time",
7134 _ => unreachable!(),
7135 };
7136 if let Value::NodeSet(ns) = &arg!(0) {
7138 if ns.is_empty() { return Ok(Value::NodeSet(Vec::new())); }
7139 }
7140 let lex = value_to_string_with(&arg!(0), idx, ctx.bindings);
7141 let new_tz_minutes: Option<i16> = if args.len() == 2 {
7142 let is_empty = matches!(&arg!(1),
7144 Value::NodeSet(ns) if ns.is_empty());
7145 if is_empty {
7146 None
7147 } else {
7148 let dur = value_to_string_with(&arg!(1), idx, ctx.bindings);
7149 let secs = parse_day_time_duration_secs(&dur).unwrap_or(0);
7150 let mins = secs / 60;
7151 if mins.abs() > 14 * 60 {
7152 return Err(xpath_err(format!(
7153 "adjust timezone exceeds 14 hours: {dur}"
7154 )));
7155 }
7156 Some(mins as i16)
7157 }
7158 } else {
7159 Some(0) };
7161 Ok(Value::Typed(Box::new(TypedAtomic {
7162 kind,
7163 lexical: adjust_timezone(&lex, kind, new_tz_minutes),
7164 numeric: None,
7165 boolean: None, user_type: None,
7166 })))
7167 }
7168
7169 "matches" => {
7180 if args.len() < 2 || args.len() > 3 {
7181 return Err(xpath_err(format!(
7182 "matches() requires 2 or 3 arguments (got {})", args.len()
7183 )));
7184 }
7185 let input = arg_str!(0);
7186 let pattern = arg_str!(1);
7187 let flags = if args.len() == 3 { arg_str!(2) } else { String::new() };
7188 if flags.is_empty() {
7199 return crate::regex::compile_with_cached(
7200 &pattern, ctx.bindings.regex_dialect(),
7201 )
7202 .map(|p| Value::Boolean(p.find_match(&input)))
7203 .map_err(|e| xpath_err(format!("invalid regex: {e}"))
7204 .with_xpath_code("FORX0002"));
7205 }
7206 let re = compile_xpath_regex_dialect(&pattern, &flags,
7207 ctx.bindings.regex_dialect())?;
7208 Ok(Value::Boolean(re.is_match(&input)))
7209 }
7210 "replace" => {
7211 if args.len() < 3 || args.len() > 4 {
7212 return Err(xpath_err(format!(
7213 "replace() requires 3 or 4 arguments (got {})", args.len()
7214 )));
7215 }
7216 let input = arg_str!(0);
7217 let pattern = arg_str!(1);
7218 let replacement = arg_str!(2);
7219 let flags = if args.len() == 4 { arg_str!(3) } else { String::new() };
7220 let re = compile_xpath_regex_dialect(&pattern, &flags,
7221 ctx.bindings.regex_dialect())?;
7222 if re.is_match("") {
7226 return Err(xpath_err(format!(
7227 "replace(): the pattern '{pattern}' matches a \
7228 zero-length string (FORX0003)"
7229 )).with_xpath_code("FORX0003"));
7230 }
7231 let group_count = re.captures_len().saturating_sub(1);
7244 let translated = translate_xpath_replacement(&replacement, group_count)?;
7245 Ok(Value::String(re.replace_all(&input, translated.as_str()).into_owned()))
7246 }
7247 "tokenize" => {
7248 if args.is_empty() || args.len() > 3 {
7252 return Err(xpath_err(format!(
7253 "tokenize() requires 1 to 3 arguments (got {})", args.len()
7254 )));
7255 }
7256 let input = arg_str!(0);
7257 let pattern = if args.len() >= 2 { arg_str!(1) } else { r"\s+".to_string() };
7258 let flags = if args.len() == 3 { arg_str!(2) } else { String::new() };
7259 if input.is_empty() {
7263 return Ok(Value::NodeSet(Vec::new()));
7264 }
7265 let re = compile_xpath_regex_dialect(&pattern, &flags,
7266 ctx.bindings.regex_dialect())?;
7267 if re.is_match("") {
7271 return Err(xpath_err(format!(
7272 "tokenize(): the pattern '{pattern}' matches a \
7273 zero-length string (FORX0003)"
7274 )).with_xpath_code("FORX0003"));
7275 }
7276 let parts: Vec<String> = re.split(&input).map(str::to_string).collect();
7277 match idx.allocate_rtf_text_nodes(parts.clone()) {
7282 Some(ids) => Ok(Value::NodeSet(ids)),
7283 None => Ok(Value::String(parts.join(" "))),
7284 }
7285 }
7286
7287 "lower-case" => {
7296 check_args!(1);
7297 Ok(Value::String(arg_str!(0).to_lowercase()))
7298 }
7299 "upper-case" => {
7300 check_args!(1);
7301 Ok(Value::String(arg_str!(0).to_uppercase()))
7302 }
7303 "string-join" => {
7304 if args.is_empty() || args.len() > 2 {
7309 return Err(xpath_err(format!(
7310 "string-join() requires 1 or 2 arguments (got {})", args.len()
7311 )));
7312 }
7313 let sep = if args.len() == 2 { arg_str!(1) } else { String::new() };
7314 let pieces = sequence_to_strings(&arg!(0), idx);
7315 Ok(Value::String(pieces.join(&sep)))
7316 }
7317 "abs" => {
7318 check_args!(1);
7319 let a = arg!(0);
7320 Ok(preserve_numeric_kind(&a, value_to_number(&a, idx).abs()))
7321 }
7322 "min" | "max" => {
7333 if args.is_empty() || args.len() > 2 {
7334 return Err(xpath_err(format!(
7335 "{name}() requires 1 or 2 arguments (got {})", args.len()
7336 )));
7337 }
7338 let coll = effective_collation(
7339 if args.len() == 2 { Some(arg_str!(1)) } else { None });
7340 let ci = is_ascii_ci_collation(coll.as_deref());
7341 let op = if name == "min" { MinMaxOp::Min } else { MinMaxOp::Max };
7342 min_max_avg(&arg!(0), idx, op, ci)
7343 }
7344 "avg" => {
7345 check_args!(1);
7346 min_max_avg(&arg!(0), idx, MinMaxOp::Avg, false)
7347 }
7348 "distinct-values" => {
7349 if args.is_empty() || args.len() > 2 {
7350 return Err(xpath_err(format!(
7351 "distinct-values() requires 1 or 2 arguments (got {})", args.len()
7352 )));
7353 }
7354 let coll = effective_collation(if args.len() == 2 { Some(arg_str!(1)) } else { None });
7355 let ci = is_ascii_ci_collation(coll.as_deref());
7356 let mut seen = std::collections::HashSet::new();
7357 let mut keep = Vec::new();
7358 for s in sequence_to_strings(&arg!(0), idx) {
7359 let k = if ci { ascii_ci_fold(&s) } else { s.clone() };
7360 if seen.insert(k) { keep.push(s); }
7361 }
7362 match idx.allocate_rtf_text_nodes(keep.clone()) {
7363 Some(ids) => Ok(Value::NodeSet(ids)),
7364 None => Ok(Value::String(keep.join(" "))),
7365 }
7366 }
7367 "index-of" => {
7368 if args.len() < 2 || args.len() > 3 {
7372 return Err(xpath_err(format!(
7373 "index-of() requires 2 or 3 arguments (got {})", args.len()
7374 )));
7375 }
7376 let target = arg_str!(1);
7377 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
7378 let items = sequence_to_strings(&arg!(0), idx);
7379 let (key_target, items_keys): (String, Vec<String>) =
7380 if is_ascii_ci_collation(coll.as_deref()) {
7381 (ascii_ci_fold(&target),
7382 items.iter().map(|s| ascii_ci_fold(s)).collect())
7383 } else {
7384 (target, items)
7385 };
7386 let positions: Vec<String> = items_keys.iter().enumerate()
7387 .filter_map(|(i, s)|
7388 if s == &key_target { Some((i + 1).to_string()) } else { None })
7389 .collect();
7390 match idx.allocate_rtf_text_nodes(positions.clone()) {
7391 Some(ids) => Ok(Value::NodeSet(ids)),
7392 None => Ok(Value::String(positions.join(" "))),
7393 }
7394 }
7395 "subsequence" => {
7396 if args.len() < 2 || args.len() > 3 {
7406 return Err(xpath_err(format!(
7407 "subsequence() requires 2 or 3 arguments (got {})", args.len()
7408 )));
7409 }
7410 let is_empty = |v: &Value| matches!(v,
7415 Value::Sequence(items) if items.is_empty())
7416 || matches!(v, Value::NodeSet(ns) if ns.is_empty());
7417 if ctx.static_ctx.xpath_2_0 && is_empty(&arg!(1)) {
7418 return Err(xpath_err(
7419 "subsequence(): the start argument must not be the \
7420 empty sequence (XPTY0004)"
7421 ).with_xpath_code("XPTY0004"));
7422 }
7423 if ctx.static_ctx.xpath_2_0 && args.len() == 3 && is_empty(&arg!(2)) {
7424 return Err(xpath_err(
7425 "subsequence(): the length argument must not be the \
7426 empty sequence (XPTY0004)"
7427 ).with_xpath_code("XPTY0004"));
7428 }
7429 let seq = arg!(0);
7430 let start_f = value_to_number(&arg!(1), idx).round();
7431 let length_f = if args.len() == 3 {
7432 value_to_number(&arg!(2), idx).round()
7433 } else {
7434 f64::INFINITY
7435 };
7436 let slice_bounds = |count: usize| -> (usize, usize) {
7439 if start_f.is_nan() || length_f.is_nan() {
7440 return (0, 0);
7441 }
7442 let end = start_f + length_f; if end.is_nan() { return (0, 0); } let lo_p = start_f.max(1.0); if lo_p >= end { return (0, 0); }
7446 let lo = ((lo_p - 1.0) as usize).min(count);
7447 let hi = if end.is_infinite() && end > 0.0 {
7448 count
7449 } else {
7450 ((end - 1.0).max(0.0) as usize).min(count)
7451 };
7452 (lo, hi.max(lo))
7453 };
7454 if let Value::NodeSet(ns) = &seq {
7458 let (lo, hi) = slice_bounds(ns.len());
7459 return Ok(Value::NodeSet(ns[lo..hi].to_vec()));
7460 }
7461 if let Value::Sequence(items) = &seq {
7462 let (lo, hi) = slice_bounds(items.len());
7463 let out: Vec<Value> = items[lo..hi].to_vec();
7464 return Ok(if out.len() == 1 {
7465 out.into_iter().next().unwrap()
7466 } else {
7467 Value::Sequence(out)
7468 });
7469 }
7470 let pieces = sequence_to_strings(&seq, idx);
7471 let (lo, hi) = slice_bounds(pieces.len());
7472 let pieces: Vec<String> = pieces[lo..hi].to_vec();
7473 match idx.allocate_rtf_text_nodes(pieces.clone()) {
7474 Some(ids) => Ok(Value::NodeSet(ids)),
7475 None => Ok(Value::String(pieces.join(""))),
7476 }
7477 }
7478 "reverse" => {
7479 check_args!(1);
7480 if let Value::NodeSet(ns) = arg!(0) {
7481 let mut rev = ns;
7482 rev.reverse();
7483 return Ok(Value::NodeSet(rev));
7484 }
7485 if let Value::Sequence(items) = arg!(0) {
7486 let mut rev = items;
7487 rev.reverse();
7488 return Ok(Value::Sequence(rev));
7489 }
7490 let mut pieces = sequence_to_strings(&arg!(0), idx);
7491 pieces.reverse();
7492 match idx.allocate_rtf_text_nodes(pieces.clone()) {
7493 Some(ids) => Ok(Value::NodeSet(ids)),
7494 None => Ok(Value::String(pieces.join(""))),
7495 }
7496 }
7497 "unordered" => {
7498 check_args!(1);
7504 Ok(arg!(0))
7505 }
7506 "path" => {
7519 if args.len() > 1 {
7520 return Err(xpath_err(format!(
7521 "path() requires 0 or 1 arguments (got {})", args.len()
7522 )));
7523 }
7524 let v = if args.is_empty() {
7525 Value::NodeSet(vec![ctx.context_node])
7526 } else { arg!(0) };
7527 let node = match v {
7528 Value::NodeSet(ref ns) => ns.first().copied(),
7529 _ => None,
7530 };
7531 let Some(node) = node else {
7532 return Ok(Value::NodeSet(Vec::new()));
7533 };
7534 Ok(Value::String(node_path_string(node, idx)))
7535 }
7536 "sort" => {
7544 if args.is_empty() || args.len() > 3 {
7545 return Err(xpath_err(format!(
7546 "sort() requires 1 to 3 arguments (got {})", args.len()
7547 )));
7548 }
7549 if args.len() == 3 {
7550 return Err(xpath_err(
7551 "sort() with key-extractor function not supported"));
7552 }
7553 let input = arg!(0);
7554 let mut items = items_of(&input);
7556 let nums: Option<Vec<f64>> = items.iter().map(|v| {
7560 let s = value_to_string_with(v, idx, ctx.bindings);
7561 s.trim().parse::<f64>().ok()
7562 }).collect();
7563 if let Some(nums) = nums {
7564 let mut indexed: Vec<(usize, f64)> = nums.into_iter().enumerate().collect();
7565 indexed.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
7566 let order: Vec<usize> = indexed.into_iter().map(|(i, _)| i).collect();
7567 let sorted: Vec<Value> = order.into_iter().map(|i| items[i].clone()).collect();
7568 return Ok(if sorted.len() == 1 { sorted.into_iter().next().unwrap() }
7569 else { Value::Sequence(sorted) });
7570 }
7571 let keys: Vec<String> = items.iter()
7573 .map(|v| value_to_string_with(v, idx, ctx.bindings))
7574 .collect();
7575 let mut order: Vec<usize> = (0..items.len()).collect();
7576 order.sort_by(|&a, &b| keys[a].cmp(&keys[b]));
7577 let sorted: Vec<Value> = order.into_iter().map(|i| std::mem::replace(
7578 &mut items[i], Value::NodeSet(Vec::new()))).collect();
7579 Ok(if sorted.len() == 1 { sorted.into_iter().next().unwrap() }
7580 else { Value::Sequence(sorted) })
7581 }
7582 "head" => {
7583 check_args!(1);
7584 if let Value::NodeSet(ns) = arg!(0) {
7585 return Ok(Value::NodeSet(ns.into_iter().take(1).collect()));
7586 }
7587 let pieces = sequence_to_strings(&arg!(0), idx);
7588 Ok(match pieces.into_iter().next() {
7589 Some(s) => Value::String(s),
7590 None => Value::NodeSet(Vec::new()),
7591 })
7592 }
7593 "tail" => {
7594 check_args!(1);
7595 if let Value::NodeSet(ns) = arg!(0) {
7596 return Ok(Value::NodeSet(ns.into_iter().skip(1).collect()));
7597 }
7598 let pieces: Vec<String> = sequence_to_strings(&arg!(0), idx).into_iter().skip(1).collect();
7599 match idx.allocate_rtf_text_nodes(pieces.clone()) {
7600 Some(ids) => Ok(Value::NodeSet(ids)),
7601 None => Ok(Value::String(pieces.join(""))),
7602 }
7603 }
7604 "insert-before" => {
7611 check_args!(3);
7612 let seq = arg!(0);
7613 let pos = value_to_number(&arg!(1), idx).round() as i64;
7614 let ins = arg!(2);
7615 let any_seq = matches!(seq, Value::Sequence(_))
7616 || matches!(ins, Value::Sequence(_));
7617 if any_seq {
7618 let to_items = |v: Value| -> Vec<Value> {
7619 match v {
7620 Value::Sequence(xs) => xs,
7621 Value::NodeSet(ns) => ns.into_iter()
7622 .map(|n| Value::NodeSet(vec![n])).collect(),
7623 other => vec![other],
7624 }
7625 };
7626 let mut a = to_items(seq);
7627 let mut b = to_items(ins);
7628 let p = if pos < 1 { 0 } else { (pos as usize - 1).min(a.len()) };
7629 let tail = a.split_off(p);
7630 a.append(&mut b);
7631 a.extend(tail);
7632 return Ok(Value::Sequence(a));
7633 }
7634 let into_ids = |v: Value| -> Vec<NodeId> {
7635 match v.untyped() {
7636 Value::NodeSet(ns) => ns,
7637 Value::String(s) => idx.allocate_rtf_text_nodes(vec![s])
7638 .unwrap_or_default(),
7639 Value::Number(n) => idx.allocate_rtf_text_nodes(
7640 vec![value_to_string(&Value::Number(n), idx)])
7641 .unwrap_or_default(),
7642 Value::Boolean(b) => idx.allocate_rtf_text_nodes(
7643 vec![if b { "true".into() } else { "false".into() }])
7644 .unwrap_or_default(),
7645 Value::ForeignNodeSet(_) => Vec::new(),
7646 Value::IntRange { lo, hi } => idx.allocate_rtf_text_nodes(
7647 (lo..=hi).map(|i| i.to_string()).collect()
7648 ).unwrap_or_default(),
7649 Value::Typed(_) | Value::Sequence(_) => unreachable!(),
7650 Value::Map(_) | Value::Array(_) | Value::Function(_) => Vec::new(),
7651 }
7652 };
7653 let mut a = into_ids(seq);
7654 let mut b = into_ids(ins);
7655 let p = if pos < 1 { 0 } else { (pos as usize - 1).min(a.len()) };
7656 let tail = a.split_off(p);
7657 a.append(&mut b);
7658 a.extend(tail);
7659 Ok(Value::NodeSet(a))
7660 }
7661 "remove" => {
7665 check_args!(2);
7666 let seq = arg!(0);
7667 let pos = value_to_number(&arg!(1), idx).round() as i64;
7668 if let Value::Sequence(mut items) = seq {
7669 if pos >= 1 && (pos as usize) <= items.len() {
7670 items.remove(pos as usize - 1);
7671 }
7672 return Ok(Value::Sequence(items));
7673 }
7674 let mut items: Vec<NodeId> = match seq.untyped() {
7675 Value::NodeSet(ns) => ns,
7676 Value::String(s) => idx.allocate_rtf_text_nodes(vec![s])
7677 .unwrap_or_default(),
7678 Value::Number(n) => idx.allocate_rtf_text_nodes(
7679 vec![value_to_string(&Value::Number(n), idx)])
7680 .unwrap_or_default(),
7681 Value::Boolean(b) => idx.allocate_rtf_text_nodes(
7682 vec![if b { "true".into() } else { "false".into() }])
7683 .unwrap_or_default(),
7684 Value::ForeignNodeSet(_) => Vec::new(),
7685 Value::IntRange { lo, hi } => idx.allocate_rtf_text_nodes(
7686 (lo..=hi).map(|i| i.to_string()).collect()
7687 ).unwrap_or_default(),
7688 Value::Typed(_) | Value::Sequence(_) => unreachable!(),
7691 Value::Map(_) | Value::Array(_) | Value::Function(_) => Vec::new(),
7692 };
7693 if pos >= 1 && (pos as usize) <= items.len() {
7694 items.remove(pos as usize - 1);
7695 }
7696 Ok(Value::NodeSet(items))
7697 }
7698 "empty-sequence" => {
7699 check_args!(0);
7700 Ok(Value::NodeSet(Vec::new()))
7701 }
7702 "format-date" => {
7703 if args.len() < 2 || args.len() > 5 {
7706 return Err(xpath_err(format!(
7707 "format-date() requires 2 to 5 arguments (got {})", args.len()
7708 )));
7709 }
7710 let v = format_date_time_picture(&arg_str!(0), &arg_str!(1), DateKind::Date)?;
7711 let lang = if args.len() > 2 { arg_str!(2) } else { String::new() };
7712 let cal = if args.len() > 3 { arg_str!(3) } else { String::new() };
7713 Ok(Value::String(format!("{}{v}", format_date_locale_prefix(&lang, &cal))))
7714 }
7715 "format-time" => {
7716 if args.len() < 2 || args.len() > 5 {
7717 return Err(xpath_err(format!(
7718 "format-time() requires 2 to 5 arguments (got {})", args.len()
7719 )));
7720 }
7721 let v = format_date_time_picture(&arg_str!(0), &arg_str!(1), DateKind::Time)?;
7722 let lang = if args.len() > 2 { arg_str!(2) } else { String::new() };
7723 let cal = if args.len() > 3 { arg_str!(3) } else { String::new() };
7724 Ok(Value::String(format!("{}{v}", format_date_locale_prefix(&lang, &cal))))
7725 }
7726 "deep-equal" => {
7727 if args.len() < 2 || args.len() > 3 {
7733 return Err(xpath_err(format!(
7734 "deep-equal() requires 2 or 3 arguments (got {})", args.len()
7735 )));
7736 }
7737 let a = arg!(0);
7738 let b = arg!(1);
7739 if value_seq_has_function(&a) || value_seq_has_function(&b) {
7741 return Err(xpath_err(
7742 "deep-equal() cannot compare function items (FOTY0015)")
7743 .with_xpath_code("FOTY0015"));
7744 }
7745 Ok(Value::Boolean(deep_equal_values(&a, &b, idx, ctx.bindings)))
7746 }
7747 "base-uri" => {
7748 if args.len() > 1 {
7754 return Err(xpath_err(format!(
7755 "base-uri() requires 0 or 1 arguments (got {})", args.len()
7756 )));
7757 }
7758 let target = if args.is_empty() {
7759 Some(ctx.context_node)
7760 } else {
7761 match arg!(0) {
7762 Value::NodeSet(ns) => ns.first().copied(),
7763 Value::ForeignNodeSet(_) => None,
7764 _ => return Err(xpath_err(
7765 "base-uri() argument must be a node")),
7766 }
7767 };
7768 let Some(start) = target else {
7769 return Ok(Value::NodeSet(Vec::new()));
7770 };
7771 if idx.kind(start) == XPathNodeKind::Namespace {
7774 return Ok(Value::NodeSet(Vec::new()));
7775 }
7776 let mut chain: Vec<String> = Vec::new();
7787 let mut anchor: Option<String> = None;
7788 let mut n = start;
7789 loop {
7790 if let Some(u) = ctx.bindings.node_base_uri(n) {
7791 anchor = Some(u);
7792 break;
7793 }
7794 if idx.kind(n) == XPathNodeKind::Element {
7795 for a in idx.attr_range(n) {
7796 if idx.local_name(a) == "base" && idx.namespace_uri(a) == "http://www.w3.org/XML/1998/namespace" {
7797 chain.push(idx.string_value(a));
7798 break;
7799 }
7800 }
7801 }
7802 match idx.parent(n) {
7803 Some(p) => n = p,
7804 None => break,
7805 }
7806 }
7807 let mut base = anchor.or_else(|| ctx.bindings.static_base_uri());
7808 for b in chain.into_iter().rev() {
7809 base = Some(match base {
7810 Some(r) => resolve_uri_against(&r, &b),
7811 None => b,
7812 });
7813 }
7814 match base {
7815 Some(u) => Ok(typed_str("anyURI", u)),
7816 None => Ok(Value::NodeSet(Vec::new())),
7817 }
7818 }
7819 "static-base-uri" => {
7820 if !args.is_empty() {
7824 return Err(xpath_err("static-base-uri() takes no arguments"));
7825 }
7826 match ctx.bindings.static_base_uri() {
7827 Some(s) => Ok(typed_str("anyURI", s)),
7828 None => Ok(Value::NodeSet(vec![])),
7829 }
7830 }
7831 "resolve-uri" => {
7832 if args.is_empty() || args.len() > 2 {
7839 return Err(xpath_err(format!(
7840 "resolve-uri() requires 1 or 2 arguments (got {})", args.len()
7841 )));
7842 }
7843 if let Value::NodeSet(ns) = &arg!(0) {
7847 if ns.is_empty() { return Ok(Value::NodeSet(vec![])); }
7848 }
7849 if let Value::Sequence(items) = &arg!(0) {
7850 if items.is_empty() { return Ok(Value::NodeSet(vec![])); }
7851 }
7852 let rel = value_to_string_with(&arg!(0), idx, ctx.bindings);
7853 let base = if args.len() == 2 {
7854 value_to_string_with(&arg!(1), idx, ctx.bindings)
7855 } else {
7856 ctx.bindings.static_base_uri().unwrap_or_default()
7857 };
7858 if args.len() == 2 && !base.is_empty() {
7864 let valid_ref = |u: &str|
7865 !u.contains(char::is_whitespace) && u.matches('#').count() <= 1;
7866 if !valid_ref(&rel) || !valid_ref(&base) || !uri_has_scheme(&base) {
7867 return Err(xpath_err(format!(
7868 "resolve-uri: base '{base}' must be an absolute URI and \
7869 both arguments valid URI references"
7870 )).with_xpath_code("FORG0002"));
7871 }
7872 }
7873 if rel.is_empty() && !base.is_empty() {
7876 return Ok(typed_str("anyURI", base));
7877 }
7878 if base.is_empty() {
7879 return Ok(typed_str("anyURI", rel));
7880 }
7881 Ok(typed_str("anyURI", resolve_uri_rfc3986(&base, &rel)))
7882 }
7883 "root" => {
7892 let v = if args.is_empty() {
7893 Value::NodeSet(vec![ctx.context_node])
7894 } else { arg!(0) };
7895 let node = match v {
7896 Value::NodeSet(ref ns) => ns.first().copied(),
7897 _ => None,
7898 };
7899 let r = node.map(|mut n| {
7900 while let Some(p) = idx.parent(n) { n = p; }
7901 vec![n]
7902 }).unwrap_or_default();
7903 Ok(Value::NodeSet(r))
7904 }
7905 "doc" => {
7911 check_args!(1);
7918 let arg0 = arg!(0);
7919 if matches!(&arg0,
7920 Value::NodeSet(ns) if ns.is_empty())
7921 || matches!(&arg0,
7922 Value::Sequence(items) if items.is_empty())
7923 {
7924 return Ok(Value::NodeSet(Vec::new()));
7925 }
7926 let uri = value_to_string_with(&arg0, idx, ctx.bindings);
7927 match ctx.bindings.call_function_in(
7928 "", "document",
7929 vec![Value::String(uri)],
7930 ctx.context_node,
7931 ) {
7932 Some(r) => r.map_err(|e| e.or_xpath_code("FODC0002")),
7933 None => Ok(Value::NodeSet(Vec::new())),
7934 }
7935 }
7936 "doc-available" => {
7937 check_args!(1);
7938 let arg0 = arg!(0);
7939 if matches!(&arg0,
7940 Value::NodeSet(ns) if ns.is_empty())
7941 || matches!(&arg0,
7942 Value::Sequence(items) if items.is_empty())
7943 {
7944 return Ok(Value::Boolean(false));
7945 }
7946 let uri = value_to_string_with(&arg0, idx, ctx.bindings);
7947 match ctx.bindings.call_function_in(
7948 "", "document",
7949 vec![Value::String(uri)],
7950 ctx.context_node,
7951 ) {
7952 Some(Ok(Value::NodeSet(ns))) => Ok(Value::Boolean(!ns.is_empty())),
7953 _ => Ok(Value::Boolean(false)),
7954 }
7955 }
7956 "document-uri" => {
7957 if args.len() > 1 {
7963 return Err(xpath_err(format!(
7964 "document-uri() takes 0 or 1 arguments (got {})", args.len()
7965 )));
7966 }
7967 let target = if args.is_empty() {
7968 Some(ctx.context_node)
7969 } else {
7970 match arg!(0) {
7971 Value::NodeSet(ns) => ns.first().copied(),
7972 _ => None,
7973 }
7974 };
7975 let uri = target.filter(|&n| idx.kind(n) == XPathNodeKind::Document)
7976 .and_then(|n| ctx.bindings.node_base_uri(n));
7977 match uri {
7978 Some(u) => Ok(typed_str("anyURI", u)),
7979 None => Ok(Value::NodeSet(Vec::new())),
7980 }
7981 }
7982 "round-half-to-even" => {
7984 if args.is_empty() || args.len() > 2 {
7985 return Err(xpath_err(format!(
7986 "round-half-to-even() takes 1 or 2 arguments (got {})", args.len()
7987 )));
7988 }
7989 let a = arg!(0);
7990 let precision = if args.len() == 2 {
7991 value_to_number(&arg!(1), idx) as i32
7992 } else { 0 };
7993 match &a {
7996 Value::Number(Numeric::Decimal(d)) =>
7997 Ok(Value::Number(Numeric::Decimal(round_decimal_half_to_even(*d, precision)))),
7998 Value::Number(Numeric::Integer(i)) => {
7999 use rust_decimal::prelude::ToPrimitive;
8000 let d = round_decimal_half_to_even(rust_decimal::Decimal::from(*i), precision);
8001 Ok(Value::Number(match d.to_i64() {
8002 Some(v) => Numeric::Integer(v),
8003 None => Numeric::Decimal(d),
8004 }))
8005 }
8006 _ => {
8007 let n = value_to_number(&a, idx);
8008 let scale = 10f64.powi(precision);
8009 let scaled = n * scale;
8010 let rounded = if scaled.fract().abs() == 0.5 {
8014 let floor = scaled.floor();
8015 if (floor as i64) % 2 == 0 { floor } else { floor + 1.0 }
8016 } else {
8017 scaled.round()
8018 };
8019 let result = rounded / scale;
8020 let result = if result == 0.0 { 0.0 } else { result };
8023 Ok(preserve_numeric_kind(&a, result))
8024 }
8025 }
8026 }
8027 "collection" => {
8032 if args.len() > 1 {
8033 return Err(xpath_err("collection() takes 0 or 1 arguments"));
8034 }
8035 Ok(Value::NodeSet(Vec::new()))
8036 }
8037 "dateTime" => {
8041 check_args!(2);
8042 let d = value_to_string_with(&arg!(0), idx, ctx.bindings);
8043 let t = value_to_string_with(&arg!(1), idx, ctx.bindings);
8044 match (parse_xsd_date_time(&d, DateKind::Date),
8052 parse_xsd_date_time(&t, DateKind::Time)) {
8053 (Some((y, mo, dd, _, _, _, _, tz_d)),
8054 Some((_, _, _, h, mi, s, frac, tz_t))) => {
8055 let tz = match (tz_d, tz_t) {
8056 (Some(a), Some(b)) if a != b => return Err(xpath_err(
8057 "dateTime(): the date and time have inconsistent timezones (FORG0008)")),
8058 (Some(a), _) => Some(a),
8059 (_, b) => b,
8060 };
8061 Ok(Value::String(format_datetime_lexical(y, mo, dd, h, mi, s, frac, tz)))
8062 }
8063 _ => Ok(Value::String(format!("{}T{}", d.trim(), t.trim()))),
8066 }
8067 }
8068 "local-name-from-QName" => {
8070 check_args!(1);
8071 let a = arg!(0);
8072 if sequence_len(&a) == 0 { return Ok(Value::NodeSet(Vec::new())); }
8075 let s = value_to_string_with(&a, idx, ctx.bindings);
8076 let local = if let Some(i) = s.rfind('}') {
8080 s[i + 1..].to_string()
8081 } else if let Some(i) = s.rfind(':') {
8082 s[i + 1..].to_string()
8083 } else { s };
8084 Ok(typed_str("NCName", local))
8085 }
8086 "prefix-from-QName" => {
8087 check_args!(1);
8088 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8089 if s.starts_with('{') {
8092 return Ok(Value::String(String::new()));
8093 }
8094 let prefix = s.split_once(':').map(|(p, _)| p).unwrap_or("");
8095 Ok(Value::String(prefix.to_string()))
8096 }
8097 "namespace-uri-from-QName" => {
8098 check_args!(1);
8099 let a = arg!(0);
8100 if sequence_len(&a) == 0 { return Ok(Value::NodeSet(Vec::new())); }
8101 let s = value_to_string_with(&a, idx, ctx.bindings);
8102 if s.starts_with('{') {
8103 if let Some(end) = s.find('}') {
8104 return Ok(typed_str("anyURI", s[1..end].to_string()));
8105 }
8106 }
8107 Ok(Value::String(String::new()))
8108 }
8109 "QName" => {
8112 check_args!(2);
8113 let uri = value_to_string_with(&arg!(0), idx, ctx.bindings);
8114 let lex = value_to_string_with(&arg!(1), idx, ctx.bindings);
8115 let local = lex.rsplit(':').next().unwrap_or(&lex);
8116 if uri.is_empty() {
8117 Ok(Value::String(lex.clone()))
8118 } else {
8119 Ok(Value::String(format!("{{{uri}}}{local}")))
8120 }
8121 }
8122 "resolve-QName" => {
8123 check_args!(2);
8130 if matches!(&arg!(0), Value::NodeSet(ns) if ns.is_empty()) {
8131 return Ok(Value::NodeSet(Vec::new()));
8132 }
8133 let lex = value_to_string_with(&arg!(0), idx, ctx.bindings);
8134 if !is_valid_lexical_qname(&lex) {
8135 return Err(xpath_err(format!(
8136 "resolve-QName: '{lex}' is not a valid lexical QName"
8137 )).with_xpath_code("FOCA0002"));
8138 }
8139 let (prefix, local) = match lex.split_once(':') {
8140 Some((p, l)) => (p, l),
8141 None => ("", lex.as_str()),
8142 };
8143 let elem = match arg!(1) {
8146 Value::NodeSet(ref ns) => ns.first().copied(),
8147 _ => None,
8148 };
8149 let uri = elem.and_then(|id| {
8150 idx.ns_range(id)
8151 .into_iter()
8152 .find(|&ns_id| {
8153 let p = idx.local_name(ns_id);
8154 (prefix.is_empty() && p.is_empty()) || p == prefix
8155 })
8156 .map(|ns_id| idx.string_value(ns_id))
8157 .or_else(|| (prefix == "xml")
8158 .then(|| "http://www.w3.org/XML/1998/namespace".to_string()))
8159 });
8160 match uri {
8161 Some(u) if !u.is_empty() => Ok(Value::String(format!("{{{u}}}{local}"))),
8162 _ if !prefix.is_empty() => Err(xpath_err(format!(
8165 "resolve-QName: no namespace declaration for prefix '{prefix}'"
8166 )).with_xpath_code("FONS0004")),
8167 _ => Ok(Value::String(local.to_string())),
8168 }
8169 }
8170 "normalize-unicode" => {
8175 if args.is_empty() || args.len() > 2 {
8176 return Err(xpath_err("normalize-unicode() takes 1 or 2 arguments"));
8177 }
8178 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8179 Ok(Value::String(s))
8180 }
8181 "type-available" => {
8182 check_args!(1);
8189 let name = value_to_string_with(&arg!(0), idx, ctx.bindings);
8190 let (prefix, local) = match name.split_once(':') {
8191 Some((p, l)) => (Some(p), l),
8192 None => (None, name.as_str()),
8193 };
8194 let uri = match prefix {
8195 Some(p) => ctx.bindings.resolve_prefix(p)
8196 .or_else(|| match p {
8197 "xml" => Some("http://www.w3.org/XML/1998/namespace".into()),
8198 "xs" => Some("http://www.w3.org/2001/XMLSchema".into()),
8199 _ => None,
8200 }),
8201 None => None,
8202 };
8203 let in_xsd = uri.as_deref() == Some("http://www.w3.org/2001/XMLSchema");
8204 if prefix.is_none() {
8207 return Ok(Value::Boolean(false));
8208 }
8209 if !in_xsd {
8210 return Ok(Value::Boolean(false));
8211 }
8212 let known = matches!(local,
8213 "anyType" | "anySimpleType" | "anyAtomicType" | "untyped" | "untypedAtomic"
8214 | "string" | "boolean" | "decimal" | "float" | "double"
8215 | "integer" | "long" | "int" | "short" | "byte"
8216 | "nonNegativeInteger" | "nonPositiveInteger"
8217 | "positiveInteger" | "negativeInteger"
8218 | "unsignedLong" | "unsignedInt" | "unsignedShort" | "unsignedByte"
8219 | "duration" | "dateTime" | "time" | "date"
8220 | "dayTimeDuration" | "yearMonthDuration"
8221 | "gYearMonth" | "gYear" | "gMonth" | "gMonthDay" | "gDay"
8222 | "hexBinary" | "base64Binary" | "anyURI" | "QName" | "NOTATION"
8223 | "normalizedString" | "token" | "language"
8224 | "Name" | "NCName" | "ID" | "IDREF" | "IDREFS"
8225 | "ENTITY" | "ENTITIES" | "NMTOKEN" | "NMTOKENS"
8226 | "numeric"
8227 );
8228 Ok(Value::Boolean(known))
8229 }
8230 "unparsed-entity-public-id" => {
8231 check_args!(1);
8232 Ok(Value::String(String::new()))
8233 }
8234 "in-scope-prefixes" => {
8240 check_args!(1);
8241 let elem = match arg!(0) {
8242 Value::NodeSet(ref ns) => ns.first().copied(),
8243 _ => None,
8244 };
8245 let Some(id) = elem else {
8246 return Ok(Value::NodeSet(Vec::new()));
8247 };
8248 let mut out: Vec<String> = Vec::new();
8249 for ns_id in idx.ns_range(id) {
8250 let p = idx.local_name(ns_id);
8251 out.push(if p.is_empty() { "".into() } else { p.to_string() });
8252 }
8253 if !out.iter().any(|s| s == "xml") {
8255 out.push("xml".into());
8256 }
8257 match idx.allocate_rtf_text_nodes(out.clone()) {
8258 Some(ids) => Ok(Value::NodeSet(ids)),
8259 None => Ok(Value::String(out.join(" "))),
8260 }
8261 }
8262 "namespace-uri-for-prefix" => {
8263 check_args!(2);
8264 let prefix = value_to_string_with(&arg!(0), idx, ctx.bindings);
8265 let elem = match arg!(1) {
8266 Value::NodeSet(ref ns) => ns.first().copied(),
8267 _ => None,
8268 };
8269 let Some(id) = elem else {
8270 return Ok(Value::NodeSet(Vec::new()));
8271 };
8272 for ns_id in idx.ns_range(id) {
8273 let p = idx.local_name(ns_id);
8274 let match_default = prefix.is_empty() && p.is_empty();
8275 if match_default || p == prefix {
8276 return Ok(Value::String(idx.string_value(ns_id)));
8277 }
8278 }
8279 if prefix == "xml" {
8281 return Ok(Value::String("http://www.w3.org/XML/1998/namespace".into()));
8282 }
8283 Ok(Value::NodeSet(Vec::new()))
8284 }
8285 "year-from-dateTime" | "year-from-date" => {
8286 check_args!(1);
8287 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8288 let v = s.trim();
8289 if v.is_empty() {
8290 return Ok(Value::NodeSet(Vec::new()));
8291 }
8292 let (sign, rest) = if let Some(r) = v.strip_prefix('-') { (-1i64, r) } else { (1i64, v) };
8293 let yr_end = rest.find('-').unwrap_or(rest.len());
8294 let yr: i64 = rest[..yr_end].parse().map_err(|_|
8295 xpath_err(format!("invalid date-like value: {s:?}")))?;
8296 Ok(Value::Number(Numeric::Double((sign * yr) as f64)))
8297 }
8298 "month-from-dateTime" | "month-from-date" => {
8299 check_args!(1);
8300 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8301 let v = s.trim();
8302 if v.is_empty() {
8303 return Ok(Value::NodeSet(Vec::new()));
8304 }
8305 let body = v.strip_prefix('-').unwrap_or(v);
8306 let after_year = body.split_once('-').map(|(_, r)| r).unwrap_or("");
8308 let mm: u32 = after_year.get(..2).and_then(|s| s.parse().ok())
8309 .ok_or_else(|| xpath_err(format!("invalid date-like value: {s:?}")))?;
8310 Ok(Value::Number(Numeric::Double(mm as f64)))
8311 }
8312 "day-from-dateTime" | "day-from-date" => {
8313 check_args!(1);
8314 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8315 let v = s.trim();
8316 if v.is_empty() {
8317 return Ok(Value::NodeSet(Vec::new()));
8318 }
8319 let body = v.strip_prefix('-').unwrap_or(v);
8320 let mut parts = body.splitn(3, '-');
8322 parts.next(); parts.next();
8323 let dd_seg = parts.next().unwrap_or("");
8324 let dd: u32 = dd_seg.get(..2).and_then(|s| s.parse().ok())
8325 .ok_or_else(|| xpath_err(format!("invalid date-like value: {s:?}")))?;
8326 Ok(Value::Number(Numeric::Double(dd as f64)))
8327 }
8328 "hours-from-dateTime" | "hours-from-time" => {
8329 check_args!(1);
8330 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8331 let t = s.split_once('T').map(|(_, r)| r).unwrap_or(s.trim());
8334 let hh: u32 = t.get(..2).and_then(|s| s.parse().ok())
8335 .ok_or_else(|| xpath_err(format!("invalid time-like value: {s:?}")))?;
8336 Ok(Value::Number(Numeric::Double(hh as f64)))
8337 }
8338 "minutes-from-dateTime" | "minutes-from-time" => {
8339 check_args!(1);
8340 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8341 let t = s.split_once('T').map(|(_, r)| r).unwrap_or(s.trim());
8342 let mm: u32 = t.get(3..5).and_then(|s| s.parse().ok())
8343 .ok_or_else(|| xpath_err(format!("invalid time-like value: {s:?}")))?;
8344 Ok(Value::Number(Numeric::Double(mm as f64)))
8345 }
8346 "seconds-from-dateTime" | "seconds-from-time" => {
8347 check_args!(1);
8348 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8349 let t = s.split_once('T').map(|(_, r)| r).unwrap_or(s.trim());
8350 let after = t.get(6..).unwrap_or("");
8352 let end = after.find(|c: char| c == 'Z' || c == '+' || c == '-')
8353 .unwrap_or(after.len());
8354 let ss: f64 = after[..end].parse()
8355 .map_err(|_| xpath_err(format!("invalid time-like value: {s:?}")))?;
8356 Ok(Value::Number(Numeric::Double(ss)))
8357 }
8358 "timezone-from-dateTime" | "timezone-from-date" | "timezone-from-time" => {
8359 check_args!(1);
8360 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8361 let v = s.trim();
8362 if v.is_empty() {
8364 return Ok(Value::NodeSet(Vec::new()));
8365 }
8366 let tz_start = v.rfind(|c: char| c == 'Z' || c == '+' || c == '-')
8368 .filter(|&i| i > 0);
8369 let tz = match tz_start.map(|i| &v[i..]) {
8370 Some("Z") => "PT0S".to_string(),
8371 Some(off) if off.len() == 6 => {
8372 let sign = &off[0..1];
8373 let hh: i32 = off[1..3].parse().unwrap_or(0);
8374 let mm: i32 = off[4..6].parse().unwrap_or(0);
8375 if hh == 0 && mm == 0 { "PT0S".to_string() }
8376 else if mm == 0 { format!("{sign}PT{hh}H") }
8377 else { format!("{sign}PT{hh}H{mm}M") }
8378 }
8379 _ => return Ok(Value::NodeSet(Vec::new())),
8380 };
8381 Ok(Value::String(tz.replace("+", "")))
8382 }
8383 "years-from-duration" | "months-from-duration"
8384 | "days-from-duration" | "hours-from-duration"
8385 | "minutes-from-duration" | "seconds-from-duration" => {
8386 check_args!(1);
8387 let s = value_to_string_with(&arg!(0), idx, ctx.bindings);
8388 let (sign, rest) = if let Some(r) = s.trim().strip_prefix('-') {
8389 (-1.0_f64, r)
8390 } else { (1.0, s.trim()) };
8391 let body = rest.strip_prefix('P').unwrap_or(rest);
8392 let (date_part, time_part) = match body.find('T') {
8393 Some(i) => (&body[..i], &body[i + 1..]),
8394 None => (body, ""),
8395 };
8396 fn extract(part: &str, marker: char) -> f64 {
8399 if let Some(i) = part.find(marker) {
8400 let start = part[..i].rfind(|c: char| !c.is_ascii_digit() && c != '.')
8401 .map(|n| n + 1).unwrap_or(0);
8402 part[start..i].parse().unwrap_or(0.0)
8403 } else { 0.0 }
8404 }
8405 let years = extract(date_part, 'Y');
8406 let months = extract(date_part, 'M');
8407 let days = extract(date_part, 'D');
8408 let hours = extract(time_part, 'H');
8409 let minutes = extract(time_part, 'M');
8410 let seconds = extract(time_part, 'S');
8411 let v = match name.as_ref() {
8417 "years-from-duration" => years,
8418 "months-from-duration" => months,
8419 "days-from-duration" => days,
8420 "hours-from-duration" => hours,
8421 "minutes-from-duration" => minutes,
8422 "seconds-from-duration" => seconds,
8423 _ => unreachable!(),
8424 };
8425 Ok(Value::Number(Numeric::Double(sign * v)))
8426 }
8427 "nilled" => {
8428 if args.len() != 1 {
8436 return Err(xpath_err("nilled() requires one argument"));
8437 }
8438 let v = arg!(0);
8439 let n = match v {
8440 Value::NodeSet(ref ns) => ns.first().copied(),
8441 _ => return Ok(Value::NodeSet(Vec::new())),
8442 };
8443 let id = match n { Some(id) => id, None => return Ok(Value::NodeSet(Vec::new())) };
8444 if !matches!(idx.kind(id), crate::xpath::XPathNodeKind::Element) {
8445 return Ok(Value::NodeSet(Vec::new()));
8446 }
8447 Ok(Value::Boolean(false))
8448 }
8449 "default-collation" => {
8450 check_args!(0);
8451 Ok(Value::String("http://www.w3.org/2005/xpath-functions/collation/codepoint".into()))
8453 }
8454 "implicit-timezone" => {
8455 check_args!(0);
8456 Ok(Value::String("PT0S".into()))
8459 }
8460 "format-dateTime" => {
8461 if args.len() < 2 || args.len() > 5 {
8462 return Err(xpath_err(format!(
8463 "format-dateTime() requires 2 to 5 arguments (got {})", args.len()
8464 )));
8465 }
8466 let v = format_date_time_picture(&arg_str!(0), &arg_str!(1), DateKind::DateTime)?;
8467 let lang = if args.len() > 2 { arg_str!(2) } else { String::new() };
8468 let cal = if args.len() > 3 { arg_str!(3) } else { String::new() };
8469 Ok(Value::String(format!("{}{v}", format_date_locale_prefix(&lang, &cal))))
8470 }
8471
8472 "compare" => {
8474 if args.len() < 2 || args.len() > 3 {
8475 return Err(xpath_err(format!(
8476 "compare() requires 2 or 3 arguments (got {})", args.len()
8477 )));
8478 }
8479 let a = arg_str!(0);
8480 let b = arg_str!(1);
8481 let coll = effective_collation(if args.len() == 3 { Some(arg_str!(2)) } else { None });
8482 let (ka, kb) = if is_ascii_ci_collation(coll.as_deref()) {
8483 (ascii_ci_fold(&a), ascii_ci_fold(&b))
8484 } else {
8485 (a, b)
8486 };
8487 Ok(Value::Number(Numeric::Double(match ka.cmp(&kb) {
8488 std::cmp::Ordering::Less => -1.0,
8489 std::cmp::Ordering::Equal => 0.0,
8490 std::cmp::Ordering::Greater => 1.0,
8491 })))
8492 }
8493 "codepoint-equal" => {
8494 check_args!(2);
8495 Ok(Value::Boolean(arg_str!(0) == arg_str!(1)))
8496 }
8497 "string-to-codepoints" => {
8498 check_args!(1);
8499 let s = arg_str!(0);
8500 let pieces: Vec<String> = s.chars().map(|c| (c as u32).to_string()).collect();
8501 match idx.allocate_rtf_text_nodes(pieces.clone()) {
8502 Some(ids) => Ok(Value::NodeSet(ids)),
8503 None => Ok(Value::String(pieces.join(" "))),
8504 }
8505 }
8506 "codepoints-to-string" => {
8507 check_args!(1);
8509 let codes = sequence_to_numbers(&arg!(0), idx);
8510 let mut out = String::with_capacity(codes.len());
8511 for n in codes {
8512 if let Some(c) = u32::try_from(n as i64).ok().and_then(char::from_u32) {
8513 out.push(c);
8514 }
8515 }
8516 Ok(Value::String(out))
8517 }
8518 "encode-for-uri" => {
8519 check_args!(1);
8520 let s = arg_str!(0);
8524 let mut out = String::with_capacity(s.len());
8525 for c in s.chars() {
8526 if c.is_ascii_alphanumeric()
8527 || matches!(c, '-' | '.' | '_' | '~')
8528 {
8529 out.push(c);
8530 } else {
8531 let mut buf = [0u8; 4];
8532 for b in c.encode_utf8(&mut buf).as_bytes() {
8533 let _ = write!(out, "%{:02X}", b);
8534 }
8535 }
8536 }
8537 Ok(Value::String(out))
8538 }
8539 "iri-to-uri" => {
8540 check_args!(1);
8541 let s = arg_str!(0);
8543 let mut out = String::with_capacity(s.len());
8544 for c in s.chars() {
8545 if (c as u32) < 0x80
8546 && !matches!(c, ' ' | '<' | '>' | '"' | '{' | '}' | '|' | '\\' | '^' | '`')
8547 {
8548 out.push(c);
8549 } else {
8550 let mut buf = [0u8; 4];
8551 for b in c.encode_utf8(&mut buf).as_bytes() {
8552 let _ = write!(out, "%{:02X}", b);
8553 }
8554 }
8555 }
8556 Ok(Value::String(out))
8557 }
8558 "escape-html-uri" => {
8559 check_args!(1);
8560 let s = arg_str!(0);
8562 let mut out = String::with_capacity(s.len());
8563 for c in s.chars() {
8564 if (c as u32) < 0x80 { out.push(c); }
8565 else {
8566 let mut buf = [0u8; 4];
8567 for b in c.encode_utf8(&mut buf).as_bytes() {
8568 let _ = write!(out, "%{:02X}", b);
8569 }
8570 }
8571 }
8572 Ok(Value::String(out))
8573 }
8574 "error" => {
8575 let code_local = if args.is_empty() { None } else {
8582 let s = arg_str!(0);
8583 s.rsplit([':', '}']).next()
8586 .filter(|l| !l.is_empty())
8587 .map(|l| l.to_string())
8588 };
8589 let msg = if args.len() >= 2 { arg_str!(1) }
8590 else if args.is_empty() { "fn:error invoked".to_string() }
8591 else { arg_str!(0) };
8592 let mut e = xpath_err(format!("fn:error: {msg}"));
8593 if let Some(code) = code_local { e = e.with_xpath_code(code); }
8594 Err(e)
8595 }
8596 "trace" => {
8597 if args.is_empty() || args.len() > 2 {
8601 return Err(xpath_err(format!(
8602 "trace() requires 1 or 2 arguments (got {})", args.len()
8603 )));
8604 }
8605 let v = arg!(0);
8606 if args.len() == 2 {
8607 eprintln!("[xpath trace] {}: {}", arg_str!(1), value_to_string(&v, idx));
8608 }
8609 Ok(v)
8610 }
8611 "exactly-one" => {
8616 check_args!(1);
8617 let v = arg!(0);
8618 let n = sequence_len(&v);
8619 if n != 1 {
8620 return Err(xpath_err(format!(
8621 "exactly-one(): expected one item, got {n}"
8622 )));
8623 }
8624 Ok(v)
8625 }
8626 "one-or-more" => {
8627 check_args!(1);
8628 let v = arg!(0);
8629 if sequence_len(&v) < 1 {
8630 return Err(xpath_err("one-or-more(): empty input"));
8631 }
8632 Ok(v)
8633 }
8634 "zero-or-one" => {
8635 check_args!(1);
8636 let v = arg!(0);
8637 let n = sequence_len(&v);
8638 if n > 1 {
8639 return Err(xpath_err(format!(
8640 "zero-or-one(): expected at most one item, got {n}"
8641 )));
8642 }
8643 Ok(v)
8644 }
8645 "node-name" => {
8646 check_args!(1);
8647 match arg!(0) {
8658 Value::NodeSet(ns) => {
8659 let Some(&id) = ns.first() else {
8660 return Ok(Value::NodeSet(Vec::new()));
8661 };
8662 let local = idx.local_name(id);
8663 let uri = idx.namespace_uri(id);
8664 let lex = if uri.is_empty() {
8665 local.to_string()
8666 } else {
8667 format!("{{{uri}}}{local}")
8668 };
8669 Ok(Value::Typed(Box::new(TypedAtomic {
8670 kind: "QName",
8671 lexical: lex,
8672 numeric: None,
8673 boolean: None, user_type: None,
8674 })))
8675 }
8676 _ => Ok(Value::NodeSet(Vec::new())),
8677 }
8678 }
8679
8680 "function-available" if !args.is_empty() => {
8688 let qname = arg_str!(0);
8689 Ok(Value::Boolean(xpath_function_available(&qname, ctx)))
8690 }
8691 "current" if args.is_empty() => {
8700 Ok(Value::NodeSet(vec![ctx.static_ctx.current_node.unwrap_or(ctx.context_node)]))
8701 }
8702
8703 _ => Err(xpath_err(format!("unknown XPath function: {name}()"))),
8704 }
8705}
8706
8707fn xpath_function_available(qname: &str, ctx: &EvalCtx<'_>) -> bool {
8714 use super::exslt;
8715 match qname.split_once(':') {
8716 Some((prefix, _local)) => {
8717 match resolve_prefix_or_implicit(ctx.bindings, prefix) {
8718 Some(uri) => matches!(uri.as_str(),
8719 exslt::MATH_NS | exslt::DATE_NS | exslt::STR_NS | exslt::SET_NS
8720 | exslt::REGEXP_NS | exslt::COMMON_NS | exslt::DYN_NS),
8721 None => false,
8722 }
8723 }
8724 None => matches!(qname,
8725 "last" | "position" | "count" | "id" | "local-name" | "namespace-uri"
8727 | "name" | "string" | "concat" | "starts-with" | "contains"
8728 | "substring-before" | "substring-after" | "substring" | "string-length"
8729 | "normalize-space" | "translate" | "boolean" | "not" | "true" | "false"
8730 | "lang" | "number" | "sum" | "floor" | "ceiling" | "round"
8731 | "document" | "key" | "format-number" | "current" | "unparsed-entity-uri"
8733 | "generate-id" | "system-property" | "element-available" | "function-available"),
8734 }
8735}
8736
8737fn node_set_of(v: Value) -> Vec<NodeId> {
8742 match v {
8743 Value::NodeSet(ns) => ns,
8744 _ => Vec::new(),
8745 }
8746}
8747
8748fn resolve_kind_test_namespaces(
8763 st: &crate::xpath::ast::SequenceType,
8764 bindings: &dyn XPathBindings,
8765) -> crate::xpath::ast::SequenceType {
8766 use crate::xpath::ast::ItemType;
8767 let resolve = |name: &Option<String>| -> Option<String> {
8768 let n = name.as_ref()?;
8769 let (prefix, local) = n.split_once(':')?;
8770 let uri = resolve_prefix_or_implicit(bindings, prefix)?;
8771 Some(format!("{{{uri}}}{local}"))
8772 };
8773 let item = match &st.item {
8774 ItemType::Element(name @ Some(_)) =>
8775 ItemType::Element(resolve(name).or_else(|| name.clone())),
8776 ItemType::Attribute(name @ Some(_)) =>
8777 ItemType::Attribute(resolve(name).or_else(|| name.clone())),
8778 other => other.clone(),
8779 };
8780 crate::xpath::ast::SequenceType { item, occurrence: st.occurrence }
8781}
8782
8783fn kind_test_name_matches<I: DocIndexLike>(
8791 name: Option<&str>, id: NodeId, idx: &I,
8792) -> bool {
8793 match name {
8794 None => true,
8795 Some(n) => match n.strip_prefix('{').and_then(|r| r.split_once('}')) {
8796 Some((uri, local)) =>
8797 idx.namespace_uri(id) == uri && idx.local_name(id) == local,
8798 None if n.contains(':') => idx.node_name(id) == n,
8799 None => idx.local_name(id) == n,
8800 },
8801 }
8802}
8803
8804fn value_matches_sequence_type<I: DocIndexLike>(
8805 v: &Value, st: &crate::xpath::ast::SequenceType, idx: &I,
8806) -> bool {
8807 use crate::xpath::ast::{ItemType, Occurrence};
8808 let count = sequence_len(v);
8810 let card_ok = match st.occurrence {
8811 Occurrence::One => count == 1,
8812 Occurrence::Optional => count <= 1,
8813 Occurrence::OneOrMore => count >= 1,
8814 Occurrence::ZeroOrMore => true,
8815 };
8816 if !card_ok { return false; }
8817 if count == 0 { return true; }
8824 match &st.item {
8826 ItemType::Any => true,
8827 ItemType::Atomic(name) => {
8828 let try_one = |s: &str| atomic_string_castable(s, name);
8832 match v {
8833 Value::String(_) => matches!(name.as_str(),
8840 "string" | "untypedAtomic" | "anyAtomicType" | "anySimpleType"),
8841 Value::Number(n) => name == "numeric"
8849 || xsd_is_subtype_of(n.kind(), name),
8850 Value::Boolean(_) => matches!(name.as_str(),
8851 "boolean" | "anyAtomicType"),
8852 Value::NodeSet(ns) => ns.iter().all(|&id|
8860 crate::xpath::is_synthetic_id(id)
8861 && try_one(&idx.string_value(id))),
8862 Value::ForeignNodeSet(_) => true,
8863 Value::Typed(t) => xsd_is_subtype_of(t.kind, name),
8867 Value::Sequence(items) => items.iter().all(|item|
8870 value_matches_sequence_type(item, st, idx)),
8871 Value::IntRange { .. } => name == "numeric"
8874 || xsd_is_subtype_of("integer", name),
8875 Value::Map(_) | Value::Array(_) | Value::Function(_) => false,
8877 }
8878 }
8879 ItemType::AnyNode => matches!(v,
8880 Value::NodeSet(_) | Value::ForeignNodeSet(_)),
8881 ItemType::Element(name) => match v {
8882 Value::NodeSet(ns) => ns.iter().all(|&id|
8883 matches!(idx.kind(id), crate::xpath::XPathNodeKind::Element)
8884 && kind_test_name_matches(name.as_deref(), id, idx)),
8885 _ => false,
8886 },
8887 ItemType::Attribute(name) => match v {
8888 Value::NodeSet(ns) => ns.iter().all(|&id|
8889 matches!(idx.kind(id), crate::xpath::XPathNodeKind::Attribute)
8890 && kind_test_name_matches(name.as_deref(), id, idx)),
8891 _ => false,
8892 },
8893 ItemType::Text => match v {
8894 Value::NodeSet(ns) => ns.iter().all(|&id|
8895 matches!(idx.kind(id),
8896 crate::xpath::XPathNodeKind::Text |
8897 crate::xpath::XPathNodeKind::CData)),
8898 _ => false,
8899 },
8900 ItemType::Comment => match v {
8901 Value::NodeSet(ns) => ns.iter().all(|&id|
8902 matches!(idx.kind(id), crate::xpath::XPathNodeKind::Comment)),
8903 _ => false,
8904 },
8905 ItemType::PI(target) => match v {
8906 Value::NodeSet(ns) => ns.iter().all(|&id|
8907 matches!(idx.kind(id), crate::xpath::XPathNodeKind::PI)
8908 && target.as_ref().map_or(true, |t| idx.pi_target(id) == t)),
8909 _ => false,
8910 },
8911 ItemType::Document => match v {
8912 Value::NodeSet(ns) => ns.iter().all(|&id|
8913 matches!(idx.kind(id), crate::xpath::XPathNodeKind::Document)),
8914 _ => false,
8915 },
8916 ItemType::Function(want) => match v {
8921 Value::Function(fi) => match want {
8922 None => true,
8923 Some(want) => fi.arity() == want.params.len()
8924 && match fi.declared_sig() {
8925 Some(have) => function_sig_subtype_of(have, want),
8926 None => true,
8927 },
8928 },
8929 Value::Sequence(items) => items.iter().all(|item|
8930 value_matches_sequence_type(item, st, idx)),
8931 _ => false,
8932 },
8933 ItemType::Map => match v {
8935 Value::Map(_) => true,
8936 Value::Sequence(items) => items.iter().all(|item|
8937 value_matches_sequence_type(item, st, idx)),
8938 _ => false,
8939 },
8940 ItemType::Array => match v {
8941 Value::Array(_) => true,
8942 Value::Sequence(items) => items.iter().all(|item|
8943 value_matches_sequence_type(item, st, idx)),
8944 _ => false,
8945 },
8946 ItemType::EmptySequence => false,
8950 }
8951}
8952
8953fn occurrence_subsumes(sub: Occurrence, sup: Occurrence) -> bool {
8956 let allows_zero = |o: Occurrence| matches!(o, Occurrence::Optional | Occurrence::ZeroOrMore);
8957 let allows_many = |o: Occurrence| matches!(o, Occurrence::OneOrMore | Occurrence::ZeroOrMore);
8958 (!allows_zero(sub) || allows_zero(sup)) && (!allows_many(sub) || allows_many(sup))
8959}
8960
8961fn sequence_type_subtype_of(a: &SequenceType, b: &SequenceType) -> bool {
8965 occurrence_subsumes(a.occurrence, b.occurrence)
8966 && item_type_subtype_of(&a.item, &b.item)
8967}
8968
8969fn item_type_subtype_of(a: &ItemType, b: &ItemType) -> bool {
8970 match (a, b) {
8971 (_, ItemType::Any) => true,
8972 (ItemType::EmptySequence, _) => true,
8973 (ItemType::Atomic(x), ItemType::Atomic(y)) => xsd_is_subtype_of(x, y),
8974 (ItemType::AnyNode, ItemType::AnyNode) => true,
8975 (ItemType::Element(_) | ItemType::Attribute(_) | ItemType::Text
8976 | ItemType::Comment | ItemType::PI(_) | ItemType::Document,
8977 ItemType::AnyNode) => true,
8978 (ItemType::Element(x), ItemType::Element(y)) => y.is_none() || x == y,
8979 (ItemType::Attribute(x), ItemType::Attribute(y)) => y.is_none() || x == y,
8980 (ItemType::PI(x), ItemType::PI(y)) => y.is_none() || x == y,
8981 (ItemType::Text, ItemType::Text)
8982 | (ItemType::Comment, ItemType::Comment)
8983 | (ItemType::Document, ItemType::Document) => true,
8984 (ItemType::Function(_), ItemType::Function(None)) => true,
8985 (ItemType::Function(Some(sa)), ItemType::Function(Some(sb))) =>
8986 function_sig_subtype_of(sa, sb),
8987 (ItemType::Map, ItemType::Map) => true,
8988 (ItemType::Array, ItemType::Array) => true,
8989 _ => false,
8990 }
8991}
8992
8993fn function_sig_subtype_of(have: &FunctionSig, want: &FunctionSig) -> bool {
8998 have.params.len() == want.params.len()
8999 && sequence_type_subtype_of(&have.ret, &want.ret)
9000 && have.params.iter().zip(&want.params)
9001 .all(|(hp, wp)| sequence_type_subtype_of(wp, hp))
9002}
9003
9004fn lexical_matches_type(s: &str, name: &str) -> bool {
9014 if s.is_empty() { return false; }
9015 let bytes = s.as_bytes();
9016 match name {
9017 "date" => {
9018 s.len() >= 10
9020 && parse_xsd_date_time(s, DateKind::Date).is_some()
9021 }
9022 "dateTime" => {
9023 s.contains('T') && parse_xsd_date_time(s, DateKind::DateTime).is_some()
9024 }
9025 "time" => {
9026 bytes.len() >= 8 && bytes[2] == b':' && bytes[5] == b':'
9027 && parse_xsd_date_time(s, DateKind::Time).is_some()
9028 }
9029 "duration" => {
9032 let rest = s.strip_prefix('-').unwrap_or(s);
9033 rest.starts_with('P') && rest.len() > 1
9034 }
9035 "dayTimeDuration" => {
9036 let rest = s.strip_prefix('-').unwrap_or(s);
9037 rest.starts_with('P')
9039 && !rest.contains('Y')
9040 && !rest_contains_month_designator(rest)
9041 }
9042 "yearMonthDuration" => {
9043 let rest = s.strip_prefix('-').unwrap_or(s);
9044 rest.starts_with('P')
9046 && !rest.contains('D')
9047 && !rest.contains('T')
9048 }
9049 "gYear" => s.len() >= 4 && bytes.iter().all(|&b| b == b'-' || b.is_ascii_digit() || b == b'+' || b == b'Z' || b == b':'),
9051 "gYearMonth" => s.contains('-') && s.len() >= 7,
9052 "gMonth" => s.starts_with("--") && s.len() >= 4,
9053 "gMonthDay" => s.starts_with("--") && s.len() >= 7,
9054 "gDay" => s.starts_with("---") && s.len() >= 5,
9055 "hexBinary" => s.len() % 2 == 0 && bytes.iter().all(|b| b.is_ascii_hexdigit()),
9058 "base64Binary" => true, _ => true,
9060 }
9061}
9062
9063fn date_year_out_of_range(s: &str, name: &str) -> bool {
9072 if !matches!(name, "date" | "dateTime" | "gYear" | "gYearMonth") {
9073 return false;
9074 }
9075 let neg = s.starts_with('-');
9076 let body = s.strip_prefix('-').unwrap_or(s);
9077 let ylen = body.bytes().take_while(u8::is_ascii_digit).count();
9078 if ylen == 0 { return false; }
9079 let ynum = &body[..ylen];
9080 if ynum.parse::<i32>().is_ok() || ynum.parse::<i128>().is_err() {
9083 return false;
9084 }
9085 let probe = format!("{}0001{}", if neg { "-" } else { "" }, &body[ylen..]);
9088 lexical_matches_type(&probe, name)
9089}
9090
9091fn rest_contains_month_designator(s: &str) -> bool {
9097 let t_pos = s.find('T');
9098 for (i, c) in s.char_indices() {
9099 if c == 'M' {
9100 match t_pos {
9101 Some(t) if i > t => continue,
9102 _ => return true,
9103 }
9104 }
9105 }
9106 false
9107}
9108
9109fn atomic_string_castable(s: &str, name: &str) -> bool {
9110 match name {
9111 "string" | "anyURI" | "anyAtomicType" | "untypedAtomic" => true,
9112 "boolean" => matches!(s.trim(), "true" | "false" | "1" | "0"),
9113 "integer" | "long" | "int" | "short" | "byte"
9114 | "nonNegativeInteger" | "nonPositiveInteger"
9115 | "positiveInteger" | "negativeInteger"
9116 | "unsignedLong" | "unsignedInt" | "unsignedShort" | "unsignedByte"
9117 => s.trim().parse::<i64>().is_ok(),
9118 "decimal" => s.trim().parse::<f64>().is_ok() && !s.trim().contains(['e', 'E']),
9119 "double" | "float" | "numeric" => s.trim().parse::<f64>().is_ok()
9120 || matches!(s.trim(), "NaN" | "INF" | "-INF" | "Infinity" | "-Infinity"),
9121 "date" => parse_xsd_date_only(s).is_some(),
9122 "dateTime" => parse_xsd_date_time(s, DateKind::DateTime).is_some(),
9123 "time" => parse_xsd_date_time(s, DateKind::Time).is_some(),
9124 _ => true, }
9126}
9127
9128fn canonical_float_lex(n: f64, source: &Value) -> String {
9145 let f = n as f32;
9146 if f.is_nan() { return "NaN".to_string(); }
9147 if f.is_infinite() { return (if f > 0.0 { "INF" } else { "-INF" }).to_string(); }
9148 let is_neg_zero = f == 0.0
9149 && (f.is_sign_negative()
9150 || matches!(source, Value::Number(v) if v.as_f64().is_sign_negative())
9151 || matches!(source, Value::String(s) if s.trim().starts_with('-')));
9152 if f == 0.0 {
9153 return if is_neg_zero { "-0".to_string() } else { "0".to_string() };
9154 }
9155 let abs = f.abs();
9156 if (1e-6..1e7).contains(&abs) {
9157 if f.fract() == 0.0 {
9158 return format!("{}", f as i64);
9159 }
9160 return format!("{f}");
9161 }
9162 let formatted = format!("{f:E}");
9163 let (mantissa, exp) = formatted.split_once('E').unwrap_or((formatted.as_str(), "0"));
9164 let mantissa = if mantissa.contains('.') { mantissa.to_string() }
9165 else { format!("{mantissa}.0") };
9166 let exp = exp.trim_start_matches('+');
9167 format!("{mantissa}E{exp}")
9168}
9169
9170fn canonical_double_lex(n: f64, source: &Value) -> String {
9171 if n.is_nan() { return "NaN".to_string(); }
9172 if n.is_infinite() { return (if n > 0.0 { "INF" } else { "-INF" }).to_string(); }
9173 let is_neg_zero = n == 0.0
9179 && (n.is_sign_negative()
9180 || matches!(source, Value::Number(v) if v.as_f64().is_sign_negative())
9181 || matches!(source, Value::String(s) if s.trim().starts_with('-')));
9182 if n == 0.0 {
9183 return if is_neg_zero { "-0".to_string() } else { "0".to_string() };
9184 }
9185 let abs = n.abs();
9186 if abs >= 1e-6 && abs < 1e7 {
9187 if n.fract() == 0.0 {
9192 return format!("{}", n as i64);
9193 }
9194 return format!("{n}");
9197 }
9198 let formatted = format!("{n:E}");
9203 let (mantissa, exp) = match formatted.split_once('E') {
9206 Some((m, e)) => (m.to_string(), e.to_string()),
9207 None => return formatted,
9208 };
9209 let mantissa = if !mantissa.contains('.') {
9210 format!("{mantissa}.0")
9211 } else { mantissa };
9212 format!("{mantissa}E{exp}")
9213}
9214
9215fn canonical_decimal_lex(n: f64, src: &str) -> String {
9223 if n == 0.0 { return "0".to_string(); }
9224 if src.contains(['e', 'E']) {
9225 if n.fract() == 0.0 && n.abs() < 1e15 {
9226 return (n as i64).to_string();
9227 }
9228 return format!("{n}");
9229 }
9230 let t = src.trim();
9238 let (sign, body) = match t.strip_prefix('-') {
9239 Some(rest) => ("-", rest),
9240 None => ("", t.strip_prefix('+').unwrap_or(t)),
9241 };
9242 let (whole, frac) = match body.split_once('.') {
9243 Some((w, f)) => (w, f),
9244 None => (body, ""),
9245 };
9246 let whole_trimmed = whole.trim_start_matches('0');
9247 let whole_canon = if whole_trimmed.is_empty() { "0" } else { whole_trimmed };
9248 let frac_trimmed = frac.trim_end_matches('0');
9249 if frac_trimmed.is_empty() {
9250 if whole_canon == "0" { return "0".into(); }
9252 return format!("{sign}{whole_canon}");
9253 }
9254 format!("{sign}{whole_canon}.{frac_trimmed}")
9255}
9256
9257pub fn cast_value_to_atomic<I: DocIndexLike>(
9263 v: &Value, st: &crate::xpath::ast::SequenceType, idx: &I,
9264) -> Result<Value> {
9265 cast_value_to_atomic_impl(v, st, idx).map_err(|e| e.or_xpath_code("FORG0001"))
9266}
9267
9268fn cast_value_to_atomic_impl<I: DocIndexLike>(
9269 v: &Value, st: &crate::xpath::ast::SequenceType, idx: &I,
9270) -> Result<Value> {
9271 use crate::xpath::ast::ItemType;
9272 let s = value_to_string(v, idx);
9273 let make_typed = |kind: &'static str, lex: String,
9274 numeric: Option<f64>, boolean: Option<bool>| -> Value {
9275 Value::Typed(Box::new(TypedAtomic {
9276 kind, lexical: lex, numeric, boolean, user_type: None,
9277 }))
9278 };
9279 match &st.item {
9280 ItemType::Atomic(name) => {
9281 let kind = match atomic_kind_static(name) {
9282 Some(k) => k,
9283 None => return Ok(Value::String(s)),
9284 };
9285 match name.as_str() {
9286 "string" | "anyURI" | "anyAtomicType" | "untypedAtomic"
9287 | "normalizedString" | "token" | "Name" | "NCName"
9288 | "language" | "ID" | "IDREF" | "IDREFS" | "ENTITY"
9289 | "ENTITIES" | "NMTOKEN" | "NMTOKENS" | "NOTATION" | "QName" => {
9290 Ok(make_typed(kind, s, None, None))
9291 }
9292 "boolean" => {
9293 if let Value::Number(n) = v {
9297 let b = !(n.as_f64() == 0.0 || n.as_f64().is_nan());
9298 return Ok(make_typed("boolean",
9299 (if b { "true" } else { "false" }).into(),
9300 None, Some(b)));
9301 }
9302 if let Value::Typed(t) = v {
9303 if let Some(n) = t.numeric {
9304 let b = !(n == 0.0 || n.is_nan());
9305 return Ok(make_typed("boolean",
9306 (if b { "true" } else { "false" }).into(),
9307 None, Some(b)));
9308 }
9309 }
9310 match s.trim() {
9311 "true" | "1" => Ok(make_typed("boolean", "true".into(), None, Some(true))),
9312 "false" | "0" => Ok(make_typed("boolean", "false".into(), None, Some(false))),
9313 _ => Err(xpath_err(format!("cast to xs:boolean failed: {s:?}"))),
9314 }
9315 }
9316 "integer" | "long" | "int" | "short" | "byte"
9317 | "nonNegativeInteger" | "nonPositiveInteger"
9318 | "positiveInteger" | "negativeInteger"
9319 | "unsignedLong" | "unsignedInt" | "unsignedShort" | "unsignedByte" => {
9320 let n: i64 = s.trim().parse().map_err(|_| xpath_err(
9321 format!("cast to xs:{name} failed: {s:?}")))?;
9322 Ok(make_typed(kind, n.to_string(), Some(n as f64), None))
9323 }
9324 "decimal" | "double" | "float" | "numeric" => {
9325 let trimmed = s.trim();
9330 let raw: f64 = match trimmed {
9331 "INF" | "Infinity" => f64::INFINITY,
9332 "-INF" | "-Infinity" => f64::NEG_INFINITY,
9333 "NaN" => f64::NAN,
9334 _ => trimmed.parse().map_err(|_| xpath_err(
9335 format!("cast to xs:{name} failed: {s:?}")))?,
9336 };
9337 let n = if name == "float" { raw as f32 as f64 } else { raw };
9341 let canon = match name.as_str() {
9342 "double" => canonical_double_lex(n, v),
9343 "float" => canonical_float_lex(n, v),
9344 _ => canonical_decimal_lex(n, trimmed),
9346 };
9347 Ok(make_typed(kind, canon, Some(n), None))
9348 }
9349 "date" | "dateTime" | "time"
9350 | "duration" | "dayTimeDuration" | "yearMonthDuration"
9351 | "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay"
9352 | "hexBinary" | "base64Binary" => {
9353 if name == "dateTime" {
9359 if let Value::Typed(t) = v {
9360 if t.kind == "date" {
9361 let raw = t.lexical.as_str();
9366 let (date_part, tz) = if let Some(rest) =
9367 raw.strip_suffix('Z')
9368 {
9369 (rest, "Z")
9370 } else if raw.len() >= 6 {
9371 let cand = &raw[raw.len() - 6..];
9372 let b = cand.as_bytes();
9373 if (b[0] == b'+' || b[0] == b'-')
9374 && b[1].is_ascii_digit() && b[2].is_ascii_digit()
9375 && b[3] == b':'
9376 && b[4].is_ascii_digit() && b[5].is_ascii_digit()
9377 {
9378 (&raw[..raw.len() - 6], cand)
9379 } else {
9380 (raw, "")
9381 }
9382 } else {
9383 (raw, "")
9384 };
9385 let lex = format!("{date_part}T00:00:00{tz}");
9386 return Ok(make_typed("dateTime", lex, None, None));
9387 }
9388 }
9389 }
9390 if matches!(name.as_str(),
9395 "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay")
9396 {
9397 if let Value::Typed(t) = v {
9398 let dk = match t.kind {
9399 "date" => Some(DateKind::Date),
9400 "dateTime" => Some(DateKind::DateTime),
9401 _ => None,
9402 };
9403 if let Some(dk) = dk {
9404 if let Some((y, mo, d, _, _, _, _, tz)) =
9405 parse_xsd_date_time(&t.lexical, dk)
9406 {
9407 let tzs = tz.map(format_tz_suffix).unwrap_or_default();
9408 let lex = match name.as_str() {
9409 "gYear" => format!("{y:04}{tzs}"),
9410 "gYearMonth" => format!("{y:04}-{mo:02}{tzs}"),
9411 "gMonth" => format!("--{mo:02}{tzs}"),
9412 "gMonthDay" => format!("--{mo:02}-{d:02}{tzs}"),
9413 _ => format!("---{d:02}{tzs}"),
9414 };
9415 return Ok(make_typed(kind, lex, None, None));
9416 }
9417 }
9418 }
9419 }
9420 if matches!(name.as_str(), "hexBinary" | "base64Binary") {
9423 if let Some(lex) = convert_binary_kind(v, name) {
9424 return Ok(make_typed(kind, lex, None, None));
9425 }
9426 }
9427 let trimmed = s.trim();
9428 if !lexical_matches_type(trimmed, name) {
9429 if date_year_out_of_range(trimmed, name) {
9433 return Err(xpath_err(format!(
9434 "cast to xs:{name}: year is outside the \
9435 supported range: {trimmed:?}"
9436 )).with_xpath_code("FODT0001"));
9437 }
9438 return Err(xpath_err(format!(
9439 "cast to xs:{name} failed: {trimmed:?}"
9440 )));
9441 }
9442 if matches!(name.as_str(), "dateTime" | "time")
9448 && trimmed.contains("24:00:00")
9449 {
9450 let dk = if name == "dateTime" {
9451 DateKind::DateTime } else { DateKind::Time };
9452 if let Some((y, mo, d, h, mi, sec, frac, tz)) =
9453 parse_xsd_date_time(trimmed, dk)
9454 {
9455 let lex = if name == "dateTime" {
9456 format_datetime_lexical(y, mo, d, h, mi, sec, frac, tz)
9457 } else {
9458 let mut l = format!("{h:02}:{mi:02}:{sec:02}");
9459 if frac != 0 {
9460 let mut f = format!(".{frac:06}");
9461 while f.ends_with('0') { f.pop(); }
9462 l.push_str(&f);
9463 }
9464 if let Some(tz_m) = tz { l.push_str(&format_tz_suffix(tz_m)); }
9465 l
9466 };
9467 return Ok(make_typed(kind, lex, None, None));
9468 }
9469 }
9470 if name == "hexBinary" {
9473 return Ok(make_typed(kind, trimmed.to_ascii_uppercase(), None, None));
9474 }
9475 Ok(make_typed(kind, trimmed.to_string(), None, None))
9476 }
9477 _ => Ok(Value::String(s)),
9478 }
9479 }
9480 _ => Ok(v.clone()),
9483 }
9484}
9485
9486#[derive(Debug, Clone, Copy)]
9489pub enum DateKind { Date, Time, DateTime }
9490
9491fn parse_xsd_date_time(s: &str, kind: DateKind)
9498 -> Option<(i32, u8, u8, u8, u8, u8, u32, Option<i16>)>
9499{
9500 let s = s.trim();
9501 if matches!(kind, DateKind::Time) {
9503 let (mut h, m, sec, frac, tz) = parse_xsd_time(s)?;
9504 if h == 24 && m == 0 && sec == 0 && frac == 0 { h = 0; }
9507 return Some((0, 0, 0, h, m, sec, frac, tz));
9508 }
9509 let bytes = s.as_bytes();
9514 if bytes.len() < 10 { return None; }
9515 let (neg_year, body) = match s.strip_prefix('-') {
9516 Some(rest) => (true, rest),
9517 None => (false, s),
9518 };
9519 let body_bytes = body.as_bytes();
9522 let first_dash = body_bytes.iter().position(|&c| c == b'-')?;
9523 if first_dash < 4 { return None; }
9524 if body_bytes.len() < first_dash + 6 { return None; }
9525 if body_bytes[first_dash + 3] != b'-' { return None; }
9526 let year_str = &body[..first_dash];
9527 let mut year: i32 = year_str.parse().ok()?;
9528 if neg_year { year = -year; }
9529 let month: u8 = std::str::from_utf8(&body_bytes[first_dash + 1..first_dash + 3])
9530 .ok()?.parse().ok()?;
9531 let day: u8 = std::str::from_utf8(&body_bytes[first_dash + 4..first_dash + 6])
9532 .ok()?.parse().ok()?;
9533 if !(1..=12).contains(&month) { return None; }
9538 let max_day = days_in_month(year, month as u32);
9539 if !(1..=max_day as u8).contains(&day) { return None; }
9540 let rest = &body[first_dash + 6..];
9541 match kind {
9542 DateKind::Date => {
9543 let tz = parse_tz_suffix(rest);
9544 Some((year, month, day, 0, 0, 0, 0, tz))
9545 }
9546 DateKind::DateTime => {
9547 if !rest.starts_with('T') || rest.len() < 9 { return None; }
9549 let (h, m, sec, frac, tz) = parse_xsd_time(&rest[1..])?;
9550 if h == 24 && m == 0 && sec == 0 && frac == 0 {
9554 let (y, mo, d) = add_one_day(year, month, day);
9555 return Some((y, mo, d, 0, 0, 0, 0, tz));
9556 }
9557 Some((year, month, day, h, m, sec, frac, tz))
9558 }
9559 DateKind::Time => unreachable!(),
9560 }
9561}
9562
9563fn parse_xsd_time(s: &str) -> Option<(u8, u8, u8, u32, Option<i16>)> {
9568 let bytes = s.as_bytes();
9569 if bytes.len() < 8 { return None; }
9570 let h: u8 = std::str::from_utf8(&bytes[..2]).ok()?.parse().ok()?;
9571 if bytes[2] != b':' { return None; }
9572 let m: u8 = std::str::from_utf8(&bytes[3..5]).ok()?.parse().ok()?;
9573 if bytes[5] != b':' { return None; }
9574 let sec: u8 = std::str::from_utf8(&bytes[6..8]).ok()?.parse().ok()?;
9575 let mut rest_start = 8;
9576 let mut frac_us: u32 = 0;
9577 if s.as_bytes().get(8) == Some(&b'.') {
9578 let mut i = 9;
9579 while i < bytes.len() && bytes[i].is_ascii_digit() { i += 1; }
9580 let digits = &s[9..i];
9581 let take: String = digits.chars().chain(std::iter::repeat('0')).take(6).collect();
9583 frac_us = take.parse().ok().unwrap_or(0);
9584 rest_start = i;
9585 }
9586 let tz = parse_tz_suffix(&s[rest_start..]);
9587 Some((h, m, sec, frac_us, tz))
9588}
9589
9590fn parse_tz_suffix(s: &str) -> Option<i16> {
9594 if s.is_empty() { return None; }
9595 if s == "Z" { return Some(0); }
9596 let bytes = s.as_bytes();
9597 if bytes.len() < 6 { return None; }
9598 let sign = match bytes[0] { b'+' => 1, b'-' => -1, _ => return None };
9599 let hh: i16 = std::str::from_utf8(&bytes[1..3]).ok()?.parse().ok()?;
9600 if bytes[3] != b':' { return None; }
9601 let mm: i16 = std::str::from_utf8(&bytes[4..6]).ok()?.parse().ok()?;
9602 Some(sign * (hh * 60 + mm))
9603}
9604
9605fn format_date_locale_prefix(lang: &str, calendar: &str) -> String {
9612 let mut prefix = String::new();
9613 let lang = lang.trim();
9614 if !lang.is_empty() && !lang.eq_ignore_ascii_case("en") {
9615 prefix.push_str("[Language: en]");
9616 }
9617 let cal = calendar.trim().rsplit(['}', ':']).next().unwrap_or("").trim();
9620 if !cal.is_empty()
9621 && !matches!(cal.to_ascii_uppercase().as_str(), "AD" | "ISO" | "CE") {
9622 prefix.push_str("[Calendar: AD]");
9623 }
9624 prefix
9625}
9626
9627fn format_date_time_picture(value: &str, picture: &str, kind: DateKind) -> Result<String> {
9635 let (y, mo, d, h, mi, s, frac, tz) = match parse_xsd_date_time(value, kind) {
9636 Some(t) => t,
9637 None => return Ok(value.to_string()), };
9639 let mut out = String::with_capacity(picture.len());
9640 let mut chars = picture.chars().peekable();
9641 while let Some(c) = chars.next() {
9642 match c {
9643 '[' => {
9644 if chars.peek() == Some(&'[') {
9646 chars.next();
9647 out.push('[');
9648 continue;
9649 }
9650 let mut marker = String::new();
9652 while let Some(&nc) = chars.peek() {
9653 if nc == ']' { break; }
9654 marker.push(nc);
9655 chars.next();
9656 }
9657 if chars.peek() != Some(&']') {
9660 return Err(xpath_err(format!(
9661 "format-date/format-time: unterminated picture \
9662 marker '[{marker}' (XTDE1340)"
9663 )));
9664 }
9665 chars.next();
9666 let comp = marker.trim().chars().next().unwrap_or('\0');
9667 let kind_mismatch = match (kind, comp) {
9671 (DateKind::Date, 'H' | 'h' | 'm' | 's' | 'f' | 'P') => true,
9672 (DateKind::Time, 'Y' | 'M' | 'D' | 'd' | 'F' | 'W' | 'w' | 'C' | 'E') => true,
9673 _ => false,
9674 };
9675 if kind_mismatch {
9676 return Err(xpath_err(format!(
9677 "format-date/format-time: component '{comp}' not \
9678 available for this date/time value (XTDE1350)"
9679 )));
9680 }
9681 if !is_known_picture_component(comp) {
9682 return Err(xpath_err(format!(
9683 "format-date/format-time: unknown component '{comp}' \
9684 in picture string (XTDE1340)"
9685 )));
9686 }
9687 out.push_str(&format_picture_marker(&marker, y, mo, d, h, mi, s, frac, tz));
9688 }
9689 ']' => {
9690 if chars.peek() == Some(&']') {
9693 chars.next();
9694 out.push(']');
9695 } else {
9696 out.push(']');
9697 }
9698 }
9699 other => out.push(other),
9700 }
9701 }
9702 Ok(out)
9703}
9704
9705fn is_known_picture_component(c: char) -> bool {
9708 matches!(c, 'Y' | 'M' | 'D' | 'd' | 'F' | 'W' | 'w' | 'H' | 'h'
9709 | 'm' | 's' | 'f' | 'Z' | 'z' | 'P' | 'C' | 'E')
9710}
9711
9712fn format_picture_marker(
9722 marker: &str, y: i32, mo: u8, d: u8, h: u8, mi: u8, s: u8,
9723 frac_us: u32, tz: Option<i16>,
9724) -> String {
9725 let marker = marker.trim();
9726 let comp = match marker.chars().next() { Some(c) => c, None => return String::new() };
9727 let rest = &marker[comp.len_utf8()..];
9728 let (presentation, width_spec) = match rest.split_once(',') {
9730 Some((p, w)) => (p.trim(), Some(w.trim())),
9731 None => (rest.trim(), None),
9732 };
9733 let (min_w_raw, max_w_raw) = parse_width_spec(width_spec);
9734 let unwrap_star = |v: Option<usize>|
9741 v.and_then(|n| if n == usize::MAX { None } else { Some(n) });
9742 let min_w = unwrap_star(min_w_raw);
9743 let max_w = unwrap_star(max_w_raw);
9744 let pres = if !presentation.is_empty() { presentation }
9749 else { match comp {
9750 'm' | 's' => "01",
9751 _ => "1",
9752 }};
9753
9754 match comp {
9755 'Y' => format_numeric_component((y as i64).abs(), pres, min_w, max_w, true),
9759 'M' if name_presentation(pres) =>
9760 format_named_component(mo as i64, pres, max_w, MONTH_NAMES),
9761 'M' => format_numeric_component(mo as i64, pres, min_w, max_w, false),
9762 'D' => format_numeric_component(d as i64, pres, min_w, max_w, false),
9763 'H' => format_numeric_component(h as i64, pres, min_w, max_w, false),
9764 'h' => {
9765 let hh = if h == 0 { 12 } else if h > 12 { h - 12 } else { h } as i64;
9767 format_numeric_component(hh, pres, min_w, max_w, false)
9768 }
9769 'm' => format_numeric_component(mi as i64, pres, min_w, max_w, false),
9770 's' => format_numeric_component(s as i64, pres, min_w, max_w, false),
9771 'f' => format_fractional_seconds(frac_us, pres, min_w_raw, max_w_raw),
9772 'F' => {
9777 let weekday = day_of_week(y, mo as u32, d as u32);
9778 if name_presentation(pres) {
9779 format_named_component(weekday as i64, pres, max_w, DAY_NAMES)
9780 } else {
9781 format_numeric_component(weekday as i64, pres, min_w, max_w, false)
9782 }
9783 }
9784 'W' => format_numeric_component(
9787 iso_week_of_year(y, mo as u32, d as u32), pres, min_w, max_w, false),
9788 'w' => {
9791 let wd = day_of_week(y, mo as u32, d as u32) as i64; let thursday_dom = d as i64 + 4 - wd;
9793 let wim = (thursday_dom + 6).div_euclid(7);
9794 format_numeric_component(wim, pres, min_w, max_w, false)
9795 }
9796 'd' => {
9799 let day_of_year = month_start_day(y, mo as u32) + d as u32;
9800 format_numeric_component(day_of_year as i64, pres, min_w, max_w, false)
9801 }
9802 'E' => {
9804 if y >= 0 { "AD".to_string() } else { "BC".to_string() }
9805 }
9806 'Z' | 'z' => match tz {
9813 None => String::new(),
9814 Some(off) => {
9815 let sign = if off < 0 { '-' } else { '+' };
9816 let abs = off.unsigned_abs() as i64;
9817 let (hh, mm) = (abs / 60, abs % 60);
9818 let minimal = pres == "0";
9819 let show_min_always = !minimal && max_w.map_or(true, |w| w >= 3);
9820 let mut out = String::new();
9821 if comp == 'z' { out.push_str("GMT"); }
9822 out.push(sign);
9823 if minimal { out.push_str(&hh.to_string()); }
9824 else { out.push_str(&format!("{hh:02}")); }
9825 if show_min_always || mm != 0 {
9826 out.push(':');
9827 out.push_str(&format!("{mm:02}"));
9828 }
9829 out
9830 }
9831 },
9832 'P' => {
9833 let label = if h < 12 { "am" } else { "pm" };
9837 match pres.chars().next() {
9838 Some('n') => label.to_string(), Some('N') => label.to_uppercase(), _ => label.to_string(), }
9842 }
9843 _ => marker.to_string(),
9844 }
9845}
9846
9847fn format_numeric_component(
9856 n: i64, presentation: &str,
9857 min_w: Option<usize>, max_w: Option<usize>,
9858 truncate_year: bool,
9859) -> String {
9860 let (presentation, ordinal) = if let Some(rest) = presentation.strip_suffix('o') {
9866 (rest, true)
9867 } else if let Some(rest) = presentation.strip_suffix('c') {
9868 (rest, false)
9869 } else {
9870 (presentation, false)
9871 };
9872 let worded = match presentation {
9880 "I" => Some(roman_numeral(n, true)),
9881 "i" => Some(roman_numeral(n, false)),
9882 "A" => Some(alpha_label(n, true)),
9883 "a" => Some(alpha_label(n, false)),
9884 "W" => Some(english_words(n, ordinal, WordCase::Upper)),
9885 "w" => Some(english_words(n, ordinal, WordCase::Lower)),
9886 "Ww" => Some(english_words(n, ordinal, WordCase::Title)),
9887 _ => None,
9888 };
9889 if let Some(mut s) = worded {
9890 if let Some(mw) = min_w {
9895 let len = s.chars().count();
9896 if len < mw { s.extend(std::iter::repeat(' ').take(mw - len)); }
9897 }
9898 return s;
9899 }
9900 if let Some((zero_cp, count)) = picture_digit_family(presentation) {
9905 let cap = max_w.unwrap_or(count);
9910 let (value, want) = if truncate_year && n >= 0
9911 && cap < 5 && n.to_string().len() > cap {
9912 (n % 10_i64.pow(cap as u32), cap)
9913 } else {
9914 (n, min_w.unwrap_or(count))
9915 };
9916 let ascii = if value < 0 { format!("-{:0w$}", -value, w = want) }
9917 else { format!("{:0w$}", value, w = want) };
9918 return ascii.chars()
9919 .map(|c| c.to_digit(10)
9920 .and_then(|v| char::from_u32(zero_cp + v))
9921 .unwrap_or(c))
9922 .collect();
9923 }
9924 let pres_digits = presentation.chars().filter(|c| c.is_ascii_digit()).count().max(1);
9927 let want_width = min_w.unwrap_or(pres_digits);
9928 if truncate_year && n >= 0 {
9935 let bounded_by_pres = pres_digits >= 2;
9941 if max_w.is_some() || bounded_by_pres {
9942 let cap = max_w.unwrap_or(pres_digits);
9943 let raw = n.to_string();
9944 let digits = raw.len();
9945 if cap < 5 && digits > cap {
9946 let modulus = 10_i64.pow(cap as u32);
9947 let truncated = n % modulus;
9948 return format!("{:0width$}", truncated, width = cap);
9949 }
9950 let pad = want_width.max(min_w.unwrap_or(0));
9952 return format!("{:0width$}", n, width = pad);
9953 }
9954 }
9955 let formatted = if n < 0 {
9956 format!("-{:0width$}", -n, width = want_width)
9957 } else {
9958 format!("{:0width$}", n, width = want_width)
9959 };
9960 if ordinal {
9961 format!("{formatted}{}", english_ordinal_suffix(n))
9965 } else {
9966 formatted
9967 }
9968}
9969
9970fn english_ordinal_suffix(n: i64) -> &'static str {
9974 let abs = n.unsigned_abs();
9975 if (11..=13).contains(&(abs % 100)) {
9976 return "th";
9977 }
9978 match abs % 10 {
9979 1 => "st",
9980 2 => "nd",
9981 3 => "rd",
9982 _ => "th",
9983 }
9984}
9985
9986fn format_fractional_seconds(
9994 frac_us: u32, presentation: &str,
9995 min_w: Option<usize>, max_w: Option<usize>,
9996) -> String {
9997 let pres_digits = presentation.chars().filter(|c| c.is_ascii_digit()).count();
10019 let unwrap_star = |v: Option<usize>| v.and_then(|n| if n == usize::MAX { None } else { Some(n) });
10028 let min_w_explicit = min_w.map(|n| n != usize::MAX).unwrap_or(false);
10029 let max_w_explicit_star = max_w == Some(usize::MAX);
10030 let min_w = unwrap_star(min_w);
10031 let max_w = unwrap_star(max_w);
10032 let (min_w, max_w) = match (min_w, max_w) {
10033 (Some(mn), Some(mx)) => (mn, Some(mx)),
10034 (Some(mn), None) if max_w_explicit_star => (mn, None),
10035 (Some(mn), None) => (mn, Some(mn.max(pres_digits))),
10036 (None, Some(mx)) => (pres_digits.min(mx).max(1), Some(mx)),
10037 (None, None) if pres_digits > 0 => (pres_digits, Some(pres_digits)),
10038 (None, None) if min_w_explicit => (1, None),
10039 (None, None) => (1, None),
10040 };
10041 let target = max_w.unwrap_or(6).min(6);
10044 if target == 0 { return String::new(); }
10045 let divisor = 10u32.pow((6 - target) as u32);
10046 let half = divisor / 2;
10047 let rounded = (frac_us + half) / divisor;
10048 let max_value = 10u32.pow(target as u32);
10049 let clamped = rounded.min(max_value - 1);
10050 let padded = format!("{:0width$}", clamped, width = target);
10053 let padded_bytes = padded.as_bytes();
10054 let mut end = padded_bytes.len();
10055 while end > min_w && padded_bytes[end - 1] == b'0' { end -= 1; }
10056 let trimmed = &padded[..end];
10057 trimmed.to_string()
10062}
10063
10064fn parse_width_spec(spec: Option<&str>) -> (Option<usize>, Option<usize>) {
10068 let spec = match spec { Some(s) => s, None => return (None, None) };
10074 let (min_s, max_s) = match spec.split_once('-') {
10075 Some((a, b)) => (a, Some(b)),
10076 None => (spec, None),
10077 };
10078 let parse = |s: &str| -> Option<usize> {
10079 let s = s.trim();
10080 if s.is_empty() { None }
10081 else if s == "*" { Some(usize::MAX) }
10082 else { s.parse().ok() }
10083 };
10084 (parse(min_s), max_s.and_then(parse))
10085}
10086
10087pub fn resolve_uri_against(base: &str, rel: &str) -> String {
10093 resolve_uri_rfc3986(base, rel)
10094}
10095
10096
10097fn uri_has_scheme(uri: &str) -> bool {
10100 split_uri(uri).0.is_some()
10101}
10102
10103fn resolve_uri_rfc3986(base: &str, rel: &str) -> String {
10104 let (rs, ra, rp, rq, rf) = split_uri(rel);
10105 let (bs, ba, bp, bq, _bf) = split_uri(base);
10106 let (ts, ta, tp, tq) = if let Some(s) = rs {
10108 (s, ra, remove_dot_segments(rp), rq)
10109 } else if ra.is_some() {
10110 (bs.unwrap_or(""), ra, remove_dot_segments(rp), rq)
10111 } else if rp.is_empty() {
10112 let q = if rq.is_some() { rq } else { bq };
10113 (bs.unwrap_or(""), ba, bp.to_string(), q)
10114 } else if rp.starts_with('/') {
10115 (bs.unwrap_or(""), ba, remove_dot_segments(rp), rq)
10116 } else {
10117 let merged = merge_paths(ba.is_some(), bp, rp);
10118 (bs.unwrap_or(""), ba, remove_dot_segments(&merged), rq)
10119 };
10120 let mut out = String::new();
10122 if !ts.is_empty() {
10123 out.push_str(ts);
10124 out.push(':');
10125 }
10126 if let Some(a) = ta {
10127 out.push_str("//");
10128 out.push_str(a);
10129 }
10130 out.push_str(&tp);
10131 if let Some(q) = tq {
10132 out.push('?');
10133 out.push_str(q);
10134 }
10135 if let Some(f) = rf {
10136 out.push('#');
10137 out.push_str(f);
10138 }
10139 out
10140}
10141
10142fn split_uri(s: &str) -> (Option<&str>, Option<&str>, &str, Option<&str>, Option<&str>) {
10148 let bytes = s.as_bytes();
10149 let scheme_end = bytes.iter().position(|&b|
10151 !(b.is_ascii_alphanumeric() || b == b'+' || b == b'-' || b == b'.'));
10152 let (scheme, mut rest) = match scheme_end {
10153 Some(i) if i > 0 && bytes[i] == b':' && bytes[0].is_ascii_alphabetic() =>
10154 (Some(&s[..i]), &s[i + 1..]),
10155 _ => (None, s),
10156 };
10157 let fragment = rest.find('#').map(|i| {
10159 let f = &rest[i + 1..];
10160 rest = &rest[..i];
10161 f
10162 });
10163 let query = rest.find('?').map(|i| {
10165 let q = &rest[i + 1..];
10166 rest = &rest[..i];
10167 q
10168 });
10169 let (authority, path) = if let Some(after) = rest.strip_prefix("//") {
10171 let end = after.find(|c: char| c == '/' || c == '?' || c == '#')
10172 .unwrap_or(after.len());
10173 (Some(&after[..end]), &after[end..])
10174 } else {
10175 (None, rest)
10176 };
10177 (scheme, authority, path, query, fragment)
10178}
10179
10180fn remove_dot_segments(input: &str) -> String {
10181 let mut in_buf = input.to_string();
10183 let mut out = String::with_capacity(input.len());
10184 while !in_buf.is_empty() {
10185 if in_buf.starts_with("../") {
10186 in_buf.replace_range(..3, "");
10187 } else if in_buf.starts_with("./") {
10188 in_buf.replace_range(..2, "");
10189 } else if in_buf.starts_with("/./") {
10190 in_buf.replace_range(..3, "/");
10191 } else if in_buf == "/." {
10192 in_buf = "/".to_string();
10193 } else if in_buf.starts_with("/../") {
10194 in_buf.replace_range(..4, "/");
10195 if let Some(i) = out.rfind('/') { out.truncate(i); } else { out.clear(); }
10198 } else if in_buf == "/.." {
10199 in_buf = "/".to_string();
10200 if let Some(i) = out.rfind('/') { out.truncate(i); } else { out.clear(); }
10201 } else if in_buf == "." || in_buf == ".." {
10202 in_buf.clear();
10203 } else {
10204 let split = if let Some(stripped) = in_buf.strip_prefix('/') {
10207 stripped.find('/').map(|i| i + 1).unwrap_or(in_buf.len())
10208 } else {
10209 in_buf.find('/').unwrap_or(in_buf.len())
10210 };
10211 out.push_str(&in_buf[..split]);
10212 in_buf.replace_range(..split, "");
10213 }
10214 }
10215 out
10216}
10217
10218fn merge_paths(base_has_authority: bool, base_path: &str, rel_path: &str) -> String {
10219 if base_has_authority && base_path.is_empty() {
10221 let mut s = String::with_capacity(rel_path.len() + 1);
10222 s.push('/');
10223 s.push_str(rel_path);
10224 s
10225 } else {
10226 let cut = base_path.rfind('/').map(|i| i + 1).unwrap_or(0);
10227 let mut s = String::with_capacity(cut + rel_path.len());
10228 s.push_str(&base_path[..cut]);
10229 s.push_str(rel_path);
10230 s
10231 }
10232}
10233
10234fn adjust_timezone(lex: &str, kind: &str, new_tz_minutes: Option<i16>) -> String {
10242 let dk = match kind {
10243 "date" => DateKind::Date,
10244 "dateTime" => DateKind::DateTime,
10245 "time" => DateKind::Time,
10246 _ => return lex.to_string(),
10247 };
10248 let Some((y, mo, d, mut h, mut mi, s, frac, tz)) = parse_xsd_date_time(lex, dk) else {
10249 return lex.to_string();
10250 };
10251 let (mut yy, mut mm, mut dd) = (y as i64, mo as i64, d as i64);
10255 let needs_shift = match (tz, new_tz_minutes) {
10256 (Some(_), Some(_)) => true, _ => false, };
10259 if needs_shift {
10260 let old_tz = tz.unwrap() as i64;
10261 let new_tz = new_tz_minutes.unwrap() as i64;
10262 let delta_minutes = new_tz - old_tz;
10263 if matches!(dk, DateKind::Time) {
10264 let total = (h as i64) * 60 + (mi as i64) + delta_minutes;
10266 let wrapped = ((total % 1440) + 1440) % 1440;
10267 h = (wrapped / 60) as u8;
10268 mi = (wrapped % 60) as u8;
10269 } else {
10270 let days = ymd_to_days(yy as i32, mm as u32, dd as u32);
10271 let total_min = days * 24 * 60 + (h as i64) * 60 + (mi as i64) + delta_minutes;
10272 let new_days = total_min.div_euclid(24 * 60);
10273 let leftover = total_min.rem_euclid(24 * 60);
10274 h = (leftover / 60) as u8;
10275 mi = (leftover % 60) as u8;
10276 let (ny, nm, nd) = days_to_ymd(new_days);
10277 yy = ny as i64; mm = nm as i64; dd = nd as i64;
10278 }
10279 }
10280 let tz_str = match new_tz_minutes {
10281 None => String::new(),
10282 Some(0) => "Z".to_string(),
10283 Some(m) => format!("{}{:02}:{:02}",
10284 if m < 0 { '-' } else { '+' },
10285 (m.unsigned_abs() / 60), (m.unsigned_abs() % 60)),
10286 };
10287 match dk {
10288 DateKind::Time => {
10289 let mut s_out = format!("{:02}:{:02}:{:02}", h, mi, s);
10290 if frac > 0 {
10291 let mut frac_s = format!("{:06}", frac);
10293 while frac_s.ends_with('0') { frac_s.pop(); }
10294 if !frac_s.is_empty() { s_out.push('.'); s_out.push_str(&frac_s); }
10295 }
10296 s_out.push_str(&tz_str);
10297 s_out
10298 }
10299 DateKind::Date => {
10300 let sign = if yy < 0 { "-" } else { "" };
10301 format!("{}{:04}-{:02}-{:02}{}", sign, yy.unsigned_abs(), mm, dd, tz_str)
10302 }
10303 DateKind::DateTime => {
10304 let sign = if yy < 0 { "-" } else { "" };
10305 let mut s_out = format!("{}{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
10306 sign, yy.unsigned_abs(), mm, dd, h, mi, s);
10307 if frac > 0 {
10308 let mut frac_s = format!("{:06}", frac);
10309 while frac_s.ends_with('0') { frac_s.pop(); }
10310 if !frac_s.is_empty() { s_out.push('.'); s_out.push_str(&frac_s); }
10311 }
10312 s_out.push_str(&tz_str);
10313 s_out
10314 }
10315 }
10316}
10317
10318fn node_path_string<I: DocIndexLike>(node: NodeId, idx: &I) -> String {
10325 use crate::xpath::XPathNodeKind;
10326 if matches!(idx.kind(node), XPathNodeKind::Document) {
10328 return "/".to_string();
10329 }
10330 let mut segments: Vec<String> = Vec::new();
10332 let mut cur = node;
10333 loop {
10334 let parent = idx.parent(cur);
10335 let segment = match idx.kind(cur) {
10336 XPathNodeKind::Element => {
10337 let local = idx.local_name(cur).to_string();
10338 let uri = idx.namespace_uri(cur).to_string();
10339 let pos = match parent {
10340 Some(p) => element_index_among_siblings(p, cur, &local, &uri, idx),
10341 None => 1,
10342 };
10343 format!("Q{{{uri}}}{local}[{pos}]")
10344 }
10345 XPathNodeKind::Attribute => {
10346 let local = idx.local_name(cur).to_string();
10347 let uri = idx.namespace_uri(cur).to_string();
10348 format!("@Q{{{uri}}}{local}")
10349 }
10350 XPathNodeKind::Text | XPathNodeKind::CData => {
10351 let pos = match parent {
10352 Some(p) => kind_index_among_siblings(p, cur, XPathNodeKind::Text, idx),
10353 None => 1,
10354 };
10355 format!("text()[{pos}]")
10356 }
10357 XPathNodeKind::Comment => {
10358 let pos = match parent {
10359 Some(p) => kind_index_among_siblings(p, cur, XPathNodeKind::Comment, idx),
10360 None => 1,
10361 };
10362 format!("comment()[{pos}]")
10363 }
10364 XPathNodeKind::PI => {
10365 let target = idx.pi_target(cur).to_string();
10366 let pos = match parent {
10367 Some(p) => pi_index_among_siblings(p, cur, &target, idx),
10368 None => 1,
10369 };
10370 format!("processing-instruction({target})[{pos}]")
10371 }
10372 XPathNodeKind::Namespace => {
10373 let prefix = idx.local_name(cur);
10374 format!("namespace::{prefix}")
10375 }
10376 XPathNodeKind::Document => break,
10377 };
10378 segments.push(segment);
10379 match parent {
10380 Some(p) if matches!(idx.kind(p), XPathNodeKind::Document) => break,
10381 Some(p) => { cur = p; }
10382 None => {
10383 segments.push("Q{}root()".to_string());
10387 return segments.into_iter().rev().collect::<Vec<_>>().join("/");
10388 }
10389 }
10390 }
10391 let mut parts: Vec<String> = Vec::with_capacity(segments.len() + 1);
10393 parts.push(String::new());
10394 parts.extend(segments.into_iter().rev());
10395 parts.join("/")
10396}
10397
10398fn element_index_among_siblings<I: DocIndexLike>(
10401 parent: NodeId, child: NodeId, local: &str, uri: &str, idx: &I,
10402) -> usize {
10403 let mut count = 0usize;
10404 for &sib in idx.children(parent) {
10405 if matches!(idx.kind(sib), crate::xpath::XPathNodeKind::Element)
10406 && idx.local_name(sib) == local
10407 && idx.namespace_uri(sib) == uri
10408 {
10409 count += 1;
10410 if sib == child { return count; }
10411 }
10412 }
10413 count
10414}
10415
10416fn kind_index_among_siblings<I: DocIndexLike>(
10420 parent: NodeId, child: NodeId,
10421 kind: crate::xpath::XPathNodeKind, idx: &I,
10422) -> usize {
10423 let mut count = 0usize;
10424 for &sib in idx.children(parent) {
10425 if idx.kind(sib) == kind {
10426 count += 1;
10427 if sib == child { return count; }
10428 }
10429 }
10430 count
10431}
10432
10433fn pi_index_among_siblings<I: DocIndexLike>(
10436 parent: NodeId, child: NodeId, target: &str, idx: &I,
10437) -> usize {
10438 let mut count = 0usize;
10439 for &sib in idx.children(parent) {
10440 if matches!(idx.kind(sib), crate::xpath::XPathNodeKind::PI)
10441 && idx.pi_target(sib) == target
10442 {
10443 count += 1;
10444 if sib == child { return count; }
10445 }
10446 }
10447 count
10448}
10449
10450fn deep_equal_values<I: DocIndexLike>(
10458 a: &Value, b: &Value, idx: &I, bindings: &dyn XPathBindings,
10459) -> bool {
10460 let a_items = items_of(a);
10461 let b_items = items_of(b);
10462 if a_items.len() != b_items.len() { return false; }
10463 a_items.iter().zip(b_items.iter()).all(|(x, y)|
10464 deep_equal_one(x, y, idx, bindings))
10465}
10466
10467fn seq_from_items(mut items: Vec<Value>) -> Value {
10473 match items.len() {
10474 0 => Value::NodeSet(Vec::new()),
10475 1 => items.pop().unwrap(),
10476 _ => Value::Sequence(items),
10477 }
10478}
10479
10480fn key_number(v: &Value) -> Option<f64> {
10483 match v {
10484 Value::Number(n) => Some(n.as_f64()),
10485 Value::Typed(t) => t.numeric,
10486 _ => None,
10487 }
10488}
10489
10490pub fn map_key_eq<I: DocIndexLike>(a: &Value, b: &Value, idx: &I) -> bool {
10494 match (key_number(a), key_number(b)) {
10495 (Some(x), Some(y)) => (x.is_nan() && y.is_nan()) || x == y,
10496 (None, None) => value_to_string(a, idx) == value_to_string(b, idx),
10497 _ => false,
10498 }
10499}
10500
10501pub fn first_atomic_key<I: DocIndexLike>(v: &Value, idx: &I) -> Value {
10505 match v {
10506 Value::NodeSet(ns) => Value::String(
10507 ns.first().map(|&id| idx.string_value(id)).unwrap_or_default()),
10508 Value::ForeignNodeSet(_) => Value::String(value_to_string(v, idx)),
10509 Value::Sequence(items) => items.first()
10510 .map(|x| first_atomic_key(x, idx))
10511 .unwrap_or(Value::String(String::new())),
10512 Value::IntRange { lo, .. } => Value::Number(Numeric::Integer(*lo)),
10513 other => other.clone(),
10514 }
10515}
10516
10517fn eval_lookup<I: DocIndexLike>(
10520 base: &Value, key: &crate::xpath::ast::LookupKey,
10521 ctx: &EvalCtx<'_>, idx: &I,
10522) -> Result<Value> {
10523 use crate::xpath::ast::LookupKey;
10524 let keys: Option<Vec<Value>> = match key {
10526 LookupKey::Wildcard => None,
10527 LookupKey::Name(s) => Some(vec![Value::String(s.clone())]),
10528 LookupKey::Integer(i) => Some(vec![Value::Number(Numeric::Integer(*i))]),
10529 LookupKey::Expr(e) => Some(items_of(&eval_expr(e, ctx, idx)?)),
10530 };
10531 let mut out: Vec<Value> = Vec::new();
10532 for item in items_of(base) {
10533 match &item {
10534 Value::Map(entries) => match &keys {
10535 None => for (_, val) in entries.iter() { out.extend(items_of(val)); },
10536 Some(ks) => for k in ks {
10537 for (mk, mv) in entries.iter() {
10538 if map_key_eq(mk, k, idx) { out.extend(items_of(mv)); }
10539 }
10540 },
10541 },
10542 Value::Array(members) => match &keys {
10543 None => for m in members.iter() { out.extend(items_of(m)); },
10544 Some(ks) => for k in ks {
10545 let pos = value_to_number(k, idx);
10546 if pos.fract() == 0.0 && pos >= 1.0
10547 && (pos as usize) <= members.len()
10548 {
10549 out.extend(items_of(&members[pos as usize - 1]));
10550 }
10551 },
10552 },
10553 _ => return Err(xpath_err(
10554 "the '?' lookup operator requires a map or array (XPTY0004)")),
10555 }
10556 }
10557 Ok(seq_from_items(out))
10558}
10559
10560fn items_of(v: &Value) -> Vec<Value> {
10561 match v {
10562 Value::NodeSet(ns) => ns.iter()
10563 .map(|&id| Value::NodeSet(vec![id])).collect(),
10564 Value::Sequence(items) => items.clone(),
10565 Value::ForeignNodeSet(ns) => ns.iter()
10566 .map(|&p| Value::ForeignNodeSet(vec![p])).collect(),
10567 Value::IntRange { lo, hi } =>
10568 (*lo..=*hi).map(|i| Value::Number(Numeric::Double(i as f64))).collect(),
10569 other => vec![other.clone()],
10570 }
10571}
10572
10573fn sequence_len(v: &Value) -> usize {
10580 match v {
10581 Value::NodeSet(ns) => ns.len(),
10582 Value::ForeignNodeSet(ns) => ns.len(),
10583 Value::Sequence(items) => items.iter().map(sequence_len).sum(),
10584 Value::IntRange { lo, hi } => ((hi - lo) as usize).saturating_add(1),
10585 _ => 1,
10586 }
10587}
10588
10589fn iter_items<'a>(v: &'a Value) -> Box<dyn Iterator<Item = Value> + 'a> {
10596 match v {
10597 Value::NodeSet(ns) => Box::new(ns.iter().map(|&id| Value::NodeSet(vec![id]))),
10598 Value::ForeignNodeSet(ns) => Box::new(ns.iter().map(|&p| Value::ForeignNodeSet(vec![p]))),
10599 Value::Sequence(items) => Box::new(items.iter().flat_map(iter_items)),
10600 Value::IntRange { lo, hi } => Box::new((*lo..=*hi).map(|i| Value::Number(Numeric::Double(i as f64)))),
10601 other => Box::new(std::iter::once(other.clone())),
10602 }
10603}
10604
10605fn deep_equal_one<I: DocIndexLike>(
10606 a: &Value, b: &Value, idx: &I, bindings: &dyn XPathBindings,
10607) -> bool {
10608 match (a, b) {
10609 (Value::NodeSet(an), Value::NodeSet(bn))
10610 if an.len() == 1 && bn.len() == 1 =>
10611 {
10612 deep_equal_node(an[0], bn[0], idx)
10613 }
10614 (Value::NodeSet(ns), other) | (other, Value::NodeSet(ns))
10622 if ns.len() == 1 && matches!(idx.kind(ns[0]), XPathNodeKind::Text) =>
10623 {
10624 value_to_string_with(other, idx, bindings) == idx.string_value(ns[0])
10625 }
10626 (Value::NodeSet(_), _) | (_, Value::NodeSet(_)) => false,
10630 _ if !deep_equal_types_comparable(a, b) => false,
10637 _ => values_eq(a, b, idx, bindings),
10638 }
10639}
10640
10641fn deep_equal_types_comparable(a: &Value, b: &Value) -> bool {
10645 #[derive(PartialEq, Eq, Clone, Copy)]
10646 enum Family { Numeric, String, Boolean, Date, Duration, Other }
10647 fn family_of(v: &Value) -> Family {
10648 match v {
10649 Value::Number(_) => Family::Numeric,
10650 Value::Boolean(_) => Family::Boolean,
10651 Value::String(_) => Family::String,
10652 Value::Typed(t) => match t.kind {
10653 "string" | "anyURI" | "untypedAtomic" | "normalizedString"
10654 | "token" | "Name" | "NCName" | "QName"
10655 | "language" | "NMTOKEN" | "ID" | "IDREF" | "ENTITY"
10656 => Family::String,
10657 "boolean" => Family::Boolean,
10658 "date" | "dateTime" | "time"
10659 | "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay"
10660 => Family::Date,
10661 "duration" | "dayTimeDuration" | "yearMonthDuration"
10662 => Family::Duration,
10663 k if matches!(k,
10664 "integer" | "decimal" | "double" | "float"
10665 | "long" | "int" | "short" | "byte"
10666 | "unsignedLong" | "unsignedInt" | "unsignedShort" | "unsignedByte"
10667 | "nonNegativeInteger" | "nonPositiveInteger"
10668 | "positiveInteger" | "negativeInteger" | "numeric"
10669 ) => Family::Numeric,
10670 _ => Family::Other,
10671 },
10672 _ => Family::Other,
10673 }
10674 }
10675 let fa = family_of(a);
10676 let fb = family_of(b);
10677 if fa == Family::Other || fb == Family::Other { return true; }
10680 fa == fb
10681}
10682
10683fn deep_equal_node<I: DocIndexLike>(a: NodeId, b: NodeId, idx: &I) -> bool {
10684 if idx.kind(a) != idx.kind(b) { return false; }
10685 match idx.kind(a) {
10686 XPathNodeKind::Element => {
10687 if idx.namespace_uri(a) != idx.namespace_uri(b) { return false; }
10688 if idx.local_name(a) != idx.local_name(b) { return false; }
10689 let mut a_attrs: Vec<NodeId> = idx.attr_range(a).collect();
10692 let mut b_attrs: Vec<NodeId> = idx.attr_range(b).collect();
10693 if a_attrs.len() != b_attrs.len() { return false; }
10694 a_attrs.sort_by_key(|&id| (idx.namespace_uri(id).to_string(),
10699 idx.local_name(id).to_string()));
10700 b_attrs.sort_by_key(|&id| (idx.namespace_uri(id).to_string(),
10701 idx.local_name(id).to_string()));
10702 for (&aa, &bb) in a_attrs.iter().zip(b_attrs.iter()) {
10703 if idx.namespace_uri(aa) != idx.namespace_uri(bb) { return false; }
10704 if idx.local_name(aa) != idx.local_name(bb) { return false; }
10705 if idx.string_value(aa) != idx.string_value(bb) { return false; }
10706 }
10707 let a_kids = idx.children(a);
10712 let b_kids = idx.children(b);
10713 if a_kids.len() != b_kids.len() { return false; }
10714 a_kids.iter().zip(b_kids.iter())
10715 .all(|(&x, &y)| deep_equal_node(x, y, idx))
10716 }
10717 XPathNodeKind::Document => {
10718 let a_kids = idx.children(a);
10719 let b_kids = idx.children(b);
10720 if a_kids.len() != b_kids.len() { return false; }
10721 a_kids.iter().zip(b_kids.iter())
10722 .all(|(&x, &y)| deep_equal_node(x, y, idx))
10723 }
10724 XPathNodeKind::Attribute => {
10725 idx.namespace_uri(a) == idx.namespace_uri(b)
10726 && idx.local_name(a) == idx.local_name(b)
10727 && idx.string_value(a) == idx.string_value(b)
10728 }
10729 XPathNodeKind::Text | XPathNodeKind::CData => {
10730 idx.string_value(a) == idx.string_value(b)
10731 }
10732 XPathNodeKind::Comment => {
10733 idx.string_value(a) == idx.string_value(b)
10734 }
10735 XPathNodeKind::PI => {
10736 idx.pi_target(a) == idx.pi_target(b)
10737 && idx.string_value(a) == idx.string_value(b)
10738 }
10739 XPathNodeKind::Namespace => {
10740 idx.local_name(a) == idx.local_name(b)
10741 && idx.string_value(a) == idx.string_value(b)
10742 }
10743 }
10744}
10745
10746const MONTH_NAMES: &[&str] = &[
10750 "", "January", "February", "March", "April", "May", "June",
10751 "July", "August", "September", "October", "November", "December",
10752];
10753
10754const DAY_NAMES: &[&str] = &[
10757 "", "Monday", "Tuesday", "Wednesday", "Thursday",
10758 "Friday", "Saturday", "Sunday",
10759];
10760
10761fn name_presentation(pres: &str) -> bool {
10764 matches!(pres, "N" | "n" | "Nn"
10765 | "NN" | "nn" )
10767}
10768
10769fn format_named_component(value: i64, pres: &str, max_w: Option<usize>, table: &[&str]) -> String {
10773 let idx = value as usize;
10774 let name = table.get(idx).copied().unwrap_or("");
10775 let chosen: String = match max_w {
10784 Some(max) if max < name.chars().count() => {
10785 let abbrev3: String = name.chars().take(3).collect();
10789 if max >= 3 { abbrev3 } else { name.chars().take(max).collect() }
10790 }
10791 _ => name.to_string(),
10792 };
10793 match pres {
10794 "N" => chosen.to_uppercase(),
10795 "n" => chosen.to_lowercase(),
10796 _ => chosen,
10797 }
10798}
10799
10800const DIGIT_FAMILY_ZEROS: &[u32] = &[
10805 0x0660, 0x06F0, 0x0966, 0x09E6, 0x0A66, 0x0AE6, 0x0B66, 0x0BE6, 0x0C66, 0x0CE6, 0x0D66, 0x0E50, 0x0ED0, 0x0F20, 0x1040, 0x17E0, 0x1810, 0xFF10, 0x104A0, 0x1D7CE, ];
10826
10827fn picture_digit_family(pres: &str) -> Option<(u32, usize)> {
10832 if pres.is_empty() || pres.is_ascii() { return None; }
10833 let mut zero = None;
10834 let mut count = 0;
10835 for c in pres.chars() {
10836 let cp = c as u32;
10837 let z = DIGIT_FAMILY_ZEROS.iter().copied()
10838 .find(|&z| cp >= z && cp <= z + 9)?;
10839 match zero {
10840 Some(prev) if prev != z => return None, _ => zero = Some(z),
10842 }
10843 count += 1;
10844 }
10845 zero.map(|z| (z, count))
10846}
10847
10848fn iso_week_of_year(y: i32, m: u32, d: u32) -> i64 {
10853 let ord = (month_start_day(y, m) + d) as i64; let wd = day_of_week(y, m, d) as i64; let week = (ord - wd + 10).div_euclid(7);
10856 if week < 1 {
10857 iso_weeks_in_year(y - 1)
10858 } else if week > iso_weeks_in_year(y) {
10859 1
10860 } else {
10861 week
10862 }
10863}
10864
10865fn iso_weeks_in_year(y: i32) -> i64 {
10868 let p = |yy: i32| (yy + yy.div_euclid(4) - yy.div_euclid(100) + yy.div_euclid(400))
10869 .rem_euclid(7);
10870 if p(y) == 4 || p(y - 1) == 3 { 53 } else { 52 }
10871}
10872
10873fn day_of_week(y: i32, m: u32, d: u32) -> u32 {
10874 let days = ymd_to_days(y, m, d);
10878 let wd = ((days + 3).rem_euclid(7) as u32) + 1;
10879 wd
10880}
10881
10882fn month_start_day(y: i32, m: u32) -> u32 {
10886 let mut total = 0;
10887 let leap = is_leap_year(y);
10888 for mm in 1..m {
10889 total += match mm {
10890 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
10891 4 | 6 | 9 | 11 => 30,
10892 2 => if leap { 29 } else { 28 },
10893 _ => 0,
10894 };
10895 }
10896 total
10897}
10898
10899fn roman_numeral(n: i64, upper: bool) -> String {
10903 if !(1..=3999).contains(&n) { return n.to_string(); }
10904 const PAIRS: &[(i64, &str, &str)] = &[
10905 (1000, "M", "m"),
10906 (900, "CM", "cm"),
10907 (500, "D", "d"),
10908 (400, "CD", "cd"),
10909 (100, "C", "c"),
10910 (90, "XC", "xc"),
10911 (50, "L", "l"),
10912 (40, "XL", "xl"),
10913 (10, "X", "x"),
10914 (9, "IX", "ix"),
10915 (5, "V", "v"),
10916 (4, "IV", "iv"),
10917 (1, "I", "i"),
10918 ];
10919 let mut n = n;
10920 let mut out = String::new();
10921 for &(val, hi, lo) in PAIRS {
10922 while n >= val {
10923 out.push_str(if upper { hi } else { lo });
10924 n -= val;
10925 }
10926 }
10927 out
10928}
10929
10930fn alpha_label(n: i64, upper: bool) -> String {
10934 if n < 1 { return n.to_string(); }
10935 let base = if upper { b'A' } else { b'a' };
10936 let mut buf = Vec::new();
10937 let mut n = n;
10938 while n > 0 {
10939 n -= 1;
10940 buf.push(base + (n % 26) as u8);
10941 n /= 26;
10942 }
10943 buf.reverse();
10944 String::from_utf8(buf).unwrap_or_default()
10945}
10946
10947#[derive(Clone, Copy)]
10948pub enum WordCase { Upper, Lower, Title }
10949
10950pub fn english_words(n: i64, ordinal: bool, case: WordCase) -> String {
10957 if n < 0 {
10958 let s = english_words(-n, ordinal, case);
10959 return format!("MINUS {s}");
10960 }
10961 let words = if ordinal { english_ordinal(n) } else { english_cardinal(n) };
10962 case_transform(&words, case)
10963}
10964
10965pub fn case_transform(s: &str, case: WordCase) -> String {
10966 match case {
10967 WordCase::Upper => s.to_uppercase(),
10968 WordCase::Lower => s.to_lowercase(),
10969 WordCase::Title => {
10970 let mut out = String::with_capacity(s.len());
10972 let mut start_of_word = true;
10973 for c in s.chars() {
10974 if c.is_whitespace() || c == '-' {
10975 out.push(c);
10976 start_of_word = true;
10977 } else if start_of_word {
10978 for u in c.to_uppercase() { out.push(u); }
10979 start_of_word = false;
10980 } else {
10981 for l in c.to_lowercase() { out.push(l); }
10982 }
10983 }
10984 out
10985 }
10986 }
10987}
10988
10989fn english_cardinal(n: i64) -> String {
10990 const UNDER_20: &[&str] = &[
10991 "zero", "one", "two", "three", "four", "five", "six", "seven",
10992 "eight", "nine", "ten", "eleven", "twelve", "thirteen",
10993 "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
10994 "nineteen",
10995 ];
10996 const TENS: &[&str] = &[
10997 "", "", "twenty", "thirty", "forty", "fifty", "sixty",
10998 "seventy", "eighty", "ninety",
10999 ];
11000 if (0..20).contains(&n) { return UNDER_20[n as usize].to_string(); }
11001 if n < 100 {
11002 let t = (n / 10) as usize;
11003 let r = (n % 10) as usize;
11004 if r == 0 { return TENS[t].to_string(); }
11005 return format!("{} {}", TENS[t], UNDER_20[r]);
11007 }
11008 if n < 1000 {
11009 let h = (n / 100) as i64;
11010 let r = n % 100;
11011 let head = format!("{} hundred", UNDER_20[h as usize]);
11012 if r == 0 { head }
11013 else { format!("{head} and {}", english_cardinal(r)) }
11015 } else if n < 1_000_000 {
11016 let th = n / 1000;
11017 let r = n % 1000;
11018 let head = format!("{} thousand", english_cardinal(th));
11019 if r == 0 { head }
11020 else if r < 100 { format!("{head} and {}", english_cardinal(r)) }
11027 else { format!("{head} {}", english_cardinal(r)) }
11028 } else if n < 1_000_000_000 {
11029 let mil = n / 1_000_000;
11030 let r = n % 1_000_000;
11031 let head = format!("{} million", english_cardinal(mil));
11032 if r == 0 { head }
11033 else if r < 100 { format!("{head} and {}", english_cardinal(r)) }
11034 else { format!("{head} {}", english_cardinal(r)) }
11035 } else {
11036 n.to_string()
11039 }
11040}
11041
11042fn english_ordinal(n: i64) -> String {
11043 const ORDINAL_UNDER_20: &[&str] = &[
11044 "zeroth", "first", "second", "third", "fourth", "fifth",
11045 "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh",
11046 "twelfth", "thirteenth", "fourteenth", "fifteenth",
11047 "sixteenth", "seventeenth", "eighteenth", "nineteenth",
11048 ];
11049 const ORDINAL_TENS: &[&str] = &[
11050 "", "", "twentieth", "thirtieth", "fortieth", "fiftieth",
11051 "sixtieth", "seventieth", "eightieth", "ninetieth",
11052 ];
11053 const TENS: &[&str] = &[
11054 "", "", "twenty", "thirty", "forty", "fifty", "sixty",
11055 "seventy", "eighty", "ninety",
11056 ];
11057 if (0..20).contains(&n) { return ORDINAL_UNDER_20[n as usize].to_string(); }
11058 if n < 100 {
11059 let t = (n / 10) as usize;
11060 let r = (n % 10) as usize;
11061 if r == 0 { return ORDINAL_TENS[t].to_string(); }
11062 return format!("{} {}", TENS[t], ORDINAL_UNDER_20[r]);
11064 }
11065 let head_div = if n < 1000 { 100 }
11069 else if n < 1_000_000 { 1000 }
11070 else { 1_000_000 };
11071 let head_word = if n < 1000 {
11072 format!("{} hundred", english_cardinal(n / 100))
11073 } else if n < 1_000_000 {
11074 format!("{} thousand", english_cardinal(n / 1000))
11075 } else {
11076 format!("{} million", english_cardinal(n / 1_000_000))
11077 };
11078 let r = n % head_div;
11079 if r == 0 {
11080 format!("{head_word}th")
11084 } else if n < 1000 {
11085 format!("{head_word} and {}", english_ordinal(r))
11086 } else if r < 100 {
11087 format!("{head_word} and {}", english_ordinal(r))
11091 } else {
11092 format!("{head_word} {}", english_ordinal(r))
11093 }
11094}
11095
11096#[derive(Clone, Copy)]
11097enum MinMaxOp { Min, Max, Avg }
11098
11099fn min_max_avg<I: DocIndexLike>(
11106 v: &Value, idx: &I, op: MinMaxOp, ci: bool,
11107) -> Result<Value> {
11108 let strs = sequence_to_strings(v, idx);
11109 if strs.is_empty() {
11110 return Ok(Value::NodeSet(Vec::new()));
11111 }
11112 if let Value::Sequence(items) = v {
11115 if matches!(op, MinMaxOp::Avg) {
11117 if let Some((kind, total, count)) = duration_seq_total(items) {
11118 let units = if kind == "yearMonthDuration" {
11124 round_months_half_up(total as f64 / count as f64)
11125 } else {
11126 total / count
11127 };
11128 return Ok(duration_value(kind, units));
11129 }
11130 }
11131 if matches!(op, MinMaxOp::Min | MinMaxOp::Max)
11135 && items.iter().all(|x| matches!(x, Value::Typed(_)))
11136 {
11137 use std::cmp::Ordering;
11138 let mut best = &items[0];
11139 let mut comparable = true;
11140 for x in &items[1..] {
11141 match compare_typed_values(best, x) {
11142 Some(ord) => {
11143 let take = match op {
11144 MinMaxOp::Max => ord == Ordering::Less,
11145 _ => ord == Ordering::Greater,
11146 };
11147 if take { best = x; }
11148 }
11149 None => { comparable = false; break; }
11150 }
11151 }
11152 if comparable { return Ok(best.clone()); }
11153 }
11154 }
11155 fn is_string_item(v: &Value) -> bool {
11166 match v {
11167 Value::String(_) => true,
11168 Value::Typed(t) => matches!(t.kind,
11169 "string" | "anyURI" | "normalizedString" | "token"
11170 | "Name" | "NCName" | "language"),
11171 _ => false,
11172 }
11173 }
11174 let all_typed_string = match v {
11175 Value::Typed(_) | Value::String(_) => is_string_item(v),
11176 Value::Sequence(items) => !items.is_empty()
11177 && items.iter().all(is_string_item),
11178 _ => false,
11179 };
11180 if all_typed_string {
11181 if matches!(op, MinMaxOp::Avg) {
11182 return Ok(Value::Number(Numeric::Double(f64::NAN)));
11183 }
11184 let key = |s: &String| if ci { ascii_ci_fold(s) } else { s.clone() };
11188 let chosen = match op {
11189 MinMaxOp::Min => strs.iter().min_by(|a, b| key(a).cmp(&key(b))).cloned(),
11190 MinMaxOp::Max => strs.iter().max_by(|a, b| key(a).cmp(&key(b))).cloned(),
11191 MinMaxOp::Avg => unreachable!(),
11192 };
11193 return Ok(chosen.map(Value::String).unwrap_or(Value::NodeSet(Vec::new())));
11194 }
11195 let numeric: Option<Vec<f64>> = strs.iter()
11197 .map(|s| s.trim().parse::<f64>().ok())
11198 .collect();
11199 if let Some(nums) = numeric {
11200 let n = nums.len() as f64;
11201 let result = match op {
11202 MinMaxOp::Min => nums.into_iter().fold(f64::INFINITY, f64::min),
11203 MinMaxOp::Max => nums.into_iter().fold(f64::NEG_INFINITY, f64::max),
11204 MinMaxOp::Avg => nums.into_iter().sum::<f64>() / n,
11205 };
11206 let kind = match v {
11212 Value::Sequence(items) => items.iter().fold(Some("integer"), |acc, it| {
11213 match (acc, numeric_kind_of(it)) {
11214 (Some(a), Some(b)) => numeric_promote_kind(Some(a), Some(b)),
11215 _ => None,
11216 }
11217 }),
11218 Value::IntRange { .. } => Some("integer"),
11219 other => numeric_kind_of(other),
11220 }.unwrap_or("double");
11221 let kind = if matches!(op, MinMaxOp::Avg) && kind == "integer" { "decimal" } else { kind };
11222 return Ok(Value::Number(Numeric::of_kind(kind, result)));
11223 }
11224 if matches!(op, MinMaxOp::Avg) {
11228 return Ok(Value::Number(Numeric::Double(f64::NAN)));
11229 }
11230 let key = |s: &String| if ci { ascii_ci_fold(s) } else { s.clone() };
11231 let chosen = match op {
11232 MinMaxOp::Min => strs.iter().min_by(|a, b| key(a).cmp(&key(b))).cloned(),
11233 MinMaxOp::Max => strs.iter().max_by(|a, b| key(a).cmp(&key(b))).cloned(),
11234 MinMaxOp::Avg => unreachable!(),
11235 };
11236 Ok(chosen.map(Value::String).unwrap_or(Value::NodeSet(Vec::new())))
11237}
11238
11239#[derive(Clone, Copy)]
11241enum ValueCmp { Eq, Ne, Lt, Gt, Le, Ge }
11242
11243fn is_unordered_atomic_kind(kind: &str) -> bool {
11255 matches!(kind,
11256 "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay"
11257 | "duration" | "hexBinary" | "base64Binary"
11258 | "QName" | "NOTATION")
11259}
11260
11261fn value_compare<I: DocIndexLike>(
11262 l: &Expr, r: &Expr, ctx: &EvalCtx<'_>, idx: &I, op: ValueCmp,
11263) -> Result<Value> {
11264 let lv = atomise_singleton(eval_expr(l, ctx, idx)?, idx, ctx.bindings)?;
11265 let rv = atomise_singleton(eval_expr(r, ctx, idx)?, idx, ctx.bindings)?;
11266 let (lv, rv) = match (lv, rv) {
11267 (None, _) | (_, None) => return Ok(Value::NodeSet(Vec::new())),
11268 (Some(a), Some(b)) => (a, b),
11269 };
11270 if value_is_function(&lv) || value_is_function(&rv) {
11273 return Err(xpath_err(
11274 "a function item cannot appear in a value comparison (FOTY0013)")
11275 .with_xpath_code("FOTY0013"));
11276 }
11277 let coll = DEFAULT_COLLATION.with(|c| c.borrow().clone());
11281 let collated = coll.as_deref().filter(|u|
11282 *u == "http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive");
11283 if let Some(_) = collated {
11284 if values_both_stringy(&lv, &rv) {
11285 let a = ascii_ci_fold(&value_to_string_with(&lv, idx, ctx.bindings));
11286 let b = ascii_ci_fold(&value_to_string_with(&rv, idx, ctx.bindings));
11287 let result = match op {
11288 ValueCmp::Eq => a == b,
11289 ValueCmp::Ne => a != b,
11290 ValueCmp::Lt => a < b,
11291 ValueCmp::Gt => a > b,
11292 ValueCmp::Le => a <= b,
11293 ValueCmp::Ge => a >= b,
11294 };
11295 return Ok(Value::Boolean(result));
11296 }
11297 }
11298 let result = match op {
11299 ValueCmp::Eq => values_eq(&lv, &rv, idx, ctx.bindings),
11300 ValueCmp::Ne => values_ne(&lv, &rv, idx, ctx.bindings),
11301 ValueCmp::Lt | ValueCmp::Gt | ValueCmp::Le | ValueCmp::Ge => {
11302 for v in [&lv, &rv] {
11308 if let Value::Typed(t) = v {
11309 if is_unordered_atomic_kind(t.kind) {
11310 return Err(xpath_err(format!(
11311 "value comparison operator is not defined on xs:{}",
11312 t.kind,
11313 )).with_xpath_code("XPTY0004"));
11314 }
11315 }
11316 }
11317 if let Some(ord) = compare_typed_values(&lv, &rv) {
11324 use std::cmp::Ordering;
11325 match (op, ord) {
11326 (ValueCmp::Lt, Ordering::Less) => true,
11327 (ValueCmp::Gt, Ordering::Greater) => true,
11328 (ValueCmp::Le, Ordering::Less | Ordering::Equal) => true,
11329 (ValueCmp::Ge, Ordering::Greater | Ordering::Equal) => true,
11330 _ => false,
11331 }
11332 } else {
11333 let a = value_to_number_with(&lv, idx, ctx.bindings);
11334 let b = value_to_number_with(&rv, idx, ctx.bindings);
11335 match op {
11336 ValueCmp::Lt => a < b,
11337 ValueCmp::Gt => a > b,
11338 ValueCmp::Le => a <= b,
11339 ValueCmp::Ge => a >= b,
11340 _ => unreachable!(),
11341 }
11342 }
11343 }
11344 };
11345 Ok(Value::Boolean(result))
11346}
11347
11348fn values_both_stringy(a: &Value, b: &Value) -> bool {
11353 fn is_str(v: &Value) -> bool {
11354 match v {
11355 Value::String(_) => true,
11356 Value::Typed(t) => matches!(t.kind,
11357 "string" | "untypedAtomic" | "anyURI"
11358 | "normalizedString" | "token" | "Name" | "NCName"
11359 | "language" | "ID" | "IDREF" | "ENTITY" | "NMTOKEN"
11360 | "NOTATION" | "QName"),
11361 _ => false,
11362 }
11363 }
11364 is_str(a) && is_str(b)
11365}
11366
11367pub fn date_value_to_utc_micros(lex: &str, kind: DateKind) -> Option<i128> {
11373 let (y, mo, d, h, mi, sec, frac, tz) = parse_xsd_date_time(lex, kind)?;
11374 let tz_min = tz.unwrap_or(0) as i64;
11375 let day_count = match kind {
11376 DateKind::Time => 0,
11377 _ => ymd_to_days(y, mo as u32, d as u32),
11378 };
11379 let secs = day_count * 86_400
11380 + (h as i64) * 3600 + (mi as i64) * 60 + (sec as i64)
11381 - tz_min * 60;
11382 Some((secs as i128) * 1_000_000 + (frac as i128))
11383}
11384
11385fn compare_typed_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
11390 fn date_kind(v: &Value) -> Option<(&'static str, String)> {
11395 match v {
11396 Value::Typed(t) => {
11397 let kind = match t.kind {
11398 "date" => "date",
11399 "dateTime" => "dateTime",
11400 "time" => "time",
11401 "gYear" | "gYearMonth"
11402 | "gMonth" | "gMonthDay" | "gDay" => "date",
11403 "dayTimeDuration" => "dayTime",
11404 "yearMonthDuration"=> "yearMonth",
11405 "duration" => "duration",
11406 _ => return None,
11407 };
11408 Some((kind, t.lexical.clone()))
11409 }
11410 _ => None,
11411 }
11412 }
11413 let (ka, la) = date_kind(a)?;
11414 let (kb, lb) = date_kind(b)?;
11415 if ka != kb { return None; }
11419 match ka {
11420 "date" | "dateTime" | "time" => {
11421 let dk = match ka {
11422 "date" => DateKind::Date,
11423 "dateTime" => DateKind::DateTime,
11424 "time" => DateKind::Time,
11425 _ => unreachable!(),
11426 };
11427 let a_us = date_value_to_utc_micros(&la, dk)?;
11428 let b_us = date_value_to_utc_micros(&lb, dk)?;
11429 Some(a_us.cmp(&b_us))
11430 }
11431 "dayTime" => {
11432 let a_s = parse_day_time_duration_secs(&la)?;
11435 let b_s = parse_day_time_duration_secs(&lb)?;
11436 Some(a_s.cmp(&b_s))
11437 }
11438 "yearMonth" => {
11439 let a_m = parse_year_month_duration_months(&la)?;
11441 let b_m = parse_year_month_duration_months(&lb)?;
11442 Some(a_m.cmp(&b_m))
11443 }
11444 _ => None,
11445 }
11446}
11447
11448fn atomise_singleton<I: DocIndexLike>(
11454 v: Value, idx: &I, _bindings: &dyn XPathBindings,
11455) -> Result<Option<Value>> {
11456 match v {
11457 Value::NodeSet(ns) => match ns.len() {
11458 0 => Ok(None),
11459 1 => Ok(Some(Value::String(idx.string_value(ns[0])))),
11460 _ => Err(xpath_err(
11461 "value-comparison operand must have at most one item")),
11462 }
11463 Value::ForeignNodeSet(_) => Ok(Some(v)),
11464 Value::Sequence(items) => match items.len() {
11465 0 => Ok(None),
11466 1 => Ok(Some(items.into_iter().next().unwrap())),
11467 _ => Err(xpath_err(
11468 "value-comparison operand must have at most one item")),
11469 }
11470 other => Ok(Some(other)),
11472 }
11473}
11474
11475fn try_membership_filter<I: DocIndexLike>(
11493 pred: &Expr,
11494 items: &[Value],
11495 ctx: &EvalCtx<'_>,
11496 idx: &I,
11497) -> Result<Option<Vec<Value>>> {
11498 let Some((rhs, negated)) = classify_membership_pred(pred) else {
11499 return Ok(None);
11500 };
11501 if expr_references_context_item(rhs) {
11505 return Ok(None);
11506 }
11507 let rhs_val = eval_expr(rhs, ctx, idx)?;
11508 let set = match build_membership_set(&rhs_val, idx) {
11513 Some(s) => s,
11514 None => return Ok(None),
11515 };
11516 let mut kept = Vec::with_capacity(items.len());
11517 for item in items {
11518 let hit = match &set {
11519 MembershipSet::Int(s) => match item_as_int(item) {
11520 Some(n) => s.contains(&n),
11521 None => false,
11522 },
11523 MembershipSet::Str(s) => {
11524 let item_s = match item {
11525 Value::String(t) => t.clone(),
11526 Value::NodeSet(ns) if ns.len() == 1 => idx.string_value(ns[0]),
11527 _ => continue,
11528 };
11529 s.contains(&item_s)
11530 }
11531 };
11532 if hit ^ negated {
11533 kept.push(item.clone());
11534 }
11535 }
11536 Ok(Some(kept))
11537}
11538
11539fn classify_membership_pred(pred: &Expr) -> Option<(&Expr, bool)> {
11544 if let Expr::FunctionCall(name, args) = pred {
11546 if name == "not" && args.len() == 1 {
11547 return classify_membership_pred(&args[0]).map(|(rhs, neg)| (rhs, !neg));
11548 }
11549 }
11550 match pred {
11551 Expr::Eq(a, b) => match (is_bare_dot(a), is_bare_dot(b)) {
11552 (true, false) => Some((b.as_ref(), false)),
11553 (false, true) => Some((a.as_ref(), false)),
11554 _ => None,
11555 },
11556 Expr::Ne(a, b) => match (is_bare_dot(a), is_bare_dot(b)) {
11557 (true, false) => Some((b.as_ref(), true)),
11558 (false, true) => Some((a.as_ref(), true)),
11559 _ => None,
11560 },
11561 _ => None,
11562 }
11563}
11564
11565fn is_bare_dot(e: &Expr) -> bool {
11566 if let Expr::Path(p) = e {
11567 return is_bare_dot_path(p);
11568 }
11569 false
11570}
11571
11572pub fn expr_references_context_item(e: &Expr) -> bool {
11579 use Expr::*;
11580 match e {
11581 Path(p) => path_uses_context_item(p),
11582 ContextItem => true,
11583 Variable(_) | Literal(_) | Integer(_) | Decimal(_) | Double(_) => false,
11584 Or(l, r) | And(l, r)
11585 | Eq(l, r) | Ne(l, r) | Lt(l, r) | Gt(l, r) | Le(l, r) | Ge(l, r)
11586 | ValueEq(l, r) | ValueNe(l, r)
11587 | ValueLt(l, r) | ValueGt(l, r) | ValueLe(l, r) | ValueGe(l, r)
11588 | Add(l, r) | Sub(l, r) | Mul(l, r) | Div(l, r) | Mod(l, r)
11589 | Union(l, r) | IDiv(l, r) | Intersect(l, r) | Except(l, r)
11590 | Range(l, r) | SimpleMap(l, r) | NodeBefore(l, r) | NodeAfter(l, r)
11591 | NodeIs(l, r) =>
11592 expr_references_context_item(l) || expr_references_context_item(r),
11593 Neg(x) | InstanceOf(x, _) | CastAs(x, _)
11594 | CastableAs(x, _) | TreatAs(x, _) => expr_references_context_item(x),
11595 IfThenElse { cond, then_branch, else_branch } =>
11596 expr_references_context_item(cond)
11597 || expr_references_context_item(then_branch)
11598 || expr_references_context_item(else_branch),
11599 For { bindings, body } | Let { bindings, body }
11600 | Quantified { bindings, test: body, .. } =>
11601 bindings.iter().any(|(_, e)| expr_references_context_item(e))
11602 || expr_references_context_item(body),
11603 Sequence(items) => items.iter().any(expr_references_context_item),
11604 FilterPath { primary, predicates, steps } => {
11605 expr_references_context_item(primary)
11606 || predicates.iter().any(expr_references_context_item)
11607 || steps.iter().any(|s| s.predicates.iter().any(expr_references_context_item))
11608 }
11609 FunctionCall(_, args) => args.iter().any(expr_references_context_item),
11610 TryCatch { body, catches } =>
11611 expr_references_context_item(body)
11612 || catches.iter().any(|c| expr_references_context_item(&c.body)),
11613 WithDefaultCollation(_, inner) => expr_references_context_item(inner),
11614 BackwardsCompat(inner) => expr_references_context_item(inner),
11615 MapConstructor(entries) => entries.iter()
11616 .any(|(k, v)| expr_references_context_item(k) || expr_references_context_item(v)),
11617 ArrayConstructor { members, .. } =>
11618 members.iter().any(expr_references_context_item),
11619 Lookup(base, key) => expr_references_context_item(base)
11620 || lookup_key_references_context_item(key),
11621 UnaryLookup(_) => true,
11623 InlineFunction { .. } | NamedFunctionRef { .. } | Placeholder => false,
11624 DynamicCall { func, args } => expr_references_context_item(func)
11625 || args.iter().any(expr_references_context_item),
11626 }
11627}
11628
11629fn lookup_key_references_context_item(key: &crate::xpath::ast::LookupKey) -> bool {
11630 matches!(key, crate::xpath::ast::LookupKey::Expr(e)
11631 if expr_references_context_item(e))
11632}
11633
11634fn path_uses_context_item(path: &crate::xpath::ast::LocationPath) -> bool {
11638 use crate::xpath::ast::LocationPath;
11639 match path {
11640 LocationPath::Absolute(_) => false,
11641 LocationPath::Relative(_) => true,
11642 }
11643}
11644
11645enum MembershipSet {
11646 Int(HashSet<i64>),
11647 Str(HashSet<String>),
11648}
11649
11650fn build_membership_set<I: DocIndexLike>(v: &Value, idx: &I) -> Option<MembershipSet> {
11655 let mut all_int = true;
11659 let mut all_str = true;
11660 for item in iter_items(v) {
11661 if item_as_int(&item).is_none() { all_int = false; }
11662 if !item_as_string_kind(&item) { all_str = false; }
11663 if !all_int && !all_str { return None; }
11664 }
11665 if all_int {
11666 let s: HashSet<i64> = iter_items(v)
11667 .filter_map(|it| item_as_int(&it))
11668 .collect();
11669 return Some(MembershipSet::Int(s));
11670 }
11671 if all_str {
11672 let s: HashSet<String> = iter_items(v)
11673 .filter_map(|it| item_as_string(&it, idx))
11674 .collect();
11675 return Some(MembershipSet::Str(s));
11676 }
11677 None
11678}
11679
11680fn item_as_int(v: &Value) -> Option<i64> {
11681 match v {
11682 Value::Number(n) if n.as_f64().is_finite() && n.as_f64().fract() == 0.0 => Some(n.as_f64() as i64),
11683 Value::Typed(t) => t.numeric.and_then(|n|
11684 if n.is_finite() && n.fract() == 0.0 { Some(n as i64) } else { None }),
11685 _ => None,
11686 }
11687}
11688
11689fn item_as_string_kind(v: &Value) -> bool {
11690 matches!(v, Value::String(_) | Value::NodeSet(_)) || matches!(v, Value::Typed(_))
11691}
11692
11693fn item_as_string<I: DocIndexLike>(v: &Value, idx: &I) -> Option<String> {
11694 match v {
11695 Value::String(s) => Some(s.clone()),
11696 Value::NodeSet(ns) if ns.len() == 1 => Some(idx.string_value(ns[0])),
11697 Value::Typed(t) => Some(t.lexical.clone()),
11698 _ => None,
11699 }
11700}
11701
11702fn filter_sequence_by_predicates<I: DocIndexLike>(
11703 items: Vec<Value>, predicates: &[Expr], ctx: &EvalCtx<'_>, idx: &I,
11704) -> Result<Vec<Value>> {
11705 let needs_flatten = items.iter().any(|v|
11709 matches!(v, Value::IntRange { .. } | Value::Sequence(_)));
11710 let mut surviving: Vec<Value> = if needs_flatten {
11711 items.iter().flat_map(iter_items).collect()
11712 } else {
11713 items
11714 };
11715 for pred in predicates {
11716 if let Some(fast) = try_membership_filter(pred, &surviving, ctx, idx)? {
11726 surviving = fast;
11727 continue;
11728 }
11729 let size = surviving.len();
11730 let mut next: Vec<Value> = Vec::with_capacity(size);
11731 for (i, item) in surviving.into_iter().enumerate() {
11732 let pos = i + 1;
11733 let (ctx_node, ctx_item) = match &item {
11739 Value::NodeSet(ns) if ns.len() == 1 =>
11740 (ns[0], None),
11741 _ => (ctx.context_node, Some(item.clone())),
11742 };
11743 let inner_ctx = EvalCtx {
11744 context_node: ctx_node,
11745 pos, size,
11746 bindings: ctx.bindings,
11747 static_ctx: ctx.static_ctx,
11748 };
11749 let pv = with_context_item(ctx_item, ||
11750 eval_expr(pred, &inner_ctx, idx)
11751 )?;
11752 let keep = match pv {
11761 Value::Number(n) => (n.as_f64() as usize) == pos && n.as_f64().fract() == 0.0,
11762 Value::Typed(ref t) if t.numeric.is_some() => {
11763 let n = t.numeric.unwrap();
11764 (n as usize) == pos && n.fract() == 0.0
11765 }
11766 Value::NodeSet(ref ns) if ns.len() == 1
11775 && matches!(idx.kind(ns[0]),
11776 crate::xpath::XPathNodeKind::Text)
11777 && idx.parent(ns[0]).is_none()
11778 => {
11779 let s = idx.string_value(ns[0]);
11780 match s.trim().parse::<f64>() {
11781 Ok(n) if n.fract() == 0.0 => (n as usize) == pos,
11782 _ => value_to_bool(&pv, idx),
11783 }
11784 }
11785 v => value_to_bool(&v, idx),
11786 };
11787 if keep { next.push(item); }
11788 }
11789 surviving = next;
11790 }
11791 Ok(surviving)
11792}
11793
11794fn sequence_to_strings<I: DocIndexLike>(v: &Value, idx: &I) -> Vec<String> {
11797 match v {
11798 Value::NodeSet(ns) => ns.iter().map(|&id| idx.string_value(id)).collect(),
11799 Value::ForeignNodeSet(_) => vec![value_to_string(v, idx)],
11800 Value::String(s) => vec![s.clone()],
11801 Value::Number(_) | Value::Boolean(_) => vec![value_to_string(v, idx)],
11802 Value::Typed(t) => vec![t.lexical.clone()],
11803 Value::Sequence(items) => items.iter()
11804 .flat_map(|item| sequence_to_strings(item, idx))
11805 .collect(),
11806 Value::IntRange { lo, hi } => (*lo..=*hi).map(|i| i.to_string()).collect(),
11807 Value::Map(_) | Value::Array(_) | Value::Function(_) => Vec::new(),
11809 }
11810}
11811
11812fn sequence_to_numbers<I: DocIndexLike>(v: &Value, idx: &I) -> Vec<f64> {
11813 sequence_to_strings(v, idx).into_iter()
11814 .map(|s| s.trim().parse().unwrap_or(f64::NAN))
11815 .collect()
11816}
11817
11818pub fn compile_xpath_2_0_regex(pattern: &str, flags: &str) -> Result<regex::Regex> {
11832 compile_xpath_regex(pattern, flags)
11833}
11834
11835fn is_ascii_ci_collation(uri: Option<&str>) -> bool {
11840 matches!(uri, Some(u) if u ==
11841 "http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive")
11842}
11843
11844fn ascii_ci_fold(s: &str) -> String {
11845 let mut out = String::with_capacity(s.len());
11847 for c in s.chars() {
11848 if c.is_ascii_uppercase() { out.push(c.to_ascii_lowercase()); }
11849 else { out.push(c); }
11850 }
11851 out
11852}
11853
11854fn collation_starts_with(s: &str, pre: &str, uri: Option<&str>) -> bool {
11855 if is_ascii_ci_collation(uri) {
11856 ascii_ci_fold(s).starts_with(ascii_ci_fold(pre).as_str())
11857 } else {
11858 s.starts_with(pre)
11859 }
11860}
11861
11862fn collation_contains(s: &str, sub: &str, uri: Option<&str>) -> bool {
11863 if is_ascii_ci_collation(uri) {
11864 ascii_ci_fold(s).contains(ascii_ci_fold(sub).as_str())
11865 } else {
11866 s.contains(sub)
11867 }
11868}
11869
11870fn collation_ends_with(s: &str, suf: &str, uri: Option<&str>) -> bool {
11871 if is_ascii_ci_collation(uri) {
11872 ascii_ci_fold(s).ends_with(ascii_ci_fold(suf).as_str())
11873 } else {
11874 s.ends_with(suf)
11875 }
11876}
11877
11878fn translate_xpath_replacement(s: &str, group_count: usize) -> Result<String> {
11885 let mut out = String::with_capacity(s.len());
11886 let mut chars = s.chars().peekable();
11887 while let Some(c) = chars.next() {
11888 match c {
11889 '\\' => {
11890 match chars.next() {
11891 Some('\\') => out.push('\\'),
11892 Some('$') => out.push('$'),
11893 Some(other) => return Err(xpath_err(format!(
11894 "replace(): replacement contains illegal escape '\\{other}'"
11895 )).with_xpath_code("FORX0004")),
11896 None => return Err(xpath_err(
11897 "replace(): replacement ends with a trailing backslash"
11898 ).with_xpath_code("FORX0004")),
11899 }
11900 }
11901 '$' => {
11902 match chars.peek() {
11903 Some(d) if d.is_ascii_digit() => {
11904 let mut digits = String::new();
11909 while let Some(&d) = chars.peek() {
11910 if d.is_ascii_digit() { digits.push(d); chars.next(); }
11911 else { break; }
11912 }
11913 let mut take = digits.len();
11916 while take > 0 {
11917 let n: usize = digits[..take].parse().unwrap_or(usize::MAX);
11918 if n <= group_count { break; }
11919 take -= 1;
11920 }
11921 if take == 0 {
11922 out.push_str("$$");
11927 out.push_str(&digits);
11928 } else if take == digits.len() {
11929 out.push('$');
11932 out.push_str(&digits);
11933 } else {
11934 out.push_str("${");
11939 out.push_str(&digits[..take]);
11940 out.push('}');
11941 out.push_str(&digits[take..]);
11942 }
11943 }
11944 _ => return Err(xpath_err(
11945 "replace(): unescaped '$' in replacement"
11946 ).with_xpath_code("FORX0004")),
11947 }
11948 }
11949 other => out.push(other),
11950 }
11951 }
11952 Ok(out)
11953}
11954
11955fn compile_xpath_regex(pattern: &str, flags: &str) -> Result<regex::Regex> {
11956 compile_xpath_regex_dialect(pattern, flags, crate::regex::Dialect::Xpath)
11957}
11958
11959fn compile_xpath_regex_dialect(
11963 pattern: &str, flags: &str, dialect: crate::regex::Dialect,
11964) -> Result<regex::Regex> {
11965 let literal = flags.contains('q');
11966 if !literal && dialect == crate::regex::Dialect::Xpath20 {
11971 crate::regex::parser::parse_with(pattern, dialect)
11972 .map_err(|e| xpath_err(format!("invalid regex: {e}"))
11973 .with_xpath_code("FORX0002"))?;
11974 }
11975 let mut inline = String::new();
11976 if !flags.is_empty() {
11977 let mut seen = [false; 128];
11981 inline.push_str("(?");
11982 for c in flags.chars() {
11983 if matches!(c, 's' | 'm' | 'i' | 'x') {
11984 let idx = c as usize;
11985 if !seen[idx] {
11986 seen[idx] = true;
11987 inline.push(c);
11988 }
11989 }
11990 }
11991 inline.push(')');
11992 if inline.ends_with("(?)") { inline.clear(); }
11994 }
11995 let body = if literal {
11996 regex::escape(pattern)
11997 } else {
11998 translate_xsd_regex_escapes(pattern)
11999 };
12000 let full = format!("{inline}{body}");
12001 regex::Regex::new(&full)
12002 .map_err(|e| xpath_err(format!("invalid regex: {e}")).with_xpath_code("FORX0002"))
12003}
12004
12005fn translate_xsd_regex_escapes(pattern: &str) -> String {
12021 const NAME_CHAR: &str = "A-Za-z0-9._\\-:\u{00B7}\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}";
12022 const NAME_CHAR_NEG: &str = "^A-Za-z0-9._\\-:\u{00B7}\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}";
12023 const NAME_START: &str = "A-Za-z_:\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}";
12024 const NAME_START_NEG: &str = "^A-Za-z_:\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}";
12025
12026 let mut out = String::with_capacity(pattern.len());
12027 let mut chars = pattern.chars().peekable();
12028 let mut in_class = false;
12029 while let Some(c) = chars.next() {
12030 match c {
12031 '\\' => {
12032 let next = chars.next().unwrap_or('\0');
12033 match next {
12034 'c' => if in_class { out.push_str(NAME_CHAR) } else { out.push_str(&format!("[{NAME_CHAR}]")) },
12035 'C' => if in_class { out.push_str(NAME_CHAR_NEG) } else { out.push_str(&format!("[{NAME_CHAR_NEG}]")) },
12036 'i' => if in_class { out.push_str(NAME_START) } else { out.push_str(&format!("[{NAME_START}]")) },
12037 'I' => if in_class { out.push_str(NAME_START_NEG) } else { out.push_str(&format!("[{NAME_START_NEG}]")) },
12038 other => { out.push('\\'); out.push(other); }
12039 }
12040 }
12041 '[' => { out.push('['); in_class = true; }
12042 ']' => { out.push(']'); in_class = false; }
12043 _ => out.push(c),
12044 }
12045 }
12046 out
12047}
12048
12049fn xs_constructor<I: DocIndexLike>(
12062 local: &str,
12063 args: &[Value],
12064 idx: &I,
12065 bindings: &dyn XPathBindings,
12066) -> Result<Value> {
12067 if args.len() != 1 {
12068 return Err(xpath_err(format!("xs:{local}(): requires exactly 1 argument")));
12069 }
12070 if let Value::NodeSet(ref ns) = args[0] {
12074 if ns.is_empty() {
12075 return Ok(Value::NodeSet(Vec::new()));
12076 }
12077 }
12078 if local == "date" {
12084 if let Value::Typed(t) = &args[0] {
12085 if t.kind == "dateTime" {
12086 if let Some(t_pos) = t.lexical.find('T') {
12089 let after = &t.lexical[t_pos + 1..];
12090 let tz_start = after.rfind(|c| c == 'Z' || c == '+' || c == '-')
12091 .filter(|&i| {
12092 i >= after.len().saturating_sub(6)
12097 });
12098 let tz = tz_start.map(|i| &after[i..]).unwrap_or("");
12099 let lex = format!("{}{}", &t.lexical[..t_pos], tz);
12100 return Ok(Value::Typed(Box::new(TypedAtomic {
12101 kind: "date", lexical: lex, numeric: None, boolean: None, user_type: None,
12102 })));
12103 }
12104 }
12105 }
12106 }
12107 if matches!(local, "hexBinary" | "base64Binary") {
12110 if let Some(lex) = convert_binary_kind(&args[0], local) {
12111 let kind = atomic_kind_static(local).unwrap();
12112 return Ok(Value::Typed(Box::new(TypedAtomic {
12113 kind, lexical: lex, numeric: None, boolean: None, user_type: None,
12114 })));
12115 }
12116 }
12117 if matches!(local, "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay") {
12121 if let Value::Typed(t) = &args[0] {
12122 let dk = match t.kind {
12123 "date" => Some(DateKind::Date),
12124 "dateTime" => Some(DateKind::DateTime),
12125 _ => None,
12126 };
12127 if let Some(dk) = dk {
12128 if let Some((y, mo, d, _, _, _, _, tz)) = parse_xsd_date_time(&t.lexical, dk) {
12129 let tzs = tz.map(format_tz_suffix).unwrap_or_default();
12130 let lex = match local {
12131 "gYear" => format!("{y:04}{tzs}"),
12132 "gYearMonth" => format!("{y:04}-{mo:02}{tzs}"),
12133 "gMonth" => format!("--{mo:02}{tzs}"),
12134 "gMonthDay" => format!("--{mo:02}-{d:02}{tzs}"),
12135 _ => format!("---{d:02}{tzs}"),
12136 };
12137 let kind = atomic_kind_static(local).unwrap();
12138 return Ok(Value::Typed(Box::new(TypedAtomic {
12139 kind, lexical: lex, numeric: None, boolean: None, user_type: None,
12140 })));
12141 }
12142 }
12143 }
12144 }
12145 let s = value_to_string_with(&args[0], idx, bindings);
12146 let trimmed = s.trim();
12147 let kind = atomic_kind_static(local).ok_or_else(|| xpath_err(format!(
12150 "xs:{local}(): unknown XSD constructor function"
12151 )))?;
12152 let lexically_invalid = match kind {
12158 "date" | "dateTime" | "time" | "duration" | "dayTimeDuration"
12159 | "yearMonthDuration" | "gYear" | "gYearMonth" | "gMonth"
12160 | "gMonthDay" | "gDay" | "hexBinary"
12161 => !lexical_matches_type(trimmed, kind),
12162 "decimal" => trimmed.contains(['e', 'E'])
12163 || trimmed.parse::<f64>().is_err(),
12164 _ => false,
12165 };
12166 if lexically_invalid {
12167 if date_year_out_of_range(trimmed, kind) {
12170 return Err(xpath_err(format!(
12171 "xs:{local}: year is outside the supported range: '{trimmed}'"
12172 )).with_xpath_code("FODT0001"));
12173 }
12174 return Err(xpath_err(format!(
12175 "xs:{local}: '{trimmed}' is not a valid lexical xs:{local}"
12176 )).with_xpath_code("FORG0001"));
12177 }
12178 let numeric = if is_numeric_kind(kind) {
12186 let raw = match trimmed {
12187 "INF" | "Infinity" => f64::INFINITY,
12188 "-INF" | "-Infinity" => f64::NEG_INFINITY,
12189 "NaN" => f64::NAN,
12190 _ => match trimmed.parse::<f64>() {
12191 Ok(n) => n,
12192 Err(_) => return Err(xpath_err(format!(
12197 "xs:{local}: '{trimmed}' is not a valid numeric \
12198 lexical form"
12199 )).with_xpath_code("FORG0001")),
12200 },
12201 };
12202 Some(if kind == "float" { raw as f32 as f64 } else { raw })
12207 } else { None };
12208 let boolean = if kind == "boolean" {
12209 let from_num = match &args[0] {
12213 Value::Number(n) => Some(!(n.as_f64() == 0.0 || n.as_f64().is_nan())),
12214 Value::Typed(t) if t.numeric.is_some() => {
12215 let n = t.numeric.unwrap();
12216 Some(!(n == 0.0 || n.is_nan()))
12217 }
12218 _ => None,
12219 };
12220 Some(from_num.unwrap_or_else(|| matches!(trimmed, "true" | "1")))
12221 } else { None };
12222 let lexical = if kind == "boolean" {
12223 if boolean == Some(true) { "true".into() } else { "false".into() }
12224 } else if kind == "double" {
12225 canonical_double_lex(numeric.unwrap_or(f64::NAN), &args[0])
12226 } else if kind == "float" {
12227 canonical_float_lex(numeric.unwrap_or(f64::NAN), &args[0])
12228 } else if kind == "decimal" {
12229 canonical_decimal_lex(numeric.unwrap_or(f64::NAN), trimmed)
12230 } else if is_integer_subkind(kind) && source_is_numeric(&args[0]) {
12231 let n = numeric.unwrap_or(f64::NAN);
12236 if !n.is_finite() {
12237 return Err(xpath_err(format!(
12238 "xs:{local}: cannot cast non-finite numeric to {kind}"
12239 )).with_xpath_code("FOCA0002"));
12240 }
12241 (n.trunc() as i64).to_string()
12242 } else if matches!(kind, "date" | "dateTime" | "time") {
12243 canonical_date_time_lex(trimmed, kind)
12244 } else if matches!(kind, "gYear" | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay") {
12245 if let Some(stripped) = trimmed.strip_suffix("+00:00")
12249 .or_else(|| trimmed.strip_suffix("-00:00")) {
12250 format!("{stripped}Z")
12251 } else {
12252 trimmed.to_string()
12253 }
12254 } else if kind == "yearMonthDuration" {
12255 canonical_year_month_duration_lex(trimmed)
12256 } else if kind == "dayTimeDuration" {
12257 canonical_day_time_duration_lex(trimmed)
12258 } else if kind == "hexBinary" {
12259 trimmed.to_ascii_uppercase()
12262 } else { trimmed.to_string() };
12263 Ok(Value::Typed(Box::new(TypedAtomic { kind, lexical, numeric, boolean, user_type: None })))
12264}
12265
12266pub fn atomic_kind_static(local: &str) -> Option<&'static str> {
12270 Some(match local {
12271 "string" => "string",
12272 "normalizedString" => "normalizedString",
12273 "token" => "token",
12274 "Name" => "Name",
12275 "NCName" => "NCName",
12276 "QName" => "QName",
12277 "ID" => "ID",
12278 "IDREF" => "IDREF",
12279 "IDREFS" => "IDREFS",
12280 "ENTITY" => "ENTITY",
12281 "ENTITIES" => "ENTITIES",
12282 "NMTOKEN" => "NMTOKEN",
12283 "NMTOKENS" => "NMTOKENS",
12284 "anyURI" => "anyURI",
12285 "language" => "language",
12286 "NOTATION" => "NOTATION",
12287 "boolean" => "boolean",
12288 "integer" => "integer",
12289 "int" => "int",
12290 "long" => "long",
12291 "short" => "short",
12292 "byte" => "byte",
12293 "unsignedInt" => "unsignedInt",
12294 "unsignedLong" => "unsignedLong",
12295 "unsignedShort" => "unsignedShort",
12296 "unsignedByte" => "unsignedByte",
12297 "nonNegativeInteger" => "nonNegativeInteger",
12298 "nonPositiveInteger" => "nonPositiveInteger",
12299 "positiveInteger" => "positiveInteger",
12300 "negativeInteger" => "negativeInteger",
12301 "decimal" => "decimal",
12302 "double" => "double",
12303 "float" => "float",
12304 "date" => "date",
12305 "dateTime" => "dateTime",
12306 "time" => "time",
12307 "duration" => "duration",
12308 "dayTimeDuration" => "dayTimeDuration",
12309 "yearMonthDuration" => "yearMonthDuration",
12310 "gYear" => "gYear",
12311 "gYearMonth" => "gYearMonth",
12312 "gMonth" => "gMonth",
12313 "gMonthDay" => "gMonthDay",
12314 "gDay" => "gDay",
12315 "hexBinary" => "hexBinary",
12316 "base64Binary" => "base64Binary",
12317 "untypedAtomic" => "untypedAtomic",
12318 "anyAtomicType" => "anyAtomicType",
12319 _ => return None,
12320 })
12321}
12322
12323fn is_numeric_kind(k: &str) -> bool {
12324 matches!(k,
12325 "integer" | "int" | "long" | "short" | "byte"
12326 | "unsignedInt" | "unsignedLong" | "unsignedShort" | "unsignedByte"
12327 | "nonNegativeInteger" | "nonPositiveInteger"
12328 | "positiveInteger" | "negativeInteger"
12329 | "decimal" | "double" | "float")
12330}
12331
12332fn is_integer_subkind(k: &str) -> bool {
12334 matches!(k,
12335 "integer" | "int" | "long" | "short" | "byte"
12336 | "unsignedInt" | "unsignedLong" | "unsignedShort" | "unsignedByte"
12337 | "nonNegativeInteger" | "nonPositiveInteger"
12338 | "positiveInteger" | "negativeInteger")
12339}
12340
12341fn source_is_numeric(v: &Value) -> bool {
12345 match v {
12346 Value::Number(_) => true,
12347 Value::Typed(t) => t.numeric.is_some(),
12348 _ => false,
12349 }
12350}
12351
12352fn canonical_year_month_duration_lex(s: &str) -> String {
12356 let months = match parse_year_month_duration_months(s) {
12357 Some(m) => m,
12358 None => return s.to_string(),
12359 };
12360 if months == 0 { return "P0M".into(); }
12361 let mut out = String::with_capacity(8);
12362 let total = if months < 0 { out.push('-'); -months } else { months };
12363 out.push('P');
12364 let y = total / 12;
12365 let m = total % 12;
12366 if y > 0 { out.push_str(&y.to_string()); out.push('Y'); }
12367 if m > 0 { out.push_str(&m.to_string()); out.push('M'); }
12368 out
12369}
12370
12371fn negate_duration_lex(s: &str) -> String {
12375 let t = s.trim();
12376 if t == "PT0S" || t == "P0M" { return t.to_string(); }
12377 if let Some(rest) = t.strip_prefix('-') {
12378 rest.to_string()
12379 } else {
12380 format!("-{t}")
12381 }
12382}
12383
12384fn format_day_time_duration_micros(total_us: i64) -> String {
12389 if total_us == 0 { return "PT0S".into(); }
12390 let neg = total_us < 0;
12391 let mut rem = total_us.unsigned_abs() as u64;
12392 let mut out = String::with_capacity(16);
12393 if neg { out.push('-'); }
12394 out.push('P');
12395 let us_per_day = 86_400u64 * 1_000_000;
12396 let days = rem / us_per_day;
12397 rem %= us_per_day;
12398 if days > 0 { out.push_str(&days.to_string()); out.push('D'); }
12399 if rem == 0 { return out; }
12400 out.push('T');
12401 let us_per_hour = 3600u64 * 1_000_000;
12402 let h = rem / us_per_hour;
12403 rem %= us_per_hour;
12404 if h > 0 { out.push_str(&h.to_string()); out.push('H'); }
12405 let us_per_min = 60u64 * 1_000_000;
12406 let m = rem / us_per_min;
12407 rem %= us_per_min;
12408 if m > 0 { out.push_str(&m.to_string()); out.push('M'); }
12409 if rem > 0 {
12410 let secs = rem / 1_000_000;
12411 let frac = rem % 1_000_000;
12412 out.push_str(&secs.to_string());
12413 if frac != 0 {
12414 let frac_str = format!("{frac:06}");
12415 let trimmed = frac_str.trim_end_matches('0');
12416 out.push('.');
12417 out.push_str(trimmed);
12418 }
12419 out.push('S');
12420 }
12421 out
12422}
12423
12424fn canonical_day_time_duration_lex(s: &str) -> String {
12429 let total_us = match parse_day_time_duration_micros(s) {
12433 Some(u) => u,
12434 None => return s.to_string(),
12435 };
12436 if total_us == 0 { return "PT0S".into(); }
12437 let mut out = String::with_capacity(16);
12438 let mut rem = if total_us < 0 { out.push('-'); -total_us } else { total_us };
12439 out.push('P');
12440 let us_per_day = 86_400 * 1_000_000;
12441 let days = rem / us_per_day;
12442 rem %= us_per_day;
12443 if days > 0 { out.push_str(&days.to_string()); out.push('D'); }
12444 if rem == 0 { return out; }
12445 out.push('T');
12446 let us_per_hour = 3600 * 1_000_000;
12447 let h = rem / us_per_hour;
12448 rem %= us_per_hour;
12449 if h > 0 { out.push_str(&h.to_string()); out.push('H'); }
12450 let us_per_min = 60 * 1_000_000;
12451 let m = rem / us_per_min;
12452 rem %= us_per_min;
12453 if m > 0 { out.push_str(&m.to_string()); out.push('M'); }
12454 if rem > 0 {
12455 let secs = rem / 1_000_000;
12456 let frac = rem % 1_000_000;
12457 out.push_str(&secs.to_string());
12458 if frac != 0 {
12459 let frac_str = format!("{frac:06}");
12460 let trimmed = frac_str.trim_end_matches('0');
12461 out.push('.');
12462 out.push_str(trimmed);
12463 }
12464 out.push('S');
12465 }
12466 out
12467}
12468
12469fn parse_day_time_duration_micros(s: &str) -> Option<i128> {
12472 let s = s.trim();
12473 let (sign, body) = match s.strip_prefix('-') {
12474 Some(rest) => (-1i128, rest),
12475 None => (1i128, s),
12476 };
12477 let body = body.strip_prefix('P')?;
12478 let (day_part, time_part) = match body.find('T') {
12479 Some(i) => (&body[..i], &body[i + 1..]),
12480 None => (body, ""),
12481 };
12482 let pull = |part: &str, marker: char| -> i128 {
12483 let Some(i) = part.find(marker) else { return 0; };
12484 let start = part[..i].rfind(|c: char| !c.is_ascii_digit() && c != '.')
12485 .map(|n| n + 1).unwrap_or(0);
12486 part[start..i].parse().unwrap_or(0)
12487 };
12488 let pull_secs = |part: &str| -> i128 {
12491 let Some(i) = part.find('S') else { return 0; };
12492 let start = part[..i].rfind(|c: char| !c.is_ascii_digit() && c != '.')
12493 .map(|n| n + 1).unwrap_or(0);
12494 let lex = &part[start..i];
12495 if let Some((whole, frac)) = lex.split_once('.') {
12496 let w: i128 = whole.parse().unwrap_or(0);
12497 let take: String = frac.chars().chain(std::iter::repeat('0')).take(6).collect();
12498 let f: i128 = take.parse().unwrap_or(0);
12499 w * 1_000_000 + f
12500 } else {
12501 lex.parse::<i128>().unwrap_or(0) * 1_000_000
12502 }
12503 };
12504 let days = pull(day_part, 'D');
12505 let hours = pull(time_part, 'H');
12506 let mins = pull(time_part, 'M');
12507 let secs_us = pull_secs(time_part);
12508 Some(sign * (days * 86_400 * 1_000_000
12509 + hours * 3600 * 1_000_000
12510 + mins * 60 * 1_000_000
12511 + secs_us))
12512}
12513
12514fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
12521 let s = s.trim();
12522 if s.len() % 2 != 0 { return None; }
12523 (0..s.len()).step_by(2)
12524 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
12525 .collect()
12526}
12527
12528fn bytes_to_hex_upper(bytes: &[u8]) -> String {
12531 use std::fmt::Write;
12532 let mut s = String::with_capacity(bytes.len() * 2);
12533 for b in bytes { let _ = write!(s, "{b:02X}"); }
12534 s
12535}
12536
12537const BASE64_ALPHABET: &[u8; 64] =
12538 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
12539
12540fn bytes_to_base64(bytes: &[u8]) -> String {
12542 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
12543 for chunk in bytes.chunks(3) {
12544 let b0 = chunk[0] as u32;
12545 let b1 = *chunk.get(1).unwrap_or(&0) as u32;
12546 let b2 = *chunk.get(2).unwrap_or(&0) as u32;
12547 let n = (b0 << 16) | (b1 << 8) | b2;
12548 out.push(BASE64_ALPHABET[(n >> 18 & 63) as usize] as char);
12549 out.push(BASE64_ALPHABET[(n >> 12 & 63) as usize] as char);
12550 out.push(if chunk.len() > 1 { BASE64_ALPHABET[(n >> 6 & 63) as usize] as char } else { '=' });
12551 out.push(if chunk.len() > 2 { BASE64_ALPHABET[(n & 63) as usize] as char } else { '=' });
12552 }
12553 out
12554}
12555
12556fn base64_to_bytes(s: &str) -> Option<Vec<u8>> {
12559 let clean: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
12560 if clean.is_empty() || clean.len() % 4 != 0 { return None; }
12561 let val = |c: u8| -> Option<u32> {
12562 match c {
12563 b'A'..=b'Z' => Some((c - b'A') as u32),
12564 b'a'..=b'z' => Some((c - b'a' + 26) as u32),
12565 b'0'..=b'9' => Some((c - b'0' + 52) as u32),
12566 b'+' => Some(62),
12567 b'/' => Some(63),
12568 _ => None,
12569 }
12570 };
12571 let mut out = Vec::with_capacity(clean.len() / 4 * 3);
12572 for chunk in clean.chunks(4) {
12573 let pad = chunk.iter().filter(|&&c| c == b'=').count();
12574 let c0 = val(chunk[0])?;
12575 let c1 = val(chunk[1])?;
12576 let c2 = if pad >= 2 { 0 } else { val(chunk[2])? };
12577 let c3 = if pad >= 1 { 0 } else { val(chunk[3])? };
12578 let n = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
12579 out.push((n >> 16) as u8);
12580 if pad < 2 { out.push((n >> 8) as u8); }
12581 if pad < 1 { out.push(n as u8); }
12582 }
12583 Some(out)
12584}
12585
12586fn convert_binary_kind(src: &Value, target: &str) -> Option<String> {
12590 let t = match src { Value::Typed(t) => t, _ => return None };
12591 let bytes = match t.kind {
12592 "hexBinary" => hex_to_bytes(&t.lexical)?,
12593 "base64Binary" => base64_to_bytes(&t.lexical)?,
12594 _ => return None,
12595 };
12596 Some(match target {
12597 "hexBinary" => bytes_to_hex_upper(&bytes),
12598 "base64Binary" => bytes_to_base64(&bytes),
12599 _ => return None,
12600 })
12601}
12602
12603fn canonical_date_time_lex(s: &str, kind: &str) -> String {
12604 let s = if let Some(stripped) = s.strip_suffix("+00:00")
12609 .or_else(|| s.strip_suffix("-00:00")) {
12610 format!("{stripped}Z")
12611 } else {
12612 s.to_string()
12613 };
12614 let s = s.as_str();
12615 if s.contains("24:00:00") {
12618 let dk = match kind {
12619 "dateTime" => DateKind::DateTime,
12620 "time" => DateKind::Time,
12621 _ => DateKind::Date,
12622 };
12623 if !matches!(dk, DateKind::Date) {
12624 if let Some((y, mo, d, h, mi, sec, frac, tz)) = parse_xsd_date_time(s, dk) {
12625 return if matches!(dk, DateKind::DateTime) {
12626 format_datetime_lexical(y, mo, d, h, mi, sec, frac, tz)
12627 } else {
12628 let mut l = format!("{h:02}:{mi:02}:{sec:02}");
12629 if frac != 0 {
12630 let mut f = format!(".{frac:06}");
12631 while f.ends_with('0') { f.pop(); }
12632 l.push_str(&f);
12633 }
12634 if let Some(tz_m) = tz { l.push_str(&format_tz_suffix(tz_m)); }
12635 l
12636 };
12637 }
12638 }
12639 }
12640 let dot = match s.find('.') { Some(i) => i, None => return s.to_string() };
12641 let after = &s[dot + 1..];
12643 let tz_off = after.find(|c: char| c == 'Z' || c == '+' || c == '-')
12644 .unwrap_or(after.len());
12645 let (digits, tz) = (&after[..tz_off], &after[tz_off..]);
12646 let trimmed = digits.trim_end_matches('0');
12647 if trimmed.is_empty() {
12648 let mut out = String::with_capacity(s.len());
12650 out.push_str(&s[..dot]);
12651 out.push_str(tz);
12652 out
12653 } else {
12654 let mut out = String::with_capacity(s.len());
12655 out.push_str(&s[..dot]);
12656 out.push('.');
12657 out.push_str(trimmed);
12658 out.push_str(tz);
12659 out
12660 }
12661}
12662
12663fn format_date_utc(secs: i64) -> String {
12665 let (y, m, d) = days_to_ymd(secs.div_euclid(86_400));
12666 format!("{y:04}-{m:02}-{d:02}Z")
12667}
12668fn format_datetime_utc(secs: i64) -> String {
12670 let days = secs.div_euclid(86_400);
12671 let day_sec = secs.rem_euclid(86_400);
12672 let (y, m, d) = days_to_ymd(days);
12673 let h = day_sec / 3600;
12674 let mi = (day_sec % 3600) / 60;
12675 let s = day_sec % 60;
12676 format!("{y:04}-{m:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z")
12677}
12678fn format_time_utc(secs: i64) -> String {
12680 let day_sec = secs.rem_euclid(86_400);
12681 let h = day_sec / 3600;
12682 let mi = (day_sec % 3600) / 60;
12683 let s = day_sec % 60;
12684 format!("{h:02}:{mi:02}:{s:02}Z")
12685}
12686fn days_to_ymd(days: i64) -> (i32, u32, u32) {
12690 let z = days + 719_468;
12692 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
12693 let doe = (z - era * 146_097) as u64;
12694 let yoe = (doe - doe/1460 + doe/36_524 - doe/146_096) / 365;
12695 let y = yoe as i64 + era * 400;
12696 let doy = doe - (365 * yoe + yoe/4 - yoe/100);
12697 let mp = (5 * doy + 2) / 153;
12698 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
12699 let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
12700 let y = if m <= 2 { y + 1 } else { y };
12701 (y as i32, m, d)
12702}
12703
12704fn dedup_sort(nodes: &mut Vec<NodeId>) {
12707 nodes.sort_unstable();
12708 nodes.dedup();
12709}
12710
12711fn dedup_foreign(nodes: &mut Vec<ForeignNodePtr>) {
12716 let mut seen = HashSet::new();
12717 nodes.retain(|&p| seen.insert(p as usize));
12718}
12719
12720#[cfg(test)]
12741mod tests {
12742 use super::*;
12743
12744 #[test]
12747 fn out_of_range_year_is_overflow_not_malformed() {
12748 assert!(!date_year_out_of_range("2024-05-01T00:00:00", "dateTime"));
12751 assert!(!date_year_out_of_range("21999-05-01", "date"));
12752 assert!(!date_year_out_of_range("-1999-05-01", "date"));
12753 assert!(!date_year_out_of_range("99999", "gYear"));
12754 assert!(!date_year_out_of_range("123456-05", "gYearMonth"));
12755
12756 assert!(date_year_out_of_range("999999999999-05-01T00:00:00", "dateTime"));
12759 assert!(date_year_out_of_range("-999999999999-05-01", "date"));
12760 assert!(date_year_out_of_range("12345678901-05", "gYearMonth"));
12761 assert!(date_year_out_of_range("999999999999", "gYear"));
12762
12763 assert!(!date_year_out_of_range("999999999999-13-99", "date"));
12767 assert!(!date_year_out_of_range("not-a-date", "date"));
12768 assert!(!date_year_out_of_range("999999999999", "gMonth"));
12770 assert!(!date_year_out_of_range("999999999999", "time"));
12771 }
12772
12773 #[test]
12776 fn duration_combine_dispatches_by_family() {
12777 let dur = |k: &'static str, lex: &str| TypedAtomic {
12778 kind: k, lexical: lex.to_string(), numeric: None, boolean: None, user_type: None,
12779 };
12780 let lex = |v: Option<Value>| match v {
12781 Some(Value::Typed(t)) => t.lexical.clone(),
12782 other => panic!("expected typed duration, got {other:?}"),
12783 };
12784 assert_eq!(
12786 lex(duration_combine(&dur("yearMonthDuration", "P2Y6M"),
12787 &dur("yearMonthDuration", "P6M"), false)),
12788 "P3Y");
12789 assert_eq!(
12790 lex(duration_combine(&dur("yearMonthDuration", "P0M"),
12791 &dur("yearMonthDuration", "P2Y"), true)),
12792 "-P2Y");
12793 assert_eq!(
12795 lex(duration_combine(&dur("dayTimeDuration", "P2D"),
12796 &dur("dayTimeDuration", "PT10.03S"), false)),
12797 "P2DT10.03S");
12798 assert!(duration_combine(&dur("yearMonthDuration", "P1Y"),
12800 &dur("dayTimeDuration", "PT1H"), false).is_none());
12801 }
12802
12803 #[test]
12806 fn format_number_preserves_negative_zero() {
12807 assert_eq!(format_number(-0.0), "-0");
12808 assert_eq!(format_number(0.0), "0");
12809 }
12810
12811 #[test]
12812 fn format_number_handles_special_values() {
12813 assert_eq!(format_number(f64::INFINITY), "Infinity");
12814 assert_eq!(format_number(f64::NEG_INFINITY), "-Infinity");
12815 assert_eq!(format_number(f64::NAN), "NaN");
12816 }
12817
12818 #[test]
12819 fn format_number_integers_are_decimal_no_dot() {
12820 assert_eq!(format_number(42.0), "42");
12821 assert_eq!(format_number(-7.0), "-7");
12822 assert_eq!(format_number(0.5), "0.5");
12823 }
12824
12825 #[test]
12828 fn ordinal_suffix_picks_st_nd_rd_th() {
12829 assert_eq!(english_ordinal_suffix(1), "st");
12830 assert_eq!(english_ordinal_suffix(2), "nd");
12831 assert_eq!(english_ordinal_suffix(3), "rd");
12832 assert_eq!(english_ordinal_suffix(4), "th");
12833 assert_eq!(english_ordinal_suffix(21), "st");
12834 assert_eq!(english_ordinal_suffix(22), "nd");
12835 assert_eq!(english_ordinal_suffix(23), "rd");
12836 }
12837
12838 #[test]
12839 fn ordinal_suffix_teens_are_all_th() {
12840 assert_eq!(english_ordinal_suffix(11), "th");
12842 assert_eq!(english_ordinal_suffix(12), "th");
12843 assert_eq!(english_ordinal_suffix(13), "th");
12844 assert_eq!(english_ordinal_suffix(111), "th");
12846 assert_eq!(english_ordinal_suffix(112), "th");
12847 assert_eq!(english_ordinal_suffix(113), "th");
12848 }
12849
12850 #[test]
12853 fn duration_split_pure_year_month() {
12854 assert_eq!(parse_duration_split("P1Y"), Some((12, 0)));
12856 assert_eq!(parse_duration_split("P0Y12M"), Some((12, 0)));
12857 assert_eq!(parse_duration_split("P12M"), Some((12, 0)));
12858 assert_eq!(parse_duration_split("P1Y6M"), Some((18, 0)));
12860 }
12861
12862 #[test]
12863 fn duration_split_pure_day_time() {
12864 assert_eq!(parse_duration_split("P1D"), Some((0, 86_400)));
12865 assert_eq!(parse_duration_split("PT24H"), Some((0, 86_400)));
12866 assert_eq!(parse_duration_split("PT1H30M"), Some((0, 5400)));
12867 assert_eq!(parse_duration_split("-PT1H"), Some((0, -3600)));
12868 assert_eq!(parse_duration_split("-P1DT12H"), Some((0, -129_600)));
12869 assert_eq!(parse_duration_split("-PT36H"), Some((0, -129_600)));
12870 }
12871
12872 #[test]
12873 fn duration_split_mixed_components() {
12874 assert_eq!(parse_duration_split("P1Y2M3DT4H5M6S"),
12875 Some((14, 3 * 86_400 + 4 * 3600 + 5 * 60 + 6)));
12876 }
12877
12878 #[test]
12879 fn duration_split_rejects_malformed() {
12880 assert_eq!(parse_duration_split("1Y"), None); assert_eq!(parse_duration_split("PY"), None); assert_eq!(parse_duration_split("P1X"), None); assert_eq!(parse_duration_split(""), None);
12884 }
12885
12886 #[test]
12889 fn canonical_double_zero_and_neg_zero() {
12890 assert_eq!(canonical_double_lex( 0.0, &Value::Number(Numeric::Double( 0.0))), "0");
12891 assert_eq!(canonical_double_lex(-0.0, &Value::Number(Numeric::Double(-0.0))), "-0");
12892 assert_eq!(canonical_double_lex(0.0, &Value::String("-0".into())), "-0");
12895 }
12896
12897 #[test]
12898 fn canonical_double_special_values() {
12899 assert_eq!(canonical_double_lex(f64::INFINITY, &Value::Number(Numeric::Double(0.0))), "INF");
12900 assert_eq!(canonical_double_lex(f64::NEG_INFINITY, &Value::Number(Numeric::Double(0.0))), "-INF");
12901 assert_eq!(canonical_double_lex(f64::NAN, &Value::Number(Numeric::Double(0.0))), "NaN");
12902 }
12903
12904 #[test]
12905 fn canonical_double_fixed_point_window() {
12906 assert_eq!(canonical_double_lex(1.5, &Value::Number(Numeric::Double(1.5))), "1.5");
12908 assert_eq!(canonical_double_lex(0.001, &Value::Number(Numeric::Double(0.001))), "0.001");
12909 assert_eq!(canonical_double_lex(42.0, &Value::Number(Numeric::Double(42.0))), "42");
12910 assert_eq!(canonical_double_lex(9_999_999.0,
12911 &Value::Number(Numeric::Double(9_999_999.0))),
12912 "9999999");
12913 }
12914
12915 #[test]
12916 fn canonical_double_scientific_outside_window() {
12917 assert_eq!(canonical_double_lex(1e7, &Value::Number(Numeric::Double(1e7))), "1.0E7");
12919 assert_eq!(canonical_double_lex(1e-8, &Value::Number(Numeric::Double(1e-8))), "1.0E-8");
12920 assert_eq!(canonical_double_lex(1.5e10, &Value::Number(Numeric::Double(1.5e10))), "1.5E10");
12923 }
12924
12925 #[test]
12928 fn canonical_decimal_drops_negative_zero() {
12929 assert_eq!(canonical_decimal_lex(-0.0, "-0.0"), "0");
12930 assert_eq!(canonical_decimal_lex( 0.0, "0"), "0");
12931 }
12932
12933 #[test]
12934 fn canonical_decimal_passes_fixed_point_through() {
12935 assert_eq!(canonical_decimal_lex(1.5, "1.5"), "1.5");
12936 assert_eq!(canonical_decimal_lex(42.0, "42"), "42");
12937 }
12938
12939 #[test]
12940 fn canonical_decimal_strips_scientific_input() {
12941 assert_eq!(canonical_decimal_lex(1e3, "1e3"), "1000");
12944 }
12945
12946 #[test]
12949 fn split_uri_full_form() {
12950 let (s, a, p, q, f) = split_uri("http://host/path?q#frag");
12951 assert_eq!(s, Some("http"));
12952 assert_eq!(a, Some("host"));
12953 assert_eq!(p, "/path");
12954 assert_eq!(q, Some("q"));
12955 assert_eq!(f, Some("frag"));
12956 }
12957
12958 #[test]
12959 fn split_uri_relative_no_scheme() {
12960 let (s, a, p, q, f) = split_uri("path/to/file");
12961 assert_eq!(s, None);
12962 assert_eq!(a, None);
12963 assert_eq!(p, "path/to/file");
12964 assert_eq!(q, None);
12965 assert_eq!(f, None);
12966 }
12967
12968 #[test]
12969 fn remove_dot_segments_matches_rfc3986_examples() {
12970 assert_eq!(remove_dot_segments("/a/b/c/./../../g"), "/a/g");
12972 assert_eq!(remove_dot_segments("mid/content=5/../6"), "mid/6");
12973 assert_eq!(remove_dot_segments("/./a/b"), "/a/b");
12974 assert_eq!(remove_dot_segments(""), "");
12975 }
12976
12977 #[test]
12978 fn resolve_uri_handles_rfc3986_normal_examples() {
12979 let base = "http://a/b/c/d;p?q";
12981 assert_eq!(resolve_uri_rfc3986(base, "g:h"), "g:h");
12982 assert_eq!(resolve_uri_rfc3986(base, "g"), "http://a/b/c/g");
12983 assert_eq!(resolve_uri_rfc3986(base, "./g"), "http://a/b/c/g");
12984 assert_eq!(resolve_uri_rfc3986(base, "g/"), "http://a/b/c/g/");
12985 assert_eq!(resolve_uri_rfc3986(base, "/g"), "http://a/g");
12986 assert_eq!(resolve_uri_rfc3986(base, "?y"), "http://a/b/c/d;p?y");
12987 assert_eq!(resolve_uri_rfc3986(base, "g?y"), "http://a/b/c/g?y");
12988 assert_eq!(resolve_uri_rfc3986(base, "#s"), "http://a/b/c/d;p?q#s");
12989 assert_eq!(resolve_uri_rfc3986(base, "g#s"), "http://a/b/c/g#s");
12990 assert_eq!(resolve_uri_rfc3986(base, "../g"), "http://a/b/g");
12991 assert_eq!(resolve_uri_rfc3986(base, "../../g"),"http://a/g");
12992 }
12993
12994 #[test]
12995 fn resolve_uri_empty_rel_yields_base() {
12996 let base = "http://www.baseuri.exmpl/tests/";
12997 assert_eq!(resolve_uri_rfc3986(base, ""), "http://www.baseuri.exmpl/tests/");
12998 }
12999
13000 }
13011
13012#[cfg(kani)]
13013mod proofs {
13014 use super::*;
13015 use std::ops::Range;
13016
13017 const MAX_NODES: usize = 3;
13018 const MAX_CHILDREN: usize = 2;
13019
13020 struct AnyIndex {
13030 parents: [Option<NodeId>; MAX_NODES],
13031 child_lens: [usize; MAX_NODES],
13032 child_buf: [[NodeId; MAX_CHILDREN]; MAX_NODES],
13033 }
13034
13035 impl AnyIndex {
13036 fn any() -> Self {
13037 let mut idx = AnyIndex {
13038 parents: [None; MAX_NODES],
13039 child_lens: [0; MAX_NODES],
13040 child_buf: [[0; MAX_CHILDREN]; MAX_NODES],
13041 };
13042 for i in 0..MAX_NODES {
13043 let p: Option<NodeId> = kani::any();
13044 if let Some(pid) = p {
13045 kani::assume(pid < i);
13046 }
13047 idx.parents[i] = p;
13048
13049 let n: usize = kani::any();
13050 kani::assume(n <= MAX_CHILDREN);
13051 idx.child_lens[i] = n;
13052 for j in 0..MAX_CHILDREN {
13053 let c: NodeId = kani::any();
13054 kani::assume(c > i && c < MAX_NODES);
13055 idx.child_buf[i][j] = c;
13056 }
13057 }
13058 idx
13059 }
13060 }
13061
13062 impl DocIndexLike for AnyIndex {
13063 fn children(&self, id: NodeId) -> &[NodeId] {
13064 if id >= MAX_NODES { return &[]; }
13065 &self.child_buf[id][..self.child_lens[id]]
13066 }
13067 fn parent(&self, id: NodeId) -> Option<NodeId> {
13068 if id >= MAX_NODES { None } else { self.parents[id] }
13069 }
13070 fn attr_range(&self, _: NodeId) -> Range<NodeId> { 0..0 }
13071
13072 fn kind(&self, _: NodeId) -> XPathNodeKind { unreachable!() }
13075 fn pi_target(&self, _: NodeId) -> &str { unreachable!() }
13076 fn string_value(&self, _: NodeId) -> String { unreachable!() }
13077 fn node_name(&self, _: NodeId) -> &str { unreachable!() }
13078 fn local_name(&self, _: NodeId) -> &str { unreachable!() }
13079 fn namespace_uri(&self, _: NodeId) -> &str { unreachable!() }
13080 }
13081
13082 fn any_node() -> NodeId {
13083 let n: NodeId = kani::any();
13084 kani::assume(n < MAX_NODES);
13085 n
13086 }
13087
13088 #[kani::proof]
13089 #[kani::unwind(4)]
13090 fn following_siblings_never_panics() {
13091 let _ = following_siblings(any_node(), &AnyIndex::any());
13092 }
13093
13094 #[kani::proof]
13095 #[kani::unwind(4)]
13096 fn preceding_siblings_never_panics() {
13097 let _ = preceding_siblings(any_node(), &AnyIndex::any());
13098 }
13099
13100 }