1use crate::abi::structs::_xmlNode;
67use crate::xml::xpath::ast::{BinaryOp, Expr, Step};
68use crate::xml::xpath::axes;
69use crate::xml::xpath::context::XPathContext;
70use crate::xml::xpath::types::{node_string_value, string_to_number, NodeSet, XPathValue};
71
72pub fn eval(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
78 ctx.push_recursion()?;
79
80 let result = match expr {
81 Expr::Step(step) => eval_step(ctx, step),
82 Expr::AbsolutePath(expr) => eval_absolute_path(ctx, expr),
83 Expr::RelativePath(left, right) => eval_relative_path(ctx, left, right),
84 Expr::Filter(expr, predicates) => eval_filter(ctx, expr, predicates),
85 Expr::Variable(name) => eval_variable(ctx, name),
86 Expr::StringLiteral(s) => Ok(XPathValue::String(s.clone())),
87 Expr::NumberLiteral(n) => Ok(XPathValue::Number(*n)),
88 Expr::BooleanLiteral(b) => Ok(XPathValue::Boolean(*b)),
89 Expr::FunctionCall { name, args } => eval_function_call(ctx, name, args),
90 Expr::BinaryOp { op, left, right } => eval_binary_op(ctx, op, left, right),
91 Expr::UnaryMinus(expr) => {
92 let val = eval(ctx, expr)?;
93 Ok(XPathValue::Number(-val.as_number()))
94 }
95 Expr::Union(left, right) => eval_union(ctx, left, right),
96 };
97
98 ctx.pop_recursion();
99 result
100}
101
102fn eval_absolute_path(ctx: &mut XPathContext, expr: &Expr) -> Result<XPathValue, String> {
108 let doc = ctx.document;
110 if doc.is_null() {
111 return Ok(XPathValue::NodeSet(NodeSet::new()));
112 }
113
114 {
115 let doc_node = doc as *mut _xmlNode;
120
121 let saved_node = ctx.context_node;
123 let saved_list = ctx.context_list.clone();
124 ctx.context_node = doc_node;
125 ctx.set_context_list(vec![doc_node]);
126
127 let result = eval(ctx, expr);
128
129 ctx.context_node = saved_node;
130 ctx.context_list = saved_list;
131
132 result
133 }
134}
135
136fn eval_relative_path(
138 ctx: &mut XPathContext,
139 left: &Expr,
140 right: &Expr,
141) -> Result<XPathValue, String> {
142 let left_val = eval(ctx, left)?;
144 let left_ns = left_val.as_node_set().clone();
145
146 let mut result = NodeSet::new();
147
148 for node in left_ns.iter() {
149 let saved_node = ctx.context_node;
151 let saved_list = ctx.context_list.clone();
152 ctx.context_node = node;
153 ctx.set_context_list(left_ns.iter().collect());
154
155 match eval(ctx, right) {
156 Ok(val) => {
157 if let XPathValue::NodeSet(ns) = val {
158 for n in ns.iter() {
159 result.push(n);
160 }
161 }
162 }
163 Err(e) => {
164 ctx.context_node = saved_node;
165 ctx.context_list = saved_list;
166 return Err(e);
167 }
168 }
169
170 ctx.context_node = saved_node;
171 ctx.context_list = saved_list;
172 }
173
174 Ok(XPathValue::NodeSet(result))
175}
176
177fn eval_step(ctx: &mut XPathContext, step: &Step) -> Result<XPathValue, String> {
188 let context_node = ctx.context_node;
189 if context_node.is_null() {
190 return Ok(XPathValue::NodeSet(NodeSet::new()));
191 }
192
193 let mut result = unsafe {
195 axes::traverse_axis(
196 context_node,
197 step.axis,
198 &step.node_test,
199 true, false, )
202 };
203
204 for predicate in &step.predicates {
206 let mut filtered = NodeSet::new();
207
208 for (i, node) in result.iter().enumerate() {
209 let saved_node = ctx.context_node;
211 let saved_pos = ctx.context_position;
212 let saved_prox = ctx.proximity_position;
213 let saved_size = ctx.context_size;
214 let saved_list = ctx.context_list.clone();
215
216 ctx.context_node = node;
217 ctx.context_position = (i + 1) as i32;
218 ctx.proximity_position = (i + 1) as i32;
221 ctx.context_size = result.len() as i32;
222
223 let pred_val = eval(ctx, predicate)?;
225
226 let matches = match pred_val {
231 XPathValue::Number(n) => {
232 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
234 || (n.round() as i32) == (i + 1) as i32
235 }
236 _ => pred_val.as_boolean(),
237 };
238
239 if matches {
240 filtered.push(node);
241 }
242
243 ctx.context_node = saved_node;
244 ctx.context_position = saved_pos;
245 ctx.proximity_position = saved_prox;
246 ctx.context_size = saved_size;
247 ctx.context_list = saved_list;
248 }
249
250 result = filtered;
251 }
252
253 Ok(XPathValue::NodeSet(result))
254}
255
256fn eval_filter(
258 ctx: &mut XPathContext,
259 expr: &Expr,
260 predicates: &[Expr],
261) -> Result<XPathValue, String> {
262 let mut result = eval(ctx, expr)?;
264
265 let ns = match &mut result {
267 XPathValue::NodeSet(ns) => ns,
268 _ => return Ok(result), };
270
271 for predicate in predicates {
272 let mut filtered = NodeSet::new();
273 let nodes: Vec<_> = ns.iter().collect();
274
275 for (i, node) in nodes.iter().enumerate() {
276 let saved_node = ctx.context_node;
277 let saved_pos = ctx.context_position;
278 let saved_prox = ctx.proximity_position;
279 let saved_size = ctx.context_size;
280 let saved_list = ctx.context_list.clone();
281
282 ctx.context_node = *node;
283 ctx.context_position = (i + 1) as i32;
284 ctx.proximity_position = (i + 1) as i32;
286 ctx.context_size = nodes.len() as i32;
287
288 let pred_val = eval(ctx, predicate)?;
289
290 let matches = match pred_val {
291 XPathValue::Number(n) => {
292 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
293 || (n.round() as i32) == (i + 1) as i32
294 }
295 _ => pred_val.as_boolean(),
296 };
297
298 if matches {
299 filtered.push(*node);
300 }
301
302 ctx.context_node = saved_node;
303 ctx.context_position = saved_pos;
304 ctx.proximity_position = saved_prox;
305 ctx.context_size = saved_size;
306 ctx.context_list = saved_list;
307 }
308
309 *ns = filtered;
310 }
311
312 Ok(result)
313}
314
315fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
321 ctx.resolve_variable(name)
322 .ok_or_else(|| format!("Undefined variable: ${}", name))
323}
324
325fn eval_function_call(
327 ctx: &mut XPathContext,
328 name: &str,
329 args: &[Expr],
330) -> Result<XPathValue, String> {
331 let mut evaluated_args = Vec::new();
333 for arg in args {
334 evaluated_args.push(eval(ctx, arg)?);
335 }
336
337 let func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> =
341 ctx.lookup_function(name).map(|f| f as *const _);
342 match func_ptr {
343 Some(p) => {
344 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
347 f(ctx, &evaluated_args)
348 }
349 None => {
350 let msg = match name.split_once(':') {
357 Some((prefix, _)) if !ctx.namespaces.contains_key(prefix) => {
358 format!("Undefined namespace prefix: {}", prefix)
359 }
360 _ => format!("Unregistered function: {}", name),
361 };
362 Err(msg)
363 }
364 }
365}
366
367fn eval_binary_op(
373 ctx: &mut XPathContext,
374 op: &BinaryOp,
375 left: &Expr,
376 right: &Expr,
377) -> Result<XPathValue, String> {
378 match op {
379 BinaryOp::Or => {
380 let left_val = eval(ctx, left)?;
382 if left_val.as_boolean() {
383 return Ok(XPathValue::Boolean(true));
384 }
385 let right_val = eval(ctx, right)?;
386 Ok(XPathValue::Boolean(right_val.as_boolean()))
387 }
388 BinaryOp::And => {
389 let left_val = eval(ctx, left)?;
391 if !left_val.as_boolean() {
392 return Ok(XPathValue::Boolean(false));
393 }
394 let right_val = eval(ctx, right)?;
395 Ok(XPathValue::Boolean(right_val.as_boolean()))
396 }
397 BinaryOp::Eq | BinaryOp::Ne => {
398 let left_val = eval(ctx, left)?;
399 let right_val = eval(ctx, right)?;
400 let eq = compare_equal(ctx, &left_val, &right_val);
401 Ok(match op {
402 BinaryOp::Eq => XPathValue::Boolean(eq),
403 BinaryOp::Ne => XPathValue::Boolean(!eq),
404 _ => unreachable!(),
405 })
406 }
407 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
408 let left_val = eval(ctx, left)?;
409 let right_val = eval(ctx, right)?;
410 let cmp = compare_ordered(ctx, &left_val, &right_val);
411 let result = match op {
412 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
413 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
414 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
415 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
416 _ => unreachable!(),
417 };
418 Ok(XPathValue::Boolean(result))
419 }
420 BinaryOp::Add => {
421 let left_val = eval(ctx, left)?;
422 let right_val = eval(ctx, right)?;
423 Ok(XPathValue::Number(
424 left_val.as_number() + right_val.as_number(),
425 ))
426 }
427 BinaryOp::Sub => {
428 let left_val = eval(ctx, left)?;
429 let right_val = eval(ctx, right)?;
430 Ok(XPathValue::Number(
431 left_val.as_number() - right_val.as_number(),
432 ))
433 }
434 BinaryOp::Mul => {
435 let left_val = eval(ctx, left)?;
436 let right_val = eval(ctx, right)?;
437 Ok(XPathValue::Number(
438 left_val.as_number() * right_val.as_number(),
439 ))
440 }
441 BinaryOp::Div => {
442 let left_val = eval(ctx, left)?;
443 let right_val = eval(ctx, right)?;
444 Ok(XPathValue::Number(
445 left_val.as_number() / right_val.as_number(),
446 ))
447 }
448 BinaryOp::Mod => {
449 let left_val = eval(ctx, left)?;
450 let right_val = eval(ctx, right)?;
451 Ok(XPathValue::Number(
452 left_val.as_number() % right_val.as_number(),
453 ))
454 }
455 BinaryOp::Union => {
456 unreachable!("Union operator should be handled by Expr::Union")
458 }
459 }
460}
461
462fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
464 let left_val = eval(ctx, left)?;
465 let right_val = eval(ctx, right)?;
466
467 let mut result = left_val.as_node_set().clone();
468 result.extend(right_val.as_node_set());
469 result.sort();
470
471 Ok(XPathValue::NodeSet(result))
472}
473
474fn compare_equal(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
480 match (a, b) {
481 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
483 for node_a in ns_a.iter() {
484 let val_a = node_string_value(node_a);
485 for node_b in ns_b.iter() {
486 let val_b = node_string_value(node_b);
487 if val_a == val_b {
488 return true;
489 }
490 }
491 }
492 false
493 }
494 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
496 for node in ns.iter() {
497 let node_str = node_string_value(node);
498 match other {
499 XPathValue::Boolean(_) => {
500 return (!ns.is_empty()) == other.as_boolean();
502 }
503 XPathValue::Number(_) => {
504 let node_num = string_to_number(&node_str);
505 if (node_num - other.as_number()).abs() < f64::EPSILON {
506 return true;
507 }
508 continue;
509 }
510 XPathValue::String(_) => {
511 if node_str == other.as_string() {
512 return true;
513 }
514 continue;
515 }
516 _ => continue,
517 };
518 }
519 false
520 }
521 _ => match (a, b) {
523 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
524 a.as_boolean() == b.as_boolean()
525 }
526 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
527 let na = a.as_number();
528 let nb = b.as_number();
529 if na.is_nan() || nb.is_nan() {
530 false
531 } else {
532 (na - nb).abs() < f64::EPSILON || na == nb
533 }
534 }
535 _ => a.as_string() == b.as_string(),
536 },
537 }
538}
539
540fn compare_ordered(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
542 match (a, b) {
543 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
545 for node_a in ns_a.iter() {
546 let num_a = string_to_number(&node_string_value(node_a));
547 for node_b in ns_b.iter() {
548 let num_b = string_to_number(&node_string_value(node_b));
549 if num_a < num_b {
550 return std::cmp::Ordering::Less;
551 }
552 if num_a > num_b {
553 return std::cmp::Ordering::Greater;
554 }
555 }
556 }
557 std::cmp::Ordering::Equal
558 }
559 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
561 let other_num = other.as_number();
562 for node in ns.iter() {
563 let node_num = string_to_number(&node_string_value(node));
564 if node_num < other_num {
565 return std::cmp::Ordering::Less;
566 }
567 if node_num > other_num {
568 return std::cmp::Ordering::Greater;
569 }
570 }
571 std::cmp::Ordering::Equal
572 }
573 _ => {
575 let na = a.as_number();
576 let nb = b.as_number();
577 if na.is_nan() || nb.is_nan() {
578 std::cmp::Ordering::Equal } else if na < nb {
580 std::cmp::Ordering::Less
581 } else if na > nb {
582 std::cmp::Ordering::Greater
583 } else {
584 std::cmp::Ordering::Equal
585 }
586 }
587 }
588}
589
590pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
598 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
599 eval(ctx, &expr)
600}
601
602#[cfg(test)]
607mod tests {
608 use super::*;
609 use crate::xml::xpath::context::XPathContext;
610 use crate::xml::xpath::functions;
611
612 fn setup_context() -> XPathContext {
613 let mut ctx = XPathContext::new(std::ptr::null_mut());
614 let funcs = functions::core_functions();
616 for (name, func) in funcs {
617 ctx.register_function(&name, func);
618 }
619 ctx
620 }
621
622 #[test]
623 fn test_eval_string_literal() {
624 let mut ctx = setup_context();
625 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
626 assert_eq!(result.as_string(), "hello");
627 }
628
629 #[test]
630 fn test_eval_number_literal() {
631 let mut ctx = setup_context();
632 let result = eval_xpath(&mut ctx, "42").unwrap();
633 assert_eq!(result.as_number(), 42.0);
634 }
635
636 #[test]
637 fn test_eval_addition() {
638 let mut ctx = setup_context();
639 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
640 assert_eq!(result.as_number(), 3.0);
641 }
642
643 #[test]
644 fn test_eval_subtraction() {
645 let mut ctx = setup_context();
646 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
647 assert_eq!(result.as_number(), 2.0);
648 }
649
650 #[test]
651 fn test_eval_multiplication() {
652 let mut ctx = setup_context();
653 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
654 assert_eq!(result.as_number(), 12.0);
655 }
656
657 #[test]
658 fn test_eval_division() {
659 let mut ctx = setup_context();
660 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
661 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
662 }
663
664 #[test]
665 fn test_eval_modulo() {
666 let mut ctx = setup_context();
667 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
668 assert_eq!(result.as_number(), 1.0);
669 }
670
671 #[test]
672 fn test_eval_equality() {
673 let mut ctx = setup_context();
674 assert!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean());
675 assert!(!eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean());
676 assert!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean());
677 }
678
679 #[test]
680 fn test_eval_comparison() {
681 let mut ctx = setup_context();
682 assert!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean());
683 assert!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean());
684 assert!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean());
685 assert!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean());
686 }
687
688 #[test]
689 fn test_eval_and_or() {
690 let mut ctx = setup_context();
691 assert!(eval_xpath(&mut ctx, "true() and true()")
692 .unwrap()
693 .as_boolean());
694 assert!(!eval_xpath(&mut ctx, "true() and false()")
695 .unwrap()
696 .as_boolean());
697 assert!(eval_xpath(&mut ctx, "true() or false()")
698 .unwrap()
699 .as_boolean());
700 assert!(!eval_xpath(&mut ctx, "false() or false()")
701 .unwrap()
702 .as_boolean());
703 }
704
705 #[test]
706 fn test_eval_not() {
707 let mut ctx = setup_context();
708 assert!(!eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean());
709 assert!(eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean());
710 }
711
712 #[test]
713 fn test_eval_boolean() {
714 let mut ctx = setup_context();
715 assert!(eval_xpath(&mut ctx, "boolean('hello')")
716 .unwrap()
717 .as_boolean());
718 assert!(!eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean());
719 assert!(!eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean());
720 assert!(eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean());
721 }
722
723 #[test]
724 fn test_eval_number() {
725 let mut ctx = setup_context();
726 assert_eq!(
727 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
728 42.0
729 );
730 }
731
732 #[test]
733 fn test_eval_string() {
734 let mut ctx = setup_context();
735 assert_eq!(
736 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
737 "42"
738 );
739 }
740
741 #[test]
742 fn test_eval_concat() {
743 let mut ctx = setup_context();
744 assert_eq!(
745 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
746 .unwrap()
747 .as_string(),
748 "abc"
749 );
750 }
751
752 #[test]
753 fn test_eval_starts_with() {
754 let mut ctx = setup_context();
755 assert!(eval_xpath(&mut ctx, "starts-with('hello', 'he')")
756 .unwrap()
757 .as_boolean());
758 }
759
760 #[test]
761 fn test_eval_contains() {
762 let mut ctx = setup_context();
763 assert!(eval_xpath(&mut ctx, "contains('hello', 'ell')")
764 .unwrap()
765 .as_boolean());
766 }
767
768 #[test]
769 fn test_eval_substring() {
770 let mut ctx = setup_context();
771 assert_eq!(
772 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
773 .unwrap()
774 .as_string(),
775 "123"
776 );
777 assert_eq!(
778 eval_xpath(&mut ctx, "substring('12345', 2)")
779 .unwrap()
780 .as_string(),
781 "2345"
782 );
783 }
784
785 #[test]
786 fn test_eval_string_length() {
787 let mut ctx = setup_context();
788 assert_eq!(
789 eval_xpath(&mut ctx, "string-length('hello')")
790 .unwrap()
791 .as_number(),
792 5.0
793 );
794 }
795
796 #[test]
797 fn test_eval_normalize_space() {
798 let mut ctx = setup_context();
799 assert_eq!(
800 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
801 .unwrap()
802 .as_string(),
803 "hello world"
804 );
805 }
806
807 #[test]
808 fn test_eval_floor_ceiling_round() {
809 let mut ctx = setup_context();
810 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
811 assert_eq!(
812 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
813 4.0
814 );
815 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
816 }
817
818 #[test]
819 fn test_eval_sum() {
820 let mut ctx = setup_context();
822 ctx.document = std::ptr::null_mut();
823 }
826
827 #[test]
828 fn test_eval_variable_not_found() {
829 let mut ctx = setup_context();
830 let result = eval_xpath(&mut ctx, "$undefined_var");
831 assert!(result.is_err());
832 }
833
834 #[test]
835 fn test_eval_variable_found() {
836 let mut ctx = setup_context();
837 ctx.register_variable("x", XPathValue::Number(42.0));
838 let result = eval_xpath(&mut ctx, "$x").unwrap();
839 assert_eq!(result.as_number(), 42.0);
840 }
841
842 #[test]
843 fn test_eval_union() {
844 }
849
850 #[test]
851 fn test_eval_operator_precedence() {
852 let mut ctx = setup_context();
853 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
855 assert_eq!(result.as_number(), 7.0);
856
857 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
859 assert_eq!(result.as_number(), 9.0);
860 }
861
862 #[test]
863 fn test_eval_unary_minus() {
864 let mut ctx = setup_context();
865 let result = eval_xpath(&mut ctx, "-5").unwrap();
866 assert_eq!(result.as_number(), -5.0);
867
868 let result = eval_xpath(&mut ctx, "--5").unwrap();
869 assert_eq!(result.as_number(), 5.0);
870 }
871
872 #[test]
873 fn test_eval_true_false() {
874 let mut ctx = setup_context();
875 assert!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean());
876 assert!(!eval_xpath(&mut ctx, "false()").unwrap().as_boolean());
877 }
878
879 #[test]
880 fn test_eval_translate() {
881 let mut ctx = setup_context();
882 assert_eq!(
883 eval_xpath(
884 &mut ctx,
885 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
886 )
887 .unwrap()
888 .as_string(),
889 "HELLO"
890 );
891 }
892
893 #[test]
894 fn test_eval_empty_expression() {
895 let mut ctx = setup_context();
896 let result = eval_xpath(&mut ctx, "");
897 assert!(result.is_err());
898 }
899}