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