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> {
203 let node = get_first_node(ctx, args, 0);
204 if let Some(node) = node {
205 unsafe {
206 let name = crate::xml::string::xmlstr_to_string((*node).name);
207 if let Some(pos) = name.find(':') {
209 Ok(XPathValue::String(name[pos + 1..].to_string()))
210 } else {
211 Ok(XPathValue::String(name))
212 }
213 }
214 } else {
215 Ok(XPathValue::String(String::new()))
216 }
217}
218
219fn fn_namespace_uri(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
221 let node = get_first_node(ctx, args, 0);
222 if let Some(node) = node {
223 unsafe {
224 if let Some(ns) = (*node).ns.as_ref() {
225 let uri = crate::xml::string::xmlstr_to_string(ns.href);
226 Ok(XPathValue::String(uri))
227 } else {
228 Ok(XPathValue::String(String::new()))
229 }
230 }
231 } else {
232 Ok(XPathValue::String(String::new()))
233 }
234}
235
236fn fn_name(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
238 let node = get_first_node(ctx, args, 0);
239 if let Some(node) = node {
240 unsafe {
241 let name = crate::xml::string::xmlstr_to_string((*node).name);
242 Ok(XPathValue::String(name))
243 }
244 } else {
245 Ok(XPathValue::String(String::new()))
246 }
247}
248
249fn fn_string(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
255 if args.is_empty() {
256 Ok(XPathValue::String(node_string_value(ctx.context_node)))
258 } else {
259 Ok(XPathValue::String(args[0].as_string()))
260 }
261}
262
263fn fn_concat(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
265 let mut result = String::new();
266 for arg in args {
267 result.push_str(&arg.as_string());
268 }
269 Ok(XPathValue::String(result))
270}
271
272fn fn_starts_with(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
274 let s1 = get_string_arg(args, 0);
275 let s2 = get_string_arg(args, 1);
276 Ok(XPathValue::Boolean(s1.starts_with(&s2)))
277}
278
279fn fn_contains(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
281 let s1 = get_string_arg(args, 0);
282 let s2 = get_string_arg(args, 1);
283 Ok(XPathValue::Boolean(s1.contains(&s2)))
284}
285
286fn fn_substring_before(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
288 let s1 = get_string_arg(args, 0);
289 let s2 = get_string_arg(args, 1);
290 if let Some(pos) = s1.find(&s2) {
291 Ok(XPathValue::String(s1[..pos].to_string()))
292 } else {
293 Ok(XPathValue::String(String::new()))
294 }
295}
296
297fn fn_substring_after(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
299 let s1 = get_string_arg(args, 0);
300 let s2 = get_string_arg(args, 1);
301 if let Some(pos) = s1.find(&s2) {
302 Ok(XPathValue::String(s1[pos + s2.len()..].to_string()))
303 } else {
304 Ok(XPathValue::String(String::new()))
305 }
306}
307
308fn fn_substring(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
312 let s = get_string_arg(args, 0);
313 let start = get_number_arg(args, 1);
314 let has_length = args.len() >= 3;
315 let length = if has_length {
316 get_number_arg(args, 2)
317 } else {
318 f64::MAX
319 };
320
321 let start_rounded = start.round() as isize;
322 let length_rounded = length.round() as isize;
323
324 let start_index = if start_rounded < 1 {
326 0
327 } else {
328 (start_rounded - 1) as usize
329 };
330 let length = if length_rounded < 0 {
331 0
332 } else {
333 length_rounded as usize
334 };
335
336 if start_index >= s.len() || length == 0 {
337 Ok(XPathValue::String(String::new()))
338 } else {
339 let end = std::cmp::min(start_index + length, s.len());
340 Ok(XPathValue::String(s[start_index..end].to_string()))
341 }
342}
343
344fn fn_string_length(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
346 let s = if args.is_empty() {
347 node_string_value(ctx.context_node)
348 } else {
349 get_string_arg(args, 0)
350 };
351 Ok(XPathValue::Number(s.len() as f64))
352}
353
354fn fn_normalize_space(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
356 let s = if args.is_empty() {
357 node_string_value(ctx.context_node)
358 } else {
359 get_string_arg(args, 0)
360 };
361 let normalized: Vec<&str> = s.split_whitespace().collect();
362 Ok(XPathValue::String(normalized.join(" ")))
363}
364
365fn fn_translate(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
367 let s = get_string_arg(args, 0);
368 let from = get_string_arg(args, 1);
369 let to = get_string_arg(args, 2);
370
371 let result: String = s
372 .chars()
373 .map(|c| {
374 if let Some(pos) = from.find(c) {
375 if pos < to.len() {
376 to.chars().nth(pos).unwrap_or(c)
377 } else {
378 '\0' }
380 } else {
381 c
382 }
383 })
384 .filter(|&c| c != '\0')
385 .collect();
386
387 Ok(XPathValue::String(result))
388}
389
390fn fn_boolean(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
396 Ok(XPathValue::Boolean(get_boolean_arg(args, 0)))
397}
398
399fn fn_not(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
401 Ok(XPathValue::Boolean(!get_boolean_arg(args, 0)))
402}
403
404const fn fn_true(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
406 Ok(XPathValue::Boolean(true))
407}
408
409const fn fn_false(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
411 Ok(XPathValue::Boolean(false))
412}
413
414fn fn_lang(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
416 let lang = get_string_arg(args, 0);
417 let mut node = ctx.context_node;
418 unsafe {
419 while !node.is_null() {
420 let mut prop = (*node).properties;
421 while !prop.is_null() {
422 let attr_name = crate::xml::string::xmlstr_to_string((*prop).name);
423 if attr_name == "lang" || attr_name == "xml:lang" {
424 if !(*prop).children.is_null() {
426 let attr_val =
427 crate::xml::string::xmlstr_to_string((*(*prop).children).content);
428 if attr_val.to_lowercase() == lang.to_lowercase()
429 || attr_val
430 .to_lowercase()
431 .starts_with(&format!("{}-", lang.to_lowercase()))
432 {
433 return Ok(XPathValue::Boolean(true));
434 }
435 }
436 }
437 prop = (*prop).next;
438 }
439 node = (*node).parent;
440 }
441 }
442 Ok(XPathValue::Boolean(false))
443}
444
445fn fn_number(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
451 if args.is_empty() {
452 Ok(XPathValue::Number(string_to_number(&node_string_value(
453 ctx.context_node,
454 ))))
455 } else {
456 Ok(XPathValue::Number(get_number_arg(args, 0)))
457 }
458}
459
460fn fn_sum(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
462 let ns = get_node_set_arg(args, 0);
463 let mut total = 0.0;
464 for node in ns.iter() {
465 let s = node_string_value(node);
466 total += string_to_number(&s);
467 }
468 Ok(XPathValue::Number(total))
469}
470
471fn fn_floor(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
473 let n = get_number_arg(args, 0);
474 Ok(XPathValue::Number(n.floor()))
475}
476
477fn fn_ceiling(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
479 let n = get_number_arg(args, 0);
480 Ok(XPathValue::Number(n.ceil()))
481}
482
483fn fn_round(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
489 let n = get_number_arg(args, 0);
490 if n.is_nan() || n.is_infinite() || n == 0.0 {
491 return Ok(XPathValue::Number(n));
492 }
493 let rust_rounded = n.round();
497 let result = if n.is_sign_negative() && (n - rust_rounded).abs() == 0.5 {
498 rust_rounded + 1.0
500 } else {
501 rust_rounded
502 };
503 Ok(XPathValue::Number(result))
504}
505
506#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn test_true_false() {
516 let mut ctx = XPathContext::new(std::ptr::null_mut());
517 assert!(fn_true(&mut ctx, &[]).unwrap().as_boolean());
518 assert!(!fn_false(&mut ctx, &[]).unwrap().as_boolean());
519 }
520
521 #[test]
522 fn test_boolean_conversion() {
523 let mut ctx = XPathContext::new(std::ptr::null_mut());
524 assert!(fn_boolean(&mut ctx, &[XPathValue::Boolean(true)])
525 .unwrap()
526 .as_boolean());
527 assert!(!fn_boolean(&mut ctx, &[XPathValue::Boolean(false)])
528 .unwrap()
529 .as_boolean());
530 }
531
532 #[test]
533 fn test_not() {
534 let mut ctx = XPathContext::new(std::ptr::null_mut());
535 assert!(!fn_not(&mut ctx, &[XPathValue::Boolean(true)])
536 .unwrap()
537 .as_boolean());
538 assert!(fn_not(&mut ctx, &[XPathValue::Boolean(false)])
539 .unwrap()
540 .as_boolean());
541 }
542
543 #[test]
544 fn test_number_round() {
545 let mut ctx = XPathContext::new(std::ptr::null_mut());
546 assert_eq!(
547 fn_floor(&mut ctx, &[XPathValue::Number(3.7)])
548 .unwrap()
549 .as_number(),
550 3.0
551 );
552 assert_eq!(
553 fn_ceiling(&mut ctx, &[XPathValue::Number(3.2)])
554 .unwrap()
555 .as_number(),
556 4.0
557 );
558 assert_eq!(
559 fn_round(&mut ctx, &[XPathValue::Number(3.5)])
560 .unwrap()
561 .as_number(),
562 4.0
563 );
564 assert_eq!(
565 fn_round(&mut ctx, &[XPathValue::Number(-3.5)])
566 .unwrap()
567 .as_number(),
568 -3.0
569 );
570 }
571
572 #[test]
573 fn test_string_functions() {
574 let mut ctx = XPathContext::new(std::ptr::null_mut());
575 assert_eq!(
576 fn_concat(
577 &mut ctx,
578 &[
579 XPathValue::String("a".into()),
580 XPathValue::String("b".into()),
581 XPathValue::String("c".into())
582 ]
583 )
584 .unwrap()
585 .as_string(),
586 "abc"
587 );
588 assert!(fn_starts_with(
589 &mut ctx,
590 &[
591 XPathValue::String("hello".into()),
592 XPathValue::String("he".into())
593 ]
594 )
595 .unwrap()
596 .as_boolean());
597 assert!(!fn_starts_with(
598 &mut ctx,
599 &[
600 XPathValue::String("hello".into()),
601 XPathValue::String("x".into())
602 ]
603 )
604 .unwrap()
605 .as_boolean());
606 assert!(fn_contains(
607 &mut ctx,
608 &[
609 XPathValue::String("hello".into()),
610 XPathValue::String("ell".into())
611 ]
612 )
613 .unwrap()
614 .as_boolean());
615 assert_eq!(
616 fn_string_length(&mut ctx, &[XPathValue::String("hello".into())])
617 .unwrap()
618 .as_number(),
619 5.0
620 );
621 }
622
623 #[test]
624 fn test_core_functions_registered() {
625 let funcs = core_functions();
626 assert!(funcs.contains_key("last"));
627 assert!(funcs.contains_key("position"));
628 assert!(funcs.contains_key("count"));
629 assert!(funcs.contains_key("string"));
630 assert!(funcs.contains_key("concat"));
631 assert!(funcs.contains_key("boolean"));
632 assert!(funcs.contains_key("not"));
633 assert!(funcs.contains_key("number"));
634 assert!(funcs.contains_key("sum"));
635 assert!(funcs.contains_key("floor"));
636 assert!(funcs.contains_key("ceiling"));
637 assert!(funcs.contains_key("round"));
638 assert!(funcs.contains_key("name"));
639 assert!(funcs.contains_key("local-name"));
640 assert_eq!(funcs.len(), 27);
641 assert!(funcs.contains_key("namespace-uri"));
642 }
643}