1use std::{fmt, fs::File, io::BufReader};
2
3use calamine::{open_workbook, Data, Reader, Xlsx};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::utils::{cell_pos_to_address, diff_range, filter_same_name_sheets};
8
9#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub enum CellDiffKind {
12 Value,
13 Formula,
14}
15
16impl fmt::Display for CellDiffKind {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 match self {
19 CellDiffKind::Formula => write!(f, "formula"),
20 CellDiffKind::Value => write!(f, "value"),
21 }
22 }
23}
24
25#[derive(Clone, Debug)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub struct Diff {
29 pub old_filepath: String,
30 pub new_filepath: String,
31 pub sheet_diff: Vec<SheetDiff>,
32 pub cell_diffs: Vec<SheetCellDiff>,
33}
34
35#[derive(Clone, Debug)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct SheetDiff {
38 pub old: Option<String>,
39 pub new: Option<String>,
40}
41
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44pub struct SheetCellDiff {
45 pub sheet: String,
46 pub cells: Vec<CellDiff>,
47}
48
49#[derive(Clone, Debug)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51pub struct CellDiff {
52 pub row: usize,
53 pub col: usize,
54 pub addr: String,
55 pub kind: CellDiffKind,
56 pub old: Option<String>,
57 pub new: Option<String>,
58}
59
60impl Diff {
61 pub fn new(old_filepath: &str, new_filepath: &str) -> Self {
63 let mut ret = Diff {
64 old_filepath: old_filepath.to_owned(),
65 new_filepath: new_filepath.to_owned(),
66 sheet_diff: vec![],
67 cell_diffs: vec![],
68 };
69
70 ret.collect_diff();
71
72 ret.cell_diffs.sort_by(|a, b| a.sheet.cmp(&b.sheet));
73
74 let mut merged_cell_diffs: Vec<SheetCellDiff> = vec![];
75 ret.cell_diffs.iter().for_each(|a| {
76 let found = merged_cell_diffs.iter_mut().find(|b| b.sheet == a.sheet);
77 if let Some(found) = found {
78 found.cells.extend(a.cells.clone());
79 } else {
80 merged_cell_diffs.push(a.clone());
81 }
82 });
83 merged_cell_diffs.iter_mut().for_each(|x| {
84 x.cells
85 .sort_by(|a, b| a.addr.cmp(&b.addr).then_with(|| a.kind.cmp(&b.kind)));
86 });
87 ret.cell_diffs = merged_cell_diffs;
88
89 ret
90 }
91
92 pub fn diff(&mut self) -> Diff {
95 self.clone()
96 }
97
98 fn collect_diff(&mut self) {
100 let mut old_workbook: Xlsx<BufReader<File>> = open_workbook(self.old_filepath.as_str())
101 .expect(format!("Cannot open {}", self.old_filepath.as_str()).as_str());
102 let mut new_workbook: Xlsx<BufReader<File>> = open_workbook(self.new_filepath.as_str())
103 .expect(format!("Cannot open {}", self.new_filepath.as_str()).as_str());
104
105 let old_sheets = old_workbook.sheet_names().to_owned();
106 let new_sheets = new_workbook.sheet_names().to_owned();
107
108 self.collect_sheet_diff(&old_sheets, &new_sheets);
109
110 let same_name_sheets = filter_same_name_sheets(&old_sheets, &new_sheets);
111 self.collect_cell_value_diff(&mut old_workbook, &mut new_workbook, &same_name_sheets);
112 self.collect_cell_formula_diff(&mut old_workbook, &mut new_workbook, &same_name_sheets);
113 }
114
115 fn collect_sheet_diff(&mut self, old_sheets: &Vec<String>, new_sheets: &Vec<String>) {
117 if *old_sheets == *new_sheets {
118 return;
119 }
120
121 for sheet in old_sheets {
122 if !new_sheets.contains(sheet) {
123 self.sheet_diff.push(SheetDiff {
124 old: Some(sheet.to_owned()),
125 new: None,
126 });
127 }
128 }
129 for sheet in new_sheets {
130 if !old_sheets.contains(sheet) {
131 self.sheet_diff.push(SheetDiff {
132 old: None,
133 new: Some(sheet.to_owned()),
134 });
135 }
136 }
137 }
138
139 fn collect_cell_value_diff(
141 &mut self,
142 old_workbook: &mut Xlsx<BufReader<File>>,
143 new_workbook: &mut Xlsx<BufReader<File>>,
144 same_name_sheets: &Vec<String>,
145 ) {
146 for sheet in same_name_sheets {
147 if let (Ok(old_range), Ok(new_range)) = (
148 old_workbook.worksheet_range(sheet),
149 new_workbook.worksheet_range(sheet),
150 ) {
151 let mut cell_diffs: Vec<CellDiff> = vec![];
152
153 let (start_row, start_col, end_row, end_col) = diff_range(
154 old_range.start(),
155 new_range.start(),
156 old_range.end(),
157 new_range.end(),
158 );
159
160 for row in start_row..end_row {
161 for col in start_col..end_col {
162 let old_cell = old_range.get_value((row, col)).unwrap_or(&Data::Empty);
163 let new_cell = new_range.get_value((row, col)).unwrap_or(&Data::Empty);
164
165 if old_cell != new_cell {
166 let row = (row + 1) as usize;
167 let col = (col + 1) as usize;
168 cell_diffs.push(CellDiff {
169 row,
170 col,
171 addr: cell_pos_to_address(row, col),
172 kind: CellDiffKind::Value,
173 old: if old_cell != &Data::Empty {
174 Some(old_cell.to_string())
175 } else {
176 None
177 },
178 new: if new_cell != &Data::Empty {
179 Some(new_cell.to_string())
180 } else {
181 None
182 },
183 });
184 }
185 }
186 }
187
188 if !cell_diffs.is_empty() {
189 let sheet_cell_diff = SheetCellDiff {
190 sheet: sheet.to_owned(),
191 cells: cell_diffs,
192 };
193 self.cell_diffs.push(sheet_cell_diff);
194 }
195 } else {
196 println!("Failed to read sheet: {}", sheet);
197 }
198 }
199 }
200
201 fn collect_cell_formula_diff(
203 &mut self,
204 old_workbook: &mut Xlsx<BufReader<File>>,
205 new_workbook: &mut Xlsx<BufReader<File>>,
206 same_name_sheets: &Vec<String>,
207 ) {
208 for sheet in same_name_sheets {
209 if let (Ok(old_range), Ok(new_range)) = (
210 old_workbook.worksheet_formula(sheet),
211 new_workbook.worksheet_formula(sheet),
212 ) {
213 let mut cell_diffs: Vec<CellDiff> = vec![];
214
215 let (start_row, start_col, end_row, end_col) = diff_range(
216 old_range.start(),
217 new_range.start(),
218 old_range.end(),
219 new_range.end(),
220 );
221
222 for row in start_row..end_row {
223 for col in start_col..end_col {
224 let old_cell = match old_range.get_value((row, col)) {
225 Some(x) => &Data::String(x.to_string()),
226 None => &Data::Empty,
227 };
228 let new_cell = match new_range.get_value((row, col)) {
229 Some(x) => &Data::String(x.to_string()),
230 None => &Data::Empty,
231 };
232
233 if old_cell != new_cell {
234 let row = (row + 1) as usize;
235 let col = (col + 1) as usize;
236 cell_diffs.push(CellDiff {
237 row,
238 col,
239 addr: cell_pos_to_address(row, col),
240 kind: CellDiffKind::Formula,
241 old: if old_cell != &Data::Empty {
242 Some(old_cell.to_string())
243 } else {
244 None
245 },
246 new: if new_cell != &Data::Empty {
247 Some(new_cell.to_string())
248 } else {
249 None
250 },
251 });
252 }
253 }
254 }
255
256 if !cell_diffs.is_empty() {
257 let sheet_cell_diff = SheetCellDiff {
258 sheet: sheet.to_owned(),
259 cells: cell_diffs,
260 };
261 self.cell_diffs.push(sheet_cell_diff);
262 }
263 } else {
264 println!("Failed to read sheet: {}", sheet);
265 }
266 }
267 }
268}