1use super::super::utils::collapse_if_scalar;
8use crate::args::{ArgSchema, ShapeKind};
9use crate::function::Function;
10use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
11use formualizer_common::{ArgKind, CoercionPolicy, ExcelError, ExcelErrorKind, LiteralValue};
12use formualizer_macros::func_caps;
13
14fn scalar_like_value(arg: &ArgumentHandle<'_, '_>) -> Result<LiteralValue, ExcelError> {
15 Ok(match arg.value()? {
16 CalcValue::Scalar(v) => v,
17 CalcValue::Range(rv) => rv.get_cell(0, 0),
18 })
19}
20
21fn coerce_text(v: &LiteralValue) -> String {
23 match v {
24 LiteralValue::Text(s) => s.clone(),
25 LiteralValue::Empty => String::new(),
26 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
27 LiteralValue::Int(i) => i.to_string(),
28 LiteralValue::Number(f) => {
29 let s = f.to_string();
30 if s.ends_with(".0") {
31 s[..s.len() - 2].to_string()
32 } else {
33 s
34 }
35 }
36 other => other.to_string(),
37 }
38}
39
40fn get_delimiters(arg: &ArgumentHandle<'_, '_>) -> Result<Vec<String>, ExcelError> {
42 let cv = arg.value()?;
43 match cv {
44 CalcValue::Scalar(v) => match v {
45 LiteralValue::Error(e) => Err(e),
46 LiteralValue::Array(arr) => {
47 let mut delims = Vec::new();
48 for row in arr {
49 for cell in row {
50 let s = coerce_text(&cell);
51 if !s.is_empty() {
52 delims.push(s);
53 }
54 }
55 }
56 Ok(delims)
57 }
58 other => {
59 let s = coerce_text(&other);
60 if s.is_empty() {
61 Ok(vec![])
62 } else {
63 Ok(vec![s])
64 }
65 }
66 },
67 CalcValue::Range(rv) => {
68 let mut delims = Vec::new();
69 rv.for_each_cell(&mut |cell| {
70 let s = coerce_text(cell);
71 if !s.is_empty() {
72 delims.push(s);
73 }
74 Ok(())
75 })?;
76 Ok(delims)
77 }
78 }
79}
80
81fn arg_textsplit() -> Vec<ArgSchema> {
86 vec![
87 ArgSchema {
89 kinds: smallvec::smallvec![ArgKind::Any],
90 required: true,
91 by_ref: false,
92 shape: ShapeKind::Scalar,
93 coercion: CoercionPolicy::None,
94 max: None,
95 repeating: None,
96 default: None,
97 },
98 ArgSchema {
100 kinds: smallvec::smallvec![ArgKind::Any],
101 required: true,
102 by_ref: false,
103 shape: ShapeKind::Scalar,
104 coercion: CoercionPolicy::None,
105 max: None,
106 repeating: None,
107 default: None,
108 },
109 ArgSchema {
111 kinds: smallvec::smallvec![ArgKind::Any],
112 required: false,
113 by_ref: false,
114 shape: ShapeKind::Scalar,
115 coercion: CoercionPolicy::None,
116 max: None,
117 repeating: None,
118 default: None,
119 },
120 ArgSchema {
122 kinds: smallvec::smallvec![ArgKind::Logical],
123 required: false,
124 by_ref: false,
125 shape: ShapeKind::Scalar,
126 coercion: CoercionPolicy::Logical,
127 max: None,
128 repeating: None,
129 default: Some(LiteralValue::Boolean(false)),
130 },
131 ArgSchema {
133 kinds: smallvec::smallvec![ArgKind::Number],
134 required: false,
135 by_ref: false,
136 shape: ShapeKind::Scalar,
137 coercion: CoercionPolicy::NumberLenientText,
138 max: None,
139 repeating: None,
140 default: Some(LiteralValue::Number(0.0)),
141 },
142 ArgSchema {
144 kinds: smallvec::smallvec![ArgKind::Any],
145 required: false,
146 by_ref: false,
147 shape: ShapeKind::Scalar,
148 coercion: CoercionPolicy::None,
149 max: None,
150 repeating: None,
151 default: Some(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))),
152 },
153 ]
154}
155
156fn split_by_delimiters(text: &str, delimiters: &[String], case_insensitive: bool) -> Vec<String> {
158 if delimiters.is_empty() {
159 return vec![text.to_string()];
160 }
161
162 let working_text = if case_insensitive {
163 text.to_lowercase()
164 } else {
165 text.to_string()
166 };
167
168 let delims_working: Vec<String> = if case_insensitive {
169 delimiters.iter().map(|d| d.to_lowercase()).collect()
170 } else {
171 delimiters.to_vec()
172 };
173
174 let mut result = Vec::new();
175 let mut current_start = 0;
176
177 while current_start < text.len() {
178 let mut earliest_match: Option<(usize, usize)> = None; for delim in &delims_working {
181 if delim.is_empty() {
182 continue;
183 }
184 if let Some(pos) = working_text[current_start..].find(delim.as_str()) {
185 let abs_pos = current_start + pos;
186 match earliest_match {
187 None => earliest_match = Some((abs_pos, delim.len())),
188 Some((ep, _)) if abs_pos < ep => earliest_match = Some((abs_pos, delim.len())),
189 _ => {}
190 }
191 }
192 }
193
194 match earliest_match {
195 Some((pos, len)) => {
196 result.push(text[current_start..pos].to_string());
197 current_start = pos + len;
198 }
199 None => {
200 result.push(text[current_start..].to_string());
201 break;
202 }
203 }
204 }
205
206 if current_start == text.len() && !text.is_empty() {
208 let ends_with_delim = delims_working.iter().any(|d| {
209 if d.is_empty() {
210 return false;
211 }
212 working_text.ends_with(d.as_str())
213 });
214 if ends_with_delim {
215 result.push(String::new());
216 }
217 }
218
219 result
220}
221
222#[derive(Debug)]
223pub struct TextSplitFn;
224
225impl Function for TextSplitFn {
226 func_caps!(PURE);
227
228 fn name(&self) -> &'static str {
229 "TEXTSPLIT"
230 }
231
232 fn min_args(&self) -> usize {
233 2
234 }
235
236 fn arg_schema(&self) -> &'static [ArgSchema] {
237 use once_cell::sync::Lazy;
238 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_textsplit);
239 &SCHEMA
240 }
241
242 fn eval<'a, 'b, 'c>(
243 &self,
244 args: &'c [ArgumentHandle<'a, 'b>],
245 ctx: &dyn FunctionContext<'b>,
246 ) -> Result<CalcValue<'b>, ExcelError> {
247 let text_val = scalar_like_value(&args[0])?;
249 let text = match text_val {
250 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
251 other => coerce_text(&other),
252 };
253
254 let col_delimiters = get_delimiters(&args[1])?;
256
257 let row_delimiters = if args.len() > 2 {
259 let val = scalar_like_value(&args[2])?;
261 match val {
262 LiteralValue::Empty => vec![],
263 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
264 _ => get_delimiters(&args[2])?,
265 }
266 } else {
267 vec![]
268 };
269
270 let ignore_empty = if args.len() > 3 {
272 match scalar_like_value(&args[3])? {
273 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
274 LiteralValue::Boolean(b) => b,
275 LiteralValue::Number(n) => n != 0.0,
276 LiteralValue::Int(i) => i != 0,
277 _ => false,
278 }
279 } else {
280 false
281 };
282
283 let case_insensitive = if args.len() > 4 {
285 match scalar_like_value(&args[4])? {
286 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
287 LiteralValue::Number(n) => n.trunc() as i32 == 1,
288 LiteralValue::Int(i) => i == 1,
289 _ => false,
290 }
291 } else {
292 false
293 };
294
295 let pad_with = if args.len() > 5 {
297 scalar_like_value(&args[5])?
298 } else {
299 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))
300 };
301
302 let row_parts = if row_delimiters.is_empty() {
304 vec![text.clone()]
305 } else {
306 split_by_delimiters(&text, &row_delimiters, case_insensitive)
307 };
308
309 let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
311 let mut max_cols = 0;
312
313 for row_text in row_parts {
314 if ignore_empty && row_text.is_empty() {
315 continue;
316 }
317
318 let col_parts = split_by_delimiters(&row_text, &col_delimiters, case_insensitive);
319
320 let row: Vec<LiteralValue> = if ignore_empty {
321 col_parts
322 .into_iter()
323 .filter(|s| !s.is_empty())
324 .map(LiteralValue::Text)
325 .collect()
326 } else {
327 col_parts.into_iter().map(LiteralValue::Text).collect()
328 };
329
330 if !row.is_empty() {
331 max_cols = max_cols.max(row.len());
332 rows.push(row);
333 }
334 }
335
336 if rows.is_empty() {
338 return Ok(CalcValue::Scalar(LiteralValue::Text(String::new())));
339 }
340
341 for row in &mut rows {
343 while row.len() < max_cols {
344 row.push(pad_with.clone());
345 }
346 }
347
348 Ok(collapse_if_scalar(rows, ctx.date_system()))
349 }
350}
351
352fn arg_valuetotext() -> Vec<ArgSchema> {
357 vec![
358 ArgSchema {
360 kinds: smallvec::smallvec![ArgKind::Any],
361 required: true,
362 by_ref: false,
363 shape: ShapeKind::Scalar,
364 coercion: CoercionPolicy::None,
365 max: None,
366 repeating: None,
367 default: None,
368 },
369 ArgSchema {
371 kinds: smallvec::smallvec![ArgKind::Number],
372 required: false,
373 by_ref: false,
374 shape: ShapeKind::Scalar,
375 coercion: CoercionPolicy::NumberLenientText,
376 max: None,
377 repeating: None,
378 default: Some(LiteralValue::Number(0.0)),
379 },
380 ]
381}
382
383fn value_to_text_repr(v: &LiteralValue, strict: bool) -> String {
385 match v {
386 LiteralValue::Text(s) => {
387 if strict {
388 format!("\"{}\"", s)
389 } else {
390 s.clone()
391 }
392 }
393 LiteralValue::Number(n) => {
394 let s = n.to_string();
395 if s.ends_with(".0") {
396 s[..s.len() - 2].to_string()
397 } else {
398 s
399 }
400 }
401 LiteralValue::Int(i) => i.to_string(),
402 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
403 LiteralValue::Empty => String::new(),
404 LiteralValue::Error(e) => e.to_string(),
405 LiteralValue::Array(arr) => {
406 let rows: Vec<String> = arr
408 .iter()
409 .map(|row| {
410 row.iter()
411 .map(|cell| value_to_text_repr(cell, strict))
412 .collect::<Vec<_>>()
413 .join(",")
414 })
415 .collect();
416 format!("{{{}}}", rows.join(";"))
417 }
418 LiteralValue::Date(d) => d.format("%Y-%m-%d").to_string(),
419 LiteralValue::DateTime(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
420 LiteralValue::Time(t) => t.format("%H:%M:%S").to_string(),
421 LiteralValue::Duration(dur) => {
422 let total_secs = dur.num_seconds();
423 let hours = total_secs / 3600;
424 let mins = (total_secs % 3600) / 60;
425 let secs = total_secs % 60;
426 format!("{}:{:02}:{:02}", hours, mins, secs)
427 }
428 LiteralValue::Pending => String::new(),
429 }
430}
431
432#[derive(Debug)]
433pub struct ValueToTextFn;
434
435impl Function for ValueToTextFn {
436 func_caps!(PURE);
437
438 fn name(&self) -> &'static str {
439 "VALUETOTEXT"
440 }
441
442 fn min_args(&self) -> usize {
443 1
444 }
445
446 fn arg_schema(&self) -> &'static [ArgSchema] {
447 use once_cell::sync::Lazy;
448 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_valuetotext);
449 &SCHEMA
450 }
451
452 fn eval<'a, 'b, 'c>(
453 &self,
454 args: &'c [ArgumentHandle<'a, 'b>],
455 _ctx: &dyn FunctionContext<'b>,
456 ) -> Result<CalcValue<'b>, ExcelError> {
457 let value = scalar_like_value(&args[0])?;
459
460 let format = if args.len() > 1 {
462 match scalar_like_value(&args[1])? {
463 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
464 LiteralValue::Number(n) => n.trunc() as i32,
465 LiteralValue::Int(i) => i as i32,
466 _ => 0,
467 }
468 } else {
469 0
470 };
471
472 let strict = format == 1;
473
474 if let LiteralValue::Error(e) = &value {
476 if strict {
479 return Ok(CalcValue::Scalar(LiteralValue::Text(e.to_string())));
480 } else {
481 return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
482 }
483 }
484
485 let result = value_to_text_repr(&value, strict);
486 Ok(CalcValue::Scalar(LiteralValue::Text(result)))
487 }
488}
489
490fn arg_arraytotext() -> Vec<ArgSchema> {
495 vec![
496 ArgSchema {
498 kinds: smallvec::smallvec![ArgKind::Any, ArgKind::Range],
499 required: true,
500 by_ref: false,
501 shape: ShapeKind::Range,
502 coercion: CoercionPolicy::None,
503 max: None,
504 repeating: None,
505 default: None,
506 },
507 ArgSchema {
509 kinds: smallvec::smallvec![ArgKind::Number],
510 required: false,
511 by_ref: false,
512 shape: ShapeKind::Scalar,
513 coercion: CoercionPolicy::NumberLenientText,
514 max: None,
515 repeating: None,
516 default: Some(LiteralValue::Number(0.0)),
517 },
518 ]
519}
520
521#[derive(Debug)]
522pub struct ArrayToTextFn;
523
524impl Function for ArrayToTextFn {
525 func_caps!(PURE);
526
527 fn name(&self) -> &'static str {
528 "ARRAYTOTEXT"
529 }
530
531 fn min_args(&self) -> usize {
532 1
533 }
534
535 fn arg_schema(&self) -> &'static [ArgSchema] {
536 use once_cell::sync::Lazy;
537 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_arraytotext);
538 &SCHEMA
539 }
540
541 fn eval<'a, 'b, 'c>(
542 &self,
543 args: &'c [ArgumentHandle<'a, 'b>],
544 _ctx: &dyn FunctionContext<'b>,
545 ) -> Result<CalcValue<'b>, ExcelError> {
546 let format = if args.len() > 1 {
548 match scalar_like_value(&args[1])? {
549 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
550 LiteralValue::Number(n) => n.trunc() as i32,
551 LiteralValue::Int(i) => i as i32,
552 _ => 0,
553 }
554 } else {
555 0
556 };
557
558 let strict = format == 1;
559
560 let rows: Vec<Vec<LiteralValue>> = if let Ok(rv) = args[0].range_view() {
562 let (num_rows, num_cols) = rv.dims();
563 let mut result = Vec::with_capacity(num_rows);
564 for r in 0..num_rows {
565 let mut row = Vec::with_capacity(num_cols);
566 for c in 0..num_cols {
567 row.push(rv.get_cell(r, c));
568 }
569 result.push(row);
570 }
571 result
572 } else {
573 let cv = args[0].value()?;
574 match cv.into_literal() {
575 LiteralValue::Array(arr) => arr,
576 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
577 other => vec![vec![other]],
578 }
579 };
580
581 let result = if strict {
582 let row_strs: Vec<String> = rows
585 .iter()
586 .map(|row| {
587 row.iter()
588 .map(|cell| value_to_text_repr(cell, true))
589 .collect::<Vec<_>>()
590 .join(",")
591 })
592 .collect();
593 format!("{{{}}}", row_strs.join(";"))
594 } else {
595 let all_values: Vec<String> = rows
597 .iter()
598 .flat_map(|row| row.iter().map(|cell| value_to_text_repr(cell, false)))
599 .collect();
600 all_values.join(", ")
601 };
602
603 Ok(CalcValue::Scalar(LiteralValue::Text(result)))
604 }
605}
606
607pub fn register_builtins() {
612 use crate::function_registry::register_function;
613 use std::sync::Arc;
614
615 register_function(Arc::new(TextSplitFn));
616 register_function(Arc::new(ValueToTextFn));
617 register_function(Arc::new(ArrayToTextFn));
618}
619
620#[cfg(test)]
625mod tests {
626 use super::*;
627 use crate::test_workbook::TestWorkbook;
628 use crate::traits::ArgumentHandle;
629 use formualizer_parse::parser::{ASTNode, ASTNodeType};
630 use std::sync::Arc;
631
632 fn lit(v: LiteralValue) -> ASTNode {
633 ASTNode::new(ASTNodeType::Literal(v), None)
634 }
635
636 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
637 wb.interpreter()
638 }
639
640 #[test]
641 fn test_valuetotext_concise() {
642 let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
643 let ctx = interp(&wb);
644 let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();
645
646 let num = lit(LiteralValue::Number(123.0));
648 let args = vec![ArgumentHandle::new(&num, &ctx)];
649 match f
650 .dispatch(&args, &ctx.function_context(None))
651 .unwrap()
652 .into_literal()
653 {
654 LiteralValue::Text(s) => assert_eq!(s, "123"),
655 v => panic!("unexpected {v:?}"),
656 }
657
658 let text = lit(LiteralValue::Text("hello".to_string()));
660 let args = vec![ArgumentHandle::new(&text, &ctx)];
661 match f
662 .dispatch(&args, &ctx.function_context(None))
663 .unwrap()
664 .into_literal()
665 {
666 LiteralValue::Text(s) => assert_eq!(s, "hello"),
667 v => panic!("unexpected {v:?}"),
668 }
669 }
670
671 #[test]
672 fn test_valuetotext_strict() {
673 let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
674 let ctx = interp(&wb);
675 let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();
676
677 let text = lit(LiteralValue::Text("hello".to_string()));
679 let format = lit(LiteralValue::Number(1.0));
680 let args = vec![
681 ArgumentHandle::new(&text, &ctx),
682 ArgumentHandle::new(&format, &ctx),
683 ];
684 match f
685 .dispatch(&args, &ctx.function_context(None))
686 .unwrap()
687 .into_literal()
688 {
689 LiteralValue::Text(s) => assert_eq!(s, "\"hello\""),
690 v => panic!("unexpected {v:?}"),
691 }
692 }
693
694 #[test]
695 fn test_arraytotext_concise() {
696 let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
697 let ctx = interp(&wb);
698 let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();
699
700 let arr = lit(LiteralValue::Array(vec![vec![
702 LiteralValue::Number(1.0),
703 LiteralValue::Number(2.0),
704 LiteralValue::Number(3.0),
705 ]]));
706 let args = vec![ArgumentHandle::new(&arr, &ctx)];
707 match f
708 .dispatch(&args, &ctx.function_context(None))
709 .unwrap()
710 .into_literal()
711 {
712 LiteralValue::Text(s) => assert_eq!(s, "1, 2, 3"),
713 v => panic!("unexpected {v:?}"),
714 }
715 }
716
717 #[test]
718 fn test_arraytotext_strict() {
719 let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
720 let ctx = interp(&wb);
721 let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();
722
723 let arr = lit(LiteralValue::Array(vec![
725 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
726 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
727 ]));
728 let format = lit(LiteralValue::Number(1.0));
729 let args = vec![
730 ArgumentHandle::new(&arr, &ctx),
731 ArgumentHandle::new(&format, &ctx),
732 ];
733 match f
734 .dispatch(&args, &ctx.function_context(None))
735 .unwrap()
736 .into_literal()
737 {
738 LiteralValue::Text(s) => assert_eq!(s, "{1,2;3,4}"),
739 v => panic!("unexpected {v:?}"),
740 }
741 }
742
743 #[test]
744 fn test_textsplit_basic() {
745 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
746 let ctx = interp(&wb);
747 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
748
749 let text = lit(LiteralValue::Text("a,b,c".to_string()));
751 let delim = lit(LiteralValue::Text(",".to_string()));
752 let args = vec![
753 ArgumentHandle::new(&text, &ctx),
754 ArgumentHandle::new(&delim, &ctx),
755 ];
756 match f
757 .dispatch(&args, &ctx.function_context(None))
758 .unwrap()
759 .into_literal()
760 {
761 LiteralValue::Array(arr) => {
762 assert_eq!(arr.len(), 1);
763 assert_eq!(arr[0].len(), 3);
764 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
765 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
766 assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
767 }
768 v => panic!("unexpected {v:?}"),
769 }
770 }
771
772 #[test]
773 fn test_textsplit_2d() {
774 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
775 let ctx = interp(&wb);
776 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
777
778 let text = lit(LiteralValue::Text("a,b;c,d".to_string()));
780 let col_delim = lit(LiteralValue::Text(",".to_string()));
781 let row_delim = lit(LiteralValue::Text(";".to_string()));
782 let args = vec![
783 ArgumentHandle::new(&text, &ctx),
784 ArgumentHandle::new(&col_delim, &ctx),
785 ArgumentHandle::new(&row_delim, &ctx),
786 ];
787 match f
788 .dispatch(&args, &ctx.function_context(None))
789 .unwrap()
790 .into_literal()
791 {
792 LiteralValue::Array(arr) => {
793 assert_eq!(arr.len(), 2);
794 assert_eq!(arr[0].len(), 2);
795 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
796 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
797 assert_eq!(arr[1][0], LiteralValue::Text("c".to_string()));
798 assert_eq!(arr[1][1], LiteralValue::Text("d".to_string()));
799 }
800 v => panic!("unexpected {v:?}"),
801 }
802 }
803
804 #[test]
805 fn test_textsplit_ignore_empty() {
806 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
807 let ctx = interp(&wb);
808 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
809
810 let text = lit(LiteralValue::Text("a,,b".to_string()));
812 let delim = lit(LiteralValue::Text(",".to_string()));
813 let row_delim = lit(LiteralValue::Empty);
814 let ignore_empty = lit(LiteralValue::Boolean(true));
815 let args = vec![
816 ArgumentHandle::new(&text, &ctx),
817 ArgumentHandle::new(&delim, &ctx),
818 ArgumentHandle::new(&row_delim, &ctx),
819 ArgumentHandle::new(&ignore_empty, &ctx),
820 ];
821 match f
822 .dispatch(&args, &ctx.function_context(None))
823 .unwrap()
824 .into_literal()
825 {
826 LiteralValue::Array(arr) => {
827 assert_eq!(arr.len(), 1);
828 assert_eq!(arr[0].len(), 2);
829 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
830 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
831 }
832 v => panic!("unexpected {v:?}"),
833 }
834 }
835
836 #[test]
837 fn test_textsplit_case_insensitive() {
838 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
839 let ctx = interp(&wb);
840 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
841
842 let text = lit(LiteralValue::Text("aXbxc".to_string()));
844 let delim = lit(LiteralValue::Text("X".to_string()));
845 let row_delim = lit(LiteralValue::Empty);
846 let ignore_empty = lit(LiteralValue::Boolean(false));
847 let match_mode = lit(LiteralValue::Number(1.0)); let args = vec![
849 ArgumentHandle::new(&text, &ctx),
850 ArgumentHandle::new(&delim, &ctx),
851 ArgumentHandle::new(&row_delim, &ctx),
852 ArgumentHandle::new(&ignore_empty, &ctx),
853 ArgumentHandle::new(&match_mode, &ctx),
854 ];
855 match f
856 .dispatch(&args, &ctx.function_context(None))
857 .unwrap()
858 .into_literal()
859 {
860 LiteralValue::Array(arr) => {
861 assert_eq!(arr.len(), 1);
862 assert_eq!(arr[0].len(), 3);
863 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
864 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
865 assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
866 }
867 v => panic!("unexpected {v:?}"),
868 }
869 }
870}