1use crate::xml::xpath::context::XPathContext;
16use crate::xml::xpath::types::{node_string_value, string_to_number, NodeSet, XPathValue};
17use std::collections::HashMap;
18
19pub type XPathFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
23
24pub fn core_functions() -> HashMap<String, XPathFunction> {
26 let mut funcs: HashMap<String, XPathFunction> = HashMap::new();
27
28 funcs.insert("last".into(), fn_last);
30 funcs.insert("position".into(), fn_position);
31 funcs.insert("count".into(), fn_count);
32 funcs.insert("id".into(), fn_id);
33 funcs.insert("local-name".into(), fn_local_name);
34 funcs.insert("namespace-uri".into(), fn_namespace_uri);
35 funcs.insert("name".into(), fn_name);
36
37 funcs.insert("string".into(), fn_string);
39 funcs.insert("concat".into(), fn_concat);
40 funcs.insert("starts-with".into(), fn_starts_with);
41 funcs.insert("contains".into(), fn_contains);
42 funcs.insert("substring-before".into(), fn_substring_before);
43 funcs.insert("substring-after".into(), fn_substring_after);
44 funcs.insert("substring".into(), fn_substring);
45 funcs.insert("string-length".into(), fn_string_length);
46 funcs.insert("normalize-space".into(), fn_normalize_space);
47 funcs.insert("translate".into(), fn_translate);
48
49 funcs.insert("boolean".into(), fn_boolean);
51 funcs.insert("not".into(), fn_not);
52 funcs.insert("true".into(), fn_true);
53 funcs.insert("false".into(), fn_false);
54 funcs.insert("lang".into(), fn_lang);
55
56 funcs.insert("number".into(), fn_number);
58 funcs.insert("sum".into(), fn_sum);
59 funcs.insert("floor".into(), fn_floor);
60 funcs.insert("ceiling".into(), fn_ceiling);
61 funcs.insert("round".into(), fn_round);
62
63 funcs
64}
65
66fn get_string_arg(args: &[XPathValue], index: usize) -> String {
71 if index < args.len() {
72 args[index].as_string()
73 } else {
74 String::new()
75 }
76}
77
78fn get_number_arg(args: &[XPathValue], index: usize) -> f64 {
79 if index < args.len() {
80 args[index].as_number()
81 } else {
82 f64::NAN
83 }
84}
85
86fn get_boolean_arg(args: &[XPathValue], index: usize) -> bool {
87 if index < args.len() {
88 args[index].as_boolean()
89 } else {
90 false
91 }
92}
93
94fn get_node_set_arg(args: &[XPathValue], index: usize) -> NodeSet {
95 if index < args.len() {
96 match &args[index] {
97 XPathValue::NodeSet(ns) => ns.clone(),
98 _ => NodeSet::new(),
99 }
100 } else {
101 NodeSet::new()
102 }
103}
104
105fn get_first_node(
106 ctx: &XPathContext,
107 args: &[XPathValue],
108 index: usize,
109) -> Option<*mut crate::abi::structs::_xmlNode> {
110 if index < args.len() {
111 match &args[index] {
112 XPathValue::NodeSet(ns) => ns.first(),
113 _ => None,
114 }
115 } else {
116 Some(ctx.context_node)
118 }
119}
120
121fn fn_last(ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
127 Ok(XPathValue::Number(ctx.last() as f64))
128}
129
130fn fn_position(ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
132 Ok(XPathValue::Number(ctx.position() as f64))
133}
134
135fn fn_count(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
137 let ns = get_node_set_arg(args, 0);
138 Ok(XPathValue::Number(ns.len() as f64))
139}
140
141fn fn_id(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
143 Ok(XPathValue::NodeSet(NodeSet::new()))
146}
147
148fn fn_local_name(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
150 let node = get_first_node(ctx, args, 0);
151 if let Some(node) = node {
152 unsafe {
153 let name = crate::xml::string::xmlstr_to_string((*node).name);
154 if let Some(pos) = name.find(':') {
156 Ok(XPathValue::String(name[pos + 1..].to_string()))
157 } else {
158 Ok(XPathValue::String(name))
159 }
160 }
161 } else {
162 Ok(XPathValue::String(String::new()))
163 }
164}
165
166fn fn_namespace_uri(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
168 let node = get_first_node(ctx, args, 0);
169 if let Some(node) = node {
170 unsafe {
171 if let Some(ns) = (*node).ns.as_ref() {
172 let uri = crate::xml::string::xmlstr_to_string(ns.href);
173 Ok(XPathValue::String(uri))
174 } else {
175 Ok(XPathValue::String(String::new()))
176 }
177 }
178 } else {
179 Ok(XPathValue::String(String::new()))
180 }
181}
182
183fn fn_name(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
185 let node = get_first_node(ctx, args, 0);
186 if let Some(node) = node {
187 unsafe {
188 let name = crate::xml::string::xmlstr_to_string((*node).name);
189 Ok(XPathValue::String(name))
190 }
191 } else {
192 Ok(XPathValue::String(String::new()))
193 }
194}
195
196fn fn_string(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
202 if args.is_empty() {
203 Ok(XPathValue::String(node_string_value(ctx.context_node)))
205 } else {
206 Ok(XPathValue::String(args[0].as_string()))
207 }
208}
209
210fn fn_concat(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
212 let mut result = String::new();
213 for arg in args {
214 result.push_str(&arg.as_string());
215 }
216 Ok(XPathValue::String(result))
217}
218
219fn fn_starts_with(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
221 let s1 = get_string_arg(args, 0);
222 let s2 = get_string_arg(args, 1);
223 Ok(XPathValue::Boolean(s1.starts_with(&s2)))
224}
225
226fn fn_contains(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
228 let s1 = get_string_arg(args, 0);
229 let s2 = get_string_arg(args, 1);
230 Ok(XPathValue::Boolean(s1.contains(&s2)))
231}
232
233fn fn_substring_before(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
235 let s1 = get_string_arg(args, 0);
236 let s2 = get_string_arg(args, 1);
237 if let Some(pos) = s1.find(&s2) {
238 Ok(XPathValue::String(s1[..pos].to_string()))
239 } else {
240 Ok(XPathValue::String(String::new()))
241 }
242}
243
244fn fn_substring_after(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
246 let s1 = get_string_arg(args, 0);
247 let s2 = get_string_arg(args, 1);
248 if let Some(pos) = s1.find(&s2) {
249 Ok(XPathValue::String(s1[pos + s2.len()..].to_string()))
250 } else {
251 Ok(XPathValue::String(String::new()))
252 }
253}
254
255fn fn_substring(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
259 let s = get_string_arg(args, 0);
260 let start = get_number_arg(args, 1);
261 let has_length = args.len() >= 3;
262 let length = if has_length {
263 get_number_arg(args, 2)
264 } else {
265 f64::MAX
266 };
267
268 let start_rounded = start.round() as isize;
269 let length_rounded = length.round() as isize;
270
271 let start_index = if start_rounded < 1 {
273 0
274 } else {
275 (start_rounded - 1) as usize
276 };
277 let length = if length_rounded < 0 {
278 0
279 } else {
280 length_rounded as usize
281 };
282
283 if start_index >= s.len() || length == 0 {
284 Ok(XPathValue::String(String::new()))
285 } else {
286 let end = std::cmp::min(start_index + length, s.len());
287 Ok(XPathValue::String(s[start_index..end].to_string()))
288 }
289}
290
291fn fn_string_length(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
293 let s = if args.is_empty() {
294 node_string_value(ctx.context_node)
295 } else {
296 get_string_arg(args, 0)
297 };
298 Ok(XPathValue::Number(s.len() as f64))
299}
300
301fn fn_normalize_space(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
303 let s = if args.is_empty() {
304 node_string_value(ctx.context_node)
305 } else {
306 get_string_arg(args, 0)
307 };
308 let normalized: Vec<&str> = s.split_whitespace().collect();
309 Ok(XPathValue::String(normalized.join(" ")))
310}
311
312fn fn_translate(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
314 let s = get_string_arg(args, 0);
315 let from = get_string_arg(args, 1);
316 let to = get_string_arg(args, 2);
317
318 let result: String = s
319 .chars()
320 .map(|c| {
321 if let Some(pos) = from.find(c) {
322 if pos < to.len() {
323 to.chars().nth(pos).unwrap_or(c)
324 } else {
325 '\0' }
327 } else {
328 c
329 }
330 })
331 .filter(|&c| c != '\0')
332 .collect();
333
334 Ok(XPathValue::String(result))
335}
336
337fn fn_boolean(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
343 Ok(XPathValue::Boolean(get_boolean_arg(args, 0)))
344}
345
346fn fn_not(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
348 Ok(XPathValue::Boolean(!get_boolean_arg(args, 0)))
349}
350
351fn fn_true(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
353 Ok(XPathValue::Boolean(true))
354}
355
356fn fn_false(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
358 Ok(XPathValue::Boolean(false))
359}
360
361fn fn_lang(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
363 let lang = get_string_arg(args, 0);
364 let mut node = ctx.context_node;
365 unsafe {
366 while !node.is_null() {
367 let mut prop = (*node).properties;
368 while !prop.is_null() {
369 let attr_name = crate::xml::string::xmlstr_to_string((*prop).name);
370 if attr_name == "lang" || attr_name == "xml:lang" {
371 if !(*prop).children.is_null() {
373 let attr_val =
374 crate::xml::string::xmlstr_to_string((*(*prop).children).content);
375 if attr_val.to_lowercase() == lang.to_lowercase()
376 || attr_val
377 .to_lowercase()
378 .starts_with(&format!("{}-", lang.to_lowercase()))
379 {
380 return Ok(XPathValue::Boolean(true));
381 }
382 }
383 }
384 prop = (*prop).next;
385 }
386 node = (*node).parent;
387 }
388 }
389 Ok(XPathValue::Boolean(false))
390}
391
392fn fn_number(ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
398 if args.is_empty() {
399 Ok(XPathValue::Number(string_to_number(&node_string_value(
400 ctx.context_node,
401 ))))
402 } else {
403 Ok(XPathValue::Number(get_number_arg(args, 0)))
404 }
405}
406
407fn fn_sum(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
409 let ns = get_node_set_arg(args, 0);
410 let mut total = 0.0;
411 for node in ns.iter() {
412 let s = node_string_value(node);
413 total += string_to_number(&s);
414 }
415 Ok(XPathValue::Number(total))
416}
417
418fn fn_floor(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
420 let n = get_number_arg(args, 0);
421 Ok(XPathValue::Number(n.floor()))
422}
423
424fn fn_ceiling(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
426 let n = get_number_arg(args, 0);
427 Ok(XPathValue::Number(n.ceil()))
428}
429
430fn fn_round(_ctx: &mut XPathContext, args: &[XPathValue]) -> Result<XPathValue, String> {
436 let n = get_number_arg(args, 0);
437 if n.is_nan() || n.is_infinite() || n == 0.0 {
438 return Ok(XPathValue::Number(n));
439 }
440 let rust_rounded = n.round();
444 let result = if n.is_sign_negative() && (n - rust_rounded).abs() == 0.5 {
445 rust_rounded + 1.0
447 } else {
448 rust_rounded
449 };
450 Ok(XPathValue::Number(result))
451}
452
453#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn test_true_false() {
463 let mut ctx = XPathContext::new(std::ptr::null_mut());
464 assert_eq!(fn_true(&mut ctx, &[]).unwrap().as_boolean(), true);
465 assert_eq!(fn_false(&mut ctx, &[]).unwrap().as_boolean(), false);
466 }
467
468 #[test]
469 fn test_boolean_conversion() {
470 let mut ctx = XPathContext::new(std::ptr::null_mut());
471 assert_eq!(
472 fn_boolean(&mut ctx, &[XPathValue::Boolean(true)])
473 .unwrap()
474 .as_boolean(),
475 true
476 );
477 assert_eq!(
478 fn_boolean(&mut ctx, &[XPathValue::Boolean(false)])
479 .unwrap()
480 .as_boolean(),
481 false
482 );
483 }
484
485 #[test]
486 fn test_not() {
487 let mut ctx = XPathContext::new(std::ptr::null_mut());
488 assert_eq!(
489 fn_not(&mut ctx, &[XPathValue::Boolean(true)])
490 .unwrap()
491 .as_boolean(),
492 false
493 );
494 assert_eq!(
495 fn_not(&mut ctx, &[XPathValue::Boolean(false)])
496 .unwrap()
497 .as_boolean(),
498 true
499 );
500 }
501
502 #[test]
503 fn test_number_round() {
504 let mut ctx = XPathContext::new(std::ptr::null_mut());
505 assert_eq!(
506 fn_floor(&mut ctx, &[XPathValue::Number(3.7)])
507 .unwrap()
508 .as_number(),
509 3.0
510 );
511 assert_eq!(
512 fn_ceiling(&mut ctx, &[XPathValue::Number(3.2)])
513 .unwrap()
514 .as_number(),
515 4.0
516 );
517 assert_eq!(
518 fn_round(&mut ctx, &[XPathValue::Number(3.5)])
519 .unwrap()
520 .as_number(),
521 4.0
522 );
523 assert_eq!(
524 fn_round(&mut ctx, &[XPathValue::Number(-3.5)])
525 .unwrap()
526 .as_number(),
527 -3.0
528 );
529 }
530
531 #[test]
532 fn test_string_functions() {
533 let mut ctx = XPathContext::new(std::ptr::null_mut());
534 assert_eq!(
535 fn_concat(
536 &mut ctx,
537 &[
538 XPathValue::String("a".into()),
539 XPathValue::String("b".into()),
540 XPathValue::String("c".into())
541 ]
542 )
543 .unwrap()
544 .as_string(),
545 "abc"
546 );
547 assert_eq!(
548 fn_starts_with(
549 &mut ctx,
550 &[
551 XPathValue::String("hello".into()),
552 XPathValue::String("he".into())
553 ]
554 )
555 .unwrap()
556 .as_boolean(),
557 true
558 );
559 assert_eq!(
560 fn_starts_with(
561 &mut ctx,
562 &[
563 XPathValue::String("hello".into()),
564 XPathValue::String("x".into())
565 ]
566 )
567 .unwrap()
568 .as_boolean(),
569 false
570 );
571 assert_eq!(
572 fn_contains(
573 &mut ctx,
574 &[
575 XPathValue::String("hello".into()),
576 XPathValue::String("ell".into())
577 ]
578 )
579 .unwrap()
580 .as_boolean(),
581 true
582 );
583 assert_eq!(
584 fn_string_length(&mut ctx, &[XPathValue::String("hello".into())])
585 .unwrap()
586 .as_number(),
587 5.0
588 );
589 }
590
591 #[test]
592 fn test_core_functions_registered() {
593 let funcs = core_functions();
594 assert!(funcs.contains_key("last"));
595 assert!(funcs.contains_key("position"));
596 assert!(funcs.contains_key("count"));
597 assert!(funcs.contains_key("string"));
598 assert!(funcs.contains_key("concat"));
599 assert!(funcs.contains_key("boolean"));
600 assert!(funcs.contains_key("not"));
601 assert!(funcs.contains_key("number"));
602 assert!(funcs.contains_key("sum"));
603 assert!(funcs.contains_key("floor"));
604 assert!(funcs.contains_key("ceiling"));
605 assert!(funcs.contains_key("round"));
606 assert!(funcs.contains_key("name"));
607 assert!(funcs.contains_key("local-name"));
608 assert_eq!(funcs.len(), 27);
609 assert!(funcs.contains_key("namespace-uri"));
610 }
611}