1use crate::cell::serial_to_date;
10
11pub fn builtin_format_code(id: u32) -> Option<&'static str> {
13 match id {
14 0 => Some("General"),
15 1 => Some("0"),
16 2 => Some("0.00"),
17 3 => Some("#,##0"),
18 4 => Some("#,##0.00"),
19 5 => Some("#,##0_);(#,##0)"),
20 6 => Some("#,##0_);[Red](#,##0)"),
21 7 => Some("#,##0.00_);(#,##0.00)"),
22 8 => Some("#,##0.00_);[Red](#,##0.00)"),
23 9 => Some("0%"),
24 10 => Some("0.00%"),
25 11 => Some("0.00E+00"),
26 12 => Some("# ?/?"),
27 13 => Some("# ??/??"),
28 14 => Some("m/d/yyyy"),
29 15 => Some("d-mmm-yy"),
30 16 => Some("d-mmm"),
31 17 => Some("mmm-yy"),
32 18 => Some("h:mm AM/PM"),
33 19 => Some("h:mm:ss AM/PM"),
34 20 => Some("h:mm"),
35 21 => Some("h:mm:ss"),
36 22 => Some("m/d/yyyy h:mm"),
37 37 => Some("#,##0_);(#,##0)"),
38 38 => Some("#,##0_);[Red](#,##0)"),
39 39 => Some("#,##0.00_);(#,##0.00)"),
40 40 => Some("#,##0.00_);[Red](#,##0.00)"),
41 41 => Some(r#"_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)"#),
42 42 => Some(r#"_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)"#),
43 43 => Some(r#"_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)"#),
44 44 => Some(r#"_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)"#),
45 45 => Some("mm:ss"),
46 46 => Some("[h]:mm:ss"),
47 47 => Some("mm:ss.0"),
48 48 => Some("##0.0E+0"),
49 49 => Some("@"),
50 _ => None,
51 }
52}
53
54pub fn format_number(value: f64, format_code: &str) -> String {
59 if format_code.is_empty() || format_code.eq_ignore_ascii_case("General") {
60 return format_general(value);
61 }
62
63 let sections = parse_sections(format_code);
64
65 let has_any_condition = sections.iter().any(|s| extract_condition(s).is_some());
66 let section = pick_section(§ions, value);
67
68 let (cleaned, _color) = strip_color_and_condition(section);
69
70 let use_abs = if has_any_condition {
76 sections.len() >= 2
77 } else {
78 sections.len() >= 2 && value < 0.0
79 };
80 let effective_value = if use_abs { value.abs() } else { value };
81
82 if cleaned == "@" {
83 return format_general(effective_value);
84 }
85
86 if is_date_time_format(&cleaned) {
87 return format_date_time(effective_value, &cleaned);
88 }
89
90 if cleaned.contains('?') && cleaned.contains('/') {
91 return format_fraction(effective_value, &cleaned);
92 }
93
94 if format_has_unquoted_char(&cleaned, 'E') || format_has_unquoted_char(&cleaned, 'e') {
95 return format_scientific(effective_value, &cleaned);
96 }
97
98 format_numeric(effective_value, &cleaned)
99}
100
101pub fn format_with_builtin(value: f64, id: u32) -> Option<String> {
104 let code = builtin_format_code(id)?;
105 Some(format_number(value, code))
106}
107
108fn format_general(value: f64) -> String {
109 if value == 0.0 {
110 return "0".to_string();
111 }
112 if value.fract() == 0.0 && value.is_finite() && value.abs() < 1e15 {
113 return format!("{}", value as i64);
114 }
115 let abs = value.abs();
117 if (1e-4..1e15).contains(&abs) {
118 let s = format!("{:.10}", value);
119 trim_trailing_zeros(&s)
120 } else if abs < 1e-4 && abs > 0.0 {
121 format!("{:.6E}", value)
122 } else {
123 format!("{}", value)
124 }
125}
126
127fn trim_trailing_zeros(s: &str) -> String {
128 if let Some(dot) = s.find('.') {
129 let trimmed = s.trim_end_matches('0');
130 if trimmed.len() == dot + 1 {
131 trimmed[..dot].to_string()
132 } else {
133 trimmed.to_string()
134 }
135 } else {
136 s.to_string()
137 }
138}
139
140fn parse_sections(format_code: &str) -> Vec<&str> {
141 let mut sections = Vec::new();
142 let mut start = 0;
143 let mut in_quotes = false;
144 let mut prev_backslash = false;
145
146 for (i, ch) in format_code.char_indices() {
147 if prev_backslash {
148 prev_backslash = false;
149 continue;
150 }
151 if ch == '\\' {
152 prev_backslash = true;
153 continue;
154 }
155 if ch == '"' {
156 in_quotes = !in_quotes;
157 continue;
158 }
159 if !in_quotes && ch == ';' {
160 sections.push(&format_code[start..i]);
161 start = i + 1;
162 }
163 }
164 sections.push(&format_code[start..]);
165 sections
166}
167
168#[derive(Debug, Clone, Copy, PartialEq)]
170enum ConditionOp {
171 Gt,
172 Ge,
173 Lt,
174 Le,
175 Eq,
176 Ne,
177}
178
179#[derive(Debug, Clone, PartialEq)]
181struct Condition {
182 op: ConditionOp,
183 threshold: f64,
184}
185
186impl Condition {
187 fn matches(&self, value: f64) -> bool {
188 match self.op {
189 ConditionOp::Gt => value > self.threshold,
190 ConditionOp::Ge => value >= self.threshold,
191 ConditionOp::Lt => value < self.threshold,
192 ConditionOp::Le => value <= self.threshold,
193 ConditionOp::Eq => (value - self.threshold).abs() < 1e-12,
194 ConditionOp::Ne => (value - self.threshold).abs() >= 1e-12,
195 }
196 }
197}
198
199fn parse_condition(content: &str) -> Option<Condition> {
202 let s = content.trim();
203 if s.is_empty() {
204 return None;
205 }
206
207 let (op, rest) = if let Some(r) = s.strip_prefix(">=") {
209 (ConditionOp::Ge, r)
210 } else if let Some(r) = s.strip_prefix("<=") {
211 (ConditionOp::Le, r)
212 } else if let Some(r) = s.strip_prefix("<>").or_else(|| s.strip_prefix("!=")) {
213 (ConditionOp::Ne, r)
214 } else if let Some(r) = s.strip_prefix('>') {
215 (ConditionOp::Gt, r)
216 } else if let Some(r) = s.strip_prefix('<') {
217 (ConditionOp::Lt, r)
218 } else if let Some(r) = s.strip_prefix('=') {
219 (ConditionOp::Eq, r)
220 } else {
221 return None;
222 };
223
224 let threshold: f64 = rest.trim().parse().ok()?;
225 Some(Condition { op, threshold })
226}
227
228fn extract_condition(section: &str) -> Option<Condition> {
230 let mut chars = section.chars().peekable();
231 while let Some(&ch) = chars.peek() {
232 if ch == '[' {
233 chars.next();
234 let mut bracket_content = String::new();
235 while let Some(&c) = chars.peek() {
236 if c == ']' {
237 chars.next();
238 break;
239 }
240 bracket_content.push(c);
241 chars.next();
242 }
243 let lower = bracket_content.to_ascii_lowercase();
244 let is_known_non_condition = is_color_code(&lower)
245 || lower.starts_with("dbnum")
246 || lower.starts_with('$')
247 || lower.starts_with("natnum")
248 || (lower.starts_with('h') && lower.contains(':'))
249 || lower.starts_with("mm")
250 || lower.starts_with("ss");
251 if !is_known_non_condition {
252 if let Some(cond) = parse_condition(&bracket_content) {
253 return Some(cond);
254 }
255 }
256 } else {
257 chars.next();
258 }
259 }
260 None
261}
262
263fn pick_section<'a>(sections: &[&'a str], value: f64) -> &'a str {
269 let conditions: Vec<Option<Condition>> =
271 sections.iter().map(|s| extract_condition(s)).collect();
272
273 let has_any_condition = conditions.iter().any(|c| c.is_some());
274
275 if has_any_condition {
276 for (i, cond) in conditions.iter().enumerate() {
278 if let Some(c) = cond {
279 if c.matches(value) {
280 return sections[i];
281 }
282 }
283 }
284 for (i, cond) in conditions.iter().enumerate() {
287 if cond.is_none() {
288 return sections[i];
289 }
290 }
291 return sections.last().unwrap_or(&"General");
293 }
294
295 match sections.len() {
297 1 => sections[0],
298 2 => {
299 if value >= 0.0 {
300 sections[0]
301 } else {
302 sections[1]
303 }
304 }
305 3 | 4.. => {
306 if value > 0.0 {
307 sections[0]
308 } else if value < 0.0 {
309 sections[1]
310 } else {
311 sections[2]
312 }
313 }
314 _ => "General",
315 }
316}
317
318fn strip_color_and_condition(section: &str) -> (String, Option<String>) {
321 let mut result = String::with_capacity(section.len());
322 let mut color = None;
323 let mut chars = section.chars().peekable();
324
325 while let Some(&ch) = chars.peek() {
326 if ch == '[' {
327 let mut bracket_content = String::new();
328 chars.next(); while let Some(&c) = chars.peek() {
330 if c == ']' {
331 chars.next(); break;
333 }
334 bracket_content.push(c);
335 chars.next();
336 }
337 let lower = bracket_content.to_ascii_lowercase();
338 if is_color_code(&lower) {
339 color = Some(bracket_content);
340 } else if lower.starts_with('h') && lower.contains(':') {
341 result.push('[');
343 result.push_str(&bracket_content);
344 result.push(']');
345 } else if lower.starts_with("mm") || lower.starts_with("ss") {
346 result.push('[');
347 result.push_str(&bracket_content);
348 result.push(']');
349 } else if parse_condition(&bracket_content).is_some() {
350 } else if lower.starts_with("dbnum")
353 || lower.starts_with("$")
354 || lower.starts_with("natnum")
355 {
356 } else {
358 result.push('[');
360 result.push_str(&bracket_content);
361 result.push(']');
362 }
363 } else {
364 result.push(ch);
365 chars.next();
366 }
367 }
368
369 (result, color)
370}
371
372fn is_color_code(lower: &str) -> bool {
373 matches!(
374 lower,
375 "red"
376 | "blue"
377 | "green"
378 | "yellow"
379 | "cyan"
380 | "magenta"
381 | "white"
382 | "black"
383 | "color1"
384 | "color2"
385 | "color3"
386 | "color4"
387 | "color5"
388 | "color6"
389 | "color7"
390 | "color8"
391 | "color9"
392 | "color10"
393 )
394}
395
396fn is_date_time_format(format: &str) -> bool {
397 let mut in_quotes = false;
398 let mut prev_backslash = false;
399 for ch in format.chars() {
400 if prev_backslash {
401 prev_backslash = false;
402 continue;
403 }
404 if ch == '\\' {
405 prev_backslash = true;
406 continue;
407 }
408 if ch == '"' {
409 in_quotes = !in_quotes;
410 continue;
411 }
412 if in_quotes {
413 continue;
414 }
415 let lower = ch.to_ascii_lowercase();
416 if matches!(lower, 'y' | 'd' | 'h' | 's') {
417 return true;
418 }
419 if lower == 'm' {
420 return true;
421 }
422 }
423 false
424}
425
426fn format_date_time(value: f64, format: &str) -> String {
427 let int_part = value.floor() as i64;
428 let frac = value.fract().abs();
429 let total_seconds = (frac * 86_400.0).round() as u64;
430 let mut hours = (total_seconds / 3600) as u32;
431 let minutes = ((total_seconds % 3600) / 60) as u32;
432 let seconds = (total_seconds % 60) as u32;
433 let subsec_frac = (frac * 86_400.0) - (total_seconds as f64);
434
435 let date_opt = serial_to_date(value);
436 let (year, month, day) = if let Some(date) = date_opt {
437 (date.year() as u32, date.month(), date.day())
438 } else {
439 (1900, 1, 1)
440 };
441
442 let has_ampm = {
443 let lower = format.to_ascii_lowercase();
444 lower.contains("am/pm") || lower.contains("a/p")
445 };
446
447 let mut ampm_str = "";
448 if has_ampm {
449 if hours == 0 {
450 hours = 12;
451 ampm_str = "AM";
452 } else if hours < 12 {
453 ampm_str = "AM";
454 } else if hours == 12 {
455 ampm_str = "PM";
456 } else {
457 hours -= 12;
458 ampm_str = "PM";
459 }
460 }
461
462 let month_names_short = [
463 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
464 ];
465 let month_names_long = [
466 "January",
467 "February",
468 "March",
469 "April",
470 "May",
471 "June",
472 "July",
473 "August",
474 "September",
475 "October",
476 "November",
477 "December",
478 ];
479
480 let mut result = String::with_capacity(format.len() * 2);
481 let chars: Vec<char> = format.chars().collect();
482 let len = chars.len();
483 let mut i = 0;
484 let mut in_quotes = false;
485
486 while i < len {
487 let ch = chars[i];
488
489 if ch == '"' {
490 in_quotes = !in_quotes;
491 i += 1;
492 continue;
493 }
494 if in_quotes {
495 result.push(ch);
496 i += 1;
497 continue;
498 }
499 if ch == '\\' && i + 1 < len {
500 result.push(chars[i + 1]);
501 i += 2;
502 continue;
503 }
504
505 if ch == '_' && i + 1 < len {
507 result.push(' ');
508 i += 2;
509 continue;
510 }
511 if ch == '*' && i + 1 < len {
513 i += 2;
514 continue;
515 }
516
517 let lower = ch.to_ascii_lowercase();
518
519 if lower == 'y' {
520 let count = count_char(&chars, i, 'y');
521 if count <= 2 {
522 result.push_str(&format!("{:02}", year % 100));
523 } else {
524 result.push_str(&format!("{:04}", year));
525 }
526 i += count;
527 continue;
528 }
529
530 if lower == 'm' {
531 let count = count_char(&chars, i, 'm');
532 if is_m_minute_context(&chars, i) {
533 if count == 1 {
535 result.push_str(&format!("{}", minutes));
536 } else {
537 result.push_str(&format!("{:02}", minutes));
538 }
539 } else {
540 match count {
542 1 => result.push_str(&format!("{}", month)),
543 2 => result.push_str(&format!("{:02}", month)),
544 3 => {
545 if (1..=12).contains(&month) {
546 result.push_str(month_names_short[(month - 1) as usize]);
547 }
548 }
549 4 => {
550 if (1..=12).contains(&month) {
551 result.push_str(month_names_long[(month - 1) as usize]);
552 }
553 }
554 _ => {
555 result.push_str(&format!("{:02}", month));
556 }
557 }
558 }
559 i += count;
560 continue;
561 }
562
563 if lower == 'd' {
564 let count = count_char(&chars, i, 'd');
565 match count {
566 1 => result.push_str(&format!("{}", day)),
567 2 => result.push_str(&format!("{:02}", day)),
568 3 => {
569 if let Some(date) = date_opt {
570 let day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
571 let wd = date.weekday().num_days_from_monday() as usize;
572 result.push_str(day_names[wd]);
573 }
574 }
575 _ => {
576 if let Some(date) = date_opt {
577 let day_names = [
578 "Monday",
579 "Tuesday",
580 "Wednesday",
581 "Thursday",
582 "Friday",
583 "Saturday",
584 "Sunday",
585 ];
586 let wd = date.weekday().num_days_from_monday() as usize;
587 result.push_str(day_names[wd]);
588 }
589 }
590 }
591 i += count;
592 continue;
593 }
594
595 if lower == 'h' {
596 let count = count_char(&chars, i, 'h');
597 if i > 0 && chars[i - 1] == '[' {
599 let serial_days = value.floor() as i64;
601 let elapsed_h = (serial_days as u64) * 24 + total_seconds / 3600;
602 let mut end = i + count;
604 if end < len && chars[end] == ']' {
605 end += 1; }
607 result.push_str(&format!("{}", elapsed_h));
608 i = end;
609 continue;
610 }
611 if count == 1 {
612 result.push_str(&format!("{}", hours));
613 } else {
614 result.push_str(&format!("{:02}", hours));
615 }
616 i += count;
617 continue;
618 }
619
620 if lower == 's' {
621 let count = count_char(&chars, i, 's');
622 if count == 1 {
623 result.push_str(&format!("{}", seconds));
624 } else {
625 result.push_str(&format!("{:02}", seconds));
626 }
627 i += count;
628 continue;
629 }
630
631 if lower == 'a' {
633 if i + 4 < len {
634 let slice: String = chars[i..i + 5].iter().collect();
635 if slice.eq_ignore_ascii_case("AM/PM") {
636 result.push_str(ampm_str);
637 i += 5;
638 continue;
639 }
640 }
641 if i + 2 < len {
642 let slice: String = chars[i..i + 3].iter().collect();
643 if slice.eq_ignore_ascii_case("A/P") {
644 if ampm_str == "AM" {
645 result.push('A');
646 } else {
647 result.push('P');
648 }
649 i += 3;
650 continue;
651 }
652 }
653 result.push(ch);
654 i += 1;
655 continue;
656 }
657
658 if ch == '[' {
660 if i + 2 < len && chars[i + 1].eq_ignore_ascii_case(&'h') {
662 result.push(ch);
664 i += 1;
665 continue;
666 }
667 if i + 2 < len && chars[i + 1].eq_ignore_ascii_case(&'m') {
668 let count = count_char(&chars, i + 1, 'm');
669 let end = i + 1 + count;
670 if end < len && chars[end] == ']' {
671 let elapsed_m = (int_part as u64) * 24 * 60 + total_seconds / 60;
672 result.push_str(&format!("{}", elapsed_m));
673 i = end + 1;
674 continue;
675 }
676 }
677 if i + 2 < len && chars[i + 1].eq_ignore_ascii_case(&'s') {
678 let count = count_char(&chars, i + 1, 's');
679 let end = i + 1 + count;
680 if end < len && chars[end] == ']' {
681 let elapsed_s = (int_part as u64) * 24 * 3600 + total_seconds;
682 result.push_str(&format!("{}", elapsed_s));
683 i = end + 1;
684 continue;
685 }
686 }
687 result.push(ch);
688 i += 1;
689 continue;
690 }
691
692 if ch == '.' && i + 1 < len && chars[i + 1] == '0' {
693 result.push('.');
695 let count = count_char(&chars, i + 1, '0');
696 let sub = subsec_frac.abs();
697 let digits = format!("{:.*}", count, sub);
698 if let Some(dot_pos) = digits.find('.') {
700 result.push_str(&digits[dot_pos + 1..]);
701 }
702 i += 1 + count;
703 continue;
704 }
705
706 result.push(ch);
708 i += 1;
709 }
710
711 result
712}
713
714fn is_m_minute_context(chars: &[char], pos: usize) -> bool {
718 let mut j = pos;
720 while j > 0 {
721 j -= 1;
722 let c = chars[j].to_ascii_lowercase();
723 if c == 'h' {
724 return true;
725 }
726 if c == ':' || c == ' ' || c == ']' || c == '[' {
727 continue;
728 }
729 break;
730 }
731 let m_count = count_char(chars, pos, 'm');
733 let mut k = pos + m_count;
734 while k < chars.len() {
735 let c = chars[k].to_ascii_lowercase();
736 if c == 's' {
737 return true;
738 }
739 if c == ':' || c == ' ' || c == '0' || c == '.' {
740 k += 1;
741 continue;
742 }
743 break;
744 }
745 false
746}
747
748fn count_char(chars: &[char], start: usize, target: char) -> usize {
749 let lower_target = target.to_ascii_lowercase();
750 let mut count = 0;
751 let mut i = start;
752 while i < chars.len() && chars[i].to_ascii_lowercase() == lower_target {
753 count += 1;
754 i += 1;
755 }
756 count
757}
758
759fn format_numeric(value: f64, format: &str) -> String {
760 let is_negative = value < 0.0;
761 let abs_val = value.abs();
762
763 let has_percent = format_has_unquoted_char(format, '%');
766 let display_val = if has_percent {
767 abs_val * 100.0
768 } else {
769 abs_val
770 };
771
772 let decimal_places = count_decimal_places(format);
774
775 let has_comma_grouping = has_thousands_separator(format);
777
778 let trailing_comma_count = count_trailing_commas(format);
780 let display_val = display_val / 1000f64.powi(trailing_comma_count as i32);
781
782 let rounded = if decimal_places > 0 {
784 let factor = 10f64.powi(decimal_places as i32);
785 (display_val * factor).round() / factor
786 } else {
787 display_val.round()
788 };
789
790 let int_part = rounded.trunc() as u64;
791 let frac_part =
792 ((rounded - rounded.trunc()).abs() * 10f64.powi(decimal_places as i32)).round() as u64;
793
794 let int_str = format!("{}", int_part);
796 let int_display = if has_comma_grouping {
797 add_thousands_separators(&int_str)
798 } else {
799 int_str.clone()
800 };
801
802 let min_int_digits = count_integer_zeros(format);
804 let padded_int = if int_display.len() < min_int_digits && int_part == 0 {
805 let needed = min_int_digits - int_display.len();
806 let mut s = "0".repeat(needed);
807 s.push_str(&int_display);
808 if has_comma_grouping {
809 add_thousands_separators(&s)
810 } else {
811 s
812 }
813 } else {
814 int_display
815 };
816
817 let mut output = String::with_capacity(format.len() + 10);
820 let chars: Vec<char> = format.chars().collect();
821 let len = chars.len();
822 let mut i = 0;
823 let mut in_quotes = false;
824 let mut number_placed = false;
825
826 while i < len {
827 let ch = chars[i];
828
829 if ch == '"' {
830 in_quotes = !in_quotes;
831 i += 1;
832 continue;
833 }
834 if in_quotes {
835 output.push(ch);
836 i += 1;
837 continue;
838 }
839 if ch == '\\' && i + 1 < len {
840 output.push(chars[i + 1]);
841 i += 2;
842 continue;
843 }
844 if ch == '_' && i + 1 < len {
845 output.push(' ');
846 i += 2;
847 continue;
848 }
849 if ch == '*' && i + 1 < len {
850 i += 2;
851 continue;
852 }
853
854 if (ch == '0' || ch == '#' || ch == ',') && !number_placed {
855 let num_end = find_numeric_end(&chars, i);
857 let num_str = if decimal_places > 0 {
859 let frac_str = format!("{:0>width$}", frac_part, width = decimal_places);
860 format!("{}.{}", padded_int, frac_str)
861 } else {
862 padded_int.clone()
863 };
864
865 if is_negative {
866 output.push('-');
867 }
868 output.push_str(&num_str);
869 number_placed = true;
870 i = num_end;
871 continue;
872 }
873
874 if ch == '.' && !number_placed {
875 continue;
877 }
878
879 if ch == '%' {
880 output.push('%');
881 i += 1;
882 continue;
883 }
884
885 if ch == '(' || ch == ')' || ch == '-' || ch == '+' || ch == ' ' || ch == ':' || ch == '/' {
886 output.push(ch);
887 i += 1;
888 continue;
889 }
890
891 if ch == '0' || ch == '#' || ch == ',' || ch == '.' {
892 i += 1;
893 continue;
894 }
895
896 output.push(ch);
897 i += 1;
898 }
899
900 if !number_placed {
903 let has_digit_placeholder = format.chars().any(|c| c == '0' || c == '#');
904 if has_digit_placeholder {
905 if is_negative {
906 output.push('-');
907 }
908 if decimal_places > 0 {
909 let frac_str = format!("{:0>width$}", frac_part, width = decimal_places);
910 output.push_str(&format!("{}.{}", padded_int, frac_str));
911 } else {
912 output.push_str(&padded_int);
913 }
914 }
915 }
916
917 output
918}
919
920fn format_has_unquoted_char(format: &str, target: char) -> bool {
921 let mut in_quotes = false;
922 let mut prev_backslash = false;
923 for ch in format.chars() {
924 if prev_backslash {
925 prev_backslash = false;
926 continue;
927 }
928 if ch == '\\' {
929 prev_backslash = true;
930 continue;
931 }
932 if ch == '"' {
933 in_quotes = !in_quotes;
934 continue;
935 }
936 if !in_quotes && ch == target {
937 return true;
938 }
939 }
940 false
941}
942
943fn count_decimal_places(format: &str) -> usize {
944 let mut in_quotes = false;
945 let mut prev_backslash = false;
946 let mut found_dot = false;
947 let mut count = 0;
948
949 for ch in format.chars() {
950 if prev_backslash {
951 prev_backslash = false;
952 continue;
953 }
954 if ch == '\\' {
955 prev_backslash = true;
956 continue;
957 }
958 if ch == '"' {
959 in_quotes = !in_quotes;
960 continue;
961 }
962 if in_quotes {
963 continue;
964 }
965 if ch == '.' && !found_dot {
966 found_dot = true;
967 continue;
968 }
969 if found_dot && (ch == '0' || ch == '#') {
970 count += 1;
971 } else if found_dot && ch != '0' && ch != '#' {
972 break;
973 }
974 }
975 count
976}
977
978fn has_thousands_separator(format: &str) -> bool {
979 let mut in_quotes = false;
980 let mut prev_backslash = false;
981 let chars: Vec<char> = format.chars().collect();
982
983 for (i, &ch) in chars.iter().enumerate() {
984 if prev_backslash {
985 prev_backslash = false;
986 continue;
987 }
988 if ch == '\\' {
989 prev_backslash = true;
990 continue;
991 }
992 if ch == '"' {
993 in_quotes = !in_quotes;
994 continue;
995 }
996 if in_quotes {
997 continue;
998 }
999 if ch == ',' {
1001 let has_digit_before = chars[..i].iter().rev().any(|&c| c == '0' || c == '#');
1002 let has_digit_after = chars[i + 1..].iter().any(|&c| c == '0' || c == '#');
1003 if has_digit_before && has_digit_after {
1004 return true;
1005 }
1006 }
1007 }
1008 false
1009}
1010
1011fn count_trailing_commas(format: &str) -> usize {
1012 let mut in_quotes = false;
1013 let mut prev_backslash = false;
1014 let chars: Vec<char> = format.chars().collect();
1015 let mut count = 0;
1016
1017 let mut last_digit_pos = None;
1019 for (i, &ch) in chars.iter().enumerate() {
1020 if prev_backslash {
1021 prev_backslash = false;
1022 continue;
1023 }
1024 if ch == '\\' {
1025 prev_backslash = true;
1026 continue;
1027 }
1028 if ch == '"' {
1029 in_quotes = !in_quotes;
1030 continue;
1031 }
1032 if in_quotes {
1033 continue;
1034 }
1035 if ch == '0' || ch == '#' {
1036 last_digit_pos = Some(i);
1037 }
1038 }
1039
1040 if let Some(pos) = last_digit_pos {
1041 for &ch in &chars[pos + 1..] {
1042 if ch == ',' {
1043 count += 1;
1044 } else {
1045 break;
1046 }
1047 }
1048 }
1049 count
1050}
1051
1052fn count_integer_zeros(format: &str) -> usize {
1053 let mut in_quotes = false;
1054 let mut prev_backslash = false;
1055 let mut count = 0;
1056 let mut found_dot = false;
1057
1058 for ch in format.chars() {
1059 if prev_backslash {
1060 prev_backslash = false;
1061 continue;
1062 }
1063 if ch == '\\' {
1064 prev_backslash = true;
1065 continue;
1066 }
1067 if ch == '"' {
1068 in_quotes = !in_quotes;
1069 continue;
1070 }
1071 if in_quotes {
1072 continue;
1073 }
1074 if ch == '.' {
1075 found_dot = true;
1076 continue;
1077 }
1078 if !found_dot && ch == '0' {
1079 count += 1;
1080 }
1081 }
1082 count
1083}
1084
1085fn add_thousands_separators(s: &str) -> String {
1086 let bytes = s.as_bytes();
1087 let len = bytes.len();
1088 if len <= 3 {
1089 return s.to_string();
1090 }
1091 let mut result = String::with_capacity(len + len / 3);
1092 let remainder = len % 3;
1093 if remainder > 0 {
1094 result.push_str(&s[..remainder]);
1095 if len > remainder {
1096 result.push(',');
1097 }
1098 }
1099 for (i, chunk) in s.as_bytes()[remainder..].chunks(3).enumerate() {
1100 if i > 0 {
1101 result.push(',');
1102 }
1103 result.push_str(std::str::from_utf8(chunk).unwrap_or(""));
1104 }
1105 result
1106}
1107
1108fn find_numeric_end(chars: &[char], start: usize) -> usize {
1109 let mut i = start;
1110 let mut in_quotes = false;
1111 while i < chars.len() {
1112 let ch = chars[i];
1113 if ch == '"' {
1114 in_quotes = !in_quotes;
1115 i += 1;
1116 continue;
1117 }
1118 if in_quotes {
1119 i += 1;
1120 continue;
1121 }
1122 if ch == '0' || ch == '#' || ch == ',' || ch == '.' {
1123 i += 1;
1124 } else {
1125 break;
1126 }
1127 }
1128 i
1129}
1130
1131fn format_scientific(value: f64, format: &str) -> String {
1132 let decimal_places = count_decimal_places(format);
1133 let formatted = format!("{:.*E}", decimal_places, value.abs());
1134
1135 let parts: Vec<&str> = formatted.split('E').collect();
1137 if parts.len() != 2 {
1138 return formatted;
1139 }
1140
1141 let mantissa = parts[0];
1142 let exp_str = parts[1];
1143 let exp: i32 = exp_str.parse().unwrap_or(0);
1144
1145 let exp_width = count_exponent_zeros(format).max(2);
1147
1148 let has_plus = format.contains("E+") || format.contains("e+");
1150 let exp_sign = if exp >= 0 {
1151 if has_plus {
1152 "+"
1153 } else {
1154 ""
1155 }
1156 } else {
1157 "-"
1158 };
1159
1160 let exp_display = format!(
1161 "{}{:0>width$}",
1162 exp_sign,
1163 exp.unsigned_abs(),
1164 width = exp_width
1165 );
1166
1167 let sign = if value < 0.0 { "-" } else { "" };
1168
1169 let e_char = if format.contains('e') { 'e' } else { 'E' };
1171
1172 format!("{}{}{}{}", sign, mantissa, e_char, exp_display)
1173}
1174
1175fn count_exponent_zeros(format: &str) -> usize {
1176 let upper = format.to_uppercase();
1177 if let Some(pos) = upper.find("E+").or_else(|| upper.find("E-")) {
1178 let after = &format[pos + 2..];
1179 after.chars().take_while(|&c| c == '0').count()
1180 } else {
1181 2
1182 }
1183}
1184
1185fn format_fraction(value: f64, format: &str) -> String {
1186 let abs = value.abs();
1187 let whole = abs.floor() as i64;
1188 let frac = abs - whole as f64;
1189
1190 let sign = if value < 0.0 { "-" } else { "" };
1191
1192 let denom_q_count = format
1194 .split('/')
1195 .nth(1)
1196 .map(|s| s.chars().filter(|&c| c == '?').count())
1197 .unwrap_or(1);
1198 let max_denom = if denom_q_count >= 4 {
1199 9999
1200 } else if denom_q_count >= 3 {
1201 999
1202 } else if denom_q_count >= 2 {
1203 99
1204 } else {
1205 9
1206 };
1207
1208 if frac < 1e-10 {
1209 if format.contains('#') {
1210 return format!("{}{}", sign, whole);
1211 }
1212 return format!("{}{} ", sign, whole);
1213 }
1214
1215 let (num, den) = best_fraction(frac, max_denom);
1217
1218 let has_whole = format.contains('#');
1219
1220 if has_whole {
1221 if whole > 0 {
1222 format!("{}{} {}/{}", sign, whole, num, den)
1223 } else {
1224 format!("{}{}/{}", sign, num, den)
1225 }
1226 } else {
1227 let total_num = whole as u64 * den + num;
1228 format!("{}{}/{}", sign, total_num, den)
1229 }
1230}
1231
1232fn best_fraction(value: f64, max_denom: u64) -> (u64, u64) {
1233 if value <= 0.0 {
1234 return (0, 1);
1235 }
1236 let mut best_num = 0u64;
1237 let mut best_den = 1u64;
1238 let mut best_err = value.abs();
1239
1240 for den in 1..=max_denom {
1241 let num = (value * den as f64).round() as u64;
1242 if num == 0 {
1243 continue;
1244 }
1245 let err = (value - num as f64 / den as f64).abs();
1246 if err < best_err {
1247 best_err = err;
1248 best_num = num;
1249 best_den = den;
1250 }
1251 if best_err < 1e-10 {
1252 break;
1253 }
1254 }
1255 (best_num, best_den)
1256}
1257
1258use chrono::Datelike;
1259
1260#[cfg(test)]
1261mod tests {
1262 use super::*;
1263
1264 #[test]
1265 fn test_builtin_format_code_general() {
1266 assert_eq!(builtin_format_code(0), Some("General"));
1267 }
1268
1269 #[test]
1270 fn test_builtin_format_code_integer() {
1271 assert_eq!(builtin_format_code(1), Some("0"));
1272 }
1273
1274 #[test]
1275 fn test_builtin_format_code_decimal() {
1276 assert_eq!(builtin_format_code(2), Some("0.00"));
1277 }
1278
1279 #[test]
1280 fn test_builtin_format_code_thousands() {
1281 assert_eq!(builtin_format_code(3), Some("#,##0"));
1282 }
1283
1284 #[test]
1285 fn test_builtin_format_code_date() {
1286 assert_eq!(builtin_format_code(14), Some("m/d/yyyy"));
1287 }
1288
1289 #[test]
1290 fn test_builtin_format_code_text() {
1291 assert_eq!(builtin_format_code(49), Some("@"));
1292 }
1293
1294 #[test]
1295 fn test_builtin_format_code_unknown() {
1296 assert_eq!(builtin_format_code(100), None);
1297 assert_eq!(builtin_format_code(50), None);
1298 }
1299
1300 #[test]
1301 fn test_format_general_zero() {
1302 assert_eq!(format_number(0.0, "General"), "0");
1303 }
1304
1305 #[test]
1306 fn test_format_general_integer() {
1307 assert_eq!(format_number(42.0, "General"), "42");
1308 assert_eq!(format_number(-100.0, "General"), "-100");
1309 }
1310
1311 #[test]
1312 fn test_format_general_decimal() {
1313 assert_eq!(format_number(3.14, "General"), "3.14");
1314 }
1315
1316 #[test]
1317 fn test_format_general_large_number() {
1318 assert_eq!(format_number(1000000.0, "General"), "1000000");
1319 }
1320
1321 #[test]
1322 fn test_format_integer() {
1323 assert_eq!(format_number(42.0, "0"), "42");
1324 assert_eq!(format_number(42.7, "0"), "43");
1325 assert_eq!(format_number(0.0, "0"), "0");
1326 }
1327
1328 #[test]
1329 fn test_format_decimal_2() {
1330 assert_eq!(format_number(3.14159, "0.00"), "3.14");
1331 assert_eq!(format_number(3.0, "0.00"), "3.00");
1332 assert_eq!(format_number(0.5, "0.00"), "0.50");
1333 }
1334
1335 #[test]
1336 fn test_format_thousands() {
1337 assert_eq!(format_number(1234.0, "#,##0"), "1,234");
1338 assert_eq!(format_number(1234567.0, "#,##0"), "1,234,567");
1339 assert_eq!(format_number(999.0, "#,##0"), "999");
1340 assert_eq!(format_number(0.0, "#,##0"), "0");
1341 }
1342
1343 #[test]
1344 fn test_format_thousands_decimal() {
1345 assert_eq!(format_number(1234.56, "#,##0.00"), "1,234.56");
1346 assert_eq!(format_number(0.0, "#,##0.00"), "0.00");
1347 }
1348
1349 #[test]
1350 fn test_format_percent() {
1351 assert_eq!(format_number(0.75, "0%"), "75%");
1352 assert_eq!(format_number(0.5, "0%"), "50%");
1353 assert_eq!(format_number(1.0, "0%"), "100%");
1354 }
1355
1356 #[test]
1357 fn test_format_percent_decimal() {
1358 assert_eq!(format_number(0.7534, "0.00%"), "75.34%");
1359 assert_eq!(format_number(0.5, "0.00%"), "50.00%");
1360 }
1361
1362 #[test]
1363 fn test_format_scientific() {
1364 let result = format_number(1234.5, "0.00E+00");
1365 assert_eq!(result, "1.23E+03");
1366 }
1367
1368 #[test]
1369 fn test_format_scientific_small() {
1370 let result = format_number(0.001, "0.00E+00");
1371 assert_eq!(result, "1.00E-03");
1372 }
1373
1374 #[test]
1375 fn test_format_date_mdy() {
1376 let serial =
1378 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1379 assert_eq!(format_number(serial, "m/d/yyyy"), "1/15/2024");
1380 }
1381
1382 #[test]
1383 fn test_format_date_dmy() {
1384 let serial =
1385 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 3, 5).unwrap());
1386 assert_eq!(format_number(serial, "d-mmm-yy"), "5-Mar-24");
1387 }
1388
1389 #[test]
1390 fn test_format_date_dm() {
1391 let serial =
1392 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
1393 assert_eq!(format_number(serial, "d-mmm"), "15-Jun");
1394 }
1395
1396 #[test]
1397 fn test_format_date_my() {
1398 let serial =
1399 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 12, 1).unwrap());
1400 assert_eq!(format_number(serial, "mmm-yy"), "Dec-24");
1401 }
1402
1403 #[test]
1404 fn test_format_time_hm_ampm() {
1405 let serial = crate::cell::datetime_to_serial(
1406 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1407 .unwrap()
1408 .and_hms_opt(14, 30, 0)
1409 .unwrap(),
1410 );
1411 assert_eq!(format_number(serial, "h:mm AM/PM"), "2:30 PM");
1412 }
1413
1414 #[test]
1415 fn test_format_time_hm_ampm_morning() {
1416 let serial = crate::cell::datetime_to_serial(
1417 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1418 .unwrap()
1419 .and_hms_opt(9, 5, 0)
1420 .unwrap(),
1421 );
1422 assert_eq!(format_number(serial, "h:mm AM/PM"), "9:05 AM");
1423 }
1424
1425 #[test]
1426 fn test_format_time_hms() {
1427 let serial = crate::cell::datetime_to_serial(
1428 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1429 .unwrap()
1430 .and_hms_opt(14, 30, 45)
1431 .unwrap(),
1432 );
1433 assert_eq!(format_number(serial, "h:mm:ss"), "14:30:45");
1434 }
1435
1436 #[test]
1437 fn test_format_time_hm_24h() {
1438 let serial = crate::cell::datetime_to_serial(
1439 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1440 .unwrap()
1441 .and_hms_opt(14, 30, 0)
1442 .unwrap(),
1443 );
1444 assert_eq!(format_number(serial, "h:mm"), "14:30");
1445 }
1446
1447 #[test]
1448 fn test_format_datetime_combined() {
1449 let serial = crate::cell::datetime_to_serial(
1450 chrono::NaiveDate::from_ymd_opt(2024, 1, 15)
1451 .unwrap()
1452 .and_hms_opt(14, 30, 0)
1453 .unwrap(),
1454 );
1455 assert_eq!(format_number(serial, "m/d/yyyy h:mm"), "1/15/2024 14:30");
1456 }
1457
1458 #[test]
1459 fn test_format_date_yyyy_mm_dd() {
1460 let serial =
1461 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
1462 assert_eq!(format_number(serial, "yyyy-mm-dd"), "2024-06-15");
1463 }
1464
1465 #[test]
1466 fn test_format_date_dd_mm_yyyy() {
1467 let serial =
1468 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
1469 assert_eq!(format_number(serial, "dd/mm/yyyy"), "15/06/2024");
1470 }
1471
1472 #[test]
1473 fn test_format_text_passthrough() {
1474 assert_eq!(format_number(42.0, "@"), "42");
1475 }
1476
1477 #[test]
1478 fn test_format_multi_section_positive_negative() {
1479 assert_eq!(format_number(42.0, "#,##0;-#,##0"), "42");
1480 assert_eq!(format_number(-42.0, "#,##0;-#,##0"), "-42");
1481 }
1482
1483 #[test]
1484 fn test_format_multi_section_three_parts() {
1485 assert_eq!(format_number(42.0, "#,##0;-#,##0;\"zero\""), "42");
1486 assert_eq!(format_number(-42.0, "#,##0;-#,##0;\"zero\""), "-42");
1487 }
1488
1489 #[test]
1490 fn test_format_color_stripped() {
1491 assert_eq!(format_number(42.0, "[Red]0"), "42");
1492 assert_eq!(format_number(42.0, "[Blue]0.00"), "42.00");
1493 }
1494
1495 #[test]
1496 fn test_format_with_builtin_general() {
1497 assert_eq!(format_with_builtin(42.0, 0), Some("42".to_string()));
1498 }
1499
1500 #[test]
1501 fn test_format_with_builtin_percent() {
1502 assert_eq!(format_with_builtin(0.5, 9), Some("50%".to_string()));
1503 }
1504
1505 #[test]
1506 fn test_format_with_builtin_unknown() {
1507 assert_eq!(format_with_builtin(42.0, 100), None);
1508 }
1509
1510 #[test]
1511 fn test_format_fraction_simple() {
1512 let result = format_number(1.5, "# ?/?");
1513 assert_eq!(result, "1 1/2");
1514 }
1515
1516 #[test]
1517 fn test_format_fraction_two_digit() {
1518 let result = format_number(0.333, "# ??/??");
1519 assert!(result.contains("/"), "result was: {}", result);
1521 }
1522
1523 #[test]
1524 fn test_format_negative_in_parens() {
1525 let result = format_number(-1234.0, "#,##0_);(#,##0)");
1526 assert!(result.contains("1,234"), "result was: {}", result);
1527 assert!(result.contains("("), "result was: {}", result);
1528 }
1529
1530 #[test]
1531 fn test_format_empty_format_uses_general() {
1532 assert_eq!(format_number(42.0, ""), "42");
1533 }
1534
1535 #[test]
1536 fn test_format_date_long_month() {
1537 let serial =
1538 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1539 assert_eq!(format_number(serial, "d mmmm yyyy"), "15 January 2024");
1540 }
1541
1542 #[test]
1543 fn test_format_time_ampm_midnight() {
1544 let serial =
1545 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
1546 assert_eq!(format_number(serial, "h:mm AM/PM"), "12:00 AM");
1548 }
1549
1550 #[test]
1551 fn test_format_time_ampm_noon() {
1552 let serial = crate::cell::datetime_to_serial(
1553 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1554 .unwrap()
1555 .and_hms_opt(12, 0, 0)
1556 .unwrap(),
1557 );
1558 assert_eq!(format_number(serial, "h:mm AM/PM"), "12:00 PM");
1559 }
1560
1561 #[test]
1562 fn test_format_builtin_mmss() {
1563 let serial = crate::cell::datetime_to_serial(
1564 chrono::NaiveDate::from_ymd_opt(2024, 1, 1)
1565 .unwrap()
1566 .and_hms_opt(0, 5, 30)
1567 .unwrap(),
1568 );
1569 assert_eq!(format_number(serial, "mm:ss"), "05:30");
1570 }
1571
1572 #[test]
1573 fn test_format_general_negative_decimal() {
1574 let result = format_number(-3.14, "General");
1575 assert_eq!(result, "-3.14");
1576 }
1577
1578 #[test]
1579 fn test_format_date_two_digit_year() {
1580 let serial =
1581 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
1582 assert_eq!(format_number(serial, "yy"), "24");
1583 }
1584
1585 #[test]
1586 fn test_format_thousands_separator_with_zero() {
1587 assert_eq!(add_thousands_separators("0"), "0");
1588 assert_eq!(add_thousands_separators("100"), "100");
1589 assert_eq!(add_thousands_separators("1000"), "1,000");
1590 assert_eq!(add_thousands_separators("1000000"), "1,000,000");
1591 }
1592
1593 #[test]
1594 fn test_parse_sections_single() {
1595 let sections = parse_sections("0.00");
1596 assert_eq!(sections, vec!["0.00"]);
1597 }
1598
1599 #[test]
1600 fn test_parse_sections_multi() {
1601 let sections = parse_sections("0.00;-0.00;\"zero\"");
1602 assert_eq!(sections, vec!["0.00", "-0.00", "\"zero\""]);
1603 }
1604
1605 #[test]
1606 fn test_parse_sections_quoted_semicolon() {
1607 let sections = parse_sections("\"a;b\"");
1608 assert_eq!(sections, vec!["\"a;b\""]);
1609 }
1610
1611 #[test]
1612 fn test_strip_color() {
1613 let (cleaned, color) = strip_color_and_condition("[Red]0.00");
1614 assert_eq!(cleaned, "0.00");
1615 assert_eq!(color, Some("Red".to_string()));
1616 }
1617
1618 #[test]
1619 fn test_strip_condition() {
1620 let (cleaned, _) = strip_color_and_condition("[>100]0.00");
1621 assert_eq!(cleaned, "0.00");
1622 }
1623
1624 #[test]
1625 fn test_is_date_time_format_checks() {
1626 assert!(is_date_time_format("yyyy-mm-dd"));
1627 assert!(is_date_time_format("h:mm:ss"));
1628 assert!(is_date_time_format("m/d/yyyy"));
1629 assert!(!is_date_time_format("0.00"));
1630 assert!(!is_date_time_format("#,##0"));
1631 assert!(!is_date_time_format("\"yyyy\"0"));
1632 }
1633
1634 #[test]
1635 fn test_count_decimal_places_none() {
1636 assert_eq!(count_decimal_places("0"), 0);
1637 assert_eq!(count_decimal_places("#,##0"), 0);
1638 }
1639
1640 #[test]
1641 fn test_count_decimal_places_two() {
1642 assert_eq!(count_decimal_places("0.00"), 2);
1643 assert_eq!(count_decimal_places("#,##0.00"), 2);
1644 }
1645
1646 #[test]
1647 fn test_count_decimal_places_three() {
1648 assert_eq!(count_decimal_places("0.000"), 3);
1649 }
1650
1651 #[test]
1652 fn test_parse_condition_operators() {
1653 let c = parse_condition(">100").unwrap();
1654 assert_eq!(c.op, ConditionOp::Gt);
1655 assert_eq!(c.threshold, 100.0);
1656
1657 let c = parse_condition(">=50").unwrap();
1658 assert_eq!(c.op, ConditionOp::Ge);
1659 assert_eq!(c.threshold, 50.0);
1660
1661 let c = parse_condition("<1000").unwrap();
1662 assert_eq!(c.op, ConditionOp::Lt);
1663 assert_eq!(c.threshold, 1000.0);
1664
1665 let c = parse_condition("<=0").unwrap();
1666 assert_eq!(c.op, ConditionOp::Le);
1667 assert_eq!(c.threshold, 0.0);
1668
1669 let c = parse_condition("=0").unwrap();
1670 assert_eq!(c.op, ConditionOp::Eq);
1671 assert_eq!(c.threshold, 0.0);
1672
1673 let c = parse_condition("<>5").unwrap();
1674 assert_eq!(c.op, ConditionOp::Ne);
1675 assert_eq!(c.threshold, 5.0);
1676
1677 let c = parse_condition("!=5").unwrap();
1678 assert_eq!(c.op, ConditionOp::Ne);
1679 assert_eq!(c.threshold, 5.0);
1680
1681 assert!(parse_condition("Red").is_none());
1682 assert!(parse_condition("").is_none());
1683 }
1684
1685 #[test]
1686 fn test_condition_matches() {
1687 let c = Condition {
1688 op: ConditionOp::Gt,
1689 threshold: 100.0,
1690 };
1691 assert!(c.matches(150.0));
1692 assert!(!c.matches(100.0));
1693 assert!(!c.matches(50.0));
1694 }
1695
1696 #[test]
1697 fn test_conditional_two_sections_color_and_condition() {
1698 let fmt = "[Red][>100]0;[Blue][<=100]0";
1700 assert_eq!(format_number(150.0, fmt), "150");
1701 assert_eq!(format_number(50.0, fmt), "50");
1702 assert_eq!(format_number(100.0, fmt), "100");
1703 }
1704
1705 #[test]
1706 fn test_conditional_three_sections_cascading() {
1707 let fmt = "[>1000]#,##0;[>100]0.0;0.00";
1709 assert_eq!(format_number(5000.0, fmt), "5,000");
1710 assert_eq!(format_number(500.0, fmt), "500.0");
1711 assert_eq!(format_number(50.0, fmt), "50.00");
1712 }
1713
1714 #[test]
1715 fn test_conditional_equals_zero() {
1716 let fmt = "[=0]\"zero\";0";
1718 assert_eq!(format_number(0.0, fmt), "zero");
1719 assert_eq!(format_number(42.0, fmt), "42");
1720 }
1721
1722 #[test]
1723 fn test_conditional_with_sign_format() {
1724 let fmt = "[Red][>0]+0;[Blue][<0]-0;0";
1726 assert_eq!(format_number(5.0, fmt), "+5");
1727 assert_eq!(format_number(-3.0, fmt), "-3");
1728 assert_eq!(format_number(0.0, fmt), "0");
1729 }
1730
1731 #[test]
1732 fn test_extract_condition_from_section() {
1733 let cond = extract_condition("[Red][>100]0.00").unwrap();
1734 assert_eq!(cond.op, ConditionOp::Gt);
1735 assert_eq!(cond.threshold, 100.0);
1736
1737 let cond = extract_condition("[<=0]0");
1738 assert!(cond.is_some());
1739 assert_eq!(cond.unwrap().op, ConditionOp::Le);
1740
1741 assert!(extract_condition("[Red]0.00").is_none());
1742 assert!(extract_condition("0.00").is_none());
1743 }
1744}