sheets_diff/core/
diff.rs

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/// main struct
26#[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    /// init
62    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    /// get serde-ready diff
93    /// #[cfg(feature = "serde")]
94    pub fn diff(&mut self) -> Diff {
95        self.clone()
96    }
97
98    /// collect sheet diff and cell range diff
99    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    /// collect sheet diff by name
116    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    /// collect value diff in cell range
140    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    /// collect formula diff in cell range
202    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}