1use crate::xml::xpath::context::XPathContext;
69use crate::xml::xpath::types::{node_string_value, string_to_number, NodeSet, XPathValue};
70use std::collections::HashMap;
71
72pub type XPathFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
76
77pub fn core_functions() -> HashMap<String, XPathFunction> {
79 let mut funcs: HashMap<String, XPathFunction> = HashMap::new();
80
81 funcs.insert("last".into(), fn_last);
83 funcs.insert("position".into(), fn_position);
84 funcs.insert("count".into(), fn_count);
85 funcs.insert("id".into(), fn_id);
86 funcs.insert("local-name".into(), fn_local_name);
87 funcs.insert("namespace-uri".into(), fn_namespace_uri);
88 funcs.insert("name".into(), fn_name);
89
90 funcs.insert("string".into(), fn_string);
92 funcs.insert("concat".into(), fn_concat);
93 funcs.insert("starts-with".into(), fn_starts_with);
94 funcs.insert("contains".into(), fn_contains);
95 funcs.insert("substring-before".into(), fn_substring_before);
96 funcs.insert("substring-after".into(), fn_substring_after);
97 funcs.insert("substring".into(), fn_substring);
98 funcs.insert("string-length".into(), fn_string_length);
99 funcs.insert("normalize-space".into(), fn_normalize_space);
100 funcs.insert("translate".into(), fn_translate);
101
102 funcs.insert("boolean".into(), fn_boolean);
104 funcs.insert("not".into(), fn_not);
105 funcs.insert("true".into(), fn_true);
106 funcs.insert("false".into(), fn_false);
107 funcs.insert("lang".into(), fn_lang);
108
109 funcs.insert("number".into(), fn_number);
111 funcs.insert("sum".into(), fn_sum);
112 funcs.insert("floor".into(), fn_floor);
113 funcs.insert("ceiling".into(), fn_ceiling);
114 funcs.insert("round".into(), fn_round);
115
116 funcs
117}
118
119fn get_string_arg(args: &[XPathValue], index: usize) -> String {
124 if index < args.len() {
125 args[index].as_string()
126 } else {
127 String::new()
128 }
129}
130
131fn get_number_arg(args: &[XPathValue], index: usize) -> f64 {
132 if index < args.len() {
133 args[index].as_number()
134 } else {
135 f64::NAN
136 }
137}
138
139fn get_boolean_arg(args: &[XPathValue], index: usize) -> bool {
140 if index < args.len() {
141 args[index].as_boolean()
142 } else {
143 false
144 }
145}
146
147fn get_node_set_arg(args: &[XPathValue], index: usize) -> NodeSet {
148 if index < args.len() {
149 match &args[index] {
150 XPathValue::NodeSet(ns) => ns.clone(),
151 _ => NodeSet::new(),
152 }
153 } else {
154 NodeSet::new()
155 }
156}
157
158fn get_first_node(
159 ctx: &XPathContext,
160 args: &[XPathValue],
161 index: usize,
162) -> Option<*mut crate::abi::structs::_xmlNode> {
163 if index < args.len() {
164 match &args[index] {
165 XPathValue::NodeSet(ns) => ns.first(),
166 _ => None,
167 }
168 } else {
169 Some(ctx.context_node)
171 }
172}
173
174const fn fn_last(ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
180 Ok(XPathValue::Number(ctx.last() as f64))
181}
182
183const fn fn_position(ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
185 Ok(XPathValue::Number(ctx.position() as f64))
186}
187
188fn fn_count(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
190 let ns = get_node_set_arg(args, 0);
191 Ok(XPathValue::Number(ns.len() as f64))
192}
193
194const fn fn_id(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
196 Ok(XPathValue::NodeSet(NodeSet::new()))
199}
200
201fn fn_local_name(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
209 let node = get_first_node(ctx, args, 0);
210 if let Some(node) = node {
211 unsafe {
212 let name = crate::xml::string::xmlstr_to_string((*node).name);
213 if let Some(pos) = name.find(':') {
215 Ok(XPathValue::String(name[pos + 1..].to_string()))
216 } else {
217 Ok(XPathValue::String(name))
218 }
219 }
220 } else {
221 Ok(XPathValue::String(String::new()))
222 }
223}
224
225fn fn_namespace_uri(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
234 let node = get_first_node(ctx, args, 0);
235 if let Some(node) = node {
236 unsafe {
237 if let Some(ns) = (*node).ns.as_ref() {
238 let uri = crate::xml::string::xmlstr_to_string(ns.href);
239 Ok(XPathValue::String(uri))
240 } else {
241 Ok(XPathValue::String(String::new()))
242 }
243 }
244 } else {
245 Ok(XPathValue::String(String::new()))
246 }
247}
248
249fn fn_name(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
257 let node = get_first_node(ctx, args, 0);
258 if let Some(node) = node {
259 unsafe {
260 let name = crate::xml::string::xmlstr_to_string((*node).name);
261 Ok(XPathValue::String(name))
262 }
263 } else {
264 Ok(XPathValue::String(String::new()))
265 }
266}
267
268fn fn_string(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
274 if args.is_empty() {
275 Ok(XPathValue::String(node_string_value(ctx.context_node)))
277 } else {
278 Ok(XPathValue::String(args[0].as_string()))
279 }
280}
281
282fn fn_concat(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
284 let mut result = String::new();
285 for arg in args {
286 result.push_str(&arg.as_string());
287 }
288 Ok(XPathValue::String(result))
289}
290
291fn fn_starts_with(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
293 let s1 = get_string_arg(args, 0);
294 let s2 = get_string_arg(args, 1);
295 Ok(XPathValue::Boolean(s1.starts_with(&s2)))
296}
297
298fn fn_contains(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
300 let s1 = get_string_arg(args, 0);
301 let s2 = get_string_arg(args, 1);
302 Ok(XPathValue::Boolean(s1.contains(&s2)))
303}
304
305fn fn_substring_before(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
307 let s1 = get_string_arg(args, 0);
308 let s2 = get_string_arg(args, 1);
309 if let Some(pos) = s1.find(&s2) {
310 Ok(XPathValue::String(s1[..pos].to_string()))
311 } else {
312 Ok(XPathValue::String(String::new()))
313 }
314}
315
316fn fn_substring_after(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
318 let s1 = get_string_arg(args, 0);
319 let s2 = get_string_arg(args, 1);
320 if let Some(pos) = s1.find(&s2) {
321 Ok(XPathValue::String(s1[pos + s2.len()..].to_string()))
322 } else {
323 Ok(XPathValue::String(String::new()))
324 }
325}
326
327fn fn_substring(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
331 let s = get_string_arg(args, 0);
332 let start = get_number_arg(args, 1);
333 let has_length = args.len() >= 3;
334 let length = if has_length {
335 get_number_arg(args, 2)
336 } else {
337 f64::MAX
338 };
339
340 let start_rounded = start.round() as isize;
341 let length_rounded = length.round() as isize;
342
343 let start_index = if start_rounded < 1 {
345 0
346 } else {
347 (start_rounded - 1) as usize
348 };
349 let length = if length_rounded < 0 {
350 0
351 } else {
352 length_rounded as usize
353 };
354
355 if start_index >= s.len() || length == 0 {
356 Ok(XPathValue::String(String::new()))
357 } else {
358 let end = std::cmp::min(start_index + length, s.len());
359 Ok(XPathValue::String(s[start_index..end].to_string()))
360 }
361}
362
363fn fn_string_length(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
365 let s = if args.is_empty() {
366 node_string_value(ctx.context_node)
367 } else {
368 get_string_arg(args, 0)
369 };
370 Ok(XPathValue::Number(s.len() as f64))
371}
372
373fn fn_normalize_space(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
375 let s = if args.is_empty() {
376 node_string_value(ctx.context_node)
377 } else {
378 get_string_arg(args, 0)
379 };
380 let normalized: Vec<&str> = s.split_whitespace().collect();
381 Ok(XPathValue::String(normalized.join(" ")))
382}
383
384fn fn_translate(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
386 let s = get_string_arg(args, 0);
387 let from = get_string_arg(args, 1);
388 let to = get_string_arg(args, 2);
389
390 let result: String = s
391 .chars()
392 .map(|c| {
393 if let Some(pos) = from.find(c) {
394 if pos < to.len() {
395 to.chars().nth(pos).unwrap_or(c)
396 } else {
397 '\0' }
399 } else {
400 c
401 }
402 })
403 .filter(|&c| c != '\0')
404 .collect();
405
406 Ok(XPathValue::String(result))
407}
408
409fn fn_boolean(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
415 Ok(XPathValue::Boolean(get_boolean_arg(args, 0)))
416}
417
418fn fn_not(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
420 Ok(XPathValue::Boolean(!get_boolean_arg(args, 0)))
421}
422
423const fn fn_true(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
425 Ok(XPathValue::Boolean(true))
426}
427
428const fn fn_false(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
430 Ok(XPathValue::Boolean(false))
431}
432
433fn fn_lang(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
443 let lang = get_string_arg(args, 0);
444 let mut node = ctx.context_node;
445 unsafe {
446 while !node.is_null() {
447 let mut prop = (*node).properties;
448 while !prop.is_null() {
449 let attr_name = crate::xml::string::xmlstr_to_string((*prop).name);
450 if attr_name == "lang" || attr_name == "xml:lang" {
451 if !(*prop).children.is_null() {
453 let attr_val =
454 crate::xml::string::xmlstr_to_string((*(*prop).children).content);
455 if attr_val.to_lowercase() == lang.to_lowercase()
456 || attr_val
457 .to_lowercase()
458 .starts_with(&format!("{}-", lang.to_lowercase()))
459 {
460 return Ok(XPathValue::Boolean(true));
461 }
462 }
463 }
464 prop = (*prop).next;
465 }
466 node = (*node).parent;
467 }
468 }
469 Ok(XPathValue::Boolean(false))
470}
471
472fn fn_number(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
478 if args.is_empty() {
479 Ok(XPathValue::Number(string_to_number(&node_string_value(
480 ctx.context_node,
481 ))))
482 } else {
483 Ok(XPathValue::Number(get_number_arg(args, 0)))
484 }
485}
486
487fn fn_sum(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
489 let ns = get_node_set_arg(args, 0);
490 let mut total = 0.0;
491 for node in ns.iter() {
492 let s = node_string_value(node);
493 total += string_to_number(&s);
494 }
495 Ok(XPathValue::Number(total))
496}
497
498fn fn_floor(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
500 let n = get_number_arg(args, 0);
501 Ok(XPathValue::Number(n.floor()))
502}
503
504fn fn_ceiling(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
506 let n = get_number_arg(args, 0);
507 Ok(XPathValue::Number(n.ceil()))
508}
509
510fn fn_round(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
516 let n = get_number_arg(args, 0);
517 if n.is_nan() || n.is_infinite() || n == 0.0 {
518 return Ok(XPathValue::Number(n));
519 }
520 let rust_rounded = n.round();
524 let result = if n.is_sign_negative() && (n - rust_rounded).abs() == 0.5 {
525 rust_rounded + 1.0
527 } else {
528 rust_rounded
529 };
530 Ok(XPathValue::Number(result))
531}
532
533#[cfg(test)]
538mod tests {
539 use super::*;
540
541 #[test]
542 fn test_true_false() {
543 let mut ctx = XPathContext::new(std::ptr::null_mut());
544 assert!(fn_true(&mut ctx, &[]).unwrap().as_boolean());
545 assert!(!fn_false(&mut ctx, &[]).unwrap().as_boolean());
546 }
547
548 #[test]
549 fn test_boolean_conversion() {
550 let mut ctx = XPathContext::new(std::ptr::null_mut());
551 assert!(fn_boolean(&mut ctx, &[XPathValue::Boolean(true)])
552 .unwrap()
553 .as_boolean());
554 assert!(!fn_boolean(&mut ctx, &[XPathValue::Boolean(false)])
555 .unwrap()
556 .as_boolean());
557 }
558
559 #[test]
560 fn test_not() {
561 let mut ctx = XPathContext::new(std::ptr::null_mut());
562 assert!(!fn_not(&mut ctx, &[XPathValue::Boolean(true)])
563 .unwrap()
564 .as_boolean());
565 assert!(fn_not(&mut ctx, &[XPathValue::Boolean(false)])
566 .unwrap()
567 .as_boolean());
568 }
569
570 #[test]
571 fn test_number_round() {
572 let mut ctx = XPathContext::new(std::ptr::null_mut());
573 assert_eq!(
574 fn_floor(&mut ctx, &[XPathValue::Number(3.7)])
575 .unwrap()
576 .as_number(),
577 3.0
578 );
579 assert_eq!(
580 fn_ceiling(&mut ctx, &[XPathValue::Number(3.2)])
581 .unwrap()
582 .as_number(),
583 4.0
584 );
585 assert_eq!(
586 fn_round(&mut ctx, &[XPathValue::Number(3.5)])
587 .unwrap()
588 .as_number(),
589 4.0
590 );
591 assert_eq!(
592 fn_round(&mut ctx, &[XPathValue::Number(-3.5)])
593 .unwrap()
594 .as_number(),
595 -3.0
596 );
597 }
598
599 #[test]
600 fn test_string_functions() {
601 let mut ctx = XPathContext::new(std::ptr::null_mut());
602 assert_eq!(
603 fn_concat(
604 &mut ctx,
605 &[
606 XPathValue::String("a".into()),
607 XPathValue::String("b".into()),
608 XPathValue::String("c".into())
609 ]
610 )
611 .unwrap()
612 .as_string(),
613 "abc"
614 );
615 assert!(fn_starts_with(
616 &mut ctx,
617 &[
618 XPathValue::String("hello".into()),
619 XPathValue::String("he".into())
620 ]
621 )
622 .unwrap()
623 .as_boolean());
624 assert!(!fn_starts_with(
625 &mut ctx,
626 &[
627 XPathValue::String("hello".into()),
628 XPathValue::String("x".into())
629 ]
630 )
631 .unwrap()
632 .as_boolean());
633 assert!(fn_contains(
634 &mut ctx,
635 &[
636 XPathValue::String("hello".into()),
637 XPathValue::String("ell".into())
638 ]
639 )
640 .unwrap()
641 .as_boolean());
642 assert_eq!(
643 fn_string_length(&mut ctx, &[XPathValue::String("hello".into())])
644 .unwrap()
645 .as_number(),
646 5.0
647 );
648 }
649
650 #[test]
651 fn test_core_functions_registered() {
652 let funcs = core_functions();
653 assert!(funcs.contains_key("last"));
654 assert!(funcs.contains_key("position"));
655 assert!(funcs.contains_key("count"));
656 assert!(funcs.contains_key("string"));
657 assert!(funcs.contains_key("concat"));
658 assert!(funcs.contains_key("boolean"));
659 assert!(funcs.contains_key("not"));
660 assert!(funcs.contains_key("number"));
661 assert!(funcs.contains_key("sum"));
662 assert!(funcs.contains_key("floor"));
663 assert!(funcs.contains_key("ceiling"));
664 assert!(funcs.contains_key("round"));
665 assert!(funcs.contains_key("name"));
666 assert!(funcs.contains_key("local-name"));
667 assert_eq!(funcs.len(), 27);
668 assert!(funcs.contains_key("namespace-uri"));
669 }
670}