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> {
260 let node = get_first_node(ctx, args, 0);
261 if let Some(node) = node {
262 unsafe {
263 use crate::abi::types::xmlElementType as ET;
264 let t = (*node).type_;
265 let name = crate::xml::string::xmlstr_to_string((*node).name);
266 if (t == ET::XML_ELEMENT_NODE as i32 || t == ET::XML_ATTRIBUTE_NODE as i32)
267 && !name.is_empty()
268 && !(*node).ns.is_null()
269 && !(*(*node).ns).prefix.is_null()
270 {
271 let prefix = crate::xml::string::xmlstr_to_string((*(*node).ns).prefix);
272 Ok(XPathValue::String(format!("{prefix}:{name}")))
273 } else {
274 Ok(XPathValue::String(name))
275 }
276 }
277 } else {
278 Ok(XPathValue::String(String::new()))
279 }
280}
281
282fn fn_string(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
288 if args.is_empty() {
289 Ok(XPathValue::String(node_string_value(ctx.context_node)))
291 } else {
292 Ok(XPathValue::String(args[0].as_string()))
293 }
294}
295
296fn fn_concat(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
298 let mut result = String::new();
299 for arg in args {
300 result.push_str(&arg.as_string());
301 }
302 Ok(XPathValue::String(result))
303}
304
305fn fn_starts_with(_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 Ok(XPathValue::Boolean(s1.starts_with(&s2)))
310}
311
312fn fn_contains(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
314 let s1 = get_string_arg(args, 0);
315 let s2 = get_string_arg(args, 1);
316 Ok(XPathValue::Boolean(s1.contains(&s2)))
317}
318
319fn fn_substring_before(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
321 let s1 = get_string_arg(args, 0);
322 let s2 = get_string_arg(args, 1);
323 if let Some(pos) = s1.find(&s2) {
324 Ok(XPathValue::String(s1[..pos].to_string()))
325 } else {
326 Ok(XPathValue::String(String::new()))
327 }
328}
329
330fn fn_substring_after(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
332 let s1 = get_string_arg(args, 0);
333 let s2 = get_string_arg(args, 1);
334 if let Some(pos) = s1.find(&s2) {
335 Ok(XPathValue::String(s1[pos + s2.len()..].to_string()))
336 } else {
337 Ok(XPathValue::String(String::new()))
338 }
339}
340
341fn fn_substring(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
349 let s = get_string_arg(args, 0);
350 let start = get_number_arg(args, 1);
351 let has_length = args.len() >= 3;
352 let length = if has_length {
353 get_number_arg(args, 2)
354 } else {
355 f64::MAX
356 };
357
358 let start_r = start.round();
359 let end_r = start_r + length.round();
360
361 let mut out = String::new();
362 for (i, c) in s.chars().enumerate() {
363 let p = (i + 1) as f64;
364 if p >= start_r && p < end_r {
365 out.push(c);
366 }
367 }
368 Ok(XPathValue::String(out))
369}
370
371fn fn_string_length(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
376 let s = if args.is_empty() {
377 node_string_value(ctx.context_node)
378 } else {
379 get_string_arg(args, 0)
380 };
381 Ok(XPathValue::Number(s.chars().count() as f64))
382}
383
384fn fn_normalize_space(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
386 let s = if args.is_empty() {
387 node_string_value(ctx.context_node)
388 } else {
389 get_string_arg(args, 0)
390 };
391 let normalized: Vec<&str> = s.split_whitespace().collect();
392 Ok(XPathValue::String(normalized.join(" ")))
393}
394
395fn fn_translate(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
397 let s = get_string_arg(args, 0);
398 let from = get_string_arg(args, 1);
399 let to = get_string_arg(args, 2);
400
401 let result: String = s
402 .chars()
403 .map(|c| {
404 if let Some(pos) = from.chars().position(|x| x == c) {
408 let to_chars: Vec<char> = to.chars().collect();
409 if pos < to_chars.len() {
410 to_chars[pos]
411 } else {
412 '\0' }
414 } else {
415 c
416 }
417 })
418 .filter(|&c| c != '\0')
419 .collect();
420
421 Ok(XPathValue::String(result))
422}
423
424fn fn_boolean(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
430 Ok(XPathValue::Boolean(get_boolean_arg(args, 0)))
431}
432
433fn fn_not(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
435 Ok(XPathValue::Boolean(!get_boolean_arg(args, 0)))
436}
437
438const fn fn_true(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
440 Ok(XPathValue::Boolean(true))
441}
442
443const fn fn_false(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
445 Ok(XPathValue::Boolean(false))
446}
447
448fn fn_lang(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
458 let lang = get_string_arg(args, 0);
459 let mut node = ctx.context_node;
460 unsafe {
461 while !node.is_null() {
462 let mut prop = (*node).properties;
463 while !prop.is_null() {
464 let attr_name = crate::xml::string::xmlstr_to_string((*prop).name);
465 if attr_name == "lang" || attr_name == "xml:lang" {
466 if !(*prop).children.is_null() {
468 let attr_val =
469 crate::xml::string::xmlstr_to_string((*(*prop).children).content);
470 if attr_val.to_lowercase() == lang.to_lowercase()
471 || attr_val
472 .to_lowercase()
473 .starts_with(&format!("{}-", lang.to_lowercase()))
474 {
475 return Ok(XPathValue::Boolean(true));
476 }
477 }
478 }
479 prop = (*prop).next;
480 }
481 node = (*node).parent;
482 }
483 }
484 Ok(XPathValue::Boolean(false))
485}
486
487fn fn_number(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
493 if args.is_empty() {
494 Ok(XPathValue::Number(string_to_number(&node_string_value(
495 ctx.context_node,
496 ))))
497 } else {
498 Ok(XPathValue::Number(get_number_arg(args, 0)))
499 }
500}
501
502fn fn_sum(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
504 let ns = get_node_set_arg(args, 0);
505 let mut total = 0.0;
506 for node in ns.iter() {
507 let s = node_string_value(node);
508 total += string_to_number(&s);
509 }
510 Ok(XPathValue::Number(total))
511}
512
513fn fn_floor(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
515 let n = get_number_arg(args, 0);
516 Ok(XPathValue::Number(n.floor()))
517}
518
519fn fn_ceiling(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
521 let n = get_number_arg(args, 0);
522 Ok(XPathValue::Number(n.ceil()))
523}
524
525fn fn_round(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
531 let n = get_number_arg(args, 0);
532 if n.is_nan() || n.is_infinite() || n == 0.0 {
533 return Ok(XPathValue::Number(n));
534 }
535 let rust_rounded = n.round();
539 let result = if n.is_sign_negative() && (n - rust_rounded).abs() == 0.5 {
540 rust_rounded + 1.0
542 } else {
543 rust_rounded
544 };
545 Ok(XPathValue::Number(result))
546}
547
548#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn test_true_false() {
558 let mut ctx = XPathContext::new(std::ptr::null_mut());
559 assert!(fn_true(&mut ctx, &[]).unwrap().as_boolean());
560 assert!(!fn_false(&mut ctx, &[]).unwrap().as_boolean());
561 }
562
563 #[test]
564 fn test_boolean_conversion() {
565 let mut ctx = XPathContext::new(std::ptr::null_mut());
566 assert!(fn_boolean(&mut ctx, &[XPathValue::Boolean(true)])
567 .unwrap()
568 .as_boolean());
569 assert!(!fn_boolean(&mut ctx, &[XPathValue::Boolean(false)])
570 .unwrap()
571 .as_boolean());
572 }
573
574 #[test]
575 fn test_not() {
576 let mut ctx = XPathContext::new(std::ptr::null_mut());
577 assert!(!fn_not(&mut ctx, &[XPathValue::Boolean(true)])
578 .unwrap()
579 .as_boolean());
580 assert!(fn_not(&mut ctx, &[XPathValue::Boolean(false)])
581 .unwrap()
582 .as_boolean());
583 }
584
585 #[test]
586 fn test_number_round() {
587 let mut ctx = XPathContext::new(std::ptr::null_mut());
588 assert_eq!(
589 fn_floor(&mut ctx, &[XPathValue::Number(3.7)])
590 .unwrap()
591 .as_number(),
592 3.0
593 );
594 assert_eq!(
595 fn_ceiling(&mut ctx, &[XPathValue::Number(3.2)])
596 .unwrap()
597 .as_number(),
598 4.0
599 );
600 assert_eq!(
601 fn_round(&mut ctx, &[XPathValue::Number(3.5)])
602 .unwrap()
603 .as_number(),
604 4.0
605 );
606 assert_eq!(
607 fn_round(&mut ctx, &[XPathValue::Number(-3.5)])
608 .unwrap()
609 .as_number(),
610 -3.0
611 );
612 }
613
614 #[test]
615 fn test_string_functions() {
616 let mut ctx = XPathContext::new(std::ptr::null_mut());
617 assert_eq!(
618 fn_concat(
619 &mut ctx,
620 &[
621 XPathValue::String("a".into()),
622 XPathValue::String("b".into()),
623 XPathValue::String("c".into())
624 ]
625 )
626 .unwrap()
627 .as_string(),
628 "abc"
629 );
630 assert!(fn_starts_with(
631 &mut ctx,
632 &[
633 XPathValue::String("hello".into()),
634 XPathValue::String("he".into())
635 ]
636 )
637 .unwrap()
638 .as_boolean());
639 assert!(!fn_starts_with(
640 &mut ctx,
641 &[
642 XPathValue::String("hello".into()),
643 XPathValue::String("x".into())
644 ]
645 )
646 .unwrap()
647 .as_boolean());
648 assert!(fn_contains(
649 &mut ctx,
650 &[
651 XPathValue::String("hello".into()),
652 XPathValue::String("ell".into())
653 ]
654 )
655 .unwrap()
656 .as_boolean());
657 assert_eq!(
658 fn_string_length(&mut ctx, &[XPathValue::String("hello".into())])
659 .unwrap()
660 .as_number(),
661 5.0
662 );
663 }
664
665 #[test]
666 fn test_core_functions_registered() {
667 let funcs = core_functions();
668 assert!(funcs.contains_key("last"));
669 assert!(funcs.contains_key("position"));
670 assert!(funcs.contains_key("count"));
671 assert!(funcs.contains_key("string"));
672 assert!(funcs.contains_key("concat"));
673 assert!(funcs.contains_key("boolean"));
674 assert!(funcs.contains_key("not"));
675 assert!(funcs.contains_key("number"));
676 assert!(funcs.contains_key("sum"));
677 assert!(funcs.contains_key("floor"));
678 assert!(funcs.contains_key("ceiling"));
679 assert!(funcs.contains_key("round"));
680 assert!(funcs.contains_key("name"));
681 assert!(funcs.contains_key("local-name"));
682 assert_eq!(funcs.len(), 27);
683 assert!(funcs.contains_key("namespace-uri"));
684 }
685}