edit_xlsx/api/
workbook.rs

1use std::{fs, slice};
2use std::cell::RefCell;
3use std::fs::File;
4use std::hash::{Hash, Hasher};
5use std::path::Path;
6use std::rc::Rc;
7use futures::executor::block_on;
8use futures::join;
9use zip::result::ZipError;
10use crate::api::worksheet::WorkSheet;
11use crate::file::XlsxFileType;
12use crate::utils::{id_util, zip_util};
13use crate::result::{WorkSheetError, WorkbookError, WorkbookResult};
14use crate::{Properties, xml};
15use crate::xml::content_types::ContentTypes;
16use crate::xml::core_properties::CoreProperties;
17use crate::xml::app_properties::AppProperties;
18use crate::xml::io::{Io, IoV2};
19use crate::xml::medias::Medias;
20use crate::xml::metadata::Metadata;
21use crate::xml::style::StyleSheet;
22use crate::xml::relationships::Relationships;
23use crate::xml::shared_string::SharedString;
24use crate::xml::theme::{Theme, Themes};
25
26#[derive(Debug)]
27pub struct Workbook {
28    pub sheets: Vec<WorkSheet>,
29    pub(crate) tmp_path: String,
30    pub(crate) file_path: String,
31    closed: bool,
32    pub(crate) workbook: Rc<RefCell<xml::workbook::Workbook>>,
33    pub(crate) style_sheet: Rc<RefCell<StyleSheet>>,
34    pub(crate) workbook_rel: Rc<RefCell<Relationships>>,
35    pub(crate) content_types: Rc<RefCell<ContentTypes>>,
36    pub(crate) medias: Rc<RefCell<Medias>>,
37    pub(crate) themes: Rc<RefCell<Themes>>,
38    pub(crate) metadata: Rc<RefCell<Metadata>>,
39    pub(crate) core_properties: Option<CoreProperties>,
40    pub(crate) app_properties: Option<AppProperties>,
41    pub(crate) shared_string: Rc<SharedString>,
42}
43
44///
45/// Private methods
46///
47impl Workbook {
48    fn get_core_properties(&mut self) -> &mut CoreProperties {
49        self.core_properties.get_or_insert(CoreProperties::from_path(&self.file_path).unwrap())
50    }
51
52    fn get_app_properties(&mut self) -> &mut AppProperties {
53        self.app_properties.get_or_insert(AppProperties::from_path(&self.file_path).unwrap())
54    }
55}
56
57impl Workbook {
58    pub fn new() -> Workbook {
59        let file_path = concat!(env!("CARGO_MANIFEST_DIR"), "/resources/new.xlsx");
60        let mut wb = Self::from_path(file_path).unwrap();
61        wb.file_path = String::from(file_path);
62        wb
63    }
64
65    pub fn get_worksheet_mut(&mut self, id: u32) -> WorkbookResult<&mut WorkSheet> {
66        let sheet = self.sheets
67            .iter_mut()
68            .find(|sheet| sheet.id == id).ok_or(WorkSheetError::FileNotFound)?;
69        Ok(sheet)
70    }
71
72    pub fn get_worksheet(&self, id: u32) -> WorkbookResult<&WorkSheet> {
73        let sheet = self.sheets
74            .iter()
75            .find(|sheet| sheet.id == id).ok_or(WorkSheetError::FileNotFound)?;
76        Ok(sheet)
77    }
78    
79    pub fn get_worksheet_by_name(&self, name: &str) -> WorkbookResult<&WorkSheet> {
80        let sheet = self.sheets
81            .iter()
82            .find(|sheet| sheet.name == name);
83        match sheet {
84            Some(sheet) => Ok(sheet),
85            None => Err(WorkbookError::SheetError(WorkSheetError::FileNotFound))
86        }
87    }
88    
89    pub fn get_worksheet_mut_by_name(&mut self, name: &str) -> WorkbookResult<& mut WorkSheet> {
90        let sheet = self.sheets
91            .iter_mut()
92            .find(|sheet| sheet.name == name);
93        match sheet {
94            Some(sheet) => Ok(sheet),
95            None => Err(WorkbookError::SheetError(WorkSheetError::FileNotFound))
96        }
97    }
98
99    pub fn add_worksheet(&mut self) -> WorkbookResult<&mut WorkSheet> {
100        let (r_id, target_id) = self.workbook_rel.borrow_mut().add_worksheet_v2();
101        let (sheet_id, name) = self.workbook.borrow_mut().add_worksheet_v2(r_id, None)?;
102        let worksheet = WorkSheet::add_worksheet(sheet_id, &name, target_id, self);
103        self.sheets.push(worksheet);
104        self.get_worksheet_mut(sheet_id)
105    }
106
107    pub fn add_worksheet_by_name(&mut self, name: &str) -> WorkbookResult<&mut WorkSheet> {
108        let (r_id, target_id) = self.workbook_rel.borrow_mut().add_worksheet_v2();
109        let (sheet_id, name) = self.workbook.borrow_mut().add_worksheet_v2(r_id, Some(name))?;
110        let worksheet = WorkSheet::add_worksheet(sheet_id, &name, target_id, self);
111        self.sheets.push(worksheet);
112        self.get_worksheet_mut(sheet_id)
113    }
114
115    pub fn duplicate_worksheet(&mut self, id: u32) -> WorkbookResult<&mut WorkSheet> {
116        let copy_worksheet = self.sheets
117            .iter()
118            .find(|sheet| sheet.id == id).ok_or(WorkSheetError::FileNotFound)?;
119        let (r_id, target_id) = self.workbook_rel.borrow_mut().add_worksheet_v2();
120        let (sheet_id, new_name) = self.workbook.borrow_mut().add_worksheet_v2(r_id, None)?;
121        let worksheet = WorkSheet::from_worksheet(sheet_id, &new_name, target_id, copy_worksheet);
122        self.sheets.push(worksheet);
123        self.get_worksheet_mut(sheet_id)
124    }
125
126    pub fn duplicate_worksheet_by_name(&mut self, name: &str) -> WorkbookResult<&mut WorkSheet> {
127        let copy_worksheet = self.sheets
128            .iter()
129            .find(|sheet| sheet.name == name).ok_or(WorkSheetError::FileNotFound)?;
130        let new_name = format!("{} Duplicated", name);
131        let (r_id, target_id) = self.workbook_rel.borrow_mut().add_worksheet_v2();
132        let (sheet_id, _) = self.workbook.borrow_mut().add_worksheet_v2(r_id, Some(&new_name))?;
133        let worksheet = WorkSheet::from_worksheet(sheet_id, &new_name, target_id, copy_worksheet);
134        self.sheets.push(worksheet);
135        self.get_worksheet_mut(sheet_id)
136    }
137
138    pub fn set_size(&mut self, width: u32, height: u32) -> WorkbookResult<()> {
139        let workbook = &mut self.workbook.borrow_mut();
140        let book_view = workbook.book_views.book_views.get_mut(0).unwrap();
141        book_view.window_width = width;
142        book_view.window_height = height;
143        Ok(())
144    }
145
146    pub fn set_tab_ratio(&mut self, tab_ratio: f64) -> WorkbookResult<()> {
147        let tab_ratio = (tab_ratio * 10.0).round() as u32;
148        let workbook = &mut self.workbook.borrow_mut();
149        let book_view = workbook.book_views.book_views.get_mut(0).unwrap();
150        book_view.tab_ratio = Some(tab_ratio);
151        Ok(())
152    }
153
154    pub fn define_name(&mut self, name: &str, value: &str) -> WorkbookResult<()> {
155        self.workbook.borrow_mut().defined_names.add_define_name(name, value, None);
156        Ok(())
157    }
158
159    pub fn define_local_name(&mut self, name: &str, value: &str, sheet_id: u32) -> WorkbookResult<()> {
160        if sheet_id > self.sheets.len() as u32 {
161            return Err(WorkbookError::SheetError(WorkSheetError::FileNotFound));
162        }
163        self.workbook.borrow_mut().defined_names.add_define_name(name, value, Some(sheet_id - 1));
164        Ok(())
165    }
166
167    /// Get the Range Reference for the given Workbook-level Name (if found)
168    pub fn get_defined_name(&self, name: &str) -> WorkbookResult<String> {
169        let book = self.workbook.borrow();
170        book.defined_names
171            .get_defined_name(name, None)
172            .map(String::from) 
173            .ok_or(WorkbookError::SheetError(WorkSheetError::FileNotFound))
174    }
175    /// Get the Range Reference for the given Worksheet-level Name (if found)
176    pub fn get_defined_local_name(&self, name: &str, sheet_id: u32) -> WorkbookResult<String> {
177        if sheet_id > self.sheets.len() as u32 {
178            return Err(WorkbookError::SheetError(WorkSheetError::FileNotFound));
179        }
180        let book = self.workbook.borrow();
181        book.defined_names
182            .get_defined_name(name, Some(sheet_id - 1))
183            .map(String::from) 
184            .ok_or(WorkbookError::SheetError(WorkSheetError::FileNotFound))   
185    }
186
187    pub fn worksheets_mut(&mut self) -> slice::IterMut<WorkSheet> {
188        self.sheets.iter_mut()
189    }
190
191    pub fn worksheets(&self) -> slice::Iter<WorkSheet> {
192        self.sheets.iter()
193    }
194
195    pub fn read_only_recommended(&mut self) -> WorkbookResult<()> {
196        let workbook = &mut self.workbook.borrow_mut();
197        let mut file_sharing = workbook.file_sharing.take().unwrap_or_default();
198        file_sharing.read_only_recommended = 1;
199        workbook.file_sharing = Some(file_sharing);
200        Ok(())
201    }
202
203    pub fn set_properties(&mut self, properties: &Properties) -> WorkbookResult<()> {
204        let core_properties = self.get_core_properties();
205        core_properties.update_by_properties(properties);
206        let app_properties = self.get_app_properties();
207        app_properties.update_by_properties(properties);
208        Ok(())
209    }
210}
211
212impl Workbook {
213    fn from_path_v2<P: AsRef<Path>>(file_path: P) -> WorkbookResult<Workbook> {
214        let file_name = file_path.as_ref().file_name().ok_or(ZipError::FileNotFound)?;
215        let tmp_path = format!("./~${}_{:X}", file_name.to_str().ok_or(ZipError::FileNotFound)?, id_util::new_id());
216        let file = File::open(&file_path)?;
217        let mut archive = zip::ZipArchive::new(file)?;
218        let mut medias = Medias::default();
219        let mut themes = Themes::default();
220        let workbook_xml = xml::workbook::Workbook::from_zip_file(&mut archive, "xl/workbook.xml");
221        let workbook_rel = Relationships::from_zip_file(&mut archive, "xl/_rels/workbook.xml.rels");
222        let content_types = ContentTypes::from_zip_file(&mut archive, "[Content_Types].xml");
223        let style_sheet = StyleSheet::from_zip_file(&mut archive, "xl/styles.xml");
224        let metadata = Metadata::from_zip_file(&mut archive, "xl/metadata.xml");
225        let shared_string = SharedString::from_zip_file(&mut archive, "xl/sharedStrings.xml");
226        let mut theme_paths = Vec::new();
227        for i in 0..archive.len() {
228            let mut file = archive.by_index(i)?;
229            if file.is_file() {
230                let file_name = file.name();
231                if file_name.starts_with("xl/media/") {
232                    medias.add_existed_media(&file_name);
233                }
234                else if file_name.starts_with("xl/theme/") {
235                    theme_paths.push(file_name.to_string());
236                }
237            }
238        }
239        theme_paths.iter().for_each(|file_name| {
240            let theme = Theme::from_zip_file(&mut archive, &file_name).unwrap();
241            themes.add_theme(theme);
242        });
243        let workbook = Rc::new(RefCell::new(workbook_xml.unwrap_or_default()));
244        let workbook_rel = Rc::new(RefCell::new(workbook_rel.unwrap_or_default()));
245        let content_types = Rc::new(RefCell::new(content_types.unwrap_or_default()));
246        let style_sheet = Rc::new(RefCell::new(style_sheet.unwrap_or_default()));
247        let metadata = Rc::new(RefCell::new(metadata.unwrap_or_default()));
248        let shared_string = Rc::new(shared_string.unwrap_or_default());
249        let medias = Rc::new(RefCell::new(medias));
250        let themes = Rc::new(RefCell::new(themes));
251        let sheets = workbook.borrow().sheets.sheets.iter().map(
252            |sheet_xml| {
253                let binding = workbook_rel.borrow();
254                let (target, target_id) = binding.get_target(&sheet_xml.r_id);
255                WorkSheet::from_archive(
256                    sheet_xml.sheet_id,
257                    &sheet_xml.name,
258                    target,
259                    target_id,
260                    &file_path,
261                    &mut archive,
262                    Rc::clone(&workbook),
263                    Rc::clone(&workbook_rel),
264                    Rc::clone(&style_sheet),
265                    Rc::clone(&content_types),
266                    Rc::clone(&medias),
267                    Rc::clone(&themes),
268                    Rc::clone(&metadata),
269                    Rc::clone(&shared_string),
270                )
271            }).collect::<Vec<WorkSheet>>();
272        let api_workbook = Workbook {
273            sheets,
274            tmp_path,
275            file_path: file_path.as_ref().to_str().unwrap().to_string(),
276            closed: false,
277            workbook: Rc::clone(&workbook),
278            workbook_rel: Rc::clone(&workbook_rel),
279            style_sheet: Rc::clone(&style_sheet),
280            content_types: Rc::clone(&content_types),
281            medias: Rc::clone(&medias),
282            themes: Rc::clone(&themes),
283            metadata,
284            core_properties: None,
285            app_properties: None,
286            shared_string,
287        };
288        Ok(api_workbook)
289    }
290
291    pub fn from_path<P: AsRef<Path>>(file_path: P) -> WorkbookResult<Workbook> {
292        let workbook = Self::from_path_v2(file_path);
293        // let workbook = block_on(workbook);
294        workbook
295    }
296
297    async fn save_async(&self) -> WorkbookResult<()> {
298        let workbook = self.workbook.borrow();
299        let workbook = workbook.save_async(&self.tmp_path);
300        let style_sheet = self.style_sheet.borrow();
301        let style_sheet = style_sheet.save_async(&self.tmp_path);
302        let workbook_rel = self.workbook_rel.borrow();
303        let workbook_rel = workbook_rel.save_async(&self.tmp_path, XlsxFileType::WorkbookRels);
304        let content_types = self.content_types.borrow();
305        let content_types = content_types.save_async(&self.tmp_path);
306        let medias = self.medias.borrow();
307        let medias = medias.save_async(&self.tmp_path);
308        let metadata = self.metadata.borrow();
309        let metadata = metadata.save_async(&self.tmp_path);
310        join!(workbook, style_sheet, workbook_rel, content_types, medias, metadata);
311        Ok(())
312    }
313
314    pub fn save_as<P: AsRef<Path>>(&self, file_path: P) -> WorkbookResult<()> {
315        if self.closed {
316            return Err(WorkbookError::FileNotFound);
317        }
318        // Extract xlsx to tmp dir
319        zip_util::extract_dir(&self.file_path, &self.tmp_path)?;
320        // save sheets
321        self.sheets.iter().for_each(|s| s.save_as(&self.tmp_path).unwrap());
322        block_on(self.save_async()).unwrap();
323        // save if modified
324        if let Some(core_propertises) = &self.core_properties {
325            core_propertises.save(&self.tmp_path);
326        }
327        if let Some(app_properties) = &self.app_properties {
328            app_properties.save(&self.tmp_path);
329        }
330        // package files
331        zip_util::zip_dir(&self.tmp_path, file_path)?;
332        // clean cache
333        fs::remove_dir_all(&self.tmp_path).unwrap();
334        Ok(())
335    }
336
337    pub fn save(&mut self) -> WorkbookResult<()> {
338        self.save_as(&self.file_path.clone())
339    }
340
341    pub fn finish(&mut self) {
342        if !self.closed {
343            fs::remove_dir_all(&self.tmp_path);
344            self.closed = true;
345        }
346    }
347}
348
349impl Drop for Workbook {
350    fn drop(&mut self) {
351        self.finish();
352    }
353}