1use crate::abi::structs::_xmlNode;
16use crate::xml::xpath::ast::{Axis, BinaryOp, Expr, NameTest, NodeTest, Step};
17use crate::xml::xpath::axes;
18use crate::xml::xpath::context::XPathContext;
19use crate::xml::xpath::functions;
20use crate::xml::xpath::types::{node_string_value, string_to_number, NodeSet, XPathValue};
21use std::collections::HashMap;
22
23pub fn eval(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
29 ctx.push_recursion()?;
30
31 let result = match expr {
32 Expr::Step(step) => eval_step(ctx, step),
33 Expr::AbsolutePath(expr) => eval_absolute_path(ctx, expr),
34 Expr::RelativePath(left, right) => eval_relative_path(ctx, left, right),
35 Expr::Filter(expr, predicates) => eval_filter(ctx, expr, predicates),
36 Expr::Variable(name) => eval_variable(ctx, name),
37 Expr::StringLiteral(s) => Ok(XPathValue::String(s.clone())),
38 Expr::NumberLiteral(n) => Ok(XPathValue::Number(*n)),
39 Expr::BooleanLiteral(b) => Ok(XPathValue::Boolean(*b)),
40 Expr::FunctionCall { name, args } => eval_function_call(ctx, name, args),
41 Expr::BinaryOp { op, left, right } => eval_binary_op(ctx, op, left, right),
42 Expr::UnaryMinus(expr) => {
43 let val = eval(ctx, expr)?;
44 Ok(XPathValue::Number(-val.as_number()))
45 }
46 Expr::Union(left, right) => eval_union(ctx, left, right),
47 };
48
49 ctx.pop_recursion();
50 result
51}
52
53fn eval_absolute_path(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
59 let doc = ctx.document;
61 if doc.is_null() {
62 return Ok(XPathValue::NodeSet(NodeSet::new()));
63 }
64
65 unsafe {
66 let doc_node = doc as *mut _xmlNode;
71
72 let saved_node = ctx.context_node;
74 let saved_list = ctx.context_list.clone();
75 ctx.context_node = doc_node;
76 ctx.set_context_list(vec![doc_node]);
77
78 let result = eval(ctx, expr);
79
80 ctx.context_node = saved_node;
81 ctx.context_list = saved_list;
82
83 result
84 }
85}
86
87fn eval_relative_path(
89 ctx: &mut XPathContext,
90 left: &Expr,
91 right: &Expr,
92) -> Result<XPathValue, String> {
93 let left_val = eval(ctx, left)?;
95 let left_ns = left_val.as_node_set().clone();
96
97 let mut result = NodeSet::new();
98
99 for node in left_ns.iter() {
100 let saved_node = ctx.context_node;
102 let saved_list = ctx.context_list.clone();
103 ctx.context_node = node;
104 ctx.set_context_list(left_ns.iter().collect());
105
106 match eval(ctx, right) {
107 Ok(val) => {
108 if let XPathValue::NodeSet(ns) = val {
109 for n in ns.iter() {
110 result.push(n);
111 }
112 }
113 }
114 Err(e) => {
115 ctx.context_node = saved_node;
116 ctx.context_list = saved_list;
117 return Err(e);
118 }
119 }
120
121 ctx.context_node = saved_node;
122 ctx.context_list = saved_list;
123 }
124
125 Ok(XPathValue::NodeSet(result))
126}
127
128fn eval_step(ctx: &mut XPathContext, step: &Step) -> Result<XPathValue, String> {
130 let context_node = ctx.context_node;
131 if context_node.is_null() {
132 return Ok(XPathValue::NodeSet(NodeSet::new()));
133 }
134
135 let mut result = unsafe {
137 axes::traverse_axis(
138 context_node,
139 step.axis,
140 &step.node_test,
141 true, false, )
144 };
145
146 for predicate in &step.predicates {
148 let mut filtered = NodeSet::new();
149
150 for (i, node) in result.iter().enumerate() {
151 let saved_node = ctx.context_node;
153 let saved_pos = ctx.context_position;
154 let saved_prox = ctx.proximity_position;
155 let saved_size = ctx.context_size;
156 let saved_list = ctx.context_list.clone();
157
158 ctx.context_node = node;
159 ctx.context_position = (i + 1) as i32;
160 ctx.proximity_position = (i + 1) as i32;
163 ctx.context_size = result.len() as i32;
164
165 let pred_val = eval(ctx, predicate)?;
167
168 let matches = match pred_val {
173 XPathValue::Number(n) => {
174 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
176 || (n.round() as i32) == (i + 1) as i32
177 }
178 _ => pred_val.as_boolean(),
179 };
180
181 if matches {
182 filtered.push(node);
183 }
184
185 ctx.context_node = saved_node;
186 ctx.context_position = saved_pos;
187 ctx.proximity_position = saved_prox;
188 ctx.context_size = saved_size;
189 ctx.context_list = saved_list;
190 }
191
192 result = filtered;
193 }
194
195 Ok(XPathValue::NodeSet(result))
196}
197
198fn eval_filter(
200 ctx: &mut XPathContext,
201 expr: &Expr,
202 predicates: &[Expr],
203) -> Result<XPathValue, String> {
204 let mut result = eval(ctx, expr)?;
206
207 let ns = match &mut result {
209 XPathValue::NodeSet(ns) => ns,
210 _ => return Ok(result), };
212
213 for predicate in predicates {
214 let mut filtered = NodeSet::new();
215 let nodes: Vec<_> = ns.iter().collect();
216
217 for (i, node) in nodes.iter().enumerate() {
218 let saved_node = ctx.context_node;
219 let saved_pos = ctx.context_position;
220 let saved_prox = ctx.proximity_position;
221 let saved_size = ctx.context_size;
222 let saved_list = ctx.context_list.clone();
223
224 ctx.context_node = *node;
225 ctx.context_position = (i + 1) as i32;
226 ctx.proximity_position = (i + 1) as i32;
228 ctx.context_size = nodes.len() as i32;
229
230 let pred_val = eval(ctx, predicate)?;
231
232 let matches = match pred_val {
233 XPathValue::Number(n) => {
234 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
235 || (n.round() as i32) == (i + 1) as i32
236 }
237 _ => pred_val.as_boolean(),
238 };
239
240 if matches {
241 filtered.push(*node);
242 }
243
244 ctx.context_node = saved_node;
245 ctx.context_position = saved_pos;
246 ctx.proximity_position = saved_prox;
247 ctx.context_size = saved_size;
248 ctx.context_list = saved_list;
249 }
250
251 *ns = filtered;
252 }
253
254 Ok(result)
255}
256
257fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
263 ctx.resolve_variable(name)
264 .ok_or_else(|| format!("Undefined variable: ${}", name))
265}
266
267fn eval_function_call(
269 ctx: &mut XPathContext,
270 name: &str,
271 args: &[Expr],
272) -> Result<XPathValue, String> {
273 let mut evaluated_args = Vec::new();
275 for arg in args {
276 evaluated_args.push(eval(ctx, arg)?);
277 }
278
279 let func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> =
283 ctx.lookup_function(name).map(|f| f as *const _);
284 match func_ptr {
285 Some(p) => {
286 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
289 f(ctx, &evaluated_args)
290 }
291 None => {
292 let msg = match name.split_once(':') {
299 Some((prefix, _)) if !ctx.namespaces.contains_key(prefix) => {
300 format!("Undefined namespace prefix: {}", prefix)
301 }
302 _ => format!("Unregistered function: {}", name),
303 };
304 Err(msg)
305 }
306 }
307}
308
309fn eval_binary_op(
315 ctx: &mut XPathContext,
316 op: &BinaryOp,
317 left: &Expr,
318 right: &Expr,
319) -> Result<XPathValue, String> {
320 match op {
321 BinaryOp::Or => {
322 let left_val = eval(ctx, left)?;
324 if left_val.as_boolean() {
325 return Ok(XPathValue::Boolean(true));
326 }
327 let right_val = eval(ctx, right)?;
328 Ok(XPathValue::Boolean(right_val.as_boolean()))
329 }
330 BinaryOp::And => {
331 let left_val = eval(ctx, left)?;
333 if !left_val.as_boolean() {
334 return Ok(XPathValue::Boolean(false));
335 }
336 let right_val = eval(ctx, right)?;
337 Ok(XPathValue::Boolean(right_val.as_boolean()))
338 }
339 BinaryOp::Eq | BinaryOp::Ne => {
340 let left_val = eval(ctx, left)?;
341 let right_val = eval(ctx, right)?;
342 let eq = compare_equal(ctx, &left_val, &right_val);
343 Ok(match op {
344 BinaryOp::Eq => XPathValue::Boolean(eq),
345 BinaryOp::Ne => XPathValue::Boolean(!eq),
346 _ => unreachable!(),
347 })
348 }
349 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
350 let left_val = eval(ctx, left)?;
351 let right_val = eval(ctx, right)?;
352 let cmp = compare_ordered(ctx, &left_val, &right_val);
353 let result = match op {
354 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
355 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
356 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
357 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
358 _ => unreachable!(),
359 };
360 Ok(XPathValue::Boolean(result))
361 }
362 BinaryOp::Add => {
363 let left_val = eval(ctx, left)?;
364 let right_val = eval(ctx, right)?;
365 Ok(XPathValue::Number(
366 left_val.as_number() + right_val.as_number(),
367 ))
368 }
369 BinaryOp::Sub => {
370 let left_val = eval(ctx, left)?;
371 let right_val = eval(ctx, right)?;
372 Ok(XPathValue::Number(
373 left_val.as_number() - right_val.as_number(),
374 ))
375 }
376 BinaryOp::Mul => {
377 let left_val = eval(ctx, left)?;
378 let right_val = eval(ctx, right)?;
379 Ok(XPathValue::Number(
380 left_val.as_number() * right_val.as_number(),
381 ))
382 }
383 BinaryOp::Div => {
384 let left_val = eval(ctx, left)?;
385 let right_val = eval(ctx, right)?;
386 Ok(XPathValue::Number(
387 left_val.as_number() / right_val.as_number(),
388 ))
389 }
390 BinaryOp::Mod => {
391 let left_val = eval(ctx, left)?;
392 let right_val = eval(ctx, right)?;
393 Ok(XPathValue::Number(
394 left_val.as_number() % right_val.as_number(),
395 ))
396 }
397 BinaryOp::Union => {
398 unreachable!("Union operator should be handled by Expr::Union")
400 }
401 }
402}
403
404fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
406 let left_val = eval(ctx, left)?;
407 let right_val = eval(ctx, right)?;
408
409 let mut result = left_val.as_node_set().clone();
410 result.extend(right_val.as_node_set());
411 result.sort();
412
413 Ok(XPathValue::NodeSet(result))
414}
415
416fn compare_equal(ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
422 match (a, b) {
423 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
425 for node_a in ns_a.iter() {
426 let val_a = node_string_value(node_a);
427 for node_b in ns_b.iter() {
428 let val_b = node_string_value(node_b);
429 if val_a == val_b {
430 return true;
431 }
432 }
433 }
434 false
435 }
436 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
438 for node in ns.iter() {
439 let node_str = node_string_value(node);
440 let other_val = match other {
441 XPathValue::Boolean(_) => {
442 return (ns.len() > 0) == other.as_boolean();
444 }
445 XPathValue::Number(_) => {
446 let node_num = string_to_number(&node_str);
447 if (node_num - other.as_number()).abs() < f64::EPSILON {
448 return true;
449 }
450 continue;
451 }
452 XPathValue::String(_) => {
453 if node_str == other.as_string() {
454 return true;
455 }
456 continue;
457 }
458 _ => continue,
459 };
460 }
461 false
462 }
463 _ => match (a, b) {
465 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
466 a.as_boolean() == b.as_boolean()
467 }
468 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
469 let na = a.as_number();
470 let nb = b.as_number();
471 if na.is_nan() || nb.is_nan() {
472 false
473 } else {
474 (na - nb).abs() < f64::EPSILON || na == nb
475 }
476 }
477 _ => a.as_string() == b.as_string(),
478 },
479 }
480}
481
482fn compare_ordered(ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
484 match (a, b) {
485 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
487 for node_a in ns_a.iter() {
488 let num_a = string_to_number(&node_string_value(node_a));
489 for node_b in ns_b.iter() {
490 let num_b = string_to_number(&node_string_value(node_b));
491 if num_a < num_b {
492 return std::cmp::Ordering::Less;
493 }
494 if num_a > num_b {
495 return std::cmp::Ordering::Greater;
496 }
497 }
498 }
499 std::cmp::Ordering::Equal
500 }
501 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
503 let other_num = other.as_number();
504 for node in ns.iter() {
505 let node_num = string_to_number(&node_string_value(node));
506 if node_num < other_num {
507 return std::cmp::Ordering::Less;
508 }
509 if node_num > other_num {
510 return std::cmp::Ordering::Greater;
511 }
512 }
513 std::cmp::Ordering::Equal
514 }
515 _ => {
517 let na = a.as_number();
518 let nb = b.as_number();
519 if na.is_nan() || nb.is_nan() {
520 std::cmp::Ordering::Equal } else if na < nb {
522 std::cmp::Ordering::Less
523 } else if na > nb {
524 std::cmp::Ordering::Greater
525 } else {
526 std::cmp::Ordering::Equal
527 }
528 }
529 }
530}
531
532pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
540 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
541 eval(ctx, &expr)
542}
543
544#[cfg(test)]
549mod tests {
550 use super::*;
551 use crate::xml::xpath::context::XPathContext;
552
553 fn setup_context() -> XPathContext {
554 let mut ctx = XPathContext::new(std::ptr::null_mut());
555 let funcs = functions::core_functions();
557 for (name, func) in funcs {
558 ctx.register_function(&name, func);
559 }
560 ctx
561 }
562
563 #[test]
564 fn test_eval_string_literal() {
565 let mut ctx = setup_context();
566 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
567 assert_eq!(result.as_string(), "hello");
568 }
569
570 #[test]
571 fn test_eval_number_literal() {
572 let mut ctx = setup_context();
573 let result = eval_xpath(&mut ctx, "42").unwrap();
574 assert_eq!(result.as_number(), 42.0);
575 }
576
577 #[test]
578 fn test_eval_addition() {
579 let mut ctx = setup_context();
580 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
581 assert_eq!(result.as_number(), 3.0);
582 }
583
584 #[test]
585 fn test_eval_subtraction() {
586 let mut ctx = setup_context();
587 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
588 assert_eq!(result.as_number(), 2.0);
589 }
590
591 #[test]
592 fn test_eval_multiplication() {
593 let mut ctx = setup_context();
594 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
595 assert_eq!(result.as_number(), 12.0);
596 }
597
598 #[test]
599 fn test_eval_division() {
600 let mut ctx = setup_context();
601 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
602 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
603 }
604
605 #[test]
606 fn test_eval_modulo() {
607 let mut ctx = setup_context();
608 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
609 assert_eq!(result.as_number(), 1.0);
610 }
611
612 #[test]
613 fn test_eval_equality() {
614 let mut ctx = setup_context();
615 assert_eq!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean(), true);
616 assert_eq!(eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean(), false);
617 assert_eq!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean(), true);
618 }
619
620 #[test]
621 fn test_eval_comparison() {
622 let mut ctx = setup_context();
623 assert_eq!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean(), true);
624 assert_eq!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean(), true);
625 assert_eq!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean(), true);
626 assert_eq!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean(), true);
627 }
628
629 #[test]
630 fn test_eval_and_or() {
631 let mut ctx = setup_context();
632 assert_eq!(
633 eval_xpath(&mut ctx, "true() and true()")
634 .unwrap()
635 .as_boolean(),
636 true
637 );
638 assert_eq!(
639 eval_xpath(&mut ctx, "true() and false()")
640 .unwrap()
641 .as_boolean(),
642 false
643 );
644 assert_eq!(
645 eval_xpath(&mut ctx, "true() or false()")
646 .unwrap()
647 .as_boolean(),
648 true
649 );
650 assert_eq!(
651 eval_xpath(&mut ctx, "false() or false()")
652 .unwrap()
653 .as_boolean(),
654 false
655 );
656 }
657
658 #[test]
659 fn test_eval_not() {
660 let mut ctx = setup_context();
661 assert_eq!(
662 eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean(),
663 false
664 );
665 assert_eq!(
666 eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean(),
667 true
668 );
669 }
670
671 #[test]
672 fn test_eval_boolean() {
673 let mut ctx = setup_context();
674 assert_eq!(
675 eval_xpath(&mut ctx, "boolean('hello')")
676 .unwrap()
677 .as_boolean(),
678 true
679 );
680 assert_eq!(
681 eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean(),
682 false
683 );
684 assert_eq!(
685 eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean(),
686 false
687 );
688 assert_eq!(
689 eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean(),
690 true
691 );
692 }
693
694 #[test]
695 fn test_eval_number() {
696 let mut ctx = setup_context();
697 assert_eq!(
698 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
699 42.0
700 );
701 }
702
703 #[test]
704 fn test_eval_string() {
705 let mut ctx = setup_context();
706 assert_eq!(
707 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
708 "42"
709 );
710 }
711
712 #[test]
713 fn test_eval_concat() {
714 let mut ctx = setup_context();
715 assert_eq!(
716 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
717 .unwrap()
718 .as_string(),
719 "abc"
720 );
721 }
722
723 #[test]
724 fn test_eval_starts_with() {
725 let mut ctx = setup_context();
726 assert_eq!(
727 eval_xpath(&mut ctx, "starts-with('hello', 'he')")
728 .unwrap()
729 .as_boolean(),
730 true
731 );
732 }
733
734 #[test]
735 fn test_eval_contains() {
736 let mut ctx = setup_context();
737 assert_eq!(
738 eval_xpath(&mut ctx, "contains('hello', 'ell')")
739 .unwrap()
740 .as_boolean(),
741 true
742 );
743 }
744
745 #[test]
746 fn test_eval_substring() {
747 let mut ctx = setup_context();
748 assert_eq!(
749 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
750 .unwrap()
751 .as_string(),
752 "123"
753 );
754 assert_eq!(
755 eval_xpath(&mut ctx, "substring('12345', 2)")
756 .unwrap()
757 .as_string(),
758 "2345"
759 );
760 }
761
762 #[test]
763 fn test_eval_string_length() {
764 let mut ctx = setup_context();
765 assert_eq!(
766 eval_xpath(&mut ctx, "string-length('hello')")
767 .unwrap()
768 .as_number(),
769 5.0
770 );
771 }
772
773 #[test]
774 fn test_eval_normalize_space() {
775 let mut ctx = setup_context();
776 assert_eq!(
777 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
778 .unwrap()
779 .as_string(),
780 "hello world"
781 );
782 }
783
784 #[test]
785 fn test_eval_floor_ceiling_round() {
786 let mut ctx = setup_context();
787 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
788 assert_eq!(
789 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
790 4.0
791 );
792 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
793 }
794
795 #[test]
796 fn test_eval_sum() {
797 let mut ctx = setup_context();
799 ctx.document = std::ptr::null_mut();
800 }
803
804 #[test]
805 fn test_eval_variable_not_found() {
806 let mut ctx = setup_context();
807 let result = eval_xpath(&mut ctx, "$undefined_var");
808 assert!(result.is_err());
809 }
810
811 #[test]
812 fn test_eval_variable_found() {
813 let mut ctx = setup_context();
814 ctx.register_variable("x", XPathValue::Number(42.0));
815 let result = eval_xpath(&mut ctx, "$x").unwrap();
816 assert_eq!(result.as_number(), 42.0);
817 }
818
819 #[test]
820 fn test_eval_union() {
821 }
826
827 #[test]
828 fn test_eval_operator_precedence() {
829 let mut ctx = setup_context();
830 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
832 assert_eq!(result.as_number(), 7.0);
833
834 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
836 assert_eq!(result.as_number(), 9.0);
837 }
838
839 #[test]
840 fn test_eval_unary_minus() {
841 let mut ctx = setup_context();
842 let result = eval_xpath(&mut ctx, "-5").unwrap();
843 assert_eq!(result.as_number(), -5.0);
844
845 let result = eval_xpath(&mut ctx, "--5").unwrap();
846 assert_eq!(result.as_number(), 5.0);
847 }
848
849 #[test]
850 fn test_eval_true_false() {
851 let mut ctx = setup_context();
852 assert_eq!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean(), true);
853 assert_eq!(eval_xpath(&mut ctx, "false()").unwrap().as_boolean(), false);
854 }
855
856 #[test]
857 fn test_eval_translate() {
858 let mut ctx = setup_context();
859 assert_eq!(
860 eval_xpath(
861 &mut ctx,
862 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
863 )
864 .unwrap()
865 .as_string(),
866 "HELLO"
867 );
868 }
869
870 #[test]
871 fn test_eval_empty_expression() {
872 let mut ctx = setup_context();
873 let result = eval_xpath(&mut ctx, "");
874 assert!(result.is_err());
875 }
876}