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_size = ctx.context_size;
155 let saved_list = ctx.context_list.clone();
156
157 ctx.context_node = node;
158 ctx.context_position = (i + 1) as i32;
159 ctx.context_size = result.len() as i32;
160
161 let pred_val = eval(ctx, predicate)?;
163
164 let matches = match pred_val {
169 XPathValue::Number(n) => {
170 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
172 || (n.round() as i32) == (i + 1) as i32
173 }
174 _ => pred_val.as_boolean(),
175 };
176
177 if matches {
178 filtered.push(node);
179 }
180
181 ctx.context_node = saved_node;
182 ctx.context_position = saved_pos;
183 ctx.context_size = saved_size;
184 ctx.context_list = saved_list;
185 }
186
187 result = filtered;
188 }
189
190 Ok(XPathValue::NodeSet(result))
191}
192
193fn eval_filter(
195 ctx: &mut XPathContext,
196 expr: &Expr,
197 predicates: &[Expr],
198) -> Result<XPathValue, String> {
199 let mut result = eval(ctx, expr)?;
201
202 let ns = match &mut result {
204 XPathValue::NodeSet(ns) => ns,
205 _ => return Ok(result), };
207
208 for predicate in predicates {
209 let mut filtered = NodeSet::new();
210 let nodes: Vec<_> = ns.iter().collect();
211
212 for (i, node) in nodes.iter().enumerate() {
213 let saved_node = ctx.context_node;
214 let saved_pos = ctx.context_position;
215 let saved_size = ctx.context_size;
216 let saved_list = ctx.context_list.clone();
217
218 ctx.context_node = *node;
219 ctx.context_position = (i + 1) as i32;
220 ctx.context_size = nodes.len() as i32;
221
222 let pred_val = eval(ctx, predicate)?;
223
224 let matches = match pred_val {
225 XPathValue::Number(n) => {
226 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
227 || (n.round() as i32) == (i + 1) as i32
228 }
229 _ => pred_val.as_boolean(),
230 };
231
232 if matches {
233 filtered.push(*node);
234 }
235
236 ctx.context_node = saved_node;
237 ctx.context_position = saved_pos;
238 ctx.context_size = saved_size;
239 ctx.context_list = saved_list;
240 }
241
242 *ns = filtered;
243 }
244
245 Ok(result)
246}
247
248fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
254 ctx.resolve_variable(name)
255 .ok_or_else(|| format!("Undefined variable: ${}", name))
256}
257
258fn eval_function_call(
260 ctx: &mut XPathContext,
261 name: &str,
262 args: &[Expr],
263) -> Result<XPathValue, String> {
264 let mut evaluated_args = Vec::new();
266 for arg in args {
267 evaluated_args.push(eval(ctx, arg)?);
268 }
269
270 let func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> =
274 ctx.lookup_function(name).map(|f| f as *const _);
275 match func_ptr {
276 Some(p) => {
277 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
280 f(ctx, &evaluated_args)
281 }
282 None => Err(format!("Unknown XPath function: {}", name)),
283 }
284}
285
286fn eval_binary_op(
292 ctx: &mut XPathContext,
293 op: &BinaryOp,
294 left: &Expr,
295 right: &Expr,
296) -> Result<XPathValue, String> {
297 match op {
298 BinaryOp::Or => {
299 let left_val = eval(ctx, left)?;
301 if left_val.as_boolean() {
302 return Ok(XPathValue::Boolean(true));
303 }
304 let right_val = eval(ctx, right)?;
305 Ok(XPathValue::Boolean(right_val.as_boolean()))
306 }
307 BinaryOp::And => {
308 let left_val = eval(ctx, left)?;
310 if !left_val.as_boolean() {
311 return Ok(XPathValue::Boolean(false));
312 }
313 let right_val = eval(ctx, right)?;
314 Ok(XPathValue::Boolean(right_val.as_boolean()))
315 }
316 BinaryOp::Eq | BinaryOp::Ne => {
317 let left_val = eval(ctx, left)?;
318 let right_val = eval(ctx, right)?;
319 let eq = compare_equal(ctx, &left_val, &right_val);
320 Ok(match op {
321 BinaryOp::Eq => XPathValue::Boolean(eq),
322 BinaryOp::Ne => XPathValue::Boolean(!eq),
323 _ => unreachable!(),
324 })
325 }
326 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
327 let left_val = eval(ctx, left)?;
328 let right_val = eval(ctx, right)?;
329 let cmp = compare_ordered(ctx, &left_val, &right_val);
330 let result = match op {
331 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
332 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
333 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
334 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
335 _ => unreachable!(),
336 };
337 Ok(XPathValue::Boolean(result))
338 }
339 BinaryOp::Add => {
340 let left_val = eval(ctx, left)?;
341 let right_val = eval(ctx, right)?;
342 Ok(XPathValue::Number(
343 left_val.as_number() + right_val.as_number(),
344 ))
345 }
346 BinaryOp::Sub => {
347 let left_val = eval(ctx, left)?;
348 let right_val = eval(ctx, right)?;
349 Ok(XPathValue::Number(
350 left_val.as_number() - right_val.as_number(),
351 ))
352 }
353 BinaryOp::Mul => {
354 let left_val = eval(ctx, left)?;
355 let right_val = eval(ctx, right)?;
356 Ok(XPathValue::Number(
357 left_val.as_number() * right_val.as_number(),
358 ))
359 }
360 BinaryOp::Div => {
361 let left_val = eval(ctx, left)?;
362 let right_val = eval(ctx, right)?;
363 Ok(XPathValue::Number(
364 left_val.as_number() / right_val.as_number(),
365 ))
366 }
367 BinaryOp::Mod => {
368 let left_val = eval(ctx, left)?;
369 let right_val = eval(ctx, right)?;
370 Ok(XPathValue::Number(
371 left_val.as_number() % right_val.as_number(),
372 ))
373 }
374 BinaryOp::Union => {
375 unreachable!("Union operator should be handled by Expr::Union")
377 }
378 }
379}
380
381fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
383 let left_val = eval(ctx, left)?;
384 let right_val = eval(ctx, right)?;
385
386 let mut result = left_val.as_node_set().clone();
387 result.extend(right_val.as_node_set());
388 result.sort();
389
390 Ok(XPathValue::NodeSet(result))
391}
392
393fn compare_equal(ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
399 match (a, b) {
400 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
402 for node_a in ns_a.iter() {
403 let val_a = node_string_value(node_a);
404 for node_b in ns_b.iter() {
405 let val_b = node_string_value(node_b);
406 if val_a == val_b {
407 return true;
408 }
409 }
410 }
411 false
412 }
413 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
415 for node in ns.iter() {
416 let node_str = node_string_value(node);
417 let other_val = match other {
418 XPathValue::Boolean(_) => {
419 return (ns.len() > 0) == other.as_boolean();
421 }
422 XPathValue::Number(_) => {
423 let node_num = string_to_number(&node_str);
424 if (node_num - other.as_number()).abs() < f64::EPSILON {
425 return true;
426 }
427 continue;
428 }
429 XPathValue::String(_) => {
430 if node_str == other.as_string() {
431 return true;
432 }
433 continue;
434 }
435 _ => continue,
436 };
437 }
438 false
439 }
440 _ => match (a, b) {
442 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
443 a.as_boolean() == b.as_boolean()
444 }
445 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
446 let na = a.as_number();
447 let nb = b.as_number();
448 if na.is_nan() || nb.is_nan() {
449 false
450 } else {
451 (na - nb).abs() < f64::EPSILON || na == nb
452 }
453 }
454 _ => a.as_string() == b.as_string(),
455 },
456 }
457}
458
459fn compare_ordered(ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
461 match (a, b) {
462 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
464 for node_a in ns_a.iter() {
465 let num_a = string_to_number(&node_string_value(node_a));
466 for node_b in ns_b.iter() {
467 let num_b = string_to_number(&node_string_value(node_b));
468 if num_a < num_b {
469 return std::cmp::Ordering::Less;
470 }
471 if num_a > num_b {
472 return std::cmp::Ordering::Greater;
473 }
474 }
475 }
476 std::cmp::Ordering::Equal
477 }
478 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
480 let other_num = other.as_number();
481 for node in ns.iter() {
482 let node_num = string_to_number(&node_string_value(node));
483 if node_num < other_num {
484 return std::cmp::Ordering::Less;
485 }
486 if node_num > other_num {
487 return std::cmp::Ordering::Greater;
488 }
489 }
490 std::cmp::Ordering::Equal
491 }
492 _ => {
494 let na = a.as_number();
495 let nb = b.as_number();
496 if na.is_nan() || nb.is_nan() {
497 std::cmp::Ordering::Equal } else if na < nb {
499 std::cmp::Ordering::Less
500 } else if na > nb {
501 std::cmp::Ordering::Greater
502 } else {
503 std::cmp::Ordering::Equal
504 }
505 }
506 }
507}
508
509pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
517 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
518 eval(ctx, &expr)
519}
520
521#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::xml::xpath::context::XPathContext;
529
530 fn setup_context() -> XPathContext {
531 let mut ctx = XPathContext::new(std::ptr::null_mut());
532 let funcs = functions::core_functions();
534 for (name, func) in funcs {
535 ctx.register_function(&name, func);
536 }
537 ctx
538 }
539
540 #[test]
541 fn test_eval_string_literal() {
542 let mut ctx = setup_context();
543 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
544 assert_eq!(result.as_string(), "hello");
545 }
546
547 #[test]
548 fn test_eval_number_literal() {
549 let mut ctx = setup_context();
550 let result = eval_xpath(&mut ctx, "42").unwrap();
551 assert_eq!(result.as_number(), 42.0);
552 }
553
554 #[test]
555 fn test_eval_addition() {
556 let mut ctx = setup_context();
557 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
558 assert_eq!(result.as_number(), 3.0);
559 }
560
561 #[test]
562 fn test_eval_subtraction() {
563 let mut ctx = setup_context();
564 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
565 assert_eq!(result.as_number(), 2.0);
566 }
567
568 #[test]
569 fn test_eval_multiplication() {
570 let mut ctx = setup_context();
571 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
572 assert_eq!(result.as_number(), 12.0);
573 }
574
575 #[test]
576 fn test_eval_division() {
577 let mut ctx = setup_context();
578 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
579 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
580 }
581
582 #[test]
583 fn test_eval_modulo() {
584 let mut ctx = setup_context();
585 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
586 assert_eq!(result.as_number(), 1.0);
587 }
588
589 #[test]
590 fn test_eval_equality() {
591 let mut ctx = setup_context();
592 assert_eq!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean(), true);
593 assert_eq!(eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean(), false);
594 assert_eq!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean(), true);
595 }
596
597 #[test]
598 fn test_eval_comparison() {
599 let mut ctx = setup_context();
600 assert_eq!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean(), true);
601 assert_eq!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean(), true);
602 assert_eq!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean(), true);
603 assert_eq!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean(), true);
604 }
605
606 #[test]
607 fn test_eval_and_or() {
608 let mut ctx = setup_context();
609 assert_eq!(
610 eval_xpath(&mut ctx, "true() and true()")
611 .unwrap()
612 .as_boolean(),
613 true
614 );
615 assert_eq!(
616 eval_xpath(&mut ctx, "true() and false()")
617 .unwrap()
618 .as_boolean(),
619 false
620 );
621 assert_eq!(
622 eval_xpath(&mut ctx, "true() or false()")
623 .unwrap()
624 .as_boolean(),
625 true
626 );
627 assert_eq!(
628 eval_xpath(&mut ctx, "false() or false()")
629 .unwrap()
630 .as_boolean(),
631 false
632 );
633 }
634
635 #[test]
636 fn test_eval_not() {
637 let mut ctx = setup_context();
638 assert_eq!(
639 eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean(),
640 false
641 );
642 assert_eq!(
643 eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean(),
644 true
645 );
646 }
647
648 #[test]
649 fn test_eval_boolean() {
650 let mut ctx = setup_context();
651 assert_eq!(
652 eval_xpath(&mut ctx, "boolean('hello')")
653 .unwrap()
654 .as_boolean(),
655 true
656 );
657 assert_eq!(
658 eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean(),
659 false
660 );
661 assert_eq!(
662 eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean(),
663 false
664 );
665 assert_eq!(
666 eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean(),
667 true
668 );
669 }
670
671 #[test]
672 fn test_eval_number() {
673 let mut ctx = setup_context();
674 assert_eq!(
675 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
676 42.0
677 );
678 }
679
680 #[test]
681 fn test_eval_string() {
682 let mut ctx = setup_context();
683 assert_eq!(
684 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
685 "42"
686 );
687 }
688
689 #[test]
690 fn test_eval_concat() {
691 let mut ctx = setup_context();
692 assert_eq!(
693 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
694 .unwrap()
695 .as_string(),
696 "abc"
697 );
698 }
699
700 #[test]
701 fn test_eval_starts_with() {
702 let mut ctx = setup_context();
703 assert_eq!(
704 eval_xpath(&mut ctx, "starts-with('hello', 'he')")
705 .unwrap()
706 .as_boolean(),
707 true
708 );
709 }
710
711 #[test]
712 fn test_eval_contains() {
713 let mut ctx = setup_context();
714 assert_eq!(
715 eval_xpath(&mut ctx, "contains('hello', 'ell')")
716 .unwrap()
717 .as_boolean(),
718 true
719 );
720 }
721
722 #[test]
723 fn test_eval_substring() {
724 let mut ctx = setup_context();
725 assert_eq!(
726 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
727 .unwrap()
728 .as_string(),
729 "123"
730 );
731 assert_eq!(
732 eval_xpath(&mut ctx, "substring('12345', 2)")
733 .unwrap()
734 .as_string(),
735 "2345"
736 );
737 }
738
739 #[test]
740 fn test_eval_string_length() {
741 let mut ctx = setup_context();
742 assert_eq!(
743 eval_xpath(&mut ctx, "string-length('hello')")
744 .unwrap()
745 .as_number(),
746 5.0
747 );
748 }
749
750 #[test]
751 fn test_eval_normalize_space() {
752 let mut ctx = setup_context();
753 assert_eq!(
754 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
755 .unwrap()
756 .as_string(),
757 "hello world"
758 );
759 }
760
761 #[test]
762 fn test_eval_floor_ceiling_round() {
763 let mut ctx = setup_context();
764 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
765 assert_eq!(
766 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
767 4.0
768 );
769 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
770 }
771
772 #[test]
773 fn test_eval_sum() {
774 let mut ctx = setup_context();
776 ctx.document = std::ptr::null_mut();
777 }
780
781 #[test]
782 fn test_eval_variable_not_found() {
783 let mut ctx = setup_context();
784 let result = eval_xpath(&mut ctx, "$undefined_var");
785 assert!(result.is_err());
786 }
787
788 #[test]
789 fn test_eval_variable_found() {
790 let mut ctx = setup_context();
791 ctx.register_variable("x", XPathValue::Number(42.0));
792 let result = eval_xpath(&mut ctx, "$x").unwrap();
793 assert_eq!(result.as_number(), 42.0);
794 }
795
796 #[test]
797 fn test_eval_union() {
798 }
803
804 #[test]
805 fn test_eval_operator_precedence() {
806 let mut ctx = setup_context();
807 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
809 assert_eq!(result.as_number(), 7.0);
810
811 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
813 assert_eq!(result.as_number(), 9.0);
814 }
815
816 #[test]
817 fn test_eval_unary_minus() {
818 let mut ctx = setup_context();
819 let result = eval_xpath(&mut ctx, "-5").unwrap();
820 assert_eq!(result.as_number(), -5.0);
821
822 let result = eval_xpath(&mut ctx, "--5").unwrap();
823 assert_eq!(result.as_number(), 5.0);
824 }
825
826 #[test]
827 fn test_eval_true_false() {
828 let mut ctx = setup_context();
829 assert_eq!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean(), true);
830 assert_eq!(eval_xpath(&mut ctx, "false()").unwrap().as_boolean(), false);
831 }
832
833 #[test]
834 fn test_eval_translate() {
835 let mut ctx = setup_context();
836 assert_eq!(
837 eval_xpath(
838 &mut ctx,
839 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
840 )
841 .unwrap()
842 .as_string(),
843 "HELLO"
844 );
845 }
846
847 #[test]
848 fn test_eval_empty_expression() {
849 let mut ctx = setup_context();
850 let result = eval_xpath(&mut ctx, "");
851 assert!(result.is_err());
852 }
853}