Skip to main content

excel_cli/excel/workbook/
save.rs

1use anyhow::Result;
2use chrono::Local;
3use rust_xlsxwriter::{Format, Workbook as XlsxWorkbook, Worksheet};
4use std::path::{Path, PathBuf};
5
6use super::Workbook;
7use crate::excel::{Cell, CellType, Sheet};
8
9impl Workbook {
10    pub fn save(&mut self) -> Result<()> {
11        if !self.is_modified {
12            println!("No changes to save.");
13            return Ok(());
14        }
15
16        self.ensure_all_sheets_loaded()?;
17
18        let mut workbook = XlsxWorkbook::new();
19        let new_filepath = timestamped_save_path(&self.file_path);
20        let number_format = Format::new().set_num_format("General");
21        let date_format = Format::new().set_num_format("yyyy-mm-dd");
22
23        for sheet in &self.sheets {
24            write_sheet(&mut workbook, sheet, &number_format, &date_format)?;
25        }
26
27        workbook.save(&new_filepath)?;
28        self.is_modified = false;
29
30        Ok(())
31    }
32}
33
34fn timestamped_save_path(file_path: &str) -> PathBuf {
35    let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
36    let path = Path::new(file_path);
37    let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("sheet");
38    let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("xlsx");
39    let parent_dir = path.parent().unwrap_or_else(|| Path::new(""));
40    parent_dir.join(format!("{file_stem}_{timestamp}.{extension}"))
41}
42
43fn write_sheet(
44    workbook: &mut XlsxWorkbook,
45    sheet: &Sheet,
46    number_format: &Format,
47    date_format: &Format,
48) -> Result<()> {
49    let worksheet = workbook.add_worksheet().set_name(&sheet.name)?;
50
51    if sheet.freeze_panes.is_frozen() {
52        worksheet.set_freeze_panes(
53            sheet.freeze_panes.rows as u32,
54            sheet.freeze_panes.cols as u16,
55        )?;
56    }
57
58    for col in 0..sheet.max_cols {
59        worksheet.set_column_width(col as u16, 15)?;
60    }
61
62    for row in 1..sheet.data.len() {
63        if row > sheet.max_rows {
64            continue;
65        }
66
67        for col in 1..sheet.data[0].len() {
68            if col > sheet.max_cols {
69                continue;
70            }
71
72            let cell = &sheet.data[row][col];
73            if cell.value.is_empty() {
74                continue;
75            }
76
77            let row_idx = (row - 1) as u32;
78            let col_idx = (col - 1) as u16;
79            write_cell(
80                worksheet,
81                cell,
82                row_idx,
83                col_idx,
84                number_format,
85                date_format,
86            )?;
87        }
88    }
89
90    Ok(())
91}
92
93fn write_cell(
94    worksheet: &mut Worksheet,
95    cell: &Cell,
96    row_idx: u32,
97    col_idx: u16,
98    number_format: &Format,
99    date_format: &Format,
100) -> Result<()> {
101    if cell.is_formula {
102        let formula_text = cell.formula.as_deref().unwrap_or(cell.value.as_str());
103        let formula = rust_xlsxwriter::Formula::new(formula_text);
104        worksheet.write_formula(row_idx, col_idx, formula)?;
105        if !cell.value.is_empty() && cell.value != formula_text {
106            worksheet.set_formula_result(row_idx, col_idx, &cell.value);
107        }
108        return Ok(());
109    }
110
111    match cell.cell_type {
112        CellType::Number => {
113            if let Ok(num) = cell.value.parse::<f64>() {
114                worksheet.write_number_with_format(row_idx, col_idx, num, number_format)?;
115            } else {
116                worksheet.write_string(row_idx, col_idx, &cell.value)?;
117            }
118        }
119        CellType::Date => {
120            worksheet.write_string_with_format(row_idx, col_idx, &cell.value, date_format)?;
121        }
122        CellType::Boolean => {
123            if let Ok(b) = cell.value.parse::<bool>() {
124                worksheet.write_boolean(row_idx, col_idx, b)?;
125            } else {
126                worksheet.write_string(row_idx, col_idx, &cell.value)?;
127            }
128        }
129        CellType::Text => {
130            worksheet.write_string(row_idx, col_idx, &cell.value)?;
131        }
132        CellType::Empty => {}
133    }
134
135    Ok(())
136}