1use crate::ids::NameId;
10use crate::namespace::context::NamespaceContextSnapshot;
11use crate::namespace::qname::QualifiedName;
12use crate::namespace::table::NameTable;
13use crate::schema::SchemaSet;
14use crate::types::value::{DateTimeValue, TimezoneOffset};
15
16use super::functions::{BuiltinCatalog, BuiltinEvaluator, FunctionCatalog, FunctionEvaluator};
17use super::iterator::XmlItem;
18use super::DomNavigator;
19use super::XPathMode;
20
21#[derive(Debug, Clone)]
34pub struct XPathContext<'a> {
35 pub names: &'a NameTable,
37 pub schema_set: Option<&'a SchemaSet>,
39 pub namespaces: NamespaceContextSnapshot,
41 pub default_element_ns: Option<NameId>,
43 pub default_function_ns: Option<&'static str>,
45 pub implicit_timezone: Option<TimezoneOffset>,
47 pub base_uri: Option<String>,
49 pub mode: XPathMode,
51 pub trace_enabled: bool,
53 function_catalog: Option<&'a dyn FunctionCatalog>,
55}
56
57impl<'a> XPathContext<'a> {
58 pub fn new(names: &'a NameTable) -> Self {
60 Self {
61 names,
62 schema_set: None,
63 namespaces: NamespaceContextSnapshot::default(),
64 default_element_ns: None,
65 default_function_ns: Some(super::functions::FN_NAMESPACE),
66 implicit_timezone: None,
67 base_uri: None,
68 mode: XPathMode::XPath20,
69 trace_enabled: false,
70 function_catalog: None,
71 }
72 }
73
74 pub fn with_schema_set(mut self, schema_set: &'a SchemaSet) -> Self {
76 self.schema_set = Some(schema_set);
77 self
78 }
79
80 pub fn with_namespaces(mut self, namespaces: NamespaceContextSnapshot) -> Self {
82 self.namespaces = namespaces;
83 self
84 }
85
86 pub fn with_default_element_ns(mut self, ns: NameId) -> Self {
88 self.default_element_ns = Some(ns);
89 self
90 }
91
92 pub fn with_default_function_ns(mut self, ns: &'static str) -> Self {
94 self.default_function_ns = Some(ns);
95 self
96 }
97
98 pub fn with_implicit_timezone(mut self, tz: TimezoneOffset) -> Self {
100 self.implicit_timezone = Some(tz);
101 self
102 }
103
104 pub fn with_base_uri(mut self, base_uri: impl Into<String>) -> Self {
106 self.base_uri = Some(base_uri.into());
107 self
108 }
109
110 pub fn with_mode(mut self, mode: XPathMode) -> Self {
112 self.mode = mode;
113 self
114 }
115
116 pub fn with_trace_enabled(mut self, enabled: bool) -> Self {
118 self.trace_enabled = enabled;
119 self
120 }
121
122 pub fn mode(&self) -> XPathMode {
124 self.mode
125 }
126
127 pub fn with_function_catalog(mut self, catalog: &'a dyn FunctionCatalog) -> Self {
129 self.function_catalog = Some(catalog);
130 self
131 }
132
133 pub fn function_catalog(&self) -> &dyn FunctionCatalog {
137 static BUILTIN: BuiltinCatalog = BuiltinCatalog;
138 self.function_catalog.unwrap_or(&BUILTIN)
139 }
140
141 pub fn resolve_prefix(&self, prefix: &str) -> Option<String> {
145 if prefix.is_empty() {
146 self.default_element_ns
148 .and_then(|id| self.names.try_resolve(id))
149 } else if let Some(prefix_id) = self.names.get(prefix) {
150 self.namespaces
151 .resolve_prefix(prefix_id)
152 .and_then(|ns_id| self.names.try_resolve(ns_id))
153 } else {
154 None
155 }
156 }
157
158 pub fn resolve_prefix_id(&self, prefix_id: NameId) -> Option<NameId> {
160 self.namespaces.resolve_prefix(prefix_id)
161 }
162
163 pub fn default_function_namespace(&self) -> &str {
168 match self.mode {
169 XPathMode::XPath10 => "",
170 XPathMode::XPath20 => self
171 .default_function_ns
172 .unwrap_or(super::functions::FN_NAMESPACE),
173 }
174 }
175
176 pub fn resolve_name(&self, id: NameId) -> Option<String> {
178 self.names.try_resolve(id)
179 }
180}
181
182pub type VarSlotId = u32;
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct VarRef {
192 pub slot: VarSlotId,
193}
194
195#[derive(Debug, Clone)]
197pub struct NameSlot {
198 pub name: QualifiedName,
199 pub slot: VarSlotId,
200}
201
202#[derive(Debug, Default)]
210pub struct NameBinder {
211 next_slot: VarSlotId,
212 stack: Vec<NameSlot>,
213 external_var_count: usize,
215}
216
217impl NameBinder {
218 pub fn new() -> Self {
220 Self {
221 next_slot: 0,
222 stack: Vec::new(),
223 external_var_count: 0,
224 }
225 }
226
227 pub fn len(&self) -> usize {
231 self.next_slot as usize
232 }
233
234 pub fn is_empty(&self) -> bool {
236 self.next_slot == 0
237 }
238
239 pub fn mark_external_boundary(&mut self) {
245 self.external_var_count = self.stack.len();
246 }
247
248 pub fn external_vars(&self) -> impl Iterator<Item = (&QualifiedName, VarSlotId)> {
252 self.stack
253 .iter()
254 .take(self.external_var_count)
255 .map(|slot| (&slot.name, slot.slot))
256 }
257
258 pub fn external_var_count(&self) -> usize {
260 self.external_var_count
261 }
262
263 pub fn push_var(&mut self, name: QualifiedName) -> VarRef {
267 let slot = self.next_slot;
268 self.next_slot += 1;
269 self.stack.push(NameSlot { name, slot });
270 VarRef { slot }
271 }
272
273 pub fn pop_var(&mut self) {
277 self.stack.pop();
278 }
279
280 pub fn resolve(&self, name: &QualifiedName) -> Result<VarRef, super::error::XPathError> {
285 for entry in self.stack.iter().rev() {
287 if entry.name == *name {
288 return Ok(VarRef { slot: entry.slot });
289 }
290 }
291 Err(super::error::XPathError::XPST0008 {
294 qname: format!("$var(local={})", name.local_name.0),
295 })
296 }
297
298 pub fn resolve_with_names(
302 &self,
303 name: &QualifiedName,
304 names: &NameTable,
305 ) -> Result<VarRef, super::error::XPathError> {
306 for entry in self.stack.iter().rev() {
308 if entry.name == *name {
309 return Ok(VarRef { slot: entry.slot });
310 }
311 }
312 let local = names
314 .try_resolve(name.local_name)
315 .unwrap_or_else(|| "<unknown>".to_string());
316 let qname_str = if let Some(prefix_id) = name.prefix {
317 let prefix = names
318 .try_resolve(prefix_id)
319 .unwrap_or_else(|| "<unknown>".to_string());
320 format!("{}:{}", prefix, local)
321 } else {
322 local.to_string()
323 };
324 Err(super::error::XPathError::XPST0008 { qname: qname_str })
325 }
326}
327
328#[derive(Debug, Clone)]
337pub struct VarStore<V> {
338 values: Vec<Option<V>>,
340}
341
342impl<V> VarStore<V> {
343 pub fn new(size: usize) -> Self {
347 let mut values = Vec::with_capacity(size);
348 values.resize_with(size, || None);
349 Self { values }
350 }
351
352 pub fn get(&self, slot: VarSlotId) -> Option<&V> {
354 self.values.get(slot as usize).and_then(|v| v.as_ref())
355 }
356
357 pub fn set(&mut self, slot: VarSlotId, value: V) {
359 if let Some(cell) = self.values.get_mut(slot as usize) {
360 *cell = Some(value);
361 }
362 }
363
364 pub fn clear_slot(&mut self, slot: VarSlotId) {
366 if let Some(cell) = self.values.get_mut(slot as usize) {
367 *cell = None;
368 }
369 }
370
371 pub fn clear(&mut self) {
373 for cell in &mut self.values {
374 *cell = None;
375 }
376 }
377
378 pub fn len(&self) -> usize {
380 self.values.len()
381 }
382
383 pub fn is_empty(&self) -> bool {
385 self.values.is_empty()
386 }
387}
388
389impl<V> Default for VarStore<V> {
390 fn default() -> Self {
391 Self::new(0)
392 }
393}
394
395pub struct DynamicContext<'a, N: DomNavigator> {
408 pub static_context: &'a XPathContext<'a>,
410 pub context_item: Option<XmlItem<N>>,
412 pub context_position: usize,
414 pub context_size: usize,
416 pub current_datetime: Option<DateTimeValue>,
418 pub implicit_timezone: Option<TimezoneOffset>,
420 pub base_uri: Option<String>,
422 pub variables: VarStore<super::functions::XPathValue<N>>,
424 function_evaluator: Option<&'a dyn FunctionEvaluator<N>>,
426}
427
428impl<'a, N: DomNavigator> DynamicContext<'a, N> {
429 pub fn new(static_context: &'a XPathContext<'a>, var_count: usize) -> Self {
433 Self {
434 static_context,
435 context_item: None,
436 context_position: 0,
437 context_size: 0,
438 current_datetime: None,
439 implicit_timezone: static_context.implicit_timezone,
440 base_uri: static_context.base_uri.clone(),
441 variables: VarStore::new(var_count),
442 function_evaluator: None,
443 }
444 }
445
446 pub fn with_context_item(mut self, item: XmlItem<N>) -> Self {
448 self.context_item = Some(item);
449 self.context_position = 1;
450 self.context_size = 1;
451 self
452 }
453
454 pub fn with_context_node(self, node: N) -> Self {
456 self.with_context_item(XmlItem::Node(node))
457 }
458
459 pub fn with_position(mut self, position: usize, size: usize) -> Self {
461 self.context_position = position;
462 self.context_size = size;
463 self
464 }
465
466 pub fn with_current_datetime(mut self, dt: DateTimeValue) -> Self {
468 self.current_datetime = Some(dt);
469 self
470 }
471
472 pub fn with_implicit_timezone(mut self, tz: TimezoneOffset) -> Self {
474 self.implicit_timezone = Some(tz);
475 self
476 }
477
478 pub fn require_context_item(&self) -> Result<&XmlItem<N>, super::error::XPathError> {
480 self.context_item
481 .as_ref()
482 .ok_or_else(|| super::error::XPathError::XPDY0002 {
483 message: "Context item is undefined".to_string(),
484 })
485 }
486
487 pub fn require_context_node(&self) -> Result<&N, super::error::XPathError> {
491 match self.context_item.as_ref() {
492 Some(XmlItem::Node(node)) => Ok(node),
493 Some(XmlItem::Atomic(_)) => Err(super::error::XPathError::XPTY0020),
494 None => Err(super::error::XPathError::XPDY0002 {
495 message: "Context item is undefined".to_string(),
496 }),
497 }
498 }
499
500 pub fn get_variable(&self, slot: VarSlotId) -> Option<&super::functions::XPathValue<N>> {
502 self.variables.get(slot)
503 }
504
505 pub fn set_variable(&mut self, slot: VarSlotId, value: super::functions::XPathValue<N>) {
507 self.variables.set(slot, value);
508 }
509
510 pub fn with_function_evaluator(mut self, evaluator: &'a dyn FunctionEvaluator<N>) -> Self {
512 self.function_evaluator = Some(evaluator);
513 self
514 }
515
516 pub fn function_evaluator(&self) -> &dyn FunctionEvaluator<N> {
520 static BUILTIN: BuiltinEvaluator = BuiltinEvaluator;
521 self.function_evaluator.unwrap_or(&BUILTIN)
522 }
523
524 pub fn has_custom_evaluator(&self) -> bool {
526 self.function_evaluator.is_some()
527 }
528
529 pub fn eval_function(
535 &mut self,
536 handle: super::functions::FunctionHandle,
537 args: Vec<super::functions::XPathValue<N>>,
538 ) -> Result<super::functions::XPathValue<N>, super::error::XPathError> {
539 if handle.is_builtin() && self.function_evaluator.is_none() {
541 return BuiltinEvaluator.eval(handle, self, args);
542 }
543
544 match self.function_evaluator {
548 Some(evaluator) => {
549 let evaluator_ptr = evaluator as *const dyn FunctionEvaluator<N>;
554 let evaluator_ref = unsafe { &*evaluator_ptr };
556 evaluator_ref.eval(handle, self, args)
557 }
558 None => {
559 BuiltinEvaluator.eval(handle, self, args)
561 }
562 }
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569
570 #[test]
571 fn test_var_store() {
572 let mut store: VarStore<i32> = VarStore::new(3);
573
574 assert!(store.get(0).is_none());
575 store.set(0, 42);
576 assert_eq!(store.get(0), Some(&42));
577
578 store.set(1, 100);
579 assert_eq!(store.get(1), Some(&100));
580
581 store.clear_slot(0);
582 assert!(store.get(0).is_none());
583
584 store.clear();
585 assert!(store.get(1).is_none());
586 }
587
588 #[test]
589 fn test_xpath_context_default_function_ns() {
590 let names = NameTable::new();
591 let ctx = XPathContext::new(&names);
592 assert_eq!(
593 ctx.default_function_namespace(),
594 super::super::functions::FN_NAMESPACE
595 );
596 }
597
598 #[test]
599 fn test_name_binder_push_pop() {
600 let names = NameTable::new();
601 let mut binder = NameBinder::new();
602 assert!(binder.is_empty());
603 assert_eq!(binder.len(), 0);
604
605 let x_id = names.add("x");
606 let y_id = names.add("y");
607
608 let name1 = QualifiedName::local(x_id);
609 let ref1 = binder.push_var(name1.clone());
610 assert_eq!(ref1.slot, 0);
611 assert_eq!(binder.len(), 1);
612
613 let name2 = QualifiedName::local(y_id);
614 let ref2 = binder.push_var(name2.clone());
615 assert_eq!(ref2.slot, 1);
616 assert_eq!(binder.len(), 2);
617
618 let resolved1 = binder.resolve(&name1).unwrap();
620 assert_eq!(resolved1.slot, 0);
621
622 let resolved2 = binder.resolve(&name2).unwrap();
623 assert_eq!(resolved2.slot, 1);
624
625 binder.pop_var();
627 let resolved1_again = binder.resolve(&name1).unwrap();
628 assert_eq!(resolved1_again.slot, 0);
629
630 let err = binder.resolve(&name2);
632 assert!(err.is_err());
633 }
634
635 #[test]
636 fn test_name_binder_shadowing() {
637 let names = NameTable::new();
638 let mut binder = NameBinder::new();
639
640 let x_id = names.add("x");
641 let name = QualifiedName::local(x_id);
642
643 let ref1 = binder.push_var(name.clone());
645 assert_eq!(ref1.slot, 0);
646
647 let ref2 = binder.push_var(name.clone());
649 assert_eq!(ref2.slot, 1);
650
651 let resolved = binder.resolve(&name).unwrap();
653 assert_eq!(resolved.slot, 1);
654
655 binder.pop_var();
657 let resolved_after_pop = binder.resolve(&name).unwrap();
658 assert_eq!(resolved_after_pop.slot, 0);
659 }
660
661 #[test]
662 fn test_name_binder_unbound_error() {
663 let names = NameTable::new();
664 let binder = NameBinder::new();
665 let undefined_id = names.add("undefined");
666 let name = QualifiedName::local(undefined_id);
667 let result = binder.resolve(&name);
668 assert!(matches!(
669 result,
670 Err(super::super::error::XPathError::XPST0008 { .. })
671 ));
672 }
673}