use crate::ids::NameId;
use crate::namespace::context::NamespaceContextSnapshot;
use crate::namespace::qname::QualifiedName;
use crate::namespace::table::NameTable;
use crate::schema::SchemaSet;
use crate::types::value::{DateTimeValue, TimezoneOffset};
use super::functions::{BuiltinCatalog, BuiltinEvaluator, FunctionCatalog, FunctionEvaluator};
use super::iterator::XmlItem;
use super::DomNavigator;
use super::XPathMode;
#[derive(Debug, Clone)]
pub struct XPathContext<'a> {
pub names: &'a NameTable,
pub schema_set: Option<&'a SchemaSet>,
pub namespaces: NamespaceContextSnapshot,
pub default_element_ns: Option<NameId>,
pub default_function_ns: Option<&'static str>,
pub implicit_timezone: Option<TimezoneOffset>,
pub base_uri: Option<String>,
pub mode: XPathMode,
pub trace_enabled: bool,
function_catalog: Option<&'a dyn FunctionCatalog>,
}
impl<'a> XPathContext<'a> {
pub fn new(names: &'a NameTable) -> Self {
Self {
names,
schema_set: None,
namespaces: NamespaceContextSnapshot::default(),
default_element_ns: None,
default_function_ns: Some(super::functions::FN_NAMESPACE),
implicit_timezone: None,
base_uri: None,
mode: XPathMode::XPath20,
trace_enabled: false,
function_catalog: None,
}
}
pub fn with_schema_set(mut self, schema_set: &'a SchemaSet) -> Self {
self.schema_set = Some(schema_set);
self
}
pub fn with_namespaces(mut self, namespaces: NamespaceContextSnapshot) -> Self {
self.namespaces = namespaces;
self
}
pub fn with_default_element_ns(mut self, ns: NameId) -> Self {
self.default_element_ns = Some(ns);
self
}
pub fn with_default_function_ns(mut self, ns: &'static str) -> Self {
self.default_function_ns = Some(ns);
self
}
pub fn with_implicit_timezone(mut self, tz: TimezoneOffset) -> Self {
self.implicit_timezone = Some(tz);
self
}
pub fn with_base_uri(mut self, base_uri: impl Into<String>) -> Self {
self.base_uri = Some(base_uri.into());
self
}
pub fn with_mode(mut self, mode: XPathMode) -> Self {
self.mode = mode;
self
}
pub fn with_trace_enabled(mut self, enabled: bool) -> Self {
self.trace_enabled = enabled;
self
}
pub fn mode(&self) -> XPathMode {
self.mode
}
pub fn with_function_catalog(mut self, catalog: &'a dyn FunctionCatalog) -> Self {
self.function_catalog = Some(catalog);
self
}
pub fn function_catalog(&self) -> &dyn FunctionCatalog {
static BUILTIN: BuiltinCatalog = BuiltinCatalog;
self.function_catalog.unwrap_or(&BUILTIN)
}
pub fn resolve_prefix(&self, prefix: &str) -> Option<String> {
if prefix.is_empty() {
self.default_element_ns
.and_then(|id| self.names.try_resolve(id))
} else if let Some(prefix_id) = self.names.get(prefix) {
self.namespaces
.resolve_prefix(prefix_id)
.and_then(|ns_id| self.names.try_resolve(ns_id))
} else {
None
}
}
pub fn resolve_prefix_id(&self, prefix_id: NameId) -> Option<NameId> {
self.namespaces.resolve_prefix(prefix_id)
}
pub fn default_function_namespace(&self) -> &str {
match self.mode {
XPathMode::XPath10 => "",
XPathMode::XPath20 => self
.default_function_ns
.unwrap_or(super::functions::FN_NAMESPACE),
}
}
pub fn resolve_name(&self, id: NameId) -> Option<String> {
self.names.try_resolve(id)
}
}
pub type VarSlotId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VarRef {
pub slot: VarSlotId,
}
#[derive(Debug, Clone)]
pub struct NameSlot {
pub name: QualifiedName,
pub slot: VarSlotId,
}
#[derive(Debug, Default)]
pub struct NameBinder {
next_slot: VarSlotId,
stack: Vec<NameSlot>,
external_var_count: usize,
}
impl NameBinder {
pub fn new() -> Self {
Self {
next_slot: 0,
stack: Vec::new(),
external_var_count: 0,
}
}
pub fn len(&self) -> usize {
self.next_slot as usize
}
pub fn is_empty(&self) -> bool {
self.next_slot == 0
}
pub fn mark_external_boundary(&mut self) {
self.external_var_count = self.stack.len();
}
pub fn external_vars(&self) -> impl Iterator<Item = (&QualifiedName, VarSlotId)> {
self.stack
.iter()
.take(self.external_var_count)
.map(|slot| (&slot.name, slot.slot))
}
pub fn external_var_count(&self) -> usize {
self.external_var_count
}
pub fn push_var(&mut self, name: QualifiedName) -> VarRef {
let slot = self.next_slot;
self.next_slot += 1;
self.stack.push(NameSlot { name, slot });
VarRef { slot }
}
pub fn pop_var(&mut self) {
self.stack.pop();
}
pub fn resolve(&self, name: &QualifiedName) -> Result<VarRef, super::error::XPathError> {
for entry in self.stack.iter().rev() {
if entry.name == *name {
return Ok(VarRef { slot: entry.slot });
}
}
Err(super::error::XPathError::XPST0008 {
qname: format!("$var(local={})", name.local_name.0),
})
}
pub fn resolve_with_names(
&self,
name: &QualifiedName,
names: &NameTable,
) -> Result<VarRef, super::error::XPathError> {
for entry in self.stack.iter().rev() {
if entry.name == *name {
return Ok(VarRef { slot: entry.slot });
}
}
let local = names
.try_resolve(name.local_name)
.unwrap_or_else(|| "<unknown>".to_string());
let qname_str = if let Some(prefix_id) = name.prefix {
let prefix = names
.try_resolve(prefix_id)
.unwrap_or_else(|| "<unknown>".to_string());
format!("{}:{}", prefix, local)
} else {
local.to_string()
};
Err(super::error::XPathError::XPST0008 { qname: qname_str })
}
}
#[derive(Debug, Clone)]
pub struct VarStore<V> {
values: Vec<Option<V>>,
}
impl<V> VarStore<V> {
pub fn new(size: usize) -> Self {
let mut values = Vec::with_capacity(size);
values.resize_with(size, || None);
Self { values }
}
pub fn get(&self, slot: VarSlotId) -> Option<&V> {
self.values.get(slot as usize).and_then(|v| v.as_ref())
}
pub fn set(&mut self, slot: VarSlotId, value: V) {
if let Some(cell) = self.values.get_mut(slot as usize) {
*cell = Some(value);
}
}
pub fn clear_slot(&mut self, slot: VarSlotId) {
if let Some(cell) = self.values.get_mut(slot as usize) {
*cell = None;
}
}
pub fn clear(&mut self) {
for cell in &mut self.values {
*cell = None;
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
impl<V> Default for VarStore<V> {
fn default() -> Self {
Self::new(0)
}
}
pub struct DynamicContext<'a, N: DomNavigator> {
pub static_context: &'a XPathContext<'a>,
pub context_item: Option<XmlItem<N>>,
pub context_position: usize,
pub context_size: usize,
pub current_datetime: Option<DateTimeValue>,
pub implicit_timezone: Option<TimezoneOffset>,
pub base_uri: Option<String>,
pub variables: VarStore<super::functions::XPathValue<N>>,
function_evaluator: Option<&'a dyn FunctionEvaluator<N>>,
}
impl<'a, N: DomNavigator> DynamicContext<'a, N> {
pub fn new(static_context: &'a XPathContext<'a>, var_count: usize) -> Self {
Self {
static_context,
context_item: None,
context_position: 0,
context_size: 0,
current_datetime: None,
implicit_timezone: static_context.implicit_timezone,
base_uri: static_context.base_uri.clone(),
variables: VarStore::new(var_count),
function_evaluator: None,
}
}
pub fn with_context_item(mut self, item: XmlItem<N>) -> Self {
self.context_item = Some(item);
self.context_position = 1;
self.context_size = 1;
self
}
pub fn with_context_node(self, node: N) -> Self {
self.with_context_item(XmlItem::Node(node))
}
pub fn with_position(mut self, position: usize, size: usize) -> Self {
self.context_position = position;
self.context_size = size;
self
}
pub fn with_current_datetime(mut self, dt: DateTimeValue) -> Self {
self.current_datetime = Some(dt);
self
}
pub fn with_implicit_timezone(mut self, tz: TimezoneOffset) -> Self {
self.implicit_timezone = Some(tz);
self
}
pub fn require_context_item(&self) -> Result<&XmlItem<N>, super::error::XPathError> {
self.context_item
.as_ref()
.ok_or_else(|| super::error::XPathError::XPDY0002 {
message: "Context item is undefined".to_string(),
})
}
pub fn require_context_node(&self) -> Result<&N, super::error::XPathError> {
match self.context_item.as_ref() {
Some(XmlItem::Node(node)) => Ok(node),
Some(XmlItem::Atomic(_)) => Err(super::error::XPathError::XPTY0020),
None => Err(super::error::XPathError::XPDY0002 {
message: "Context item is undefined".to_string(),
}),
}
}
pub fn get_variable(&self, slot: VarSlotId) -> Option<&super::functions::XPathValue<N>> {
self.variables.get(slot)
}
pub fn set_variable(&mut self, slot: VarSlotId, value: super::functions::XPathValue<N>) {
self.variables.set(slot, value);
}
pub fn with_function_evaluator(mut self, evaluator: &'a dyn FunctionEvaluator<N>) -> Self {
self.function_evaluator = Some(evaluator);
self
}
pub fn function_evaluator(&self) -> &dyn FunctionEvaluator<N> {
static BUILTIN: BuiltinEvaluator = BuiltinEvaluator;
self.function_evaluator.unwrap_or(&BUILTIN)
}
pub fn has_custom_evaluator(&self) -> bool {
self.function_evaluator.is_some()
}
pub fn eval_function(
&mut self,
handle: super::functions::FunctionHandle,
args: Vec<super::functions::XPathValue<N>>,
) -> Result<super::functions::XPathValue<N>, super::error::XPathError> {
if handle.is_builtin() && self.function_evaluator.is_none() {
return BuiltinEvaluator.eval(handle, self, args);
}
match self.function_evaluator {
Some(evaluator) => {
let evaluator_ptr = evaluator as *const dyn FunctionEvaluator<N>;
let evaluator_ref = unsafe { &*evaluator_ptr };
evaluator_ref.eval(handle, self, args)
}
None => {
BuiltinEvaluator.eval(handle, self, args)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_var_store() {
let mut store: VarStore<i32> = VarStore::new(3);
assert!(store.get(0).is_none());
store.set(0, 42);
assert_eq!(store.get(0), Some(&42));
store.set(1, 100);
assert_eq!(store.get(1), Some(&100));
store.clear_slot(0);
assert!(store.get(0).is_none());
store.clear();
assert!(store.get(1).is_none());
}
#[test]
fn test_xpath_context_default_function_ns() {
let names = NameTable::new();
let ctx = XPathContext::new(&names);
assert_eq!(
ctx.default_function_namespace(),
super::super::functions::FN_NAMESPACE
);
}
#[test]
fn test_name_binder_push_pop() {
let names = NameTable::new();
let mut binder = NameBinder::new();
assert!(binder.is_empty());
assert_eq!(binder.len(), 0);
let x_id = names.add("x");
let y_id = names.add("y");
let name1 = QualifiedName::local(x_id);
let ref1 = binder.push_var(name1.clone());
assert_eq!(ref1.slot, 0);
assert_eq!(binder.len(), 1);
let name2 = QualifiedName::local(y_id);
let ref2 = binder.push_var(name2.clone());
assert_eq!(ref2.slot, 1);
assert_eq!(binder.len(), 2);
let resolved1 = binder.resolve(&name1).unwrap();
assert_eq!(resolved1.slot, 0);
let resolved2 = binder.resolve(&name2).unwrap();
assert_eq!(resolved2.slot, 1);
binder.pop_var();
let resolved1_again = binder.resolve(&name1).unwrap();
assert_eq!(resolved1_again.slot, 0);
let err = binder.resolve(&name2);
assert!(err.is_err());
}
#[test]
fn test_name_binder_shadowing() {
let names = NameTable::new();
let mut binder = NameBinder::new();
let x_id = names.add("x");
let name = QualifiedName::local(x_id);
let ref1 = binder.push_var(name.clone());
assert_eq!(ref1.slot, 0);
let ref2 = binder.push_var(name.clone());
assert_eq!(ref2.slot, 1);
let resolved = binder.resolve(&name).unwrap();
assert_eq!(resolved.slot, 1);
binder.pop_var();
let resolved_after_pop = binder.resolve(&name).unwrap();
assert_eq!(resolved_after_pop.slot, 0);
}
#[test]
fn test_name_binder_unbound_error() {
let names = NameTable::new();
let binder = NameBinder::new();
let undefined_id = names.add("undefined");
let name = QualifiedName::local(undefined_id);
let result = binder.resolve(&name);
assert!(matches!(
result,
Err(super::super::error::XPathError::XPST0008 { .. })
));
}
}