xlsx_batch_reader/write/
mod.rs

1use std::collections::HashMap;
2
3use anyhow::{anyhow, Result};
4use rust_xlsxwriter::{Workbook, Worksheet, XlsxError, Format, IntoExcelData};
5
6use crate::{CellValue, ColNum, RowNum};
7
8impl IntoExcelData for CellValue<'_> {
9    fn write<'a>(
10        self,
11        worksheet: &'a mut Worksheet,
12        row: RowNum,
13        col: ColNum,
14    ) -> Result<&'a mut Worksheet, XlsxError> {
15        match self {
16            CellValue::Blank => {},
17            CellValue::Error(_) => {},
18            CellValue::Bool(v) => {
19                worksheet.write_boolean(row, col, v)?;
20            },
21            CellValue::Number(v) => {
22                worksheet.write_number(row, col, v)?;
23            },
24            CellValue::Date(v) => {
25                worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_DATE)?;
26            },
27            CellValue::Time(v) => {
28                worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_TIME)?;
29            },
30            CellValue::Datetime(v) => {
31                worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_DATETIME)?;
32            },
33            CellValue::Shared(v) => {
34                worksheet.write_string(row, col, v)?;
35            },
36            CellValue::String(v) => {
37                worksheet.write_string(row, col, v)?;
38            }
39        };
40        Ok(worksheet)
41    }
42
43    fn write_with_format<'a, 'b>(
44        self,
45        worksheet: &'a mut Worksheet,
46        row: RowNum,
47        col: ColNum,
48        format: &'b Format,
49    ) -> Result<&'a mut Worksheet, XlsxError> {
50        match self {
51            CellValue::Blank => {},
52            CellValue::Error(_) => {},
53            CellValue::Bool(v) => {
54                worksheet.write_boolean_with_format(row, col, v, format)?;
55            },
56            CellValue::Number(v) => {
57                worksheet.write_number_with_format(row, col, v, format)?;
58            },
59            CellValue::Date(v) => {
60                worksheet.write_number_with_format(row, col, v, format)?;
61            },
62            CellValue::Time(v) => {
63                worksheet.write_number_with_format(row, col, v, format)?;
64            },
65            CellValue::Datetime(v) => {
66                worksheet.write_number_with_format(row, col, v, format)?;
67            },
68            CellValue::Shared(v) => {
69                worksheet.write_string_with_format(row, col, v, format)?;
70            },
71            CellValue::String(v) => {
72                worksheet.write_string_with_format(row, col, v, format)?;
73            }
74        };
75        Ok(worksheet)
76    }
77}
78
79pub struct XlsxWriter {
80    book: Workbook,
81    rows: HashMap<String, RowNum>,
82    open: bool,
83    columns: HashMap<String, HashMap<String, ColNum>>,
84    prepends: HashMap<String, bool>
85}
86
87impl XlsxWriter {
88    /// new xlsx writer
89    pub fn new() -> Self {
90        Self {
91            open: true,
92            book: Workbook::new(),
93            rows: HashMap::new(),
94            columns: HashMap::new(),
95            prepends: HashMap::new()
96        }
97    }
98    /// check whether the sheet exists
99    #[inline]
100    pub fn has_sheet(&mut self, shname: &String) -> bool {
101        match self.book.worksheet_from_name(&shname) {
102            Ok(_) => true,
103            Err(_) => false
104        }
105    }
106    /// set columns, if you have many sheets call this for each sheet   
107    /// shname: sheet name   
108    /// columns: column names   
109    /// add_to_top: if true, the column names will be added to the top of the sheet
110    pub fn with_columns(&mut self, shname: String, columns: Vec<String>, add_to_top: bool) {
111        let mut maps = HashMap::with_capacity(columns.len());
112        for (i, colname) in columns.into_iter().enumerate() {
113            maps.insert(colname, i as ColNum);
114        }
115        self.columns.insert(shname.clone(), maps);
116        self.prepends.insert(shname, add_to_top);
117    }
118
119    /// get raw worksheet and total rows if yu want to do some actions on raw worksheet(such as styles).
120    /// if you append data to the worksheet directly, the total rows will not change.
121    /// do not append data to the worksheet directly. use `append_row/append_rows...` method instead.
122    #[inline]
123    pub fn get_sheet_mut<'a, 'b>(&'a mut self, shname: &'b str) -> Result<(&'a mut Worksheet, RowNum)> {
124        if self.open {
125            if !self.rows.contains_key(shname) {
126                // let sheet = self.book.add_worksheet();
127                let sheet = self.book.add_worksheet_with_constant_memory();
128                sheet.set_name(shname)?;
129                if self.prepends.get(shname) == Some(&true) {
130                    if let Some(columns) = self.columns.get(shname) {
131                        for (colval, colnum) in columns {
132                            sheet.write(0, *colnum, colval)?;
133                        }
134                        self.rows.insert(shname.to_owned(), 1);
135                    } else {
136                        self.rows.insert(shname.to_owned(), 0);
137                    }
138                } else {
139                    self.rows.insert(shname.to_owned(), 0);
140                }
141            }
142            Ok((self.book.worksheet_from_name(shname)?, self.rows.get(shname).unwrap_or(&0).to_owned()))
143        } else {
144            Err(anyhow!("cannot write saved workbook"))
145        }
146    }
147    /// append one row to sheet   
148    /// name: sheet name, if not exists, create a new sheet   
149    /// nrow: number write after the pre_cells and before the row_data (usually get from XLsxSheet),not the row number of the target row   
150    /// data: write after the head and the nrow   
151    /// pre_cells: write at the head of the row   
152    /// if you will call this function many times, it is better to use append_rows
153    pub fn append_row<T: IntoExcelData, H: IntoExcelData+Clone>(&mut self, shname: &str, nrow: Option<&RowNum>, data: Vec<T>, pre_cells: &Vec<H>)  -> Result<()> {
154        let (sheet, mut irow) = self.get_sheet_mut(shname)?;
155
156        let mut icol = 0;
157        let npre = pre_cells.len() as ColNum;
158        if npre > 0 {
159            sheet.write_row(irow, icol, pre_cells.clone())?;
160            icol += npre;
161        }
162        if let Some(nrow) = nrow {
163            sheet.write_number(irow, icol, *nrow)?;
164            icol += 1;
165        }
166        sheet.write_row(irow, icol, data)?;
167        irow += 1;
168        self.rows.insert(shname.to_owned(), irow);
169        Ok(())
170    }
171    /// append many rows to sheet   
172    /// name: sheet name, if not exists, create a new sheet   
173    /// nrows: number write after the pre_cells and before the row_data (usually get from XLsxSheet),not the row number of the target row   
174    /// data: write after the head and the nrow   
175    /// pre_cells: write at the head of the row   
176    pub fn append_rows<T: IntoExcelData, H: IntoExcelData+Clone>(&mut self, shname: &str, nrows: Vec<u32>, data: Vec<Vec<T>>, pre_cells: &Vec<H>) -> Result<()> {
177        let (sheet, mut irow) = self.get_sheet_mut(shname)?;
178        // 若nrows的长度为0,则不写行号
179        if nrows.len() > 0 && nrows.len() != data.len() {
180            return Err(anyhow!("the length of nrows is not equal to the length of data".to_string()));
181        }
182
183        let mut icol;
184        let npre = pre_cells.len() as ColNum;
185        for (i, rdata) in data.into_iter().enumerate() {
186            icol = 0;
187            if npre > 0 {
188                sheet.write_row(irow, icol, pre_cells.clone())?;
189                icol += npre;
190            }
191            if let Some(nrow) = nrows.get(i) {
192                sheet.write_number(irow, icol, *nrow)?;
193                icol += 1;
194            }
195            sheet.write_row(irow, icol, rdata)?;
196            irow += 1;
197        };
198        self.rows.insert(shname.to_owned(), irow);
199        Ok(())
200    }
201    /// append one row to sheet by column name    
202    /// name: sheet name, if not exists, create a new sheet    
203    /// data: the data to write   
204    pub fn append_row_by_name<T: IntoExcelData>(&mut self, shname: &str, data: HashMap<String, T>) -> Result<()> {
205        if let Some(columns) = self.columns.get(shname) {
206            let columns = columns.clone();
207            let (sheet, mut irow) = self.get_sheet_mut(shname)?;
208            for (colname, colval) in data.into_iter() {
209                if let Some(colnum) = columns.get(&colname) {
210                    sheet.write(irow, *colnum, colval)?;
211                } else {
212                    return Err(anyhow!("column name {} not found", colname));
213                }
214            }
215            irow += 1;
216            self.rows.insert(shname.to_owned(), irow);
217            Ok(())
218        } else {
219            Err(anyhow!("columns not set"))
220        }
221    }
222    
223    /// append many rows to sheet by column name    
224    /// name: sheet name, if not exists, create a new sheet    
225    /// data: the data to write   
226    pub fn append_rows_by_name<T: IntoExcelData>(&mut self, shname: &str, data: Vec<HashMap<String, T>>) -> Result<()> {
227        if let Some(columns) = self.columns.get(shname) {
228            let columns = columns.clone();
229            let (sheet, mut irow) = self.get_sheet_mut(shname)?;
230            for rdata in data.into_iter() {
231                for (colname, colval) in rdata.into_iter() {
232                    if let Some(colnum) = columns.get(&colname) {
233                        sheet.write(irow, *colnum, colval)?;
234                    } else {
235                        return Err(anyhow!("column name {} not found", colname));
236                    }
237                }
238                irow += 1;
239            };
240            self.rows.insert(shname.to_owned(), irow);
241            Ok(())
242        } else {
243            Err(anyhow!("columns not set"))
244        }
245    }
246    /// save as xlsx file, can only run once each writer
247    pub fn save_as<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
248        if self.open {
249            self.book.save(path)?;
250            self.open = false;
251            Ok(())
252        } else {
253            Err(anyhow!("cannot save saved workbook"))
254        }
255    }
256}
257
258lazy_static::lazy_static! {
259    static ref FMT_DEFAULT_DATE: Format = Format::new().set_num_format_index(14);
260    static ref FMT_DEFAULT_TIME: Format = Format::new().set_num_format_index(21);
261    static ref FMT_DEFAULT_DATETIME: Format = Format::new().set_num_format_index(22);
262    static ref EMPTY_COLUMNS: HashMap<String, ColNum> = HashMap::new();
263}