1use std::collections::HashMap;
2
3use crate::expressions::utils::parse_reference_a1;
4use crate::formatter::dates::{date_to_serial_number, from_excel_date};
5use crate::{
6 calc_result::CalcResult,
7 cell::CellValue,
8 cf_types::{
9 CfCellResult, CfDataBar, CfIcon, CfRating, CfRule, CfRuleInput, Cfvo, ColorScaleThreshold,
10 ConditionalFormatting, ConditionalFormattingView, ExtendedStyle, Icon, IconThreshold,
11 PeriodType, TextOperator, ValueOperator,
12 },
13 expressions::types::{CellReferenceIndex, CellReferenceRC},
14 types::{Color, Dxf},
15 Model,
16};
17
18use chrono::{Datelike, Duration, Months, NaiveDate};
19
20fn parse_sqref(sqref: &str) -> Vec<(i32, i32, i32, i32)> {
26 sqref
27 .split_whitespace()
28 .filter_map(parse_range_part)
29 .collect()
30}
31
32fn parse_range_part(s: &str) -> Option<(i32, i32, i32, i32)> {
33 let upper = s.to_uppercase();
34 let parts: Vec<&str> = upper.splitn(2, ':').collect();
35 match parts.len() {
36 1 => {
37 let r = parse_reference_a1(parts[0])?;
38 Some((r.row, r.column, r.row, r.column))
39 }
40 2 => {
41 let r1 = parse_reference_a1(parts[0])?;
42 let r2 = parse_reference_a1(parts[1])?;
43 let (row_min, row_max) = (r1.row.min(r2.row), r1.row.max(r2.row));
44 let (col_min, col_max) = (r1.column.min(r2.column), r1.column.max(r2.column));
45 Some((row_min, col_min, row_max, col_max))
46 }
47 _ => None,
48 }
49}
50
51fn interpolate_color(v: f64, thresholds: &[f64], colors: &[String]) -> String {
53 let n = thresholds.len();
54 if n == 0 || colors.len() != n {
55 return "#000000".to_string();
56 }
57 if v <= thresholds[0] {
58 return colors[0].clone();
59 }
60 if v >= thresholds[n - 1] {
61 return colors[n - 1].clone();
62 }
63 for i in 0..n - 1 {
64 if v >= thresholds[i] && v <= thresholds[i + 1] {
65 let span = thresholds[i + 1] - thresholds[i];
66 let t = if span.abs() < f64::EPSILON {
67 0.0
68 } else {
69 (v - thresholds[i]) / span
70 };
71 return lerp_color(&colors[i], &colors[i + 1], t);
72 }
73 }
74 colors[0].clone()
75}
76
77fn lerp_color(c1: &str, c2: &str, t: f64) -> String {
78 let (r1, g1, b1) = parse_hex_color(c1);
79 let (r2, g2, b2) = parse_hex_color(c2);
80 let r = (r1 as f64 + (r2 as f64 - r1 as f64) * t).round() as u8;
81 let g = (g1 as f64 + (g2 as f64 - g1 as f64) * t).round() as u8;
82 let b = (b1 as f64 + (b2 as f64 - b1 as f64) * t).round() as u8;
83 format!("#{r:02X}{g:02X}{b:02X}")
84}
85
86fn parse_hex_color(s: &str) -> (u8, u8, u8) {
87 let s = s.trim_start_matches('#');
88 if s.len() == 6 {
89 let r = u8::from_str_radix(&s[0..2], 16).unwrap_or(0);
90 let g = u8::from_str_radix(&s[2..4], 16).unwrap_or(0);
91 let b = u8::from_str_radix(&s[4..6], 16).unwrap_or(0);
92 (r, g, b)
93 } else {
94 (0, 0, 0)
95 }
96}
97
98fn compute_icon_index(v: f64, thresholds: &[(f64, bool)]) -> u32 {
101 let mut idx = 0u32;
102 for (i, &(value, is_strict)) in thresholds.iter().enumerate() {
103 if v > value || (v == value && is_strict) {
104 idx = i as u32;
105 }
106 }
107 idx
108}
109
110fn parse_cf_date_bound(value: &str) -> Option<f64> {
114 let trimmed = value.trim();
115 if let Ok(serial) = crate::functions::date_and_time::parse_datevalue_text(trimmed) {
116 return Some(serial as f64);
117 }
118 trimmed.parse::<f64>().ok().map(f64::floor)
119}
120
121fn cell_value_key(v: &crate::cell::CellValue) -> Option<String> {
123 use crate::cell::CellValue;
124 match v {
125 CellValue::Number(n) => Some(format!("{n}")),
126 CellValue::String(s) => Some(s.to_lowercase()),
127 CellValue::Boolean(b) => Some(b.to_string()),
128 CellValue::None => None,
129 }
130}
131
132impl<'a> Model<'a> {
133 pub fn evaluate_conditional_formatting(&mut self) {
139 self.cf_cache.clear();
140 let sheet_count = self.workbook.worksheets.len();
141 for sheet_idx in 0..sheet_count {
142 let mut cfs = self.workbook.worksheets[sheet_idx]
143 .conditional_formatting
144 .clone();
145 cfs.sort_by_key(|cf| cf.priority);
148 for cf in cfs {
149 let ranges = parse_sqref(&cf.range);
150 if ranges.is_empty() {
151 continue;
152 }
153 self.apply_cf_rule(sheet_idx as u32, &cf.cf_rule, &ranges);
154 }
155 }
156 }
157
158 fn apply_cf_rule(&mut self, sheet: u32, rule: &CfRule, ranges: &[(i32, i32, i32, i32)]) {
163 match rule {
164 CfRule::ColorScale { thresholds } => {
165 self.apply_cf_color_scale(sheet, thresholds, ranges);
166 }
167 CfRule::CellIs {
168 operator,
169 formula,
170 formula2,
171 dxf_id,
172 stop_if_true,
173 } => {
174 self.apply_cf_cell_is(
175 sheet,
176 operator,
177 formula,
178 formula2.as_deref(),
179 *dxf_id,
180 *stop_if_true,
181 ranges,
182 );
183 }
184 CfRule::Formula {
185 formula,
186 dxf_id,
187 stop_if_true,
188 } => {
189 self.apply_cf_formula(sheet, formula, *dxf_id, *stop_if_true, ranges);
190 }
191 CfRule::DuplicateValues {
192 dxf_id,
193 stop_if_true,
194 } => {
195 self.apply_cf_duplicate_values(sheet, *dxf_id, *stop_if_true, ranges);
196 }
197 CfRule::AboveAverage {
198 dxf_id,
199 stop_if_true,
200 } => {
201 self.apply_cf_average(sheet, *dxf_id, true, *stop_if_true, ranges);
202 }
203 CfRule::BelowAverage {
204 dxf_id,
205 stop_if_true,
206 } => {
207 self.apply_cf_average(sheet, *dxf_id, false, *stop_if_true, ranges);
208 }
209 CfRule::Top10 {
210 rank,
211 percent,
212 dxf_id,
213 stop_if_true,
214 } => {
215 self.apply_cf_top_n(
216 sheet,
217 *rank,
218 *percent,
219 false,
220 *dxf_id,
221 *stop_if_true,
222 ranges,
223 );
224 }
225 CfRule::Bottom10 {
226 rank,
227 percent,
228 dxf_id,
229 stop_if_true,
230 } => {
231 self.apply_cf_top_n(sheet, *rank, *percent, true, *dxf_id, *stop_if_true, ranges);
232 }
233 CfRule::DataBar {
234 min,
235 max,
236 positive_color,
237 negative_color,
238 is_gradient,
239 show_value,
240 } => {
241 self.apply_cf_data_bar(
242 sheet,
243 min.as_ref(),
244 max.as_ref(),
245 positive_color,
246 negative_color,
247 *is_gradient,
248 *show_value,
249 ranges,
250 );
251 }
252 CfRule::IconSet {
253 thresholds,
254 show_value,
255 } => {
256 self.apply_cf_icon_set(sheet, thresholds, *show_value, ranges);
257 }
258 CfRule::Text {
259 operator,
260 value,
261 dxf_id,
262 stop_if_true,
263 } => {
264 self.apply_cf_text(sheet, operator, value, *dxf_id, *stop_if_true, ranges);
265 }
266 CfRule::UniqueValues {
267 dxf_id,
268 stop_if_true,
269 } => {
270 self.apply_cf_unique_values(sheet, *dxf_id, *stop_if_true, ranges);
271 }
272 CfRule::TimePeriod {
273 time_period,
274 date1,
275 date2,
276 dxf_id,
277 stop_if_true,
278 } => {
279 self.apply_cf_time_period(
280 sheet,
281 time_period,
282 date1.as_deref(),
283 date2.as_deref(),
284 *dxf_id,
285 *stop_if_true,
286 ranges,
287 );
288 }
289 CfRule::IconRating {
290 icon,
291 color,
292 show_value,
293 thresholds,
294 } => {
295 self.apply_cf_icon_rating(sheet, icon, color, thresholds, *show_value, ranges);
296 }
297 CfRule::Blanks {
298 dxf_id,
299 stop_if_true,
300 } => {
301 self.apply_cf_blanks(sheet, *dxf_id, false, *stop_if_true, ranges);
302 }
303 CfRule::NotBlanks {
304 dxf_id,
305 stop_if_true,
306 } => {
307 self.apply_cf_blanks(sheet, *dxf_id, true, *stop_if_true, ranges);
308 }
309 CfRule::Errors {
310 dxf_id,
311 stop_if_true,
312 } => {
313 self.apply_cf_errors(sheet, *dxf_id, false, *stop_if_true, ranges);
314 }
315 CfRule::NoErrors {
316 dxf_id,
317 stop_if_true,
318 } => {
319 self.apply_cf_errors(sheet, *dxf_id, true, *stop_if_true, ranges);
320 }
321 }
322 }
323
324 fn update_cf_cache(
325 &mut self,
326 sheet: u32,
327 row: i32,
328 col: i32,
329 result: CfCellResult,
330 stop_if_true: bool,
331 ) {
332 if stop_if_true {
333 self.cf_cache.insert((sheet, row, col), vec![result]);
335 } else {
336 self.cf_cache
337 .entry((sheet, row, col))
338 .or_default()
339 .push(result);
340 }
341 }
342
343 fn collect_numeric_values(&self, sheet: u32, ranges: &[(i32, i32, i32, i32)]) -> Vec<f64> {
345 let mut values = Vec::new();
346 for &(r1, c1, r2, c2) in ranges {
347 for row in r1..=r2 {
348 for col in c1..=c2 {
349 if let Ok(CellValue::Number(n)) = self.get_cell_value_by_index(sheet, row, col)
350 {
351 values.push(n);
352 }
353 }
354 }
355 }
356 values
357 }
358
359 fn resolve_cfvo(&mut self, cfvo: &Cfvo, values: &[f64], sheet: u32) -> f64 {
361 match cfvo {
362 Cfvo::Min => values.iter().cloned().fold(f64::INFINITY, f64::min),
363 Cfvo::Max => values.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
364 Cfvo::Number(n) => *n,
365 Cfvo::Percent(p) => {
366 if values.is_empty() {
367 return 0.0;
368 }
369 let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
370 let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
371 let p = *p;
372 min + (max - min) * p / 100.0
373 }
374 Cfvo::Percentile(p) => {
375 if values.is_empty() {
376 return 0.0;
377 }
378 let mut sorted = values.to_vec();
379 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
380 let idx = ((p / 100.0) * (sorted.len() - 1) as f64).floor() as usize;
381 sorted[idx.min(sorted.len() - 1)]
382 }
383 Cfvo::Formula(f) => self.evaluate_formula(f, sheet).unwrap_or(0.0),
384 }
385 }
386
387 fn apply_cf_color_scale(
388 &mut self,
389 sheet: u32,
390 thresholds: &[ColorScaleThreshold],
391 ranges: &[(i32, i32, i32, i32)],
392 ) {
393 if thresholds.len() < 2 {
394 return;
395 }
396 let values = self.collect_numeric_values(sheet, ranges);
397 if values.is_empty() {
398 return;
399 }
400 let colors: Vec<String> = thresholds
401 .iter()
402 .map(|t| t.color.to_rgb(&self.workbook.theme))
403 .collect();
404 let mut stops: Vec<f64> = Vec::with_capacity(thresholds.len());
405 for t in thresholds {
406 stops.push(self.resolve_cfvo(&t.cfvo, &values, sheet));
407 }
408 stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
413
414 for &(r1, c1, r2, c2) in ranges {
415 for row in r1..=r2 {
416 for col in c1..=c2 {
417 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
418 {
419 let color = interpolate_color(v, &stops, &colors);
420 self.update_cf_cache(
421 sheet,
422 row,
423 col,
424 CfCellResult::ColorScale(Color::Rgb(color)),
425 false,
426 );
427 }
428 }
429 }
430 }
431 }
432
433 #[allow(clippy::too_many_arguments)]
434 fn apply_cf_data_bar(
435 &mut self,
436 sheet: u32,
437 min: Option<&Cfvo>,
438 max: Option<&Cfvo>,
439 positive_color: &Color,
440 negative_color: &Color,
441 is_gradient: bool,
442 show_value: bool,
443 ranges: &[(i32, i32, i32, i32)],
444 ) {
445 let values = self.collect_numeric_values(sheet, ranges);
446 if values.is_empty() {
447 return;
448 }
449 let min_val = match min {
450 Some(cfvo) => self.resolve_cfvo(cfvo, &values, sheet),
451 None => values.iter().cloned().fold(0.0_f64, f64::min),
452 };
453 let max_val = match max {
454 Some(cfvo) => self.resolve_cfvo(cfvo, &values, sheet),
455 None => values.iter().cloned().fold(0.0_f64, f64::max),
456 };
457 let span = max_val - min_val;
458 if span.abs() < f64::EPSILON {
459 return;
460 }
461 let axis_position = (-min_val / span).clamp(0.0, 1.0);
462
463 for &(r1, c1, r2, c2) in ranges {
464 for row in r1..=r2 {
465 for col in c1..=c2 {
466 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
467 {
468 let proportion = ((v - min_val) / span).clamp(0.0, 1.0);
469 self.update_cf_cache(
470 sheet,
471 row,
472 col,
473 CfCellResult::DataBar {
474 positive_color: positive_color.clone(),
475 negative_color: negative_color.clone(),
476 is_gradient,
477 value: proportion,
478 axis_position,
479 show_value,
480 },
481 false,
482 );
483 }
484 }
485 }
486 }
487 }
488
489 fn apply_cf_icon_set(
490 &mut self,
491 sheet: u32,
492 thresholds: &[IconThreshold],
493 show_value: bool,
494 ranges: &[(i32, i32, i32, i32)],
495 ) {
496 if thresholds.is_empty() {
497 return;
498 }
499 let values = self.collect_numeric_values(sheet, ranges);
500 let stops: Vec<(f64, bool)> = thresholds
501 .iter()
502 .map(|t| (self.resolve_cfvo(&t.cfvo, &values, sheet), t.is_strict))
503 .collect();
504 let n = thresholds.len();
505
506 for &(r1, c1, r2, c2) in ranges {
507 for row in r1..=r2 {
508 for col in c1..=c2 {
509 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
510 {
511 let idx = (compute_icon_index(v, &stops) as usize).min(n - 1);
512 self.update_cf_cache(
513 sheet,
514 row,
515 col,
516 CfCellResult::Icon {
517 icon: thresholds[idx].icon.clone(),
518 color: thresholds[idx].color.clone(),
519 show_value,
520 },
521 false,
522 );
523 }
524 }
525 }
526 }
527 }
528
529 fn apply_cf_icon_rating(
530 &mut self,
531 sheet: u32,
532 icon: &Icon,
533 color: &Color,
534 thresholds: &[(Cfvo, bool)],
535 show_value: bool,
536 ranges: &[(i32, i32, i32, i32)],
537 ) {
538 if thresholds.is_empty() {
539 return;
540 }
541 let max = thresholds.len() as u32;
546
547 let values = self.collect_numeric_values(sheet, ranges);
548 let resolved: Vec<(f64, bool)> = thresholds
550 .iter()
551 .map(|(cfvo, is_strict)| (self.resolve_cfvo(cfvo, &values, sheet), *is_strict))
552 .collect();
553
554 for &(r1, c1, r2, c2) in ranges {
555 for row in r1..=r2 {
556 for col in c1..=c2 {
557 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
558 {
559 let mut count = 0u32;
560 for &(threshold_val, is_strict) in resolved.iter().rev() {
561 let exceeds = if is_strict {
562 v >= threshold_val
563 } else {
564 v > threshold_val
565 };
566 if exceeds {
567 count += 1;
568 }
569 }
570 self.update_cf_cache(
571 sheet,
572 row,
573 col,
574 CfCellResult::Rating {
575 icon: icon.clone(),
576 count: count.min(max),
577 max,
578 color: color.clone(),
579 show_value,
580 },
581 false,
582 );
583 }
584 }
585 }
586 }
587 }
588
589 #[allow(clippy::too_many_arguments)]
590 fn apply_cf_cell_is(
591 &mut self,
592 sheet: u32,
593 operator: &ValueOperator,
594 formula: &str,
595 formula2: Option<&str>,
596 dxf_id: u32,
597 stop_if_true: bool,
598 ranges: &[(i32, i32, i32, i32)],
599 ) {
600 let threshold = match self.evaluate_formula(formula, sheet) {
601 Some(v) => v,
602 None => return,
603 };
604 let threshold2 = formula2.and_then(|f| self.evaluate_formula(f, sheet));
605
606 for &(r1, c1, r2, c2) in ranges {
607 for row in r1..=r2 {
608 for col in c1..=c2 {
609 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
610 {
611 let matches = match operator {
612 ValueOperator::Equal => (v - threshold).abs() < f64::EPSILON,
613 ValueOperator::GreaterThan => v > threshold,
614 ValueOperator::GreaterThanOrEqual => v >= threshold,
615 ValueOperator::LessThan => v < threshold,
616 ValueOperator::LessThanOrEqual => v <= threshold,
617 ValueOperator::NotEqual => (v - threshold).abs() >= f64::EPSILON,
618 ValueOperator::Between => {
619 let t2 = threshold2.unwrap_or(threshold);
620 v >= threshold.min(t2) && v <= threshold.max(t2)
621 }
622 ValueOperator::NotBetween => {
623 let t2 = threshold2.unwrap_or(threshold);
624 v < threshold.min(t2) || v > threshold.max(t2)
625 }
626 };
627 if matches {
628 self.update_cf_cache(
629 sheet,
630 row,
631 col,
632 CfCellResult::Dxf(dxf_id),
633 stop_if_true,
634 );
635 }
636 }
637 }
638 }
639 }
640 }
641
642 fn apply_cf_average(
643 &mut self,
644 sheet: u32,
645 dxf_id: u32,
646 above: bool,
647 stop_if_true: bool,
648 ranges: &[(i32, i32, i32, i32)],
649 ) {
650 let values = self.collect_numeric_values(sheet, ranges);
651 if values.is_empty() {
652 return;
653 }
654 let avg = values.iter().sum::<f64>() / values.len() as f64;
655
656 for &(r1, c1, r2, c2) in ranges {
657 for row in r1..=r2 {
658 for col in c1..=c2 {
659 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
660 {
661 if (above && v > avg) || (!above && v < avg) {
662 self.update_cf_cache(
663 sheet,
664 row,
665 col,
666 CfCellResult::Dxf(dxf_id),
667 stop_if_true,
668 );
669 }
670 }
671 }
672 }
673 }
674 }
675
676 #[allow(clippy::too_many_arguments)]
677 fn apply_cf_top_n(
678 &mut self,
679 sheet: u32,
680 rank: u32,
681 percent: bool,
682 bottom: bool,
683 dxf_id: u32,
684 stop_if_true: bool,
685 ranges: &[(i32, i32, i32, i32)],
686 ) {
687 let values = self.collect_numeric_values(sheet, ranges);
688 if values.is_empty() {
689 return;
690 }
691 let n = if percent {
692 ((rank as f64 / 100.0) * values.len() as f64).ceil() as usize
693 } else {
694 rank as usize
695 }
696 .max(1);
697
698 let mut sorted = values.clone();
699 if bottom {
700 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
701 } else {
702 sorted.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
703 }
704 let threshold = sorted[n.min(sorted.len()) - 1];
705
706 for &(r1, c1, r2, c2) in ranges {
707 for row in r1..=r2 {
708 for col in c1..=c2 {
709 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
710 {
711 let matches = if bottom {
712 v <= threshold
713 } else {
714 v >= threshold
715 };
716 if matches {
717 self.update_cf_cache(
718 sheet,
719 row,
720 col,
721 CfCellResult::Dxf(dxf_id),
722 stop_if_true,
723 );
724 }
725 }
726 }
727 }
728 }
729 }
730
731 fn apply_cf_text(
732 &mut self,
733 sheet: u32,
734 operator: &TextOperator,
735 value: &str,
736 dxf_id: u32,
737 stop_if_true: bool,
738 ranges: &[(i32, i32, i32, i32)],
739 ) {
740 let search = value.to_lowercase();
741 for &(r1, c1, r2, c2) in ranges {
742 for row in r1..=r2 {
743 for col in c1..=c2 {
744 if let Ok(CellValue::String(s)) = self.get_cell_value_by_index(sheet, row, col)
745 {
746 let cell_lower = s.to_lowercase();
747 let matches = match operator {
748 TextOperator::Contains => cell_lower.contains(search.as_str()),
749 TextOperator::DoesNotContain => !cell_lower.contains(search.as_str()),
750 TextOperator::BeginsWith => cell_lower.starts_with(search.as_str()),
751 TextOperator::EndsWith => cell_lower.ends_with(search.as_str()),
752 TextOperator::Equals => cell_lower == search.as_str(),
753 };
754 if matches {
755 self.update_cf_cache(
756 sheet,
757 row,
758 col,
759 CfCellResult::Dxf(dxf_id),
760 stop_if_true,
761 );
762 }
763 }
764 }
765 }
766 }
767 }
768
769 fn apply_cf_formula(
774 &mut self,
775 sheet: u32,
776 formula: &str,
777 dxf_id: u32,
778 stop_if_true: bool,
779 ranges: &[(i32, i32, i32, i32)],
780 ) {
781 let Some((anchor_row, anchor_col)) = ranges
787 .iter()
788 .map(|&(r1, c1, _, _)| (r1, c1))
789 .reduce(|(r, c), (r1, c1)| (r.min(r1), c.min(c1)))
790 else {
791 return;
792 };
793 let body = formula.trim().strip_prefix('=').unwrap_or(formula.trim());
794 if body.is_empty() {
795 return;
796 }
797 let sheet_name = match self.workbook.worksheets.get(sheet as usize) {
798 Some(ws) => ws.get_name(),
799 None => return,
800 };
801 let context_rc = CellReferenceRC {
802 sheet: sheet_name,
803 row: anchor_row,
804 column: anchor_col,
805 };
806 let node = self.parse_internal_formula(body, &context_rc);
808
809 for &(r1, c1, r2, c2) in ranges {
810 for row in r1..=r2 {
811 for column in c1..=c2 {
812 let ctx = CellReferenceIndex { sheet, row, column };
813 let matches = match self.evaluate_node_in_context(&node, ctx) {
814 CalcResult::Number(n) => n != 0.0,
815 CalcResult::Boolean(b) => b,
816 _ => false,
817 };
818 if matches {
819 self.update_cf_cache(
820 sheet,
821 row,
822 column,
823 CfCellResult::Dxf(dxf_id),
824 stop_if_true,
825 );
826 }
827 }
828 }
829 }
830 }
831
832 fn apply_cf_unique_values(
833 &mut self,
834 sheet: u32,
835 dxf_id: u32,
836 stop_if_true: bool,
837 ranges: &[(i32, i32, i32, i32)],
838 ) {
839 let mut counts: HashMap<String, u32> = HashMap::new();
840 for &(r1, c1, r2, c2) in ranges {
841 for row in r1..=r2 {
842 for col in c1..=c2 {
843 if let Ok(v) = self.get_cell_value_by_index(sheet, row, col) {
844 if let Some(k) = cell_value_key(&v) {
845 *counts.entry(k).or_insert(0) += 1;
846 }
847 }
848 }
849 }
850 }
851 for &(r1, c1, r2, c2) in ranges {
852 for row in r1..=r2 {
853 for col in c1..=c2 {
854 if let Ok(v) = self.get_cell_value_by_index(sheet, row, col) {
855 if let Some(k) = cell_value_key(&v) {
856 if counts.get(&k).copied().unwrap_or(0) == 1 {
857 self.update_cf_cache(
858 sheet,
859 row,
860 col,
861 CfCellResult::Dxf(dxf_id),
862 stop_if_true,
863 );
864 }
865 }
866 }
867 }
868 }
869 }
870 }
871
872 #[allow(clippy::too_many_arguments)]
873 fn apply_cf_time_period(
874 &mut self,
875 sheet: u32,
876 period: &PeriodType,
877 date1: Option<&str>,
878 date2: Option<&str>,
879 dxf_id: u32,
880 stop_if_true: bool,
881 ranges: &[(i32, i32, i32, i32)],
882 ) {
883 let today_serial = match crate::tz::excel_serial_for_now(&self.tz) {
884 Some(s) => s.floor() as i64,
885 None => return,
886 };
887 let today = match from_excel_date(today_serial) {
888 Ok(d) => d,
889 Err(_) => return,
890 };
891
892 let serial_of = |d: NaiveDate| -> f64 {
893 date_to_serial_number(d.day(), d.month(), d.year()).unwrap_or(0) as f64
894 };
895
896 let range: (f64, f64) = match period {
897 PeriodType::Yesterday => {
898 let s = today_serial as f64 - 1.0;
899 (s, s)
900 }
901 PeriodType::Today => (today_serial as f64, today_serial as f64),
902 PeriodType::Tomorrow => {
903 let s = today_serial as f64 + 1.0;
904 (s, s)
905 }
906 PeriodType::Last7Days => (today_serial as f64 - 6.0, today_serial as f64),
907 PeriodType::Next7Days => (today_serial as f64, today_serial as f64 + 6.0),
908 PeriodType::LastWeek => {
909 let dow = today.weekday().num_days_from_monday() as i64;
910 let this_mon = today - Duration::days(dow);
911 let last_mon = this_mon - Duration::days(7);
912 let last_sun = this_mon - Duration::days(1);
913 (serial_of(last_mon), serial_of(last_sun))
914 }
915 PeriodType::ThisWeek => {
916 let dow = today.weekday().num_days_from_monday() as i64;
917 let this_mon = today - Duration::days(dow);
918 let this_sun = this_mon + Duration::days(6);
919 (serial_of(this_mon), serial_of(this_sun))
920 }
921 PeriodType::NextWeek => {
922 let dow = today.weekday().num_days_from_monday() as i64;
923 let next_mon = today - Duration::days(dow) + Duration::days(7);
924 let next_sun = next_mon + Duration::days(6);
925 (serial_of(next_mon), serial_of(next_sun))
926 }
927 PeriodType::LastMonth => {
928 let Some(this_month_start) =
929 NaiveDate::from_ymd_opt(today.year(), today.month(), 1)
930 else {
931 return;
932 };
933 let last_month_end = this_month_start - Duration::days(1);
934 let Some(last_month_start) =
935 NaiveDate::from_ymd_opt(last_month_end.year(), last_month_end.month(), 1)
936 else {
937 return;
938 };
939 (serial_of(last_month_start), serial_of(last_month_end))
940 }
941 PeriodType::ThisMonth => {
942 let Some(start) = NaiveDate::from_ymd_opt(today.year(), today.month(), 1) else {
943 return;
944 };
945 let end = start + Months::new(1) - Duration::days(1);
946 (serial_of(start), serial_of(end))
947 }
948 PeriodType::NextMonth => {
949 let Some(this_start) = NaiveDate::from_ymd_opt(today.year(), today.month(), 1)
950 else {
951 return;
952 };
953 let next_start = this_start + Months::new(1);
954 let next_end = next_start + Months::new(1) - Duration::days(1);
955 (serial_of(next_start), serial_of(next_end))
956 }
957 PeriodType::LastYear => {
958 let y = today.year() - 1;
959 let Some(start) = NaiveDate::from_ymd_opt(y, 1, 1) else {
960 return;
961 };
962 let Some(end) = NaiveDate::from_ymd_opt(y, 12, 31) else {
963 return;
964 };
965 (serial_of(start), serial_of(end))
966 }
967 PeriodType::ThisYear => {
968 let y = today.year();
969 let Some(start) = NaiveDate::from_ymd_opt(y, 1, 1) else {
970 return;
971 };
972 let Some(end) = NaiveDate::from_ymd_opt(y, 12, 31) else {
973 return;
974 };
975 (serial_of(start), serial_of(end))
976 }
977 PeriodType::NextYear => {
978 let y = today.year() + 1;
979 let Some(start) = NaiveDate::from_ymd_opt(y, 1, 1) else {
980 return;
981 };
982 let Some(end) = NaiveDate::from_ymd_opt(y, 12, 31) else {
983 return;
984 };
985 (serial_of(start), serial_of(end))
986 }
987 PeriodType::Between | PeriodType::NotBetween => {
988 let (Some(d1), Some(d2)) = (date1, date2) else {
989 return;
990 };
991 let (Some(s1), Some(s2)) = (parse_cf_date_bound(d1), parse_cf_date_bound(d2))
992 else {
993 return;
994 };
995 (s1.min(s2), s1.max(s2))
996 }
997 };
998 let negate = matches!(period, PeriodType::NotBetween);
999
1000 for &(r1, c1, r2, c2) in ranges {
1001 for row in r1..=r2 {
1002 for col in c1..=c2 {
1003 if let Ok(CellValue::Number(v)) = self.get_cell_value_by_index(sheet, row, col)
1004 {
1005 let day = v.floor();
1006 let inside = day >= range.0 && day <= range.1;
1007 if inside != negate {
1008 self.update_cf_cache(
1009 sheet,
1010 row,
1011 col,
1012 CfCellResult::Dxf(dxf_id),
1013 stop_if_true,
1014 );
1015 }
1016 }
1017 }
1018 }
1019 }
1020 }
1021
1022 fn apply_cf_duplicate_values(
1023 &mut self,
1024 sheet: u32,
1025 dxf_id: u32,
1026 stop_if_true: bool,
1027 ranges: &[(i32, i32, i32, i32)],
1028 ) {
1029 let mut counts: HashMap<String, u32> = HashMap::new();
1030 for &(r1, c1, r2, c2) in ranges {
1031 for row in r1..=r2 {
1032 for col in c1..=c2 {
1033 if let Ok(v) = self.get_cell_value_by_index(sheet, row, col) {
1034 let key = cell_value_key(&v);
1035 if let Some(k) = key {
1036 *counts.entry(k).or_insert(0) += 1;
1037 }
1038 }
1039 }
1040 }
1041 }
1042 for &(r1, c1, r2, c2) in ranges {
1043 for row in r1..=r2 {
1044 for col in c1..=c2 {
1045 if let Ok(v) = self.get_cell_value_by_index(sheet, row, col) {
1046 if let Some(k) = cell_value_key(&v) {
1047 if counts.get(&k).copied().unwrap_or(0) > 1 {
1048 self.update_cf_cache(
1049 sheet,
1050 row,
1051 col,
1052 CfCellResult::Dxf(dxf_id),
1053 stop_if_true,
1054 );
1055 }
1056 }
1057 }
1058 }
1059 }
1060 }
1061 }
1062
1063 fn apply_cf_blanks(
1064 &mut self,
1065 sheet: u32,
1066 dxf_id: u32,
1067 invert: bool,
1068 stop_if_true: bool,
1069 ranges: &[(i32, i32, i32, i32)],
1070 ) {
1071 for &(r1, c1, r2, c2) in ranges {
1072 for row in r1..=r2 {
1073 for col in c1..=c2 {
1074 let is_blank = matches!(
1075 self.get_cell_value_by_index(sheet, row, col),
1076 Ok(CellValue::None)
1077 );
1078 if is_blank != invert {
1079 self.update_cf_cache(
1080 sheet,
1081 row,
1082 col,
1083 CfCellResult::Dxf(dxf_id),
1084 stop_if_true,
1085 );
1086 }
1087 }
1088 }
1089 }
1090 }
1091
1092 fn apply_cf_errors(
1093 &mut self,
1094 sheet: u32,
1095 dxf_id: u32,
1096 invert: bool,
1097 stop_if_true: bool,
1098 ranges: &[(i32, i32, i32, i32)],
1099 ) {
1100 use crate::types::CellType;
1101 for &(r1, c1, r2, c2) in ranges {
1102 for row in r1..=r2 {
1103 for col in c1..=c2 {
1104 let is_error = self
1105 .workbook
1106 .worksheet(sheet)
1107 .ok()
1108 .and_then(|ws| ws.cell(row, col))
1109 .is_some_and(|c| c.get_type() == CellType::ErrorValue);
1110 if is_error != invert {
1111 self.update_cf_cache(
1112 sheet,
1113 row,
1114 col,
1115 CfCellResult::Dxf(dxf_id),
1116 stop_if_true,
1117 );
1118 }
1119 }
1120 }
1121 }
1122 }
1123
1124 pub fn get_extended_style_for_cell(
1129 &self,
1130 sheet: u32,
1131 row: i32,
1132 column: i32,
1133 ) -> Result<ExtendedStyle, String> {
1134 let base = self.get_style_for_cell(sheet, row, column)?;
1135 let extended = match self.cf_cache.get(&(sheet, row, column)) {
1136 Some(c) => c,
1137 None => {
1138 return Ok(ExtendedStyle {
1139 style: base,
1140 icon: None,
1141 data_bar: None,
1142 rating: None,
1143 })
1144 }
1145 };
1146 let mut style = base;
1147 let mut icon = None;
1148 let mut data_bar = None;
1149 let mut rating = None;
1150 for cf_result in extended {
1155 match cf_result {
1156 CfCellResult::Dxf(dxf_id) => {
1157 if let Some(dxf) = self.workbook.styles.dxfs.get(*dxf_id as usize) {
1158 style = dxf.apply_to(&style);
1159 }
1160 }
1161 CfCellResult::ColorScale(color) => {
1162 style.fill.color = color.clone();
1163 }
1164 CfCellResult::DataBar {
1165 positive_color,
1166 negative_color,
1167 is_gradient,
1168 value,
1169 axis_position,
1170 show_value,
1171 } => {
1172 data_bar = Some(CfDataBar {
1173 positive_color: positive_color.clone(),
1174 negative_color: negative_color.clone(),
1175 is_gradient: *is_gradient,
1176 value: *value,
1177 axis_position: *axis_position,
1178 show_value: *show_value,
1179 });
1180 }
1181 CfCellResult::Icon {
1182 icon: cf_icon,
1183 color,
1184 show_value,
1185 } => {
1186 icon = Some(CfIcon {
1187 icon: cf_icon.clone(),
1188 color: color.clone(),
1189 show_value: *show_value,
1190 });
1191 }
1192 CfCellResult::Rating {
1193 icon: cf_icon,
1194 count,
1195 max,
1196 color,
1197 show_value,
1198 } => {
1199 rating = Some(CfRating {
1200 icon: cf_icon.clone(),
1201 count: *count,
1202 max: *max,
1203 color: color.clone(),
1204 show_value: *show_value,
1205 });
1206 }
1207 }
1208 }
1209 Ok(ExtendedStyle {
1210 style,
1211 icon,
1212 data_bar,
1213 rating,
1214 })
1215 }
1216
1217 fn create_dxf(&mut self, dxf: Dxf) -> u32 {
1223 let id = self.workbook.styles.dxfs.len() as u32;
1224 self.workbook.styles.dxfs.push(dxf);
1225 id
1226 }
1227
1228 fn cf_rule_from_input(&mut self, rule: CfRuleInput) -> CfRule {
1230 match rule {
1231 CfRuleInput::ColorScale { thresholds } => CfRule::ColorScale { thresholds },
1232 CfRuleInput::CellIs {
1233 operator,
1234 formula,
1235 formula2,
1236 format,
1237 stop_if_true,
1238 } => CfRule::CellIs {
1239 operator,
1240 formula,
1241 formula2,
1242 dxf_id: self.create_dxf(format),
1243 stop_if_true,
1244 },
1245 CfRuleInput::Text {
1246 operator,
1247 value,
1248 format,
1249 stop_if_true,
1250 } => CfRule::Text {
1251 operator,
1252 value,
1253 dxf_id: self.create_dxf(format),
1254 stop_if_true,
1255 },
1256 CfRuleInput::Formula {
1257 formula,
1258 format,
1259 stop_if_true,
1260 } => CfRule::Formula {
1261 formula,
1262 dxf_id: self.create_dxf(format),
1263 stop_if_true,
1264 },
1265 CfRuleInput::TimePeriod {
1266 time_period,
1267 date1,
1268 date2,
1269 format,
1270 stop_if_true,
1271 } => CfRule::TimePeriod {
1272 time_period,
1273 date1,
1274 date2,
1275 dxf_id: self.create_dxf(format),
1276 stop_if_true,
1277 },
1278 CfRuleInput::DuplicateValues {
1279 format,
1280 stop_if_true,
1281 } => CfRule::DuplicateValues {
1282 dxf_id: self.create_dxf(format),
1283 stop_if_true,
1284 },
1285 CfRuleInput::UniqueValues {
1286 format,
1287 stop_if_true,
1288 } => CfRule::UniqueValues {
1289 dxf_id: self.create_dxf(format),
1290 stop_if_true,
1291 },
1292 CfRuleInput::Blanks {
1293 format,
1294 stop_if_true,
1295 } => CfRule::Blanks {
1296 dxf_id: self.create_dxf(format),
1297 stop_if_true,
1298 },
1299 CfRuleInput::NotBlanks {
1300 format,
1301 stop_if_true,
1302 } => CfRule::NotBlanks {
1303 dxf_id: self.create_dxf(format),
1304 stop_if_true,
1305 },
1306 CfRuleInput::Errors {
1307 format,
1308 stop_if_true,
1309 } => CfRule::Errors {
1310 dxf_id: self.create_dxf(format),
1311 stop_if_true,
1312 },
1313 CfRuleInput::NoErrors {
1314 format,
1315 stop_if_true,
1316 } => CfRule::NoErrors {
1317 dxf_id: self.create_dxf(format),
1318 stop_if_true,
1319 },
1320 CfRuleInput::AboveAverage {
1321 format,
1322 stop_if_true,
1323 } => CfRule::AboveAverage {
1324 dxf_id: self.create_dxf(format),
1325 stop_if_true,
1326 },
1327 CfRuleInput::BelowAverage {
1328 format,
1329 stop_if_true,
1330 } => CfRule::BelowAverage {
1331 dxf_id: self.create_dxf(format),
1332 stop_if_true,
1333 },
1334 CfRuleInput::Top10 {
1335 rank,
1336 percent,
1337 format,
1338 stop_if_true,
1339 } => CfRule::Top10 {
1340 rank,
1341 percent,
1342 dxf_id: self.create_dxf(format),
1343 stop_if_true,
1344 },
1345 CfRuleInput::Bottom10 {
1346 rank,
1347 percent,
1348 format,
1349 stop_if_true,
1350 } => CfRule::Bottom10 {
1351 rank,
1352 percent,
1353 dxf_id: self.create_dxf(format),
1354 stop_if_true,
1355 },
1356 CfRuleInput::DataBar {
1357 min,
1358 max,
1359 positive_color,
1360 negative_color,
1361 is_gradient,
1362 show_value,
1363 } => CfRule::DataBar {
1364 min,
1365 max,
1366 positive_color,
1367 negative_color,
1368 is_gradient,
1369 show_value,
1370 },
1371 CfRuleInput::IconSet {
1372 thresholds,
1373 show_value,
1374 } => CfRule::IconSet {
1375 thresholds,
1376 show_value,
1377 },
1378 CfRuleInput::IconRating {
1379 icon,
1380 color,
1381 thresholds,
1382 show_value,
1383 } => CfRule::IconRating {
1384 icon,
1385 color,
1386 thresholds,
1387 show_value,
1388 },
1389 }
1390 }
1391
1392 fn cf_formula_context(&self, sheet: u32) -> CellReferenceRC {
1396 let sheet_name = self
1397 .workbook
1398 .worksheets
1399 .get(sheet as usize)
1400 .map(|w| w.get_name())
1401 .unwrap_or_default();
1402 CellReferenceRC {
1403 sheet: sheet_name,
1404 row: 1,
1405 column: 1,
1406 }
1407 }
1408
1409 fn cf_rule_input_to_internal(
1417 &mut self,
1418 rule: &mut CfRuleInput,
1419 sheet: u32,
1420 ) -> Result<(), String> {
1421 let ctx = self.cf_formula_context(sheet);
1422 match rule {
1423 CfRuleInput::CellIs {
1424 formula, formula2, ..
1425 } => {
1426 *formula = self.user_formula_to_internal(formula, &ctx)?;
1427 if let Some(f2) = formula2 {
1428 *f2 = self.user_formula_to_internal(f2, &ctx)?;
1429 }
1430 }
1431 CfRuleInput::Formula { formula, .. } => {
1432 *formula = self.user_formula_to_internal(formula, &ctx)?;
1433 }
1434 CfRuleInput::ColorScale { thresholds } => {
1435 for t in thresholds {
1436 self.cfvo_to_internal(&mut t.cfvo, &ctx)?;
1437 }
1438 }
1439 CfRuleInput::DataBar { min, max, .. } => {
1440 if let Some(min) = min {
1441 self.cfvo_to_internal(min, &ctx)?;
1442 }
1443 if let Some(max) = max {
1444 self.cfvo_to_internal(max, &ctx)?;
1445 }
1446 }
1447 CfRuleInput::IconSet { thresholds, .. } => {
1448 for t in thresholds {
1449 self.cfvo_to_internal(&mut t.cfvo, &ctx)?;
1450 }
1451 }
1452 CfRuleInput::IconRating { thresholds, .. } => {
1453 for (cfvo, _) in thresholds {
1454 self.cfvo_to_internal(cfvo, &ctx)?;
1455 }
1456 }
1457 _ => {}
1459 }
1460 Ok(())
1461 }
1462
1463 fn cfvo_to_internal(&mut self, cfvo: &mut Cfvo, ctx: &CellReferenceRC) -> Result<(), String> {
1464 if let Cfvo::Formula(f) = cfvo {
1465 *f = self.user_formula_to_internal(f, ctx)?;
1466 }
1467 Ok(())
1468 }
1469
1470 fn cf_rule_to_display(&self, rule: &mut CfRule, sheet: u32) {
1473 let ctx = self.cf_formula_context(sheet);
1474 match rule {
1475 CfRule::CellIs {
1476 formula, formula2, ..
1477 } => {
1478 *formula = self.internal_formula_to_display(formula, &ctx);
1479 if let Some(f2) = formula2 {
1480 *f2 = self.internal_formula_to_display(f2, &ctx);
1481 }
1482 }
1483 CfRule::Formula { formula, .. } => {
1484 *formula = self.internal_formula_to_display(formula, &ctx);
1485 }
1486 CfRule::ColorScale { thresholds } => {
1487 for t in thresholds {
1488 self.cfvo_to_display(&mut t.cfvo, &ctx);
1489 }
1490 }
1491 CfRule::DataBar { min, max, .. } => {
1492 if let Some(min) = min {
1493 self.cfvo_to_display(min, &ctx);
1494 }
1495 if let Some(max) = max {
1496 self.cfvo_to_display(max, &ctx);
1497 }
1498 }
1499 CfRule::IconSet { thresholds, .. } => {
1500 for t in thresholds {
1501 self.cfvo_to_display(&mut t.cfvo, &ctx);
1502 }
1503 }
1504 CfRule::IconRating { thresholds, .. } => {
1505 for (cfvo, _) in thresholds {
1506 self.cfvo_to_display(cfvo, &ctx);
1507 }
1508 }
1509 _ => {}
1510 }
1511 }
1512
1513 fn cfvo_to_display(&self, cfvo: &mut Cfvo, ctx: &CellReferenceRC) {
1514 if let Cfvo::Formula(f) = cfvo {
1515 *f = self.internal_formula_to_display(f, ctx);
1516 }
1517 }
1518
1519 pub fn get_conditional_formatting_list(
1533 &self,
1534 sheet: u32,
1535 ) -> Result<Vec<ConditionalFormattingView>, String> {
1536 let mut list: Vec<(usize, ConditionalFormatting)> = self
1537 .workbook
1538 .worksheet(sheet)?
1539 .conditional_formatting
1540 .iter()
1541 .cloned()
1542 .enumerate()
1543 .collect();
1544 list.sort_by_key(|(_, cf)| std::cmp::Reverse(cf.priority));
1546 let mut result = Vec::with_capacity(list.len());
1547 for (index, mut cf) in list {
1548 self.cf_rule_to_display(&mut cf.cf_rule, sheet);
1549 result.push(ConditionalFormattingView {
1550 index,
1551 range: cf.range,
1552 cf_rule: cf.cf_rule,
1553 priority: cf.priority,
1554 });
1555 }
1556 Ok(result)
1557 }
1558
1559 pub fn get_dxf_for_conditional_formatting(
1562 &self,
1563 sheet: u32,
1564 index: usize,
1565 ) -> Result<Option<Dxf>, String> {
1566 let ws = self.workbook.worksheet(sheet)?;
1567 let cf = ws
1568 .conditional_formatting
1569 .get(index)
1570 .ok_or_else(|| format!("Conditional formatting index {index} out of bounds"))?;
1571 let dxf_id = match &cf.cf_rule {
1572 CfRule::CellIs { dxf_id, .. }
1573 | CfRule::Text { dxf_id, .. }
1574 | CfRule::TimePeriod { dxf_id, .. }
1575 | CfRule::DuplicateValues { dxf_id, .. }
1576 | CfRule::UniqueValues { dxf_id, .. }
1577 | CfRule::Blanks { dxf_id, .. }
1578 | CfRule::NotBlanks { dxf_id, .. }
1579 | CfRule::Errors { dxf_id, .. }
1580 | CfRule::NoErrors { dxf_id, .. }
1581 | CfRule::AboveAverage { dxf_id, .. }
1582 | CfRule::BelowAverage { dxf_id, .. }
1583 | CfRule::Top10 { dxf_id, .. }
1584 | CfRule::Bottom10 { dxf_id, .. }
1585 | CfRule::Formula { dxf_id, .. } => *dxf_id,
1586 _ => return Ok(None),
1587 };
1588 Ok(self.workbook.styles.dxfs.get(dxf_id as usize).cloned())
1589 }
1590
1591 pub fn add_conditional_formatting(
1594 &mut self,
1595 sheet: u32,
1596 range: &str,
1597 rule: CfRuleInput,
1598 ) -> Result<u32, String> {
1599 if parse_sqref(range).is_empty() {
1600 return Err(format!("Invalid conditional formatting range: '{range}'"));
1601 }
1602 let mut rule = rule;
1606 self.cf_rule_input_to_internal(&mut rule, sheet)?;
1607 let final_rule = self.cf_rule_from_input(rule);
1608 let ws = self.workbook.worksheet_mut(sheet)?;
1609 let priority = ws
1610 .conditional_formatting
1611 .iter()
1612 .map(|cf| cf.priority)
1613 .max()
1614 .map(|m| m + 1)
1615 .unwrap_or(1);
1616 ws.conditional_formatting.push(ConditionalFormatting {
1617 range: range.to_string(),
1618 cf_rule: final_rule,
1619 priority,
1620 });
1621 Ok(priority)
1622 }
1623
1624 pub fn delete_conditional_formatting(
1626 &mut self,
1627 sheet: u32,
1628 index: usize,
1629 ) -> Result<crate::cf_types::ConditionalFormatting, String> {
1630 let ws = self.workbook.worksheet_mut(sheet)?;
1631 if index >= ws.conditional_formatting.len() {
1632 return Err(format!(
1633 "Conditional formatting index {index} out of bounds"
1634 ));
1635 }
1636 Ok(ws.conditional_formatting.remove(index))
1637 }
1638
1639 pub fn update_conditional_formatting(
1642 &mut self,
1643 sheet: u32,
1644 index: usize,
1645 new_range: &str,
1646 new_rule: CfRuleInput,
1647 ) -> Result<ConditionalFormatting, String> {
1648 if parse_sqref(new_range).is_empty() {
1649 return Err(format!(
1650 "Invalid conditional formatting range: '{new_range}'"
1651 ));
1652 }
1653 let mut new_rule = new_rule;
1657 self.cf_rule_input_to_internal(&mut new_rule, sheet)?;
1658 let final_rule = self.cf_rule_from_input(new_rule);
1659 let ws = self.workbook.worksheet_mut(sheet)?;
1660 if index >= ws.conditional_formatting.len() {
1661 return Err(format!(
1662 "Conditional formatting index {index} out of bounds"
1663 ));
1664 }
1665 let old = ws.conditional_formatting[index].clone();
1666 ws.conditional_formatting[index].range = new_range.to_string();
1667 ws.conditional_formatting[index].cf_rule = final_rule;
1668 Ok(old)
1669 }
1670
1671 pub fn raise_conditional_formatting_priority(
1678 &mut self,
1679 sheet: u32,
1680 index: usize,
1681 ) -> Result<(), String> {
1682 let ws = self.workbook.worksheet_mut(sheet)?;
1683 if index >= ws.conditional_formatting.len() {
1684 return Err(format!(
1685 "Conditional formatting index {index} out of bounds"
1686 ));
1687 }
1688 let current = ws.conditional_formatting[index].priority;
1689 let neighbour = ws
1692 .conditional_formatting
1693 .iter()
1694 .enumerate()
1695 .filter(|(_, cf)| cf.priority > current)
1696 .min_by_key(|(_, cf)| cf.priority)
1697 .map(|(i, _)| i);
1698 if let Some(j) = neighbour {
1699 let other = ws.conditional_formatting[j].priority;
1700 ws.conditional_formatting[j].priority = current;
1701 ws.conditional_formatting[index].priority = other;
1702 }
1703 Ok(())
1704 }
1705
1706 pub fn lower_conditional_formatting_priority(
1713 &mut self,
1714 sheet: u32,
1715 index: usize,
1716 ) -> Result<(), String> {
1717 let ws = self.workbook.worksheet_mut(sheet)?;
1718 if index >= ws.conditional_formatting.len() {
1719 return Err(format!(
1720 "Conditional formatting index {index} out of bounds"
1721 ));
1722 }
1723 let current = ws.conditional_formatting[index].priority;
1724 let neighbour = ws
1727 .conditional_formatting
1728 .iter()
1729 .enumerate()
1730 .filter(|(_, cf)| cf.priority < current)
1731 .max_by_key(|(_, cf)| cf.priority)
1732 .map(|(i, _)| i);
1733 if let Some(j) = neighbour {
1734 let other = ws.conditional_formatting[j].priority;
1735 ws.conditional_formatting[j].priority = current;
1736 ws.conditional_formatting[index].priority = other;
1737 }
1738 Ok(())
1739 }
1740
1741 pub(crate) fn insert_conditional_formatting_at(
1743 &mut self,
1744 sheet: u32,
1745 index: usize,
1746 cf: crate::cf_types::ConditionalFormatting,
1747 ) -> Result<(), String> {
1748 let ws = self.workbook.worksheet_mut(sheet)?;
1749 if index > ws.conditional_formatting.len() {
1750 return Err(format!(
1751 "Conditional formatting index {index} out of bounds"
1752 ));
1753 }
1754 ws.conditional_formatting.insert(index, cf);
1755 Ok(())
1756 }
1757}