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)?;
149 let left_ns = left_val.as_node_set().clone();
150
151 let mut result = NodeSet::new();
152
153 for node in left_ns.iter() {
154 let saved_node = ctx.context_node;
156 let saved_list = ctx.context_list.clone();
157 ctx.context_node = node;
158 ctx.set_context_list(left_ns.iter().collect());
159
160 match eval(ctx, right) {
161 Ok(val) => {
162 if let XPathValue::NodeSet(ns) = val {
163 for n in ns.iter() {
164 result.push(n);
165 }
166 }
167 }
168 Err(e) => {
169 ctx.context_node = saved_node;
170 ctx.context_list = saved_list;
171 return Err(e);
172 }
173 }
174
175 ctx.context_node = saved_node;
176 ctx.context_list = saved_list;
177 }
178
179 Ok(XPathValue::NodeSet(result))
180}
181
182fn eval_step(ctx: &mut XPathContext, step: &Step) -> Result<XPathValue, String> {
193 let context_node = ctx.context_node;
194 if context_node.is_null() {
195 return Ok(XPathValue::NodeSet(NodeSet::new()));
196 }
197
198 let node_test = resolve_step_prefix(&step.node_test, &ctx.namespaces)?;
204
205 let include_namespaces = step.axis == Axis::Namespace;
209 let mut result = unsafe {
210 axes::traverse_axis(
211 context_node,
212 step.axis,
213 &node_test,
214 true, include_namespaces,
216 )
217 };
218
219 for predicate in &step.predicates {
221 let mut filtered = NodeSet::new();
222
223 for (i, node) in result.iter().enumerate() {
224 let saved_node = ctx.context_node;
226 let saved_pos = ctx.context_position;
227 let saved_prox = ctx.proximity_position;
228 let saved_size = ctx.context_size;
229 let saved_list = ctx.context_list.clone();
230
231 ctx.context_node = node;
232 ctx.context_position = (i + 1) as i32;
233 ctx.proximity_position = (i + 1) as i32;
236 ctx.context_size = result.len() as i32;
237
238 let pred_val = eval(ctx, predicate)?;
240
241 let matches = match pred_val {
246 XPathValue::Number(n) => {
247 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
249 || (n.round() as i32) == (i + 1) as i32
250 }
251 _ => pred_val.as_boolean(),
252 };
253
254 if matches {
255 filtered.push(node);
256 }
257
258 ctx.context_node = saved_node;
259 ctx.context_position = saved_pos;
260 ctx.proximity_position = saved_prox;
261 ctx.context_size = saved_size;
262 ctx.context_list = saved_list;
263 }
264
265 result = filtered;
266 }
267
268 Ok(XPathValue::NodeSet(result))
269}
270
271fn resolve_step_prefix(
279 node_test: &NodeTest,
280 namespaces: &std::collections::HashMap<String, String>,
281) -> Result<NodeTest, String> {
282 match node_test {
283 NodeTest::NameTest(NameTest::QName { prefix, local }) if !prefix.is_empty() => {
284 let uri = namespaces
285 .get(prefix)
286 .ok_or_else(|| format!("Undefined namespace prefix: {}", prefix))?;
287 Ok(NodeTest::NameTest(NameTest::QNameUri {
288 uri: uri.clone(),
289 local: local.clone(),
290 }))
291 }
292 NodeTest::NsWildcard(prefix) if !prefix.is_empty() => {
293 let uri = namespaces
294 .get(prefix)
295 .ok_or_else(|| format!("Undefined namespace prefix: {}", prefix))?;
296 Ok(NodeTest::NsWildcardUri(uri.clone()))
297 }
298 _ => Ok(node_test.clone()),
299 }
300}
301
302fn eval_filter(
304 ctx: &mut XPathContext,
305 expr: &Expr,
306 predicates: &[Expr],
307) -> Result<XPathValue, String> {
308 let mut result = eval(ctx, expr)?;
310
311 let ns = match &mut result {
313 XPathValue::NodeSet(ns) => ns,
314 _ => return Ok(result), };
316
317 for predicate in predicates {
318 let mut filtered = NodeSet::new();
319 let nodes: Vec<_> = ns.iter().collect();
320
321 for (i, node) in nodes.iter().enumerate() {
322 let saved_node = ctx.context_node;
323 let saved_pos = ctx.context_position;
324 let saved_prox = ctx.proximity_position;
325 let saved_size = ctx.context_size;
326 let saved_list = ctx.context_list.clone();
327
328 ctx.context_node = *node;
329 ctx.context_position = (i + 1) as i32;
330 ctx.proximity_position = (i + 1) as i32;
332 ctx.context_size = nodes.len() as i32;
333
334 let pred_val = eval(ctx, predicate)?;
335
336 let matches = match pred_val {
337 XPathValue::Number(n) => {
338 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
339 || (n.round() as i32) == (i + 1) as i32
340 }
341 _ => pred_val.as_boolean(),
342 };
343
344 if matches {
345 filtered.push(*node);
346 }
347
348 ctx.context_node = saved_node;
349 ctx.context_position = saved_pos;
350 ctx.proximity_position = saved_prox;
351 ctx.context_size = saved_size;
352 ctx.context_list = saved_list;
353 }
354
355 *ns = filtered;
356 }
357
358 Ok(result)
359}
360
361fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
367 ctx.resolve_variable(name)
368 .ok_or_else(|| format!("Undefined variable: ${}", name))
369}
370
371fn resolve_function_qualified_name(ctx: &XPathContext, name: &str) -> Option<String> {
376 let (prefix, local) = name.split_once(':')?;
377 let uri = ctx.namespaces.get(prefix)?;
378 Some(format!("{{{}}}{}", uri, local))
379}
380
381fn eval_function_call(
383 ctx: &mut XPathContext,
384 name: &str,
385 args: &[Expr],
386) -> Result<XPathValue, String> {
387 let mut evaluated_args = Vec::new();
389 for arg in args {
390 evaluated_args.push(eval(ctx, arg)?);
391 }
392
393 let namespaced_c_first: Option<crate::xml::xpath::context::BoxedXPathFunction> = {
410 let prefixed = name
411 .split_once(':')
412 .is_some_and(|(p, _)| !p.is_empty() && ctx.namespaces.contains_key(p));
413 if prefixed {
414 ctx.function_lookup.as_ref().and_then(|lk| lk(ctx, name))
415 } else {
416 None
417 }
418 };
419 if let Some(f) = namespaced_c_first {
420 return f(ctx, &evaluated_args);
421 }
422 let mut func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> = None;
423 if let Some(f) = ctx.lookup_function(name) {
424 func_ptr = Some(f as *const _);
425 } else if let Some(qname) = resolve_function_qualified_name(ctx, name) {
426 if let Some(f) = ctx.lookup_function(&qname) {
427 func_ptr = Some(f as *const _);
428 }
429 }
430 match func_ptr {
431 Some(p) => {
432 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
435 f(ctx, &evaluated_args)
436 }
437 None => {
438 if ctx.func_lookup_func.is_some() {
444 if let Some(result) = invoke_c_extension_function(ctx, name, &evaluated_args) {
445 return Ok(result);
446 }
447 }
448 let msg = match name.split_once(':') {
455 Some((prefix, _)) if !ctx.namespaces.contains_key(prefix) => {
456 format!("Undefined namespace prefix: {}", prefix)
457 }
458 _ => format!("Unregistered function: {}", name),
459 };
460 Err(msg)
461 }
462 }
463}
464
465fn invoke_c_extension_function(
483 ctx: &mut XPathContext,
484 name: &str,
485 args: &[XPathValue],
486) -> Option<XPathValue> {
487 let lookup = ctx.func_lookup_func?;
488 let c_ctxt = ctx.c_context;
489 if c_ctxt.is_null() {
490 return None;
491 }
492
493 let (lookup_name, lookup_uri): (Vec<u8>, Option<Vec<u8>>) = match name.split_once(':') {
501 Some((prefix, local)) => match ctx.namespaces.get(prefix) {
502 Some(uri) => (local.as_bytes().to_vec(), Some(uri.as_bytes().to_vec())),
503 None => (name.as_bytes().to_vec(), None),
504 },
505 None => (name.as_bytes().to_vec(), None),
506 };
507 let mut lookup_name_nul: Vec<crate::abi::types::xmlChar> = lookup_name.clone();
508 lookup_name_nul.push(0);
509 let lookup_uri_nul: Option<Vec<crate::abi::types::xmlChar>> = lookup_uri.as_ref().map(|u| {
510 let mut v = u.clone();
511 v.push(0);
512 v
513 });
514 let fp = unsafe {
517 lookup(
518 ctx.func_lookup_data,
519 lookup_name_nul.as_ptr() as *const crate::abi::types::xmlChar,
520 lookup_uri_nul.as_ref().map_or(std::ptr::null(), |v| {
521 v.as_ptr() as *const crate::abi::types::xmlChar
522 }),
523 )
524 };
525 if fp.is_null() {
526 return None;
527 }
528 let func: unsafe extern "C" fn(*mut c_void, c_int) = unsafe { std::mem::transmute(fp) };
529
530 unsafe {
531 let saved_function = (*c_ctxt).function;
532 (*c_ctxt).function = lookup_name_nul.as_ptr() as *const crate::abi::types::xmlChar;
533 let pc = new_parser_context(std::ptr::null(), c_ctxt);
534 if pc.is_null() {
535 (*c_ctxt).function = saved_function;
536 return None;
537 }
538
539 let mut ok = true;
543 for val in args.iter().rev() {
544 let obj = crate::abi::exports_xml2::xpath_to_object_pub(val.clone());
545 if obj.is_null() {
546 ok = false;
547 break;
548 }
549 value_push(pc, obj);
550 }
551
552 let result = if ok {
553 func(pc as *mut c_void, args.len() as c_int);
554 let res = value_pop(pc);
555 if res.is_null() {
556 None
557 } else {
558 let val = crate::abi::exports_xml2::object_to_xpathvalue_pub(res);
559 crate::abi::exports_xml2::xmlXPathFreeObject(res);
560 Some(val)
561 }
562 } else {
563 None
564 };
565
566 free_parser_context(pc);
569 (*c_ctxt).function = saved_function;
570 result
571 }
572}
573
574fn eval_binary_op(
580 ctx: &mut XPathContext,
581 op: &BinaryOp,
582 left: &Expr,
583 right: &Expr,
584) -> Result<XPathValue, String> {
585 match op {
586 BinaryOp::Or => {
587 let left_val = eval(ctx, left)?;
589 if left_val.as_boolean() {
590 return Ok(XPathValue::Boolean(true));
591 }
592 let right_val = eval(ctx, right)?;
593 Ok(XPathValue::Boolean(right_val.as_boolean()))
594 }
595 BinaryOp::And => {
596 let left_val = eval(ctx, left)?;
598 if !left_val.as_boolean() {
599 return Ok(XPathValue::Boolean(false));
600 }
601 let right_val = eval(ctx, right)?;
602 Ok(XPathValue::Boolean(right_val.as_boolean()))
603 }
604 BinaryOp::Eq | BinaryOp::Ne => {
605 let left_val = eval(ctx, left)?;
606 let right_val = eval(ctx, right)?;
607 let eq = compare_equal(ctx, &left_val, &right_val);
608 Ok(match op {
609 BinaryOp::Eq => XPathValue::Boolean(eq),
610 BinaryOp::Ne => XPathValue::Boolean(!eq),
611 _ => unreachable!(),
612 })
613 }
614 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
615 let left_val = eval(ctx, left)?;
616 let right_val = eval(ctx, right)?;
617 let cmp = compare_ordered(ctx, &left_val, &right_val);
618 let result = match op {
619 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
620 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
621 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
622 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
623 _ => unreachable!(),
624 };
625 Ok(XPathValue::Boolean(result))
626 }
627 BinaryOp::Add => {
628 let left_val = eval(ctx, left)?;
629 let right_val = eval(ctx, right)?;
630 Ok(XPathValue::Number(
631 left_val.as_number() + right_val.as_number(),
632 ))
633 }
634 BinaryOp::Sub => {
635 let left_val = eval(ctx, left)?;
636 let right_val = eval(ctx, right)?;
637 Ok(XPathValue::Number(
638 left_val.as_number() - right_val.as_number(),
639 ))
640 }
641 BinaryOp::Mul => {
642 let left_val = eval(ctx, left)?;
643 let right_val = eval(ctx, right)?;
644 Ok(XPathValue::Number(
645 left_val.as_number() * right_val.as_number(),
646 ))
647 }
648 BinaryOp::Div => {
649 let left_val = eval(ctx, left)?;
650 let right_val = eval(ctx, right)?;
651 Ok(XPathValue::Number(
652 left_val.as_number() / right_val.as_number(),
653 ))
654 }
655 BinaryOp::Mod => {
656 let left_val = eval(ctx, left)?;
657 let right_val = eval(ctx, right)?;
658 Ok(XPathValue::Number(
659 left_val.as_number() % right_val.as_number(),
660 ))
661 }
662 BinaryOp::Union => {
663 unreachable!("Union operator should be handled by Expr::Union")
665 }
666 }
667}
668
669fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
671 let left_val = eval(ctx, left)?;
672 let right_val = eval(ctx, right)?;
673
674 let mut result = left_val.as_node_set().clone();
675 result.extend(right_val.as_node_set());
676 result.sort();
677
678 Ok(XPathValue::NodeSet(result))
679}
680
681fn compare_equal(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
687 match (a, b) {
688 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
690 for node_a in ns_a.iter() {
691 let val_a = node_string_value(node_a);
692 for node_b in ns_b.iter() {
693 let val_b = node_string_value(node_b);
694 if val_a == val_b {
695 return true;
696 }
697 }
698 }
699 false
700 }
701 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
703 for node in ns.iter() {
704 let node_str = node_string_value(node);
705 match other {
706 XPathValue::Boolean(_) => {
707 return (!ns.is_empty()) == other.as_boolean();
709 }
710 XPathValue::Number(_) => {
711 let node_num = string_to_number(&node_str);
712 if (node_num - other.as_number()).abs() < f64::EPSILON {
713 return true;
714 }
715 continue;
716 }
717 XPathValue::String(_) => {
718 if node_str == other.as_string() {
719 return true;
720 }
721 continue;
722 }
723 _ => continue,
724 };
725 }
726 false
727 }
728 _ => match (a, b) {
730 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
731 a.as_boolean() == b.as_boolean()
732 }
733 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
734 let na = a.as_number();
735 let nb = b.as_number();
736 if na.is_nan() || nb.is_nan() {
737 false
738 } else {
739 (na - nb).abs() < f64::EPSILON || na == nb
740 }
741 }
742 _ => a.as_string() == b.as_string(),
743 },
744 }
745}
746
747fn compare_ordered(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
749 match (a, b) {
750 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
752 for node_a in ns_a.iter() {
753 let num_a = string_to_number(&node_string_value(node_a));
754 for node_b in ns_b.iter() {
755 let num_b = string_to_number(&node_string_value(node_b));
756 if num_a < num_b {
757 return std::cmp::Ordering::Less;
758 }
759 if num_a > num_b {
760 return std::cmp::Ordering::Greater;
761 }
762 }
763 }
764 std::cmp::Ordering::Equal
765 }
766 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
768 let other_num = other.as_number();
769 for node in ns.iter() {
770 let node_num = string_to_number(&node_string_value(node));
771 if node_num < other_num {
772 return std::cmp::Ordering::Less;
773 }
774 if node_num > other_num {
775 return std::cmp::Ordering::Greater;
776 }
777 }
778 std::cmp::Ordering::Equal
779 }
780 _ => {
782 let na = a.as_number();
783 let nb = b.as_number();
784 if na.is_nan() || nb.is_nan() {
785 std::cmp::Ordering::Equal } else if na < nb {
787 std::cmp::Ordering::Less
788 } else if na > nb {
789 std::cmp::Ordering::Greater
790 } else {
791 std::cmp::Ordering::Equal
792 }
793 }
794 }
795}
796
797pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
805 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
806 eval(ctx, &expr)
807}
808
809#[cfg(test)]
814mod tests {
815 use super::*;
816 use crate::xml::xpath::context::XPathContext;
817 use crate::xml::xpath::functions;
818
819 fn setup_context() -> XPathContext {
820 let mut ctx = XPathContext::new(std::ptr::null_mut());
821 let funcs = functions::core_functions();
823 for (name, func) in funcs {
824 ctx.register_function(&name, func);
825 }
826 ctx
827 }
828
829 #[test]
830 fn test_eval_string_literal() {
831 let mut ctx = setup_context();
832 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
833 assert_eq!(result.as_string(), "hello");
834 }
835
836 #[test]
837 fn test_eval_number_literal() {
838 let mut ctx = setup_context();
839 let result = eval_xpath(&mut ctx, "42").unwrap();
840 assert_eq!(result.as_number(), 42.0);
841 }
842
843 #[test]
844 fn test_eval_addition() {
845 let mut ctx = setup_context();
846 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
847 assert_eq!(result.as_number(), 3.0);
848 }
849
850 #[test]
851 fn test_eval_subtraction() {
852 let mut ctx = setup_context();
853 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
854 assert_eq!(result.as_number(), 2.0);
855 }
856
857 #[test]
858 fn test_eval_multiplication() {
859 let mut ctx = setup_context();
860 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
861 assert_eq!(result.as_number(), 12.0);
862 }
863
864 #[test]
865 fn test_eval_division() {
866 let mut ctx = setup_context();
867 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
868 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
869 }
870
871 #[test]
872 fn test_eval_modulo() {
873 let mut ctx = setup_context();
874 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
875 assert_eq!(result.as_number(), 1.0);
876 }
877
878 #[test]
879 fn test_eval_equality() {
880 let mut ctx = setup_context();
881 assert!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean());
882 assert!(!eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean());
883 assert!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean());
884 }
885
886 #[test]
887 fn test_eval_comparison() {
888 let mut ctx = setup_context();
889 assert!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean());
890 assert!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean());
891 assert!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean());
892 assert!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean());
893 }
894
895 #[test]
896 fn test_eval_and_or() {
897 let mut ctx = setup_context();
898 assert!(eval_xpath(&mut ctx, "true() and true()")
899 .unwrap()
900 .as_boolean());
901 assert!(!eval_xpath(&mut ctx, "true() and false()")
902 .unwrap()
903 .as_boolean());
904 assert!(eval_xpath(&mut ctx, "true() or false()")
905 .unwrap()
906 .as_boolean());
907 assert!(!eval_xpath(&mut ctx, "false() or false()")
908 .unwrap()
909 .as_boolean());
910 }
911
912 #[test]
913 fn test_eval_not() {
914 let mut ctx = setup_context();
915 assert!(!eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean());
916 assert!(eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean());
917 }
918
919 #[test]
920 fn test_eval_boolean() {
921 let mut ctx = setup_context();
922 assert!(eval_xpath(&mut ctx, "boolean('hello')")
923 .unwrap()
924 .as_boolean());
925 assert!(!eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean());
926 assert!(!eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean());
927 assert!(eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean());
928 }
929
930 #[test]
931 fn test_eval_number() {
932 let mut ctx = setup_context();
933 assert_eq!(
934 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
935 42.0
936 );
937 }
938
939 #[test]
940 fn test_eval_string() {
941 let mut ctx = setup_context();
942 assert_eq!(
943 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
944 "42"
945 );
946 }
947
948 #[test]
949 fn test_eval_concat() {
950 let mut ctx = setup_context();
951 assert_eq!(
952 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
953 .unwrap()
954 .as_string(),
955 "abc"
956 );
957 }
958
959 #[test]
960 fn test_eval_starts_with() {
961 let mut ctx = setup_context();
962 assert!(eval_xpath(&mut ctx, "starts-with('hello', 'he')")
963 .unwrap()
964 .as_boolean());
965 }
966
967 #[test]
968 fn test_eval_contains() {
969 let mut ctx = setup_context();
970 assert!(eval_xpath(&mut ctx, "contains('hello', 'ell')")
971 .unwrap()
972 .as_boolean());
973 }
974
975 #[test]
976 fn test_eval_substring() {
977 let mut ctx = setup_context();
978 assert_eq!(
979 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
980 .unwrap()
981 .as_string(),
982 "123"
983 );
984 assert_eq!(
985 eval_xpath(&mut ctx, "substring('12345', 2)")
986 .unwrap()
987 .as_string(),
988 "2345"
989 );
990 }
991
992 #[test]
993 fn test_eval_string_length() {
994 let mut ctx = setup_context();
995 assert_eq!(
996 eval_xpath(&mut ctx, "string-length('hello')")
997 .unwrap()
998 .as_number(),
999 5.0
1000 );
1001 }
1002
1003 #[test]
1004 fn test_eval_normalize_space() {
1005 let mut ctx = setup_context();
1006 assert_eq!(
1007 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
1008 .unwrap()
1009 .as_string(),
1010 "hello world"
1011 );
1012 }
1013
1014 #[test]
1015 fn test_eval_floor_ceiling_round() {
1016 let mut ctx = setup_context();
1017 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
1018 assert_eq!(
1019 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
1020 4.0
1021 );
1022 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
1023 }
1024
1025 #[test]
1026 fn test_eval_sum() {
1027 let mut ctx = setup_context();
1029 ctx.document = std::ptr::null_mut();
1030 }
1033
1034 #[test]
1035 fn test_eval_variable_not_found() {
1036 let mut ctx = setup_context();
1037 let result = eval_xpath(&mut ctx, "$undefined_var");
1038 assert!(result.is_err());
1039 }
1040
1041 #[test]
1042 fn test_eval_variable_found() {
1043 let mut ctx = setup_context();
1044 ctx.register_variable("x", XPathValue::Number(42.0));
1045 let result = eval_xpath(&mut ctx, "$x").unwrap();
1046 assert_eq!(result.as_number(), 42.0);
1047 }
1048
1049 #[test]
1050 fn test_eval_union() {
1051 }
1056
1057 #[test]
1058 fn test_eval_operator_precedence() {
1059 let mut ctx = setup_context();
1060 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
1062 assert_eq!(result.as_number(), 7.0);
1063
1064 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
1066 assert_eq!(result.as_number(), 9.0);
1067 }
1068
1069 #[test]
1070 fn test_eval_unary_minus() {
1071 let mut ctx = setup_context();
1072 let result = eval_xpath(&mut ctx, "-5").unwrap();
1073 assert_eq!(result.as_number(), -5.0);
1074
1075 let result = eval_xpath(&mut ctx, "--5").unwrap();
1076 assert_eq!(result.as_number(), 5.0);
1077 }
1078
1079 #[test]
1080 fn test_eval_true_false() {
1081 let mut ctx = setup_context();
1082 assert!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean());
1083 assert!(!eval_xpath(&mut ctx, "false()").unwrap().as_boolean());
1084 }
1085
1086 #[test]
1087 fn test_eval_translate() {
1088 let mut ctx = setup_context();
1089 assert_eq!(
1090 eval_xpath(
1091 &mut ctx,
1092 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
1093 )
1094 .unwrap()
1095 .as_string(),
1096 "HELLO"
1097 );
1098 }
1099
1100 #[test]
1101 fn test_eval_empty_expression() {
1102 let mut ctx = setup_context();
1103 let result = eval_xpath(&mut ctx, "");
1104 assert!(result.is_err());
1105 }
1106}