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> {
179 let context_node = ctx.context_node;
180 if context_node.is_null() {
181 return Ok(XPathValue::NodeSet(NodeSet::new()));
182 }
183
184 let mut result = unsafe {
186 axes::traverse_axis(
187 context_node,
188 step.axis,
189 &step.node_test,
190 true, false, )
193 };
194
195 for predicate in &step.predicates {
197 let mut filtered = NodeSet::new();
198
199 for (i, node) in result.iter().enumerate() {
200 let saved_node = ctx.context_node;
202 let saved_pos = ctx.context_position;
203 let saved_prox = ctx.proximity_position;
204 let saved_size = ctx.context_size;
205 let saved_list = ctx.context_list.clone();
206
207 ctx.context_node = node;
208 ctx.context_position = (i + 1) as i32;
209 ctx.proximity_position = (i + 1) as i32;
212 ctx.context_size = result.len() as i32;
213
214 let pred_val = eval(ctx, predicate)?;
216
217 let matches = match pred_val {
222 XPathValue::Number(n) => {
223 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
225 || (n.round() as i32) == (i + 1) as i32
226 }
227 _ => pred_val.as_boolean(),
228 };
229
230 if matches {
231 filtered.push(node);
232 }
233
234 ctx.context_node = saved_node;
235 ctx.context_position = saved_pos;
236 ctx.proximity_position = saved_prox;
237 ctx.context_size = saved_size;
238 ctx.context_list = saved_list;
239 }
240
241 result = filtered;
242 }
243
244 Ok(XPathValue::NodeSet(result))
245}
246
247fn eval_filter(
249 ctx: &mut XPathContext,
250 expr: &Expr,
251 predicates: &[Expr],
252) -> Result<XPathValue, String> {
253 let mut result = eval(ctx, expr)?;
255
256 let ns = match &mut result {
258 XPathValue::NodeSet(ns) => ns,
259 _ => return Ok(result), };
261
262 for predicate in predicates {
263 let mut filtered = NodeSet::new();
264 let nodes: Vec<_> = ns.iter().collect();
265
266 for (i, node) in nodes.iter().enumerate() {
267 let saved_node = ctx.context_node;
268 let saved_pos = ctx.context_position;
269 let saved_prox = ctx.proximity_position;
270 let saved_size = ctx.context_size;
271 let saved_list = ctx.context_list.clone();
272
273 ctx.context_node = *node;
274 ctx.context_position = (i + 1) as i32;
275 ctx.proximity_position = (i + 1) as i32;
277 ctx.context_size = nodes.len() as i32;
278
279 let pred_val = eval(ctx, predicate)?;
280
281 let matches = match pred_val {
282 XPathValue::Number(n) => {
283 (n - (i as f64 + 1.0)).abs() < f64::EPSILON
284 || (n.round() as i32) == (i + 1) as i32
285 }
286 _ => pred_val.as_boolean(),
287 };
288
289 if matches {
290 filtered.push(*node);
291 }
292
293 ctx.context_node = saved_node;
294 ctx.context_position = saved_pos;
295 ctx.proximity_position = saved_prox;
296 ctx.context_size = saved_size;
297 ctx.context_list = saved_list;
298 }
299
300 *ns = filtered;
301 }
302
303 Ok(result)
304}
305
306fn eval_variable(ctx: &mut XPathContext, name: &str) -> Result<XPathValue, String> {
312 ctx.resolve_variable(name)
313 .ok_or_else(|| format!("Undefined variable: ${}", name))
314}
315
316fn eval_function_call(
318 ctx: &mut XPathContext,
319 name: &str,
320 args: &[Expr],
321) -> Result<XPathValue, String> {
322 let mut evaluated_args = Vec::new();
324 for arg in args {
325 evaluated_args.push(eval(ctx, arg)?);
326 }
327
328 let func_ptr: Option<*const crate::xml::xpath::context::BoxedXPathFunction> =
332 ctx.lookup_function(name).map(|f| f as *const _);
333 match func_ptr {
334 Some(p) => {
335 let f: &crate::xml::xpath::context::BoxedXPathFunction = unsafe { &*p };
338 f(ctx, &evaluated_args)
339 }
340 None => {
341 let msg = match name.split_once(':') {
348 Some((prefix, _)) if !ctx.namespaces.contains_key(prefix) => {
349 format!("Undefined namespace prefix: {}", prefix)
350 }
351 _ => format!("Unregistered function: {}", name),
352 };
353 Err(msg)
354 }
355 }
356}
357
358fn eval_binary_op(
364 ctx: &mut XPathContext,
365 op: &BinaryOp,
366 left: &Expr,
367 right: &Expr,
368) -> Result<XPathValue, String> {
369 match op {
370 BinaryOp::Or => {
371 let left_val = eval(ctx, left)?;
373 if left_val.as_boolean() {
374 return Ok(XPathValue::Boolean(true));
375 }
376 let right_val = eval(ctx, right)?;
377 Ok(XPathValue::Boolean(right_val.as_boolean()))
378 }
379 BinaryOp::And => {
380 let left_val = eval(ctx, left)?;
382 if !left_val.as_boolean() {
383 return Ok(XPathValue::Boolean(false));
384 }
385 let right_val = eval(ctx, right)?;
386 Ok(XPathValue::Boolean(right_val.as_boolean()))
387 }
388 BinaryOp::Eq | BinaryOp::Ne => {
389 let left_val = eval(ctx, left)?;
390 let right_val = eval(ctx, right)?;
391 let eq = compare_equal(ctx, &left_val, &right_val);
392 Ok(match op {
393 BinaryOp::Eq => XPathValue::Boolean(eq),
394 BinaryOp::Ne => XPathValue::Boolean(!eq),
395 _ => unreachable!(),
396 })
397 }
398 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
399 let left_val = eval(ctx, left)?;
400 let right_val = eval(ctx, right)?;
401 let cmp = compare_ordered(ctx, &left_val, &right_val);
402 let result = match op {
403 BinaryOp::Lt => cmp == std::cmp::Ordering::Less,
404 BinaryOp::Gt => cmp == std::cmp::Ordering::Greater,
405 BinaryOp::Le => cmp != std::cmp::Ordering::Greater,
406 BinaryOp::Ge => cmp != std::cmp::Ordering::Less,
407 _ => unreachable!(),
408 };
409 Ok(XPathValue::Boolean(result))
410 }
411 BinaryOp::Add => {
412 let left_val = eval(ctx, left)?;
413 let right_val = eval(ctx, right)?;
414 Ok(XPathValue::Number(
415 left_val.as_number() + right_val.as_number(),
416 ))
417 }
418 BinaryOp::Sub => {
419 let left_val = eval(ctx, left)?;
420 let right_val = eval(ctx, right)?;
421 Ok(XPathValue::Number(
422 left_val.as_number() - right_val.as_number(),
423 ))
424 }
425 BinaryOp::Mul => {
426 let left_val = eval(ctx, left)?;
427 let right_val = eval(ctx, right)?;
428 Ok(XPathValue::Number(
429 left_val.as_number() * right_val.as_number(),
430 ))
431 }
432 BinaryOp::Div => {
433 let left_val = eval(ctx, left)?;
434 let right_val = eval(ctx, right)?;
435 Ok(XPathValue::Number(
436 left_val.as_number() / right_val.as_number(),
437 ))
438 }
439 BinaryOp::Mod => {
440 let left_val = eval(ctx, left)?;
441 let right_val = eval(ctx, right)?;
442 Ok(XPathValue::Number(
443 left_val.as_number() % right_val.as_number(),
444 ))
445 }
446 BinaryOp::Union => {
447 unreachable!("Union operator should be handled by Expr::Union")
449 }
450 }
451}
452
453fn eval_union(ctx: &mut XPathContext, left: &Expr, right: &Expr) -> Result<XPathValue, String> {
455 let left_val = eval(ctx, left)?;
456 let right_val = eval(ctx, right)?;
457
458 let mut result = left_val.as_node_set().clone();
459 result.extend(right_val.as_node_set());
460 result.sort();
461
462 Ok(XPathValue::NodeSet(result))
463}
464
465fn compare_equal(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> bool {
471 match (a, b) {
472 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
474 for node_a in ns_a.iter() {
475 let val_a = node_string_value(node_a);
476 for node_b in ns_b.iter() {
477 let val_b = node_string_value(node_b);
478 if val_a == val_b {
479 return true;
480 }
481 }
482 }
483 false
484 }
485 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
487 for node in ns.iter() {
488 let node_str = node_string_value(node);
489 match other {
490 XPathValue::Boolean(_) => {
491 return (!ns.is_empty()) == other.as_boolean();
493 }
494 XPathValue::Number(_) => {
495 let node_num = string_to_number(&node_str);
496 if (node_num - other.as_number()).abs() < f64::EPSILON {
497 return true;
498 }
499 continue;
500 }
501 XPathValue::String(_) => {
502 if node_str == other.as_string() {
503 return true;
504 }
505 continue;
506 }
507 _ => continue,
508 };
509 }
510 false
511 }
512 _ => match (a, b) {
514 (XPathValue::Boolean(_), _) | (_, XPathValue::Boolean(_)) => {
515 a.as_boolean() == b.as_boolean()
516 }
517 (XPathValue::Number(_), _) | (_, XPathValue::Number(_)) => {
518 let na = a.as_number();
519 let nb = b.as_number();
520 if na.is_nan() || nb.is_nan() {
521 false
522 } else {
523 (na - nb).abs() < f64::EPSILON || na == nb
524 }
525 }
526 _ => a.as_string() == b.as_string(),
527 },
528 }
529}
530
531fn compare_ordered(_ctx: &mut XPathContext, a: &XPathValue, b: &XPathValue) -> std::cmp::Ordering {
533 match (a, b) {
534 (XPathValue::NodeSet(ns_a), XPathValue::NodeSet(ns_b)) => {
536 for node_a in ns_a.iter() {
537 let num_a = string_to_number(&node_string_value(node_a));
538 for node_b in ns_b.iter() {
539 let num_b = string_to_number(&node_string_value(node_b));
540 if num_a < num_b {
541 return std::cmp::Ordering::Less;
542 }
543 if num_a > num_b {
544 return std::cmp::Ordering::Greater;
545 }
546 }
547 }
548 std::cmp::Ordering::Equal
549 }
550 (XPathValue::NodeSet(ns), other) | (other, XPathValue::NodeSet(ns)) => {
552 let other_num = other.as_number();
553 for node in ns.iter() {
554 let node_num = string_to_number(&node_string_value(node));
555 if node_num < other_num {
556 return std::cmp::Ordering::Less;
557 }
558 if node_num > other_num {
559 return std::cmp::Ordering::Greater;
560 }
561 }
562 std::cmp::Ordering::Equal
563 }
564 _ => {
566 let na = a.as_number();
567 let nb = b.as_number();
568 if na.is_nan() || nb.is_nan() {
569 std::cmp::Ordering::Equal } else if na < nb {
571 std::cmp::Ordering::Less
572 } else if na > nb {
573 std::cmp::Ordering::Greater
574 } else {
575 std::cmp::Ordering::Equal
576 }
577 }
578 }
579}
580
581pub fn eval_xpath(ctx: &mut XPathContext, expression: &str) -> Result<XPathValue, String> {
589 let expr = crate::xml::xpath::parser::parse_xpath(expression).map_err(|e| e.message)?;
590 eval(ctx, &expr)
591}
592
593#[cfg(test)]
598mod tests {
599 use super::*;
600 use crate::xml::xpath::context::XPathContext;
601 use crate::xml::xpath::functions;
602
603 fn setup_context() -> XPathContext {
604 let mut ctx = XPathContext::new(std::ptr::null_mut());
605 let funcs = functions::core_functions();
607 for (name, func) in funcs {
608 ctx.register_function(&name, func);
609 }
610 ctx
611 }
612
613 #[test]
614 fn test_eval_string_literal() {
615 let mut ctx = setup_context();
616 let result = eval_xpath(&mut ctx, "'hello'").unwrap();
617 assert_eq!(result.as_string(), "hello");
618 }
619
620 #[test]
621 fn test_eval_number_literal() {
622 let mut ctx = setup_context();
623 let result = eval_xpath(&mut ctx, "42").unwrap();
624 assert_eq!(result.as_number(), 42.0);
625 }
626
627 #[test]
628 fn test_eval_addition() {
629 let mut ctx = setup_context();
630 let result = eval_xpath(&mut ctx, "1 + 2").unwrap();
631 assert_eq!(result.as_number(), 3.0);
632 }
633
634 #[test]
635 fn test_eval_subtraction() {
636 let mut ctx = setup_context();
637 let result = eval_xpath(&mut ctx, "5 - 3").unwrap();
638 assert_eq!(result.as_number(), 2.0);
639 }
640
641 #[test]
642 fn test_eval_multiplication() {
643 let mut ctx = setup_context();
644 let result = eval_xpath(&mut ctx, "3 * 4").unwrap();
645 assert_eq!(result.as_number(), 12.0);
646 }
647
648 #[test]
649 fn test_eval_division() {
650 let mut ctx = setup_context();
651 let result = eval_xpath(&mut ctx, "10 div 3").unwrap();
652 assert!((result.as_number() - 3.3333333333333335).abs() < 1e-10);
653 }
654
655 #[test]
656 fn test_eval_modulo() {
657 let mut ctx = setup_context();
658 let result = eval_xpath(&mut ctx, "10 mod 3").unwrap();
659 assert_eq!(result.as_number(), 1.0);
660 }
661
662 #[test]
663 fn test_eval_equality() {
664 let mut ctx = setup_context();
665 assert!(eval_xpath(&mut ctx, "1 = 1").unwrap().as_boolean());
666 assert!(!eval_xpath(&mut ctx, "1 = 2").unwrap().as_boolean());
667 assert!(eval_xpath(&mut ctx, "1 != 2").unwrap().as_boolean());
668 }
669
670 #[test]
671 fn test_eval_comparison() {
672 let mut ctx = setup_context();
673 assert!(eval_xpath(&mut ctx, "1 < 2").unwrap().as_boolean());
674 assert!(eval_xpath(&mut ctx, "2 > 1").unwrap().as_boolean());
675 assert!(eval_xpath(&mut ctx, "1 <= 1").unwrap().as_boolean());
676 assert!(eval_xpath(&mut ctx, "2 >= 2").unwrap().as_boolean());
677 }
678
679 #[test]
680 fn test_eval_and_or() {
681 let mut ctx = setup_context();
682 assert!(eval_xpath(&mut ctx, "true() and true()")
683 .unwrap()
684 .as_boolean());
685 assert!(!eval_xpath(&mut ctx, "true() and false()")
686 .unwrap()
687 .as_boolean());
688 assert!(eval_xpath(&mut ctx, "true() or false()")
689 .unwrap()
690 .as_boolean());
691 assert!(!eval_xpath(&mut ctx, "false() or false()")
692 .unwrap()
693 .as_boolean());
694 }
695
696 #[test]
697 fn test_eval_not() {
698 let mut ctx = setup_context();
699 assert!(!eval_xpath(&mut ctx, "not(true())").unwrap().as_boolean());
700 assert!(eval_xpath(&mut ctx, "not(false())").unwrap().as_boolean());
701 }
702
703 #[test]
704 fn test_eval_boolean() {
705 let mut ctx = setup_context();
706 assert!(eval_xpath(&mut ctx, "boolean('hello')")
707 .unwrap()
708 .as_boolean());
709 assert!(!eval_xpath(&mut ctx, "boolean('')").unwrap().as_boolean());
710 assert!(!eval_xpath(&mut ctx, "boolean(0)").unwrap().as_boolean());
711 assert!(eval_xpath(&mut ctx, "boolean(1)").unwrap().as_boolean());
712 }
713
714 #[test]
715 fn test_eval_number() {
716 let mut ctx = setup_context();
717 assert_eq!(
718 eval_xpath(&mut ctx, "number('42')").unwrap().as_number(),
719 42.0
720 );
721 }
722
723 #[test]
724 fn test_eval_string() {
725 let mut ctx = setup_context();
726 assert_eq!(
727 eval_xpath(&mut ctx, "string(42)").unwrap().as_string(),
728 "42"
729 );
730 }
731
732 #[test]
733 fn test_eval_concat() {
734 let mut ctx = setup_context();
735 assert_eq!(
736 eval_xpath(&mut ctx, "concat('a', 'b', 'c')")
737 .unwrap()
738 .as_string(),
739 "abc"
740 );
741 }
742
743 #[test]
744 fn test_eval_starts_with() {
745 let mut ctx = setup_context();
746 assert!(eval_xpath(&mut ctx, "starts-with('hello', 'he')")
747 .unwrap()
748 .as_boolean());
749 }
750
751 #[test]
752 fn test_eval_contains() {
753 let mut ctx = setup_context();
754 assert!(eval_xpath(&mut ctx, "contains('hello', 'ell')")
755 .unwrap()
756 .as_boolean());
757 }
758
759 #[test]
760 fn test_eval_substring() {
761 let mut ctx = setup_context();
762 assert_eq!(
763 eval_xpath(&mut ctx, "substring('12345', 1, 3)")
764 .unwrap()
765 .as_string(),
766 "123"
767 );
768 assert_eq!(
769 eval_xpath(&mut ctx, "substring('12345', 2)")
770 .unwrap()
771 .as_string(),
772 "2345"
773 );
774 }
775
776 #[test]
777 fn test_eval_string_length() {
778 let mut ctx = setup_context();
779 assert_eq!(
780 eval_xpath(&mut ctx, "string-length('hello')")
781 .unwrap()
782 .as_number(),
783 5.0
784 );
785 }
786
787 #[test]
788 fn test_eval_normalize_space() {
789 let mut ctx = setup_context();
790 assert_eq!(
791 eval_xpath(&mut ctx, "normalize-space(' hello world ')")
792 .unwrap()
793 .as_string(),
794 "hello world"
795 );
796 }
797
798 #[test]
799 fn test_eval_floor_ceiling_round() {
800 let mut ctx = setup_context();
801 assert_eq!(eval_xpath(&mut ctx, "floor(3.7)").unwrap().as_number(), 3.0);
802 assert_eq!(
803 eval_xpath(&mut ctx, "ceiling(3.2)").unwrap().as_number(),
804 4.0
805 );
806 assert_eq!(eval_xpath(&mut ctx, "round(3.5)").unwrap().as_number(), 4.0);
807 }
808
809 #[test]
810 fn test_eval_sum() {
811 let mut ctx = setup_context();
813 ctx.document = std::ptr::null_mut();
814 }
817
818 #[test]
819 fn test_eval_variable_not_found() {
820 let mut ctx = setup_context();
821 let result = eval_xpath(&mut ctx, "$undefined_var");
822 assert!(result.is_err());
823 }
824
825 #[test]
826 fn test_eval_variable_found() {
827 let mut ctx = setup_context();
828 ctx.register_variable("x", XPathValue::Number(42.0));
829 let result = eval_xpath(&mut ctx, "$x").unwrap();
830 assert_eq!(result.as_number(), 42.0);
831 }
832
833 #[test]
834 fn test_eval_union() {
835 }
840
841 #[test]
842 fn test_eval_operator_precedence() {
843 let mut ctx = setup_context();
844 let result = eval_xpath(&mut ctx, "1 + 2 * 3").unwrap();
846 assert_eq!(result.as_number(), 7.0);
847
848 let result = eval_xpath(&mut ctx, "(1 + 2) * 3").unwrap();
850 assert_eq!(result.as_number(), 9.0);
851 }
852
853 #[test]
854 fn test_eval_unary_minus() {
855 let mut ctx = setup_context();
856 let result = eval_xpath(&mut ctx, "-5").unwrap();
857 assert_eq!(result.as_number(), -5.0);
858
859 let result = eval_xpath(&mut ctx, "--5").unwrap();
860 assert_eq!(result.as_number(), 5.0);
861 }
862
863 #[test]
864 fn test_eval_true_false() {
865 let mut ctx = setup_context();
866 assert!(eval_xpath(&mut ctx, "true()").unwrap().as_boolean());
867 assert!(!eval_xpath(&mut ctx, "false()").unwrap().as_boolean());
868 }
869
870 #[test]
871 fn test_eval_translate() {
872 let mut ctx = setup_context();
873 assert_eq!(
874 eval_xpath(
875 &mut ctx,
876 "translate('hello', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
877 )
878 .unwrap()
879 .as_string(),
880 "HELLO"
881 );
882 }
883
884 #[test]
885 fn test_eval_empty_expression() {
886 let mut ctx = setup_context();
887 let result = eval_xpath(&mut ctx, "");
888 assert!(result.is_err());
889 }
890}