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 CalcValue::Callable(_) => LiteralValue::Error(
19 ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
20 ),
21 })
22}
23
24fn coerce_text(v: &LiteralValue) -> String {
26 match v {
27 LiteralValue::Text(s) => s.clone(),
28 LiteralValue::Empty => String::new(),
29 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
30 LiteralValue::Int(i) => i.to_string(),
31 LiteralValue::Number(f) => {
32 let s = f.to_string();
33 if s.ends_with(".0") {
34 s[..s.len() - 2].to_string()
35 } else {
36 s
37 }
38 }
39 other => other.to_string(),
40 }
41}
42
43fn get_delimiters(arg: &ArgumentHandle<'_, '_>) -> Result<Vec<String>, ExcelError> {
45 let cv = arg.value()?;
46 match cv {
47 CalcValue::Scalar(v) => match v {
48 LiteralValue::Error(e) => Err(e),
49 LiteralValue::Array(arr) => {
50 let mut delims = Vec::new();
51 for row in arr {
52 for cell in row {
53 let s = coerce_text(&cell);
54 if !s.is_empty() {
55 delims.push(s);
56 }
57 }
58 }
59 Ok(delims)
60 }
61 other => {
62 let s = coerce_text(&other);
63 if s.is_empty() {
64 Ok(vec![])
65 } else {
66 Ok(vec![s])
67 }
68 }
69 },
70 CalcValue::Range(rv) => {
71 let mut delims = Vec::new();
72 rv.for_each_cell(&mut |cell| {
73 let s = coerce_text(cell);
74 if !s.is_empty() {
75 delims.push(s);
76 }
77 Ok(())
78 })?;
79 Ok(delims)
80 }
81 CalcValue::Callable(_) => {
82 Err(ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"))
83 }
84 }
85}
86
87fn arg_textsplit() -> Vec<ArgSchema> {
92 vec![
93 ArgSchema {
95 kinds: smallvec::smallvec![ArgKind::Any],
96 required: true,
97 by_ref: false,
98 shape: ShapeKind::Scalar,
99 coercion: CoercionPolicy::None,
100 max: None,
101 repeating: None,
102 default: None,
103 },
104 ArgSchema {
106 kinds: smallvec::smallvec![ArgKind::Any],
107 required: true,
108 by_ref: false,
109 shape: ShapeKind::Scalar,
110 coercion: CoercionPolicy::None,
111 max: None,
112 repeating: None,
113 default: None,
114 },
115 ArgSchema {
117 kinds: smallvec::smallvec![ArgKind::Any],
118 required: false,
119 by_ref: false,
120 shape: ShapeKind::Scalar,
121 coercion: CoercionPolicy::None,
122 max: None,
123 repeating: None,
124 default: None,
125 },
126 ArgSchema {
128 kinds: smallvec::smallvec![ArgKind::Logical],
129 required: false,
130 by_ref: false,
131 shape: ShapeKind::Scalar,
132 coercion: CoercionPolicy::Logical,
133 max: None,
134 repeating: None,
135 default: Some(LiteralValue::Boolean(false)),
136 },
137 ArgSchema {
139 kinds: smallvec::smallvec![ArgKind::Number],
140 required: false,
141 by_ref: false,
142 shape: ShapeKind::Scalar,
143 coercion: CoercionPolicy::NumberLenientText,
144 max: None,
145 repeating: None,
146 default: Some(LiteralValue::Number(0.0)),
147 },
148 ArgSchema {
150 kinds: smallvec::smallvec![ArgKind::Any],
151 required: false,
152 by_ref: false,
153 shape: ShapeKind::Scalar,
154 coercion: CoercionPolicy::None,
155 max: None,
156 repeating: None,
157 default: Some(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))),
158 },
159 ]
160}
161
162fn split_by_delimiters(text: &str, delimiters: &[String], case_insensitive: bool) -> Vec<String> {
164 if delimiters.is_empty() {
165 return vec![text.to_string()];
166 }
167
168 let working_text = if case_insensitive {
169 text.to_lowercase()
170 } else {
171 text.to_string()
172 };
173
174 let delims_working: Vec<String> = if case_insensitive {
175 delimiters.iter().map(|d| d.to_lowercase()).collect()
176 } else {
177 delimiters.to_vec()
178 };
179
180 let mut result = Vec::new();
181 let mut current_start = 0;
182
183 while current_start < text.len() {
184 let mut earliest_match: Option<(usize, usize)> = None; for delim in &delims_working {
187 if delim.is_empty() {
188 continue;
189 }
190 if let Some(pos) = working_text[current_start..].find(delim.as_str()) {
191 let abs_pos = current_start + pos;
192 match earliest_match {
193 None => earliest_match = Some((abs_pos, delim.len())),
194 Some((ep, _)) if abs_pos < ep => earliest_match = Some((abs_pos, delim.len())),
195 _ => {}
196 }
197 }
198 }
199
200 match earliest_match {
201 Some((pos, len)) => {
202 result.push(text[current_start..pos].to_string());
203 current_start = pos + len;
204 }
205 None => {
206 result.push(text[current_start..].to_string());
207 break;
208 }
209 }
210 }
211
212 if current_start == text.len() && !text.is_empty() {
214 let ends_with_delim = delims_working.iter().any(|d| {
215 if d.is_empty() {
216 return false;
217 }
218 working_text.ends_with(d.as_str())
219 });
220 if ends_with_delim {
221 result.push(String::new());
222 }
223 }
224
225 result
226}
227
228#[derive(Debug)]
229pub struct TextSplitFn;
230impl Function for TextSplitFn {
274 func_caps!(PURE);
275
276 fn name(&self) -> &'static str {
277 "TEXTSPLIT"
278 }
279
280 fn min_args(&self) -> usize {
281 2
282 }
283
284 fn arg_schema(&self) -> &'static [ArgSchema] {
285 use once_cell::sync::Lazy;
286 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_textsplit);
287 &SCHEMA
288 }
289
290 fn eval<'a, 'b, 'c>(
291 &self,
292 args: &'c [ArgumentHandle<'a, 'b>],
293 ctx: &dyn FunctionContext<'b>,
294 ) -> Result<CalcValue<'b>, ExcelError> {
295 let text_val = scalar_like_value(&args[0])?;
297 let text = match text_val {
298 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
299 other => coerce_text(&other),
300 };
301
302 let col_delimiters = get_delimiters(&args[1])?;
304
305 let row_delimiters = if args.len() > 2 {
307 let val = scalar_like_value(&args[2])?;
309 match val {
310 LiteralValue::Empty => vec![],
311 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
312 _ => get_delimiters(&args[2])?,
313 }
314 } else {
315 vec![]
316 };
317
318 let ignore_empty = if args.len() > 3 {
320 match scalar_like_value(&args[3])? {
321 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
322 LiteralValue::Boolean(b) => b,
323 LiteralValue::Number(n) => n != 0.0,
324 LiteralValue::Int(i) => i != 0,
325 _ => false,
326 }
327 } else {
328 false
329 };
330
331 let case_insensitive = if args.len() > 4 {
333 match scalar_like_value(&args[4])? {
334 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
335 LiteralValue::Number(n) => n.trunc() as i32 == 1,
336 LiteralValue::Int(i) => i == 1,
337 _ => false,
338 }
339 } else {
340 false
341 };
342
343 let pad_with = if args.len() > 5 {
345 scalar_like_value(&args[5])?
346 } else {
347 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))
348 };
349
350 let row_parts = if row_delimiters.is_empty() {
352 vec![text.clone()]
353 } else {
354 split_by_delimiters(&text, &row_delimiters, case_insensitive)
355 };
356
357 let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
359 let mut max_cols = 0;
360
361 for row_text in row_parts {
362 if ignore_empty && row_text.is_empty() {
363 continue;
364 }
365
366 let col_parts = split_by_delimiters(&row_text, &col_delimiters, case_insensitive);
367
368 let row: Vec<LiteralValue> = if ignore_empty {
369 col_parts
370 .into_iter()
371 .filter(|s| !s.is_empty())
372 .map(LiteralValue::Text)
373 .collect()
374 } else {
375 col_parts.into_iter().map(LiteralValue::Text).collect()
376 };
377
378 if !row.is_empty() {
379 max_cols = max_cols.max(row.len());
380 rows.push(row);
381 }
382 }
383
384 if rows.is_empty() {
386 return Ok(CalcValue::Scalar(LiteralValue::Text(String::new())));
387 }
388
389 for row in &mut rows {
391 while row.len() < max_cols {
392 row.push(pad_with.clone());
393 }
394 }
395
396 Ok(collapse_if_scalar(rows, ctx.date_system()))
397 }
398}
399
400fn arg_valuetotext() -> Vec<ArgSchema> {
405 vec![
406 ArgSchema {
408 kinds: smallvec::smallvec![ArgKind::Any],
409 required: true,
410 by_ref: false,
411 shape: ShapeKind::Scalar,
412 coercion: CoercionPolicy::None,
413 max: None,
414 repeating: None,
415 default: None,
416 },
417 ArgSchema {
419 kinds: smallvec::smallvec![ArgKind::Number],
420 required: false,
421 by_ref: false,
422 shape: ShapeKind::Scalar,
423 coercion: CoercionPolicy::NumberLenientText,
424 max: None,
425 repeating: None,
426 default: Some(LiteralValue::Number(0.0)),
427 },
428 ]
429}
430
431fn value_to_text_repr(v: &LiteralValue, strict: bool) -> String {
433 match v {
434 LiteralValue::Text(s) => {
435 if strict {
436 format!("\"{}\"", s)
437 } else {
438 s.clone()
439 }
440 }
441 LiteralValue::Number(n) => {
442 let s = n.to_string();
443 if s.ends_with(".0") {
444 s[..s.len() - 2].to_string()
445 } else {
446 s
447 }
448 }
449 LiteralValue::Int(i) => i.to_string(),
450 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
451 LiteralValue::Empty => String::new(),
452 LiteralValue::Error(e) => e.to_string(),
453 LiteralValue::Array(arr) => {
454 let rows: Vec<String> = arr
456 .iter()
457 .map(|row| {
458 row.iter()
459 .map(|cell| value_to_text_repr(cell, strict))
460 .collect::<Vec<_>>()
461 .join(",")
462 })
463 .collect();
464 format!("{{{}}}", rows.join(";"))
465 }
466 LiteralValue::Date(d) => d.format("%Y-%m-%d").to_string(),
467 LiteralValue::DateTime(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
468 LiteralValue::Time(t) => t.format("%H:%M:%S").to_string(),
469 LiteralValue::Duration(dur) => {
470 let total_secs = dur.num_seconds();
471 let hours = total_secs / 3600;
472 let mins = (total_secs % 3600) / 60;
473 let secs = total_secs % 60;
474 format!("{}:{:02}:{:02}", hours, mins, secs)
475 }
476 LiteralValue::Pending => String::new(),
477 }
478}
479
480#[derive(Debug)]
481pub struct ValueToTextFn;
482impl Function for ValueToTextFn {
526 func_caps!(PURE);
527
528 fn name(&self) -> &'static str {
529 "VALUETOTEXT"
530 }
531
532 fn min_args(&self) -> usize {
533 1
534 }
535
536 fn arg_schema(&self) -> &'static [ArgSchema] {
537 use once_cell::sync::Lazy;
538 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_valuetotext);
539 &SCHEMA
540 }
541
542 fn eval<'a, 'b, 'c>(
543 &self,
544 args: &'c [ArgumentHandle<'a, 'b>],
545 _ctx: &dyn FunctionContext<'b>,
546 ) -> Result<CalcValue<'b>, ExcelError> {
547 let value = scalar_like_value(&args[0])?;
549
550 let format = if args.len() > 1 {
552 match scalar_like_value(&args[1])? {
553 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
554 LiteralValue::Number(n) => n.trunc() as i32,
555 LiteralValue::Int(i) => i as i32,
556 _ => 0,
557 }
558 } else {
559 0
560 };
561
562 let strict = format == 1;
563
564 if let LiteralValue::Error(e) = &value {
566 if strict {
569 return Ok(CalcValue::Scalar(LiteralValue::Text(e.to_string())));
570 } else {
571 return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
572 }
573 }
574
575 let result = value_to_text_repr(&value, strict);
576 Ok(CalcValue::Scalar(LiteralValue::Text(result)))
577 }
578}
579
580fn arg_arraytotext() -> Vec<ArgSchema> {
585 vec![
586 ArgSchema {
588 kinds: smallvec::smallvec![ArgKind::Any, ArgKind::Range],
589 required: true,
590 by_ref: false,
591 shape: ShapeKind::Range,
592 coercion: CoercionPolicy::None,
593 max: None,
594 repeating: None,
595 default: None,
596 },
597 ArgSchema {
599 kinds: smallvec::smallvec![ArgKind::Number],
600 required: false,
601 by_ref: false,
602 shape: ShapeKind::Scalar,
603 coercion: CoercionPolicy::NumberLenientText,
604 max: None,
605 repeating: None,
606 default: Some(LiteralValue::Number(0.0)),
607 },
608 ]
609}
610
611#[derive(Debug)]
612pub struct ArrayToTextFn;
613impl Function for ArrayToTextFn {
657 func_caps!(PURE);
658
659 fn name(&self) -> &'static str {
660 "ARRAYTOTEXT"
661 }
662
663 fn min_args(&self) -> usize {
664 1
665 }
666
667 fn arg_schema(&self) -> &'static [ArgSchema] {
668 use once_cell::sync::Lazy;
669 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_arraytotext);
670 &SCHEMA
671 }
672
673 fn eval<'a, 'b, 'c>(
674 &self,
675 args: &'c [ArgumentHandle<'a, 'b>],
676 _ctx: &dyn FunctionContext<'b>,
677 ) -> Result<CalcValue<'b>, ExcelError> {
678 let format = if args.len() > 1 {
680 match scalar_like_value(&args[1])? {
681 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
682 LiteralValue::Number(n) => n.trunc() as i32,
683 LiteralValue::Int(i) => i as i32,
684 _ => 0,
685 }
686 } else {
687 0
688 };
689
690 let strict = format == 1;
691
692 let rows: Vec<Vec<LiteralValue>> = if let Ok(rv) = args[0].range_view() {
694 let (num_rows, num_cols) = rv.dims();
695 let mut result = Vec::with_capacity(num_rows);
696 for r in 0..num_rows {
697 let mut row = Vec::with_capacity(num_cols);
698 for c in 0..num_cols {
699 row.push(rv.get_cell(r, c));
700 }
701 result.push(row);
702 }
703 result
704 } else {
705 let cv = args[0].value()?;
706 match cv.into_literal() {
707 LiteralValue::Array(arr) => arr,
708 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
709 other => vec![vec![other]],
710 }
711 };
712
713 let result = if strict {
714 let row_strs: Vec<String> = rows
717 .iter()
718 .map(|row| {
719 row.iter()
720 .map(|cell| value_to_text_repr(cell, true))
721 .collect::<Vec<_>>()
722 .join(",")
723 })
724 .collect();
725 format!("{{{}}}", row_strs.join(";"))
726 } else {
727 let all_values: Vec<String> = rows
729 .iter()
730 .flat_map(|row| row.iter().map(|cell| value_to_text_repr(cell, false)))
731 .collect();
732 all_values.join(", ")
733 };
734
735 Ok(CalcValue::Scalar(LiteralValue::Text(result)))
736 }
737}
738
739pub fn register_builtins() {
744 use crate::function_registry::register_function;
745 use std::sync::Arc;
746
747 register_function(Arc::new(TextSplitFn));
748 register_function(Arc::new(ValueToTextFn));
749 register_function(Arc::new(ArrayToTextFn));
750}
751
752#[cfg(test)]
757mod tests {
758 use super::*;
759 use crate::test_workbook::TestWorkbook;
760 use crate::traits::ArgumentHandle;
761 use formualizer_parse::parser::{ASTNode, ASTNodeType};
762 use std::sync::Arc;
763
764 fn lit(v: LiteralValue) -> ASTNode {
765 ASTNode::new(ASTNodeType::Literal(v), None)
766 }
767
768 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
769 wb.interpreter()
770 }
771
772 #[test]
773 fn test_valuetotext_concise() {
774 let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
775 let ctx = interp(&wb);
776 let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();
777
778 let num = lit(LiteralValue::Number(123.0));
780 let args = vec![ArgumentHandle::new(&num, &ctx)];
781 match f
782 .dispatch(&args, &ctx.function_context(None))
783 .unwrap()
784 .into_literal()
785 {
786 LiteralValue::Text(s) => assert_eq!(s, "123"),
787 v => panic!("unexpected {v:?}"),
788 }
789
790 let text = lit(LiteralValue::Text("hello".to_string()));
792 let args = vec![ArgumentHandle::new(&text, &ctx)];
793 match f
794 .dispatch(&args, &ctx.function_context(None))
795 .unwrap()
796 .into_literal()
797 {
798 LiteralValue::Text(s) => assert_eq!(s, "hello"),
799 v => panic!("unexpected {v:?}"),
800 }
801 }
802
803 #[test]
804 fn test_valuetotext_strict() {
805 let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
806 let ctx = interp(&wb);
807 let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();
808
809 let text = lit(LiteralValue::Text("hello".to_string()));
811 let format = lit(LiteralValue::Number(1.0));
812 let args = vec![
813 ArgumentHandle::new(&text, &ctx),
814 ArgumentHandle::new(&format, &ctx),
815 ];
816 match f
817 .dispatch(&args, &ctx.function_context(None))
818 .unwrap()
819 .into_literal()
820 {
821 LiteralValue::Text(s) => assert_eq!(s, "\"hello\""),
822 v => panic!("unexpected {v:?}"),
823 }
824 }
825
826 #[test]
827 fn test_arraytotext_concise() {
828 let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
829 let ctx = interp(&wb);
830 let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();
831
832 let arr = lit(LiteralValue::Array(vec![vec![
834 LiteralValue::Number(1.0),
835 LiteralValue::Number(2.0),
836 LiteralValue::Number(3.0),
837 ]]));
838 let args = vec![ArgumentHandle::new(&arr, &ctx)];
839 match f
840 .dispatch(&args, &ctx.function_context(None))
841 .unwrap()
842 .into_literal()
843 {
844 LiteralValue::Text(s) => assert_eq!(s, "1, 2, 3"),
845 v => panic!("unexpected {v:?}"),
846 }
847 }
848
849 #[test]
850 fn test_arraytotext_strict() {
851 let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
852 let ctx = interp(&wb);
853 let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();
854
855 let arr = lit(LiteralValue::Array(vec![
857 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
858 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
859 ]));
860 let format = lit(LiteralValue::Number(1.0));
861 let args = vec![
862 ArgumentHandle::new(&arr, &ctx),
863 ArgumentHandle::new(&format, &ctx),
864 ];
865 match f
866 .dispatch(&args, &ctx.function_context(None))
867 .unwrap()
868 .into_literal()
869 {
870 LiteralValue::Text(s) => assert_eq!(s, "{1,2;3,4}"),
871 v => panic!("unexpected {v:?}"),
872 }
873 }
874
875 #[test]
876 fn test_textsplit_basic() {
877 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
878 let ctx = interp(&wb);
879 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
880
881 let text = lit(LiteralValue::Text("a,b,c".to_string()));
883 let delim = lit(LiteralValue::Text(",".to_string()));
884 let args = vec![
885 ArgumentHandle::new(&text, &ctx),
886 ArgumentHandle::new(&delim, &ctx),
887 ];
888 match f
889 .dispatch(&args, &ctx.function_context(None))
890 .unwrap()
891 .into_literal()
892 {
893 LiteralValue::Array(arr) => {
894 assert_eq!(arr.len(), 1);
895 assert_eq!(arr[0].len(), 3);
896 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
897 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
898 assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
899 }
900 v => panic!("unexpected {v:?}"),
901 }
902 }
903
904 #[test]
905 fn test_textsplit_2d() {
906 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
907 let ctx = interp(&wb);
908 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
909
910 let text = lit(LiteralValue::Text("a,b;c,d".to_string()));
912 let col_delim = lit(LiteralValue::Text(",".to_string()));
913 let row_delim = lit(LiteralValue::Text(";".to_string()));
914 let args = vec![
915 ArgumentHandle::new(&text, &ctx),
916 ArgumentHandle::new(&col_delim, &ctx),
917 ArgumentHandle::new(&row_delim, &ctx),
918 ];
919 match f
920 .dispatch(&args, &ctx.function_context(None))
921 .unwrap()
922 .into_literal()
923 {
924 LiteralValue::Array(arr) => {
925 assert_eq!(arr.len(), 2);
926 assert_eq!(arr[0].len(), 2);
927 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
928 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
929 assert_eq!(arr[1][0], LiteralValue::Text("c".to_string()));
930 assert_eq!(arr[1][1], LiteralValue::Text("d".to_string()));
931 }
932 v => panic!("unexpected {v:?}"),
933 }
934 }
935
936 #[test]
937 fn test_textsplit_ignore_empty() {
938 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
939 let ctx = interp(&wb);
940 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
941
942 let text = lit(LiteralValue::Text("a,,b".to_string()));
944 let delim = lit(LiteralValue::Text(",".to_string()));
945 let row_delim = lit(LiteralValue::Empty);
946 let ignore_empty = lit(LiteralValue::Boolean(true));
947 let args = vec![
948 ArgumentHandle::new(&text, &ctx),
949 ArgumentHandle::new(&delim, &ctx),
950 ArgumentHandle::new(&row_delim, &ctx),
951 ArgumentHandle::new(&ignore_empty, &ctx),
952 ];
953 match f
954 .dispatch(&args, &ctx.function_context(None))
955 .unwrap()
956 .into_literal()
957 {
958 LiteralValue::Array(arr) => {
959 assert_eq!(arr.len(), 1);
960 assert_eq!(arr[0].len(), 2);
961 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
962 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
963 }
964 v => panic!("unexpected {v:?}"),
965 }
966 }
967
968 #[test]
969 fn test_textsplit_case_insensitive() {
970 let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
971 let ctx = interp(&wb);
972 let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();
973
974 let text = lit(LiteralValue::Text("aXbxc".to_string()));
976 let delim = lit(LiteralValue::Text("X".to_string()));
977 let row_delim = lit(LiteralValue::Empty);
978 let ignore_empty = lit(LiteralValue::Boolean(false));
979 let match_mode = lit(LiteralValue::Number(1.0)); let args = vec![
981 ArgumentHandle::new(&text, &ctx),
982 ArgumentHandle::new(&delim, &ctx),
983 ArgumentHandle::new(&row_delim, &ctx),
984 ArgumentHandle::new(&ignore_empty, &ctx),
985 ArgumentHandle::new(&match_mode, &ctx),
986 ];
987 match f
988 .dispatch(&args, &ctx.function_context(None))
989 .unwrap()
990 .into_literal()
991 {
992 LiteralValue::Array(arr) => {
993 assert_eq!(arr.len(), 1);
994 assert_eq!(arr[0].len(), 3);
995 assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
996 assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
997 assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
998 }
999 v => panic!("unexpected {v:?}"),
1000 }
1001 }
1002}