use std::{borrow::Cow, collections::HashMap, ffi::c_void, ptr::null_mut, rc::Rc};
use crate::{
chvalid::XmlCharValid,
error::XmlError,
globals::{GenericErrorContext, StructuredError},
tree::{XML_XML_NAMESPACE, XmlDocPtr, XmlGenericNodePtr, XmlNsPtr},
};
use super::{
XPATH_MAX_STACK_DEPTH, XmlNodeSet, XmlXPathAxisPtr, XmlXPathCompExpr, XmlXPathError,
XmlXPathFuncLookup, XmlXPathFunction, XmlXPathObject, XmlXPathObjectType, XmlXPathOp,
XmlXPathTypePtr, XmlXPathVariableLookupFunc,
compile::XmlXPathStepOp,
functions::{
xml_xpath_boolean_function, xml_xpath_ceiling_function, xml_xpath_concat_function,
xml_xpath_contains_function, xml_xpath_count_function, xml_xpath_escape_uri_function,
xml_xpath_false_function, xml_xpath_floor_function, xml_xpath_id_function,
xml_xpath_lang_function, xml_xpath_last_function, xml_xpath_local_name_function,
xml_xpath_name_function, xml_xpath_namespace_uri_function, xml_xpath_normalize_function,
xml_xpath_not_function, xml_xpath_number_function, xml_xpath_position_function,
xml_xpath_round_function, xml_xpath_starts_with_function, xml_xpath_string_function,
xml_xpath_string_length_function, xml_xpath_substring_after_function,
xml_xpath_substring_before_function, xml_xpath_substring_function, xml_xpath_sum_function,
xml_xpath_translate_function, xml_xpath_true_function,
},
xml_xpath_cast_to_boolean, xml_xpath_cast_to_number, xml_xpath_cast_to_string, xml_xpath_err,
xml_xpath_object_copy, xml_xpath_perr_memory,
};
#[doc(alias = "xmlXPathParserContext")]
#[repr(C)]
pub struct XmlXPathParserContext<'a> {
pub(crate) cur: usize,
pub(crate) base: Box<str>,
pub(crate) error: i32,
pub(crate) context: &'a mut XmlXPathContext,
pub(crate) value_tab: Vec<XmlXPathObject>,
pub(crate) comp: XmlXPathCompExpr,
pub(crate) xptr: i32,
pub(crate) ancestor: Option<XmlGenericNodePtr>,
pub(crate) value_frame: i32,
}
impl<'a> XmlXPathParserContext<'a> {
#[doc(alias = "xmlXPathNewParserContext")]
pub fn new(xpath: &str, ctxt: &'a mut XmlXPathContext) -> Self {
Self {
cur: 0,
base: xpath.into(),
error: 0,
context: ctxt,
value_tab: vec![],
comp: XmlXPathCompExpr::default(),
xptr: 0,
ancestor: None,
value_frame: 0,
}
}
#[doc(alias = "xmlXPathCompParserContext")]
pub(super) fn from_compiled_expression(
comp: XmlXPathCompExpr,
ctxt: &'a mut XmlXPathContext,
) -> Self {
Self {
cur: 0,
base: "".into(),
error: 0,
context: ctxt,
value_tab: vec![],
comp,
xptr: 0,
ancestor: None,
value_frame: 0,
}
}
pub(crate) fn next_char(&mut self) -> Option<char> {
let res = self.current_char()?;
self.cur += res.len_utf8();
Some(res)
}
pub(crate) fn current_char(&self) -> Option<char> {
self.current_str().chars().next()
}
pub(crate) fn current_str(&self) -> &str {
&self.base[self.cur..]
}
pub(crate) fn nth_byte(&self, index: usize) -> Option<u8> {
self.current_str().as_bytes().get(index).copied()
}
pub(crate) fn skip_blanks(&mut self) {
let rem = self
.current_str()
.trim_start_matches(|c: char| c.is_xml_blank_char());
let diff = self.current_str().len() - rem.len();
self.cur += diff;
}
pub(crate) fn value(&self) -> Option<&XmlXPathObject> {
self.value_tab.last()
}
pub(crate) fn value_mut(&mut self) -> Option<&mut XmlXPathObject> {
self.value_tab.last_mut()
}
#[doc(alias = "xmlXPathStackIsNodeSet")]
fn stack_is_node_set(&self) -> bool {
self.value().is_some_and(|value| {
value.typ == XmlXPathObjectType::XPathNodeset
|| value.typ == XmlXPathObjectType::XPathXSLTTree
})
}
#[doc(alias = "xmlXPathIsPositionalPredicate")]
pub(super) fn is_positional_predicate(&self, op: &XmlXPathStepOp, max_pos: &mut i32) -> i32 {
if !matches!(
op.op,
XmlXPathOp::XPathOpPredicate | XmlXPathOp::XPathOpFilter
) {
return 0;
}
if op.ch2 == -1 || op.ch2 >= self.comp.steps.len() as i32 {
return 0;
}
let expr_op = &self.comp.steps[op.ch2 as usize];
if let Some(obj) = expr_op
.value4
.as_ref()
.and_then(|val| val.as_object())
.filter(|&obj| {
matches!(expr_op.op, XmlXPathOp::XPathOpValue)
&& matches!(obj.typ, XmlXPathObjectType::XPathNumber)
})
{
let floatval: f64 = obj.floatval;
if floatval > i32::MIN as f64 && floatval < i32::MAX as f64 {
*max_pos = floatval as i32;
if floatval == *max_pos as f64 {
return 1;
}
}
}
0
}
#[doc(alias = "valuePush")]
pub fn value_push(&mut self, value: XmlXPathObject) -> i32 {
if self.value_tab.len() == XPATH_MAX_STACK_DEPTH {
xml_xpath_perr_memory(Some(self), Some("XPath stack depth limit reached\n"));
return -1;
}
self.value_tab.push(value);
self.value_tab.len() as i32 - 1
}
#[doc(alias = "valuePop")]
pub fn value_pop(&mut self) -> Option<XmlXPathObject> {
self.value_tab.pop()
}
#[doc(alias = "xmlXPathPopNumber")]
pub fn pop_number(&mut self) -> f64 {
let Some(mut obj) = self.value_pop() else {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidOperand as i32);
self.error = XmlXPathError::XPathInvalidOperand as i32;
return 0.0;
};
if obj.typ != XmlXPathObjectType::XPathNumber {
xml_xpath_cast_to_number(&mut obj)
} else {
obj.floatval
}
}
#[doc(alias = "xmlXPathPopBoolean")]
pub fn pop_boolean(&mut self) -> bool {
let Some(obj) = self.value_pop() else {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidOperand as i32);
self.error = XmlXPathError::XPathInvalidOperand as i32;
return false;
};
if obj.typ != XmlXPathObjectType::XPathBoolean {
xml_xpath_cast_to_boolean(&obj)
} else {
obj.boolval
}
}
#[doc(alias = "xmlXPathPopString")]
pub fn pop_string(&mut self) -> Option<Cow<'static, str>> {
let Some(mut obj) = self.value_pop() else {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidOperand as i32);
self.error = XmlXPathError::XPathInvalidOperand as i32;
return None;
};
let ret = xml_xpath_cast_to_string(&mut obj);
Some(ret)
}
#[doc(alias = "xmlXPathPopNodeSet")]
pub fn pop_node_set(&mut self) -> Option<Box<XmlNodeSet>> {
if self.value().is_none() {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidOperand as i32);
self.error = XmlXPathError::XPathInvalidOperand as i32;
return None;
}
if !self.stack_is_node_set() {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidType as i32);
self.error = XmlXPathError::XPathInvalidType as i32;
return None;
}
let mut obj = self.value_pop().unwrap();
obj.nodesetval.take()
}
#[doc(alias = "xmlXPathPopExternal")]
pub fn pop_external(&mut self) -> *mut c_void {
let Some(value) = self.value() else {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidOperand as i32);
self.error = XmlXPathError::XPathInvalidOperand as i32;
return null_mut();
};
if value.typ != XmlXPathObjectType::XPathUsers {
xml_xpath_err(Some(self), XmlXPathError::XPathInvalidType as i32);
self.error = XmlXPathError::XPathInvalidType as i32;
return null_mut();
}
let mut obj = self.value_pop().unwrap();
obj.user
.take()
.and_then(|ret| ret.as_external().copied())
.unwrap_or(null_mut())
}
}
#[doc(alias = "xmlXPathContext")]
#[repr(C)]
pub struct XmlXPathContext {
pub doc: Option<XmlDocPtr>,
pub node: Option<XmlGenericNodePtr>,
pub(crate) var_hash: HashMap<(Cow<'static, str>, Option<Cow<'static, str>>), XmlXPathObject>,
pub(crate) nb_types: i32,
pub(crate) max_types: i32,
pub(crate) types: XmlXPathTypePtr,
pub(crate) func_hash: HashMap<(Option<Cow<'static, str>>, Cow<'static, str>), XmlXPathFunction>,
pub(crate) nb_axis: i32,
pub(crate) max_axis: i32,
pub(crate) axis: XmlXPathAxisPtr,
pub(crate) namespaces: Option<Vec<XmlNsPtr>>,
pub(crate) user: *mut c_void,
pub(crate) context_size: i32,
pub(crate) proximity_position: i32,
pub(crate) xptr: i32,
pub(crate) here: Option<XmlGenericNodePtr>,
pub(crate) origin: Option<XmlGenericNodePtr>,
pub(crate) ns_hash: HashMap<Cow<'static, str>, Rc<str>>,
pub(crate) var_lookup_func: Option<XmlXPathVariableLookupFunc>,
pub(crate) var_lookup_data: *mut c_void,
pub(crate) extra: *mut c_void,
pub(crate) function: Option<Rc<str>>,
pub(crate) function_uri: Option<Rc<str>>,
pub(crate) func_lookup: Option<Box<dyn XmlXPathFuncLookup>>,
pub(crate) tmp_ns_list: Option<Vec<XmlNsPtr>>,
pub(crate) tmp_ns_nr: i32,
pub(crate) user_data: Option<GenericErrorContext>,
pub(crate) error: Option<StructuredError>,
pub(crate) last_error: XmlError,
pub(crate) debug_node: Option<XmlGenericNodePtr>,
pub(crate) flags: i32,
pub(crate) op_limit: u64,
pub(crate) op_count: u64,
pub(crate) depth: i32,
}
impl XmlXPathContext {
#[doc(alias = "xmlXPathNewContext")]
pub fn new(doc: Option<XmlDocPtr>) -> Self {
let mut ret = XmlXPathContext::default();
ret.doc = doc;
ret.node = None;
ret.nb_types = 0;
ret.max_types = 0;
ret.types = null_mut();
ret.func_hash = HashMap::new();
ret.nb_axis = 0;
ret.max_axis = 0;
ret.axis = null_mut();
ret.user = null_mut();
ret.context_size = -1;
ret.proximity_position = -1;
ret.register_all_functions();
ret
}
#[doc(alias = "xmlXPathSetContextNode")]
#[cfg(feature = "xpath")]
pub fn set_context_node(&mut self, node: XmlGenericNodePtr) -> i32 {
if node.document() == self.doc {
self.node = Some(node);
return 0;
}
-1
}
#[doc(alias = "xmlXPathRegisterFunc")]
pub fn register_function(
&mut self,
name: Cow<'static, str>,
f: Option<XmlXPathFunction>,
) -> i32 {
self.register_function_ns(name, None, f)
}
#[doc(alias = "xmlXPathRegisterFuncNS")]
pub fn register_function_ns(
&mut self,
name: Cow<'static, str>,
ns_uri: Option<Cow<'static, str>>,
f: Option<XmlXPathFunction>,
) -> i32 {
let res = if let Some(f) = f {
self.func_hash.insert((ns_uri, name), f).is_some()
} else {
self.func_hash.remove(&(ns_uri, name)).is_none()
};
-(res as i32)
}
#[doc(alias = "xmlXPathRegisterNs")]
pub fn register_ns(&mut self, prefix: &str, ns_uri: Option<&str>) -> i32 {
if prefix.is_empty() {
return -1;
}
let Some(ns_uri) = ns_uri else {
return if self.ns_hash.remove(prefix).is_some() {
0
} else {
-1
};
};
self.ns_hash.insert(prefix.to_owned().into(), ns_uri.into());
0
}
#[doc(alias = "xmlXPathRegisterAllFunctions")]
pub fn register_all_functions(&mut self) {
self.register_function("boolean".into(), Some(xml_xpath_boolean_function));
self.register_function("ceiling".into(), Some(xml_xpath_ceiling_function));
self.register_function("count".into(), Some(xml_xpath_count_function));
self.register_function("concat".into(), Some(xml_xpath_concat_function));
self.register_function("contains".into(), Some(xml_xpath_contains_function));
self.register_function("id".into(), Some(xml_xpath_id_function));
self.register_function("false".into(), Some(xml_xpath_false_function));
self.register_function("floor".into(), Some(xml_xpath_floor_function));
self.register_function("last".into(), Some(xml_xpath_last_function));
self.register_function("lang".into(), Some(xml_xpath_lang_function));
self.register_function("local-name".into(), Some(xml_xpath_local_name_function));
self.register_function("not".into(), Some(xml_xpath_not_function));
self.register_function("name".into(), Some(xml_xpath_name_function));
self.register_function(
"namespace-uri".into(),
Some(xml_xpath_namespace_uri_function),
);
self.register_function("normalize-space".into(), Some(xml_xpath_normalize_function));
self.register_function("number".into(), Some(xml_xpath_number_function));
self.register_function("position".into(), Some(xml_xpath_position_function));
self.register_function("round".into(), Some(xml_xpath_round_function));
self.register_function("string".into(), Some(xml_xpath_string_function));
self.register_function(
"string-length".into(),
Some(xml_xpath_string_length_function),
);
self.register_function("starts-with".into(), Some(xml_xpath_starts_with_function));
self.register_function("substring".into(), Some(xml_xpath_substring_function));
self.register_function(
"substring-before".into(),
Some(xml_xpath_substring_before_function),
);
self.register_function(
"substring-after".into(),
Some(xml_xpath_substring_after_function),
);
self.register_function("sum".into(), Some(xml_xpath_sum_function));
self.register_function("true".into(), Some(xml_xpath_true_function));
self.register_function("translate".into(), Some(xml_xpath_translate_function));
self.register_function_ns(
"escape-uri".into(),
Some("http://www.w3.org/2002/08/xquery-functions".into()),
Some(xml_xpath_escape_uri_function),
);
}
#[doc(alias = "xmlXPathFunctionLookup")]
pub fn lookup_function(&self, name: &str) -> Option<XmlXPathFunction> {
if let Some(ret) = self
.func_lookup
.as_deref()
.and_then(|f| f.lookup(name, None))
{
return Some(ret);
}
self.lookup_function_ns(name, None)
}
#[doc(alias = "xmlXPathFunctionLookupNS")]
pub fn lookup_function_ns(&self, name: &str, ns_uri: Option<&str>) -> Option<XmlXPathFunction> {
if let Some(ret) = self
.func_lookup
.as_deref()
.and_then(|f| f.lookup(name, ns_uri))
{
return Some(ret);
}
self.func_hash
.get(&(ns_uri.map(Cow::Borrowed), Cow::Borrowed(name)))
.copied()
}
#[doc(alias = "xmlXPathNsLookup")]
pub fn lookup_ns(&self, prefix: &str) -> Option<Rc<str>> {
if prefix == "xml" {
return Some(XML_XML_NAMESPACE.into());
}
if let Some(namespaces) = self.namespaces.as_deref() {
for &ns in namespaces {
if ns.prefix().as_deref() == Some(prefix) {
return ns.href.as_deref().map(|href| href.into());
}
}
}
self.ns_hash.get(prefix).cloned()
}
#[doc(alias = "xmlXPathVariableLookup")]
pub fn lookup_variable(&mut self, name: &str) -> Option<XmlXPathObject> {
if let Some(var_lookup_func) = self.var_lookup_func {
return var_lookup_func(self.var_lookup_data, name, None);
}
self.lookup_variable_ns(name, None)
}
#[doc(alias = "xmlXPathVariableLookupNS")]
pub fn lookup_variable_ns(
&mut self,
name: &str,
ns_uri: Option<&str>,
) -> Option<XmlXPathObject> {
if let Some(var_lookup_func) = self.var_lookup_func {
if let Some(ret) = var_lookup_func(self.var_lookup_data, name, ns_uri) {
return Some(ret);
}
}
let obj = self
.var_hash
.get(&(name.into(), ns_uri.map(Cow::Borrowed)))?;
Some(xml_xpath_object_copy(obj))
}
#[doc(alias = "xmlXPathRegisterFuncLookup")]
pub fn register_func_lookup(&mut self, f: impl XmlXPathFuncLookup + 'static) {
self.func_lookup = Some(Box::new(f));
}
#[doc(alias = "xmlXPathRegisterVariable")]
pub fn register_variable(&mut self, name: &str, value: Option<XmlXPathObject>) -> i32 {
self.register_variable_ns(name, None, value)
}
#[doc(alias = "xmlXPathRegisterVariableNS")]
pub fn register_variable_ns(
&mut self,
name: &str,
ns_uri: Option<&str>,
value: Option<XmlXPathObject>,
) -> i32 {
let name = Cow::Owned(name.to_owned());
let ns_uri = ns_uri.map(|uri| Cow::Owned(uri.to_owned()));
let Some(value) = value else {
return if self.var_hash.remove(&(name, ns_uri)).is_some() {
0
} else {
-1
};
};
if self.var_hash.insert((name, ns_uri), value).is_none() {
0
} else {
-1
}
}
#[doc(alias = "xmlXPathRegisterVariableLookup")]
pub fn register_variable_lookup(&mut self, f: XmlXPathVariableLookupFunc, data: *mut c_void) {
self.var_lookup_func = Some(f);
self.var_lookup_data = data;
}
#[doc(alias = "xmlXPathRegisteredFuncsCleanup")]
pub fn cleanup_registered_func(&mut self) {
self.func_hash.clear();
}
#[doc(alias = "xmlXPathRegisteredNsCleanup")]
pub fn cleanup_registered_ns(&mut self) {
self.ns_hash.clear();
}
#[doc(alias = "xmlXPathRegisteredVariablesCleanup")]
pub fn cleanup_registered_variables(&mut self) {
self.var_hash.clear();
}
}
impl Default for XmlXPathContext {
fn default() -> Self {
Self {
doc: None,
node: None,
var_hash: HashMap::new(),
nb_types: 0,
max_types: 0,
types: null_mut(),
func_hash: HashMap::new(),
nb_axis: 0,
max_axis: 0,
axis: null_mut(),
namespaces: None,
user: null_mut(),
context_size: 0,
proximity_position: 0,
xptr: 0,
here: None,
origin: None,
ns_hash: HashMap::default(),
var_lookup_func: None,
var_lookup_data: null_mut(),
extra: null_mut(),
function: None,
function_uri: None,
func_lookup: None,
tmp_ns_list: None,
tmp_ns_nr: 0,
user_data: None,
error: None,
last_error: XmlError::default(),
debug_node: None,
flags: 0,
op_limit: 0,
op_count: 0,
depth: 0,
}
}
}
impl Drop for XmlXPathContext {
#[doc(alias = "xmlXPathFreeContext")]
fn drop(&mut self) {
self.cleanup_registered_ns();
self.cleanup_registered_func();
self.cleanup_registered_variables();
self.last_error.reset();
}
}