1use crate::abi::structs::_xmlNode;
67use crate::xml::xpath::ast::{Axis, BinaryOp, Expr, NameTest, NodeTest, Step};
68use crate::xml::xpath::axes;
69use crate::xml::xpath::context::XPathContext;
70use crate::xml::xpath::parser_context::{
71 free_parser_context, new_parser_context, value_pop, value_push,
72};
73use crate::xml::xpath::types::{node_string_value, string_to_number, NodeSet, XPathValue};
74use core::ffi::c_void;
75use std::os::raw::c_int;
76
77pub fn eval(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
83 ctx.push_recursion()?;
84
85 let result = match expr {
86 Expr::Step(step) => eval_step(ctx, step),
87 Expr::AbsolutePath(expr) => eval_absolute_path(ctx, expr),
88 Expr::RelativePath(left, right) => eval_relative_path(ctx, left, right),
89 Expr::Filter(expr, predicates) => eval_filter(ctx, expr, predicates),
90 Expr::Variable(name) => eval_variable(ctx, name),
91 Expr::StringLiteral(s) => Ok(XPathValue::String(s.clone())),
92 Expr::NumberLiteral(n) => Ok(XPathValue::Number(*n)),
93 Expr::BooleanLiteral(b) => Ok(XPathValue::Boolean(*b)),
94 Expr::FunctionCall { name, args } => eval_function_call(ctx, name, args),
95 Expr::BinaryOp { op, left, right } => eval_binary_op(ctx, op, left, right),
96 Expr::UnaryMinus(expr) => {
97 let val = eval(ctx, expr)?;
98 Ok(XPathValue::Number(-val.as_number()))
99 }
100 Expr::Union(left, right) => eval_union(ctx, left, right),
101 };
102
103 ctx.pop_recursion();
104 result
105}
106
107fn eval_absolute_path(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
113 let doc = ctx.document;
115 if doc.is_null() {
116 return Ok(XPathValue::NodeSet(NodeSet::new()));
117 }
118
119 {
120 let doc_node = doc as *mut _xmlNode;
125
126 let saved_node = ctx.context_node;
128 let saved_list = ctx.context_list.clone();
129 ctx.context_node = doc_node;
130 ctx.set_context_list(vec![doc_node]);
131
132 let result = eval(ctx, expr);
133
134 ctx.context_node = saved_node;
135 ctx.context_list = saved_list;
136
137 result
138 }
139}
140
141fn eval_relative_path(
143 ctx: &mut XPathContext,
144 left: &Expr,
145 right: &Expr,
146) -> Result<XPathValue, String> {
147 let left_val = eval(ctx, left)?;
153 let left_ns = match left_val {
154 XPathValue::NodeSet(ns) => ns,
155 _ => return Err("Invalid type".to_string()),
156 };
157 let left_ns = left_ns.clone();
158
159 let mut result = NodeSet::new();
160
161 for node in left_ns.iter() {
162 let saved_node = ctx.context_node;
164 let saved_list = ctx.context_list.clone();
165 ctx.context_node = node;
166 ctx.set_context_list(left_ns.iter().collect());
167
168 match eval(ctx, right) {
169 Ok(val) => {
170 if let XPathValue::NodeSet(ns) = val {
171 for n in ns.iter() {
172 result.push(n);
173 }
174 }
175 }
176 Err(e) => {
177 ctx.context_node = saved_node;
178 ctx.context_list = saved_list;
179 return Err(e);
180 }
181 }
182
183 ctx.context_node = saved_node;
184 ctx.context_list = saved_list;
185 }
186
187 result.sort();
191 Ok(XPathValue::NodeSet(result))
192}
193
194fn eval_step(ctx: &mut XPathContext, step: &Step) -> Result<XPathValue, String> {
205 let context_node = ctx.context_node;
206 if context_node.is_null() {
207 return Ok(XPathValue::NodeSet(NodeSet::new()));
208 }
209
210 let node_test = resolve_step_prefix(&step.node_test, &ctx.namespaces)?;
216
217 let include_namespaces = step.axis == Axis::Namespace;
221 let mut result = unsafe {
222 axes::traverse_axis(
223 context_node,
224 step.axis,
225 &node_test,
226 true, include_namespaces,
228 )
229 };
230
231 for predicate in &step.predicates {
233 let mut filtered = NodeSet::new();
234
235 for (i, node) in result.iter().enumerate() {
236 let saved_node = ctx.context_node;
238 let saved_pos = ctx.context_position;
239 let saved_prox = ctx.proximity_position;
240 let saved_size = ctx.context_size;
241 let saved_list = ctx.context_list.clone();
242
243 ctx.context_node = node;
244 ctx.context_position = (i + 1) as i32;
245 ctx.proximity_position = (i + 1) as i32;
248 ctx.context_size = result.len() as i32;
249
250 let pred_val = eval(ctx, predicate)?;
252
253 let matches = match pred_val {
258 XPathValue::Number(n) => {
259 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
261 || (n.round() as i32) == (i + 1) as i32
262 }
263 _ => pred_val.as_boolean(),
264 };
265
266 if matches {
267 filtered.push(node);
268 }
269
270 ctx.context_node = saved_node;
271 ctx.context_position = saved_pos;
272 ctx.proximity_position = saved_prox;
273 ctx.context_size = saved_size;
274 ctx.context_list = saved_list;
275 }
276
277 result = filtered;
278 }
279
280 Ok(XPathValue::NodeSet(result))
281}
282
283fn resolve_step_prefix(
291 node_test: &NodeTest,
292 namespaces: &std::collections::HashMap<String, String>,
293) -> Result<NodeTest, String> {
294 match node_test {
295 NodeTest::NameTest(NameTest::QName { prefix, local }) if !prefix.is_empty() => {
296 let uri = namespaces
297 .get(prefix)
298 .ok_or_else(|| format!("Undefined namespace prefix: {}", prefix))?;
299 Ok(NodeTest::NameTest(NameTest::QNameUri {
300 uri: uri.clone(),
301 local: local.clone(),
302 }))
303 }
304 NodeTest::NsWildcard(prefix) if !prefix.is_empty() => {
305 let uri = namespaces
306 .get(prefix)
307 .ok_or_else(|| format!("Undefined namespace prefix: {}", prefix))?;
308 Ok(NodeTest::NsWildcardUri(uri.clone()))
309 }
310 _ => Ok(node_test.clone()),
311 }
312}
313
314fn eval_filter(
316 ctx: &mut XPathContext,
317 expr: &Expr,
318 predicates: &[Expr],
319) -> Result<XPathValue, String> {
320 let mut result = eval(ctx, expr)?;
322
323 let ns = match &mut result {
325 XPathValue::NodeSet(ns) => ns,
326 _ => return Ok(result), };
328
329 for predicate in predicates {
330 let mut filtered = NodeSet::new();
331 let nodes: Vec<_> = ns.iter().collect();
332
333 for (i, node) in nodes.iter().enumerate() {
334 let saved_node = ctx.context_node;
335 let saved_pos = ctx.context_position;
336 let saved_prox = ctx.proximity_position;
337 let saved_size = ctx.context_size;
338 let saved_list = ctx.context_list.clone();
339
340 ctx.context_node = *node;
341 ctx.context_position = (i + 1) as i32;
342 ctx.proximity_position = (i + 1) as i32;
344 ctx.context_size = nodes.len() as i32;
345
346 let pred_val = eval(ctx, predicate)?;
347
348 let matches = match pred_val {
349 XPathValue::Number(n) => {
350 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
351 || (n.round() as i32) == (i + 1) as i32
352 }
353 _ => pred_val.as_boolean(),
354 };
355
356 if matches {
357 filtered.push(*node);
358 }
359
360 ctx.context_node = saved_node;
361 ctx.context_position = saved_pos;
362 ctx.proximity_position = saved_prox;
363 ctx.context_size = saved_size;
364 ctx.context_list = saved_list;
365 }
366
367 *ns = filtered;
368 }
369
370 Ok(result)
371}
372
373fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
379 ctx.resolve_variable(name)
380 .ok_or_else(|| format!("Undefined variable: ${}", name))
381}
382
383fn resolve_function_qualified_name(ctx: &XPathContext, name: &str) -> Option<String> {
388 let (prefix, local) = name.split_once(':')?;
389 let uri = ctx.namespaces.get(prefix)?;
390 Some(format!("{{{}}}{}", uri, local))
391}
392
393fn eval_function_call(
395 ctx: &mut XPathContext,
396 name: &str,
397 args: &[Expr],
398) -> Result<XPathValue, String> {
399 let mut evaluated_args = Vec::new();
401 for arg in args {
402 evaluated_args.push(eval(ctx, arg)?);
403 }
404
405 let namespaced_c_first: Option<crate::xml::xpath::context::BoxedXPathFunction> = {
422 let prefixed = name
423 .split_once(':')
424 .is_some_and(|(p, _)| !p.is_empty() && ctx.namespaces.contains_key(p));
425 if prefixed {
426 ctx.function_lookup.as_ref().and_then(|lk| lk(ctx, name))
427 } else {
428 None
429 }
430 };
431 if let Some(f) = namespaced_c_first {
432 return f(ctx, &evaluated_args);
433 }
434 let mut func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> = None;
435 if let Some(f) = ctx.lookup_function(name) {
436 func_ptr = Some(f as *const _);
437 } else if let Some(qname) = resolve_function_qualified_name(ctx, name) {
438 if let Some(f) = ctx.lookup_function(&qname) {
439 func_ptr = Some(f as *const _);
440 }
441 }
442 match func_ptr {
443 Some(p) => {
444 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
447 f(ctx, &evaluated_args)
448 }
449 None => {
450 if ctx.func_lookup_func.is_some() {
456 if let Some(result) = invoke_c_extension_function(ctx, name, &evaluated_args) {
457 return Ok(result);
458 }
459 }
460 let msg = match name.split_once(':') {
467 Some((prefix, _)) if !ctx.namespaces.contains_key(prefix) => {
468 format!("Undefined namespace prefix: {}", prefix)
469 }
470 _ => format!("Unregistered function: {}", name),
471 };
472 Err(msg)
473 }
474 }
475}
476
477fn invoke_c_extension_function(
495 ctx: &mut XPathContext,
496 name: &str,
497 args: &[XPathValue],
498) -> Option<XPathValue> {
499 let lookup = ctx.func_lookup_func?;
500 let c_ctxt = ctx.c_context;
501 if c_ctxt.is_null() {
502 return None;
503 }
504
505 let (lookup_name, lookup_uri): (Vec<u8>, Option<Vec<u8>>) = match name.split_once(':') {
513 Some((prefix, local)) => match ctx.namespaces.get(prefix) {
514 Some(uri) => (local.as_bytes().to_vec(), Some(uri.as_bytes().to_vec())),
515 None => (name.as_bytes().to_vec(), None),
516 },
517 None => (name.as_bytes().to_vec(), None),
518 };
519 let mut lookup_name_nul: Vec<crate::abi::types::xmlChar> = lookup_name.clone();
520 lookup_name_nul.push(0);
521 let lookup_uri_nul: Option<Vec<crate::abi::types::xmlChar>> = lookup_uri.as_ref().map(|u| {
522 let mut v = u.clone();
523 v.push(0);
524 v
525 });
526 let fp = unsafe {
529 lookup(
530 ctx.func_lookup_data,
531 lookup_name_nul.as_ptr() as *const crate::abi::types::xmlChar,
532 lookup_uri_nul.as_ref().map_or(std::ptr::null(), |v| {
533 v.as_ptr() as *const crate::abi::types::xmlChar
534 }),
535 )
536 };
537 if fp.is_null() {
538 return None;
539 }
540 let func: unsafe extern "C" fn(*mut c_void, c_int) = unsafe { std::mem::transmute(fp) };
541
542 unsafe {
543 let saved_function = (*c_ctxt).function;
544 (*c_ctxt).function = lookup_name_nul.as_ptr() as *const crate::abi::types::xmlChar;
545 let pc = new_parser_context(std::ptr::null(), c_ctxt);
546 if pc.is_null() {
547 (*c_ctxt).function = saved_function;
548 return None;
549 }
550
551 let mut ok = true;
555 for val in args.iter().rev() {
556 let obj = crate::abi::exports_xml2::xpath_to_object_pub(val.clone());
557 if obj.is_null() {
558 ok = false;
559 break;
560 }
561 value_push(pc, obj);
562 }
563
564 let result = if ok {
565 func(pc as *mut c_void, args.len() as c_int);
566 let res = value_pop(pc);
567 if res.is_null() {
568 None
569 } else {
570 let val = crate::abi::exports_xml2::object_to_xpathvalue_pub(res);
571 crate::abi::exports_xml2::xmlXPathFreeObject(res);
572 Some(val)
573 }
574 } else {
575 None
576 };
577
578 free_parser_context(pc);
581 (*c_ctxt).function = saved_function;
582 result
583 }
584}
585
586fn eval_binary_op(
592 ctx: &mut XPathContext,
593 op: &BinaryOp,
594 left: &Expr,
595 right: &Expr,
596) -> Result<XPathValue, String> {
597 match op {
598 BinaryOp::Or => {
599 let left_val = eval(ctx, left)?;
601 if left_val.as_boolean() {
602 return Ok(XPathValue::Boolean(true));
603 }
604 let right_val = eval(ctx, right)?;
605 Ok(XPathValue::Boolean(right_val.as_boolean()))
606 }
607 BinaryOp::And => {
608 let left_val = eval(ctx, left)?;
610 if !left_val.as_boolean() {
611 return Ok(XPathValue::Boolean(false));
612 }
613 let right_val = eval(ctx, right)?;
614 Ok(XPathValue::Boolean(right_val.as_boolean()))
615 }
616 BinaryOp::Eq | BinaryOp::Ne => {
617 let left_val = eval(ctx, left)?;
618 let right_val = eval(ctx, right)?;
619 let eq = compare_equal(ctx, &left_val, &right_val);
620 Ok(match op {
621 BinaryOp::Eq => XPathValue::Boolean(eq),
622 BinaryOp::Ne => XPathValue::Boolean(!eq),
623 _ => unreachable!(),
624 })
625 }
626 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
627 let left_val = eval(ctx, left)?;
628 let right_val = eval(ctx, right)?;
629 let cmp = compare_ordered(ctx, &left_val, &right_val);
630 let result = match op {
631 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
632 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
633 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
634 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
635 _ => unreachable!(),
636 };
637 Ok(XPathValue::Boolean(result))
638 }
639 BinaryOp::Add => {
640 let left_val = eval(ctx, left)?;
641 let right_val = eval(ctx, right)?;
642 Ok(XPathValue::Number(
643 left_val.as_number() + right_val.as_number(),
644 ))
645 }
646 BinaryOp::Sub => {
647 let left_val = eval(ctx, left)?;
648 let right_val = eval(ctx, right)?;
649 Ok(XPathValue::Number(
650 left_val.as_number() - right_val.as_number(),
651 ))
652 }
653 BinaryOp::Mul => {
654 let left_val = eval(ctx, left)?;
655 let right_val = eval(ctx, right)?;
656 Ok(XPathValue::Number(
657 left_val.as_number() * right_val.as_number(),
658 ))
659 }
660 BinaryOp::Div => {
661 let left_val = eval(ctx, left)?;
662 let right_val = eval(ctx, right)?;
663 Ok(XPathValue::Number(
664 left_val.as_number() / right_val.as_number(),
665 ))
666 }
667 BinaryOp::Mod => {
668 let left_val = eval(ctx, left)?;
669 let right_val = eval(ctx, right)?;
670 Ok(XPathValue::Number(
671 left_val.as_number() % right_val.as_number(),
672 ))
673 }
674 BinaryOp::Union => {
675 unreachable!("Union operator should be handled by Expr::Union")
677 }
678 }
679}
680
681fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
683 let left_val = eval(ctx, left)?;
687 let right_val = eval(ctx, right)?;
688 let mut result = match left_val {
689 XPathValue::NodeSet(ns) => ns,
690 _ => return Err("Invalid type".to_string()),
691 };
692 match right_val {
693 XPathValue::NodeSet(ns) => {
694 result.extend(&ns);
695 }
696 _ => return Err("Invalid type".to_string()),
697 }
698 result.sort();
699
700 Ok(XPathValue::NodeSet(result))
701}
702
703fn compare_equal(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
709 match (a, b) {
710 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
712 for node_a in ns_a.iter() {
713 let val_a = node_string_value(node_a);
714 for node_b in ns_b.iter() {
715 let val_b = node_string_value(node_b);
716 if val_a == val_b {
717 return true;
718 }
719 }
720 }
721 false
722 }
723 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
725 for node in ns.iter() {
726 let node_str = node_string_value(node);
727 match other {
728 XPathValue::Boolean(_) => {
729 return (!ns.is_empty()) == other.as_boolean();
731 }
732 XPathValue::Number(_) => {
733 let node_num = string_to_number(&node_str);
734 if (node_num - other.as_number()).abs() < f64::EPSILON {
735 return true;
736 }
737 continue;
738 }
739 XPathValue::String(_) => {
740 if node_str == other.as_string() {
741 return true;
742 }
743 continue;
744 }
745 _ => continue,
746 };
747 }
748 false
749 }
750 _ => match (a, b) {
752 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
753 a.as_boolean() == b.as_boolean()
754 }
755 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
756 let na = a.as_number();
757 let nb = b.as_number();
758 if na.is_nan() || nb.is_nan() {
759 false
760 } else {
761 (na - nb).abs() < f64::EPSILON || na == nb
762 }
763 }
764 _ => a.as_string() == b.as_string(),
765 },
766 }
767}
768
769fn compare_ordered(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
771 match (a, b) {
772 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
774 for node_a in ns_a.iter() {
775 let num_a = string_to_number(&node_string_value(node_a));
776 for node_b in ns_b.iter() {
777 let num_b = string_to_number(&node_string_value(node_b));
778 if num_a < num_b {
779 return std::cmp::Ordering::Less;
780 }
781 if num_a > num_b {
782 return std::cmp::Ordering::Greater;
783 }
784 }
785 }
786 std::cmp::Ordering::Equal
787 }
788 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
790 let other_num = other.as_number();
791 for node in ns.iter() {
792 let node_num = string_to_number(&node_string_value(node));
793 if node_num < other_num {
794 return std::cmp::Ordering::Less;
795 }
796 if node_num > other_num {
797 return std::cmp::Ordering::Greater;
798 }
799 }
800 std::cmp::Ordering::Equal
801 }
802 _ => {
804 let na = a.as_number();
805 let nb = b.as_number();
806 if na.is_nan() || nb.is_nan() {
807 std::cmp::Ordering::Equal } else if na < nb {
809 std::cmp::Ordering::Less
810 } else if na > nb {
811 std::cmp::Ordering::Greater
812 } else {
813 std::cmp::Ordering::Equal
814 }
815 }
816 }
817}
818
819pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
827 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
828 eval(ctx, &expr)
829}
830
831#[cfg(test)]
836mod tests {
837 use super::*;
838 use crate::xml::xpath::context::XPathContext;
839 use crate::xml::xpath::functions;
840
841 fn setup_context() -> XPathContext {
842 let mut ctx = XPathContext::new(std::ptr::null_mut());
843 let funcs = functions::core_functions();
845 for (name, func) in funcs {
846 ctx.register_function(&name, func);
847 }
848 ctx
849 }
850
851 #[test]
852 fn test_eval_string_literal() {
853 let mut ctx = setup_context();
854 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
855 assert_eq!(result.as_string(), "hello");
856 }
857
858 #[test]
859 fn test_eval_number_literal() {
860 let mut ctx = setup_context();
861 let result = eval_xpath(&mut ctx, "42").unwrap();
862 assert_eq!(result.as_number(), 42.0);
863 }
864
865 #[test]
866 fn test_eval_addition() {
867 let mut ctx = setup_context();
868 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
869 assert_eq!(result.as_number(), 3.0);
870 }
871
872 #[test]
873 fn test_eval_subtraction() {
874 let mut ctx = setup_context();
875 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
876 assert_eq!(result.as_number(), 2.0);
877 }
878
879 #[test]
880 fn test_eval_multiplication() {
881 let mut ctx = setup_context();
882 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
883 assert_eq!(result.as_number(), 12.0);
884 }
885
886 #[test]
887 fn test_eval_division() {
888 let mut ctx = setup_context();
889 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
890 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
891 }
892
893 #[test]
894 fn test_eval_modulo() {
895 let mut ctx = setup_context();
896 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
897 assert_eq!(result.as_number(), 1.0);
898 }
899
900 #[test]
901 fn test_eval_equality() {
902 let mut ctx = setup_context();
903 assert!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean());
904 assert!(!eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean());
905 assert!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean());
906 }
907
908 #[test]
909 fn test_eval_comparison() {
910 let mut ctx = setup_context();
911 assert!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean());
912 assert!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean());
913 assert!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean());
914 assert!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean());
915 }
916
917 #[test]
918 fn test_eval_and_or() {
919 let mut ctx = setup_context();
920 assert!(eval_xpath(&mut ctx, "true() and true()")
921 .unwrap()
922 .as_boolean());
923 assert!(!eval_xpath(&mut ctx, "true() and false()")
924 .unwrap()
925 .as_boolean());
926 assert!(eval_xpath(&mut ctx, "true() or false()")
927 .unwrap()
928 .as_boolean());
929 assert!(!eval_xpath(&mut ctx, "false() or false()")
930 .unwrap()
931 .as_boolean());
932 }
933
934 #[test]
935 fn test_eval_not() {
936 let mut ctx = setup_context();
937 assert!(!eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean());
938 assert!(eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean());
939 }
940
941 #[test]
942 fn test_eval_boolean() {
943 let mut ctx = setup_context();
944 assert!(eval_xpath(&mut ctx, "boolean('hello')")
945 .unwrap()
946 .as_boolean());
947 assert!(!eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean());
948 assert!(!eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean());
949 assert!(eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean());
950 }
951
952 #[test]
953 fn test_eval_number() {
954 let mut ctx = setup_context();
955 assert_eq!(
956 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
957 42.0
958 );
959 }
960
961 #[test]
962 fn test_eval_string() {
963 let mut ctx = setup_context();
964 assert_eq!(
965 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
966 "42"
967 );
968 }
969
970 #[test]
971 fn test_eval_concat() {
972 let mut ctx = setup_context();
973 assert_eq!(
974 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
975 .unwrap()
976 .as_string(),
977 "abc"
978 );
979 }
980
981 #[test]
982 fn test_eval_starts_with() {
983 let mut ctx = setup_context();
984 assert!(eval_xpath(&mut ctx, "starts-with('hello', 'he')")
985 .unwrap()
986 .as_boolean());
987 }
988
989 #[test]
990 fn test_eval_contains() {
991 let mut ctx = setup_context();
992 assert!(eval_xpath(&mut ctx, "contains('hello', 'ell')")
993 .unwrap()
994 .as_boolean());
995 }
996
997 #[test]
998 fn test_eval_substring() {
999 let mut ctx = setup_context();
1000 assert_eq!(
1001 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
1002 .unwrap()
1003 .as_string(),
1004 "123"
1005 );
1006 assert_eq!(
1007 eval_xpath(&mut ctx, "substring('12345', 2)")
1008 .unwrap()
1009 .as_string(),
1010 "2345"
1011 );
1012 }
1013
1014 #[test]
1015 fn test_eval_string_length() {
1016 let mut ctx = setup_context();
1017 assert_eq!(
1018 eval_xpath(&mut ctx, "string-length('hello')")
1019 .unwrap()
1020 .as_number(),
1021 5.0
1022 );
1023 }
1024
1025 #[test]
1026 fn test_eval_normalize_space() {
1027 let mut ctx = setup_context();
1028 assert_eq!(
1029 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
1030 .unwrap()
1031 .as_string(),
1032 "hello world"
1033 );
1034 }
1035
1036 #[test]
1037 fn test_eval_floor_ceiling_round() {
1038 let mut ctx = setup_context();
1039 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
1040 assert_eq!(
1041 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
1042 4.0
1043 );
1044 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
1045 }
1046
1047 #[test]
1048 fn test_eval_sum() {
1049 let mut ctx = setup_context();
1051 ctx.document = std::ptr::null_mut();
1052 }
1055
1056 #[test]
1057 fn test_eval_variable_not_found() {
1058 let mut ctx = setup_context();
1059 let result = eval_xpath(&mut ctx, "$undefined_var");
1060 assert!(result.is_err());
1061 }
1062
1063 #[test]
1064 fn test_eval_variable_found() {
1065 let mut ctx = setup_context();
1066 ctx.register_variable("x", XPathValue::Number(42.0));
1067 let result = eval_xpath(&mut ctx, "$x").unwrap();
1068 assert_eq!(result.as_number(), 42.0);
1069 }
1070
1071 #[test]
1072 fn test_eval_union() {
1073 }
1078
1079 #[test]
1080 fn test_eval_operator_precedence() {
1081 let mut ctx = setup_context();
1082 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
1084 assert_eq!(result.as_number(), 7.0);
1085
1086 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
1088 assert_eq!(result.as_number(), 9.0);
1089 }
1090
1091 #[test]
1092 fn test_eval_unary_minus() {
1093 let mut ctx = setup_context();
1094 let result = eval_xpath(&mut ctx, "-5").unwrap();
1095 assert_eq!(result.as_number(), -5.0);
1096
1097 let result = eval_xpath(&mut ctx, "--5").unwrap();
1098 assert_eq!(result.as_number(), 5.0);
1099 }
1100
1101 #[test]
1102 fn test_eval_true_false() {
1103 let mut ctx = setup_context();
1104 assert!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean());
1105 assert!(!eval_xpath(&mut ctx, "false()").unwrap().as_boolean());
1106 }
1107
1108 #[test]
1109 fn test_eval_translate() {
1110 let mut ctx = setup_context();
1111 assert_eq!(
1112 eval_xpath(
1113 &mut ctx,
1114 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
1115 )
1116 .unwrap()
1117 .as_string(),
1118 "HELLO"
1119 );
1120 }
1121
1122 #[test]
1123 fn test_eval_empty_expression() {
1124 let mut ctx = setup_context();
1125 let result = eval_xpath(&mut ctx, "");
1126 assert!(result.is_err());
1127 }
1128}