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