xlsx_diff/core/
mod.rs

1use std::error::Error;
2use std::path::PathBuf;
3extern crate serde_json;
4pub mod diff;
5pub mod parse;
6use diff::DiffItem;
7use parse::{CalamineWorkbook, FileLike, SerializableData,WorkbookData};
8use serde::Serialize;
9
10#[derive(Serialize)]
11struct ModifiedSheet {
12    sheet_name: String,
13    diff: Vec<DiffItem<Vec<SerializableData>>>,
14}
15#[derive(Serialize)]
16struct OriginWorkbookData {
17    sheet_names: Vec<String>,
18    data: Option<WorkbookData>
19}
20
21#[derive(Serialize)]
22struct OriginData {
23    old: OriginWorkbookData,
24    new: OriginWorkbookData,
25}
26#[derive(Serialize)]
27pub struct DiffResult {
28    added_sheets: Vec<String>,
29    removed_sheets: Vec<String>,
30    no_change_sheets: Vec<String>,
31    modified_sheets: Vec<ModifiedSheet>,
32    data: OriginData
33}
34/**
35 * Compare two xlsx files
36 * @param old_file_path old file path
37 * @param new_file_path new file path
38 * @param raw_data output raw data
39 * @param header_row todo
40 * @returns diff result
41 */
42pub fn diff_xlsx(
43    old_file_path: PathBuf,
44    new_file_path: PathBuf,
45    raw_data: bool,
46    // header_row: usize,
47) -> Result<DiffResult, Box<dyn Error>> {
48    let wb_old: parse::CalamineWorkbook = parse::load_workbook(&FileLike::Path(old_file_path))?;
49    let wb_new = parse::load_workbook(&FileLike::Path(new_file_path))?;
50    return build_diff(wb_old, wb_new, raw_data);
51}
52pub fn diff_xlsx_from_u8(
53    old_file: &[u8],
54    new_file: &[u8],
55    raw_data: bool,
56) -> Result<DiffResult, Box<dyn Error>> {
57    let wb_old: CalamineWorkbook = parse::load_workbook(&FileLike::Bytes(old_file.to_vec()))?;
58    let wb_new = parse::load_workbook(&FileLike::Bytes(new_file.to_vec()))?;
59    return build_diff(wb_old, wb_new, raw_data);
60}
61
62pub fn build_diff(
63    wb_old: CalamineWorkbook,
64    wb_new: CalamineWorkbook,
65    raw_data: bool,
66) -> Result<DiffResult, Box<dyn Error>> {
67    let mut modified_sheets: Vec<ModifiedSheet> = Vec::new();
68    let mut no_change_sheets: Vec<String> = Vec::new();
69    // find added and removed sheets
70    let added_sheets: Vec<String> = wb_new
71        .sheet_names
72        .iter()
73        .filter(|sheet_name| !wb_old.sheet_names.contains(*sheet_name))
74        .map(|sheet_name| sheet_name.to_string())
75        .collect();
76    let removed_sheets: Vec<String> = wb_old
77        .sheet_names
78        .iter()
79        .filter(|sheet_name| !wb_new.sheet_names.contains(*sheet_name))
80        .map(|sheet_name| sheet_name.to_string())
81        .collect();
82    for sheet_name in wb_old.sheet_names.iter() {
83        if wb_new.sheet_names.contains(sheet_name) {
84            let wb_old_first_sheet_data: &Vec<Vec<SerializableData>> =
85                wb_old.data.get(&sheet_name.to_string()).unwrap();
86            let wb_new_first_sheet_data = wb_new.data.get(&sheet_name.to_string()).unwrap();
87            let res = diff::myers_diff(&wb_old_first_sheet_data, &wb_new_first_sheet_data);
88            if res.len() == 0 {
89                no_change_sheets.push(sheet_name.to_string());
90            } else {
91                modified_sheets.push(ModifiedSheet {
92                    sheet_name: sheet_name.to_string(),
93                    diff: res,
94                });
95            }
96        }
97    }
98    return Ok(DiffResult {
99        added_sheets,
100        removed_sheets,
101        modified_sheets,
102        no_change_sheets,
103        data: OriginData{
104            old: OriginWorkbookData{
105                sheet_names: wb_old.sheet_names,
106                data: if raw_data {Some(wb_old.data)} else {None}
107            },
108            new: OriginWorkbookData{
109                sheet_names: wb_new.sheet_names,
110                data: if raw_data {Some(wb_new.data)} else {None}
111            }
112        }
113    });
114}