Skip to main content

excelize_rs/
sheet.rs

1//! Worksheet-level API.
2//!
3//! This module corresponds to `sheet.go`, `sheetpr.go` and `sheetview.go` in
4//! the Go implementation.
5
6use std::collections::HashMap;
7use std::io::Cursor;
8use std::path::Path;
9
10use quick_xml::Reader;
11use quick_xml::events::Event;
12
13use crate::constants::{
14    CONTENT_TYPE_SPREADSHEET_ML_WORKSHEET, MAX_COLUMNS, MAX_FIELD_LENGTH, SOURCE_RELATIONSHIP,
15    SOURCE_RELATIONSHIP_IMAGE, SOURCE_RELATIONSHIP_SHARED_STRINGS, SOURCE_RELATIONSHIP_WORKSHEET,
16    TOTAL_ROWS,
17};
18use crate::crypt::gen_iso_passwd_hash;
19use crate::errors::Result;
20use crate::errors::{
21    ErrGroupSheets, ErrImgExt, ErrParameterInvalid, ErrSheetIdx, ErrSheetNotExist,
22    ErrUnprotectSheet, ErrUnprotectSheetPassword, ErrWorkbook, new_field_length_error,
23};
24use crate::file::{File, namespace_strict_to_transitional};
25use crate::lib_util::{
26    cell_name_to_coordinates, coordinates_to_range_ref, count_utf16_string, in_str_slice,
27    range_ref_to_coordinates, sort_coordinates,
28};
29use crate::xml::content_types::{XlsxContentTypeEntry, XlsxDefault, XlsxOverride};
30use crate::xml::workbook::{XlsxBookViews, XlsxSheet, XlsxWorkBookView};
31pub use crate::xml::worksheet::{Panes, Selection, SheetProtectionOptions};
32use crate::xml::worksheet::{
33    XlsxBreaks, XlsxBrk, XlsxColBreaks, XlsxDimension, XlsxHeaderFooter, XlsxIgnoredError,
34    XlsxIgnoredErrors, XlsxPageMargins, XlsxPageSetUp, XlsxPane, XlsxPicture, XlsxPrintOptions,
35    XlsxRowBreaks, XlsxSelection, XlsxSheetFormatPr, XlsxSheetPr, XlsxSheetProtection,
36    XlsxSheetView, XlsxSheetViews, XlsxWorksheet,
37};
38
39/// Type of error to ignore for a range of cells.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum IgnoredErrorsType {
42    EvalError = 0,
43    TwoDigitTextYear = 1,
44    NumberStoredAsText = 2,
45    Formula = 3,
46    FormulaRange = 4,
47    UnlockedFormula = 5,
48    EmptyCellReference = 6,
49    ListDataValidation = 7,
50    CalculatedColumn = 8,
51}
52
53/// Options for `SetSheetProps`.
54#[derive(Debug, Default, Clone, PartialEq)]
55pub struct SheetPropsOptions {
56    pub code_name: Option<String>,
57    pub enable_format_conditions_calculation: Option<bool>,
58    pub published: Option<bool>,
59    pub auto_page_breaks: Option<bool>,
60    pub fit_to_page: Option<bool>,
61    pub tab_color_indexed: Option<i64>,
62    pub tab_color_rgb: Option<String>,
63    pub tab_color_theme: Option<i64>,
64    pub tab_color_tint: Option<f64>,
65    pub outline_pr_apply_styles: Option<bool>,
66    pub outline_pr_summary_below: Option<bool>,
67    pub outline_pr_summary_right: Option<bool>,
68    pub outline_pr_show_outline_symbols: Option<bool>,
69    pub base_col_width: Option<u8>,
70    pub default_col_width: Option<f64>,
71    pub default_row_height: Option<f64>,
72    pub custom_height: Option<bool>,
73    pub zero_height: Option<bool>,
74    pub thick_top: Option<bool>,
75    pub thick_bottom: Option<bool>,
76}
77
78/// Options for `SetSheetView`.
79#[derive(Debug, Default, Clone, PartialEq)]
80pub struct SheetViewOptions {
81    pub default_grid_color: Option<bool>,
82    pub right_to_left: Option<bool>,
83    pub show_formulas: Option<bool>,
84    pub show_grid_lines: Option<bool>,
85    pub show_outline_symbols: Option<bool>,
86    pub show_row_col_headers: Option<bool>,
87    pub show_ruler: Option<bool>,
88    pub show_white_space: Option<bool>,
89    pub show_zeros: Option<bool>,
90    pub tab_selected: Option<bool>,
91    pub top_left_cell: Option<String>,
92    pub view: Option<String>,
93    pub window_protection: Option<bool>,
94    pub zoom_scale: Option<f64>,
95}
96
97/// Options for `SetPageLayout`.
98#[derive(Debug, Default, Clone, PartialEq)]
99pub struct PageLayoutOptions {
100    pub black_and_white: Option<bool>,
101    pub cell_comments: Option<String>,
102    pub copies: Option<i64>,
103    pub draft: Option<bool>,
104    pub errors: Option<String>,
105    pub first_page_number: Option<String>,
106    pub fit_to_height: Option<i64>,
107    pub fit_to_width: Option<i64>,
108    pub horizontal_dpi: Option<String>,
109    pub orientation: Option<String>,
110    pub page_order: Option<String>,
111    pub paper_size: Option<i64>,
112    pub scale: Option<i64>,
113    pub use_first_page_number: Option<bool>,
114    pub use_printer_defaults: Option<bool>,
115    pub vertical_dpi: Option<String>,
116}
117
118/// Options for `SetPageMargins`.
119#[derive(Debug, Default, Clone, PartialEq)]
120pub struct PageMarginsOptions {
121    pub left: Option<f64>,
122    pub right: Option<f64>,
123    pub top: Option<f64>,
124    pub bottom: Option<f64>,
125    pub header: Option<f64>,
126    pub footer: Option<f64>,
127    pub horizontally: Option<bool>,
128    pub vertically: Option<bool>,
129}
130
131/// Header/footer options for `SetHeaderFooter`.
132#[derive(Debug, Default, Clone, PartialEq)]
133pub struct HeaderFooterOptions {
134    pub different_first: Option<bool>,
135    pub different_odd_even: Option<bool>,
136    pub scale_with_doc: Option<bool>,
137    pub align_with_margins: Option<bool>,
138    pub odd_header: Option<String>,
139    pub odd_footer: Option<String>,
140    pub even_header: Option<String>,
141    pub even_footer: Option<String>,
142    pub first_header: Option<String>,
143    pub first_footer: Option<String>,
144}
145
146// ------------------------------------------------------------------
147// Public API
148// ------------------------------------------------------------------
149
150impl File {
151    /// Get a list of worksheet / chart / dialog sheet names.
152    pub fn get_sheet_list(&self) -> Vec<String> {
153        let wb = self.workbook_reader().ok();
154        let mut list = Vec::new();
155        if let Some(wb) = wb {
156            for sheet in &wb.sheets.sheet {
157                if let Some(name) = &sheet.name {
158                    list.push(name.clone());
159                }
160            }
161        }
162        list
163    }
164
165    /// Create a new worksheet.
166    pub fn new_sheet(&self, name: &str) -> Result<i32> {
167        crate::excelize::check_sheet_name(name)?;
168        if let Ok(index) = self.get_sheet_index(name) {
169            return Ok(index);
170        }
171
172        let mut wb = self.workbook_reader()?;
173        let mut rels = self
174            .rels_reader(&self.get_workbook_rels_path())?
175            .unwrap_or_default();
176
177        // Find next sheet id and relationship id.
178        let mut max_sheet_id = 0i64;
179        let mut max_rid = 0i32;
180        for sheet in &wb.sheets.sheet {
181            if let Some(id) = sheet.sheet_id {
182                if id > max_sheet_id {
183                    max_sheet_id = id;
184                }
185            }
186            if let Some(rid) = &sheet.id {
187                if let Ok(n) = rid.trim_start_matches("rId").parse::<i32>() {
188                    if n > max_rid {
189                        max_rid = n;
190                    }
191                }
192            }
193        }
194        for rel in &rels.relationships {
195            if let Ok(n) = rel.id.trim_start_matches("rId").parse::<i32>() {
196                if n > max_rid {
197                    max_rid = n;
198                }
199            }
200        }
201        max_sheet_id += 1;
202        max_rid += 1;
203
204        let new_rid = format!("rId{max_rid}");
205        let sheet_xml = format!("xl/worksheets/sheet{max_sheet_id}.xml");
206
207        wb.sheets.sheet.push(XlsxSheet {
208            name: Some(name.to_string()),
209            sheet_id: Some(max_sheet_id),
210            id: Some(new_rid.clone()),
211            plain_id: None,
212            state: None,
213        });
214
215        rels.relationships
216            .push(crate::xml::workbook::XlsxRelationship {
217                id: new_rid,
218                r#type: SOURCE_RELATIONSHIP_WORKSHEET.to_string(),
219                target: format!("worksheets/sheet{max_sheet_id}.xml"),
220                target_mode: None,
221            });
222
223        // Insert worksheet part.
224        let ws = new_worksheet();
225        self.sheet.insert(sheet_xml.clone(), ws.clone());
226        self.pkg.insert(sheet_xml.clone(), worksheet_bytes(&ws));
227        self.checked.insert(sheet_xml.clone(), true);
228
229        // Update content types.
230        let mut ct = self.content_types_reader()?;
231        ct.entries
232            .push(XlsxContentTypeEntry::Override(XlsxOverride {
233                part_name: format!("/{sheet_xml}"),
234                content_type: CONTENT_TYPE_SPREADSHEET_ML_WORKSHEET.to_string(),
235            }));
236
237        // Ensure shared strings relationship exists.
238        ensure_shared_strings_rel(&mut rels);
239
240        *self.workbook.lock().unwrap() = Some(wb);
241        self.relationships
242            .insert(self.get_workbook_rels_path(), rels);
243        *self.content_types.lock().unwrap() = Some(ct);
244        self.sheet_map
245            .lock()
246            .unwrap()
247            .insert(name.to_string(), sheet_xml);
248
249        let mut count = self.sheet_count.lock().unwrap();
250        *count += 1;
251        Ok(max_sheet_id as i32)
252    }
253
254    /// Get the worksheet name from a 1-based sheet index.
255    pub fn get_sheet_name(&self, index: i32) -> Result<String> {
256        let wb = self.workbook_reader()?;
257        if index < 1 || index as usize > wb.sheets.sheet.len() {
258            return Err(Box::new(ErrSheetIdx));
259        }
260        Ok(wb.sheets.sheet[index as usize - 1]
261            .name
262            .clone()
263            .unwrap_or_default())
264    }
265
266    /// Get the 1-based sheet index of a worksheet by name.
267    pub fn get_sheet_index(&self, name: &str) -> Result<i32> {
268        let wb = self.workbook_reader()?;
269        for (i, sheet) in wb.sheets.sheet.iter().enumerate() {
270            if sheet
271                .name
272                .as_deref()
273                .map(|n| n.eq_ignore_ascii_case(name))
274                .unwrap_or(false)
275            {
276                return Ok(i as i32 + 1);
277            }
278        }
279        Err(Box::new(ErrSheetNotExist {
280            sheet_name: name.to_string(),
281        }))
282    }
283
284    /// Delete a worksheet by name.
285    pub fn delete_sheet(&self, name: &str) -> Result<()> {
286        crate::excelize::check_sheet_name(name)?;
287        let index = self.get_sheet_index(name)?;
288        if self.get_sheet_list().len() <= 1 {
289            return Err(Box::new(ErrWorkbook));
290        }
291        let path = self
292            .get_sheet_xml_path(name)
293            .ok_or_else(|| ErrSheetNotExist {
294                sheet_name: name.to_string(),
295            })?;
296
297        self.clear_calc_cache();
298        let active_index = self.get_active_sheet_index()?;
299        let sheet_id = self.get_sheet_id(name);
300
301        let mut wb = self.workbook_reader()?;
302        wb.sheets.sheet.remove(index as usize - 1);
303
304        let rel_path = self.get_workbook_rels_path();
305        let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
306        rels.relationships.retain(|r| {
307            let target = r.target.to_lowercase();
308            !target.contains(&format!("/{path}").to_lowercase()) && target != path.to_lowercase()
309        });
310
311        let sheet_rels = format!(
312            "xl/worksheets/_rels/{}.rels",
313            path.trim_start_matches("xl/worksheets/")
314        );
315
316        *self.workbook.lock().unwrap() = Some(wb);
317        self.relationships.insert(rel_path, rels);
318        self.sheet.remove(&path);
319        self.pkg.remove(&path);
320        self.checked.remove(&path);
321        self.relationships.remove(&sheet_rels);
322        self.pkg.remove(&sheet_rels);
323        self.sheet_map.lock().unwrap().remove(name);
324
325        self.remove_content_types_part(CONTENT_TYPE_SPREADSHEET_ML_WORKSHEET, &format!("/{path}"))?;
326        if sheet_id > 0 {
327            let _ = self.delete_calc_chain(sheet_id, "");
328        }
329
330        let mut new_active = active_index;
331        if active_index == index {
332            new_active = 1;
333        } else if active_index > index {
334            new_active = active_index - 1;
335        }
336        self.set_active_sheet(new_active)?;
337
338        let mut count = self.sheet_count.lock().unwrap();
339        *count -= 1;
340        Ok(())
341    }
342
343    /// Get the index of the active worksheet (1-based).
344    pub fn get_active_sheet_index(&self) -> Result<i32> {
345        let wb = self.workbook_reader()?;
346        let views = wb.book_views.as_ref().and_then(|v| v.workbook_view.first());
347        if let Some(view) = views {
348            return Ok(view.active_tab.unwrap_or(0) as i32 + 1);
349        }
350        Ok(1)
351    }
352
353    /// Set the active worksheet by 1-based index.
354    pub fn set_active_sheet(&self, index: i32) -> Result<()> {
355        let mut wb = self.workbook_reader()?;
356        if index < 1 || index as usize > wb.sheets.sheet.len() {
357            return Err(Box::new(ErrSheetIdx));
358        }
359        if wb.book_views.is_none() {
360            wb.book_views = Some(XlsxBookViews::default());
361        }
362        let views = wb.book_views.as_mut().unwrap();
363        if views.workbook_view.is_empty() {
364            views.workbook_view.push(XlsxWorkBookView::default());
365        }
366        views.workbook_view[0].active_tab = Some(index as i64 - 1);
367        *self.workbook.lock().unwrap() = Some(wb);
368
369        let target = index as usize;
370        for (idx, name) in self.get_sheet_list().iter().enumerate() {
371            let Ok(mut ws) = self.work_sheet_reader(name) else {
372                continue;
373            };
374            if ws.sheet_views.is_none() {
375                ws.sheet_views = Some(XlsxSheetViews {
376                    sheet_view: vec![XlsxSheetView::default()],
377                });
378            }
379            let sv = ws.sheet_views.as_mut().unwrap();
380            if sv.sheet_view.is_empty() {
381                sv.sheet_view.push(XlsxSheetView::default());
382            }
383            sv.sheet_view[0].tab_selected = Some(idx + 1 == target);
384            if let Some(path) = self.get_sheet_xml_path(name) {
385                self.sheet.insert(path, ws);
386            }
387        }
388        Ok(())
389    }
390
391    /// Set worksheet properties.
392    pub fn set_sheet_props(&self, sheet: &str, opts: &SheetPropsOptions) -> Result<()> {
393        let path = self
394            .get_sheet_xml_path(sheet)
395            .ok_or_else(|| ErrSheetNotExist {
396                sheet_name: sheet.to_string(),
397            })?;
398        let mut ws = self.work_sheet_reader(sheet)?;
399        if ws.sheet_pr.is_none() {
400            ws.sheet_pr = Some(XlsxSheetPr::default());
401        }
402        let pr = ws.sheet_pr.as_mut().unwrap();
403        if opts.code_name.is_some() {
404            pr.code_name = opts.code_name.clone();
405        }
406        if opts.enable_format_conditions_calculation.is_some() {
407            pr.enable_format_conditions_calculation = opts.enable_format_conditions_calculation;
408        }
409        if opts.published.is_some() {
410            pr.published = opts.published;
411        }
412        if let Some(indexed) = opts.tab_color_indexed {
413            if pr.tab_color.is_none() {
414                pr.tab_color = Some(crate::xml::common::XlsxColor::default());
415            }
416            pr.tab_color.as_mut().unwrap().indexed = Some(indexed);
417        }
418        if let Some(ref rgb) = opts.tab_color_rgb {
419            if pr.tab_color.is_none() {
420                pr.tab_color = Some(crate::xml::common::XlsxColor::default());
421            }
422            pr.tab_color.as_mut().unwrap().rgb = Some(rgb.clone());
423        }
424        if let Some(theme) = opts.tab_color_theme {
425            if pr.tab_color.is_none() {
426                pr.tab_color = Some(crate::xml::common::XlsxColor::default());
427            }
428            pr.tab_color.as_mut().unwrap().theme = Some(theme);
429        }
430        if let Some(tint) = opts.tab_color_tint {
431            if pr.tab_color.is_none() {
432                pr.tab_color = Some(crate::xml::common::XlsxColor::default());
433            }
434            pr.tab_color.as_mut().unwrap().tint = Some(tint);
435        }
436        let outline_needed = opts.outline_pr_apply_styles.is_some()
437            || opts.outline_pr_summary_below.is_some()
438            || opts.outline_pr_summary_right.is_some()
439            || opts.outline_pr_show_outline_symbols.is_some();
440        if outline_needed {
441            if pr.outline_pr.is_none() {
442                pr.outline_pr = Some(crate::xml::worksheet::XlsxOutlinePr::default());
443            }
444            let outline = pr.outline_pr.as_mut().unwrap();
445            if opts.outline_pr_apply_styles.is_some() {
446                outline.apply_styles = opts.outline_pr_apply_styles;
447            }
448            if opts.outline_pr_summary_below.is_some() {
449                outline.summary_below = opts.outline_pr_summary_below;
450            }
451            if opts.outline_pr_summary_right.is_some() {
452                outline.summary_right = opts.outline_pr_summary_right;
453            }
454            if opts.outline_pr_show_outline_symbols.is_some() {
455                outline.show_outline_symbols = opts.outline_pr_show_outline_symbols;
456            }
457        }
458        if opts.auto_page_breaks.is_some() || opts.fit_to_page.is_some() {
459            if pr.page_set_up_pr.is_none() {
460                pr.page_set_up_pr = Some(crate::xml::worksheet::XlsxPageSetUpPr::default());
461            }
462            let pup = pr.page_set_up_pr.as_mut().unwrap();
463            if opts.auto_page_breaks.is_some() {
464                pup.auto_page_breaks = opts.auto_page_breaks;
465            }
466            if opts.fit_to_page.is_some() {
467                pup.fit_to_page = opts.fit_to_page;
468            }
469        }
470        if ws.sheet_format_pr.is_none() {
471            ws.sheet_format_pr = Some(XlsxSheetFormatPr {
472                default_row_height: 15.0,
473                ..Default::default()
474            });
475        }
476        let sfp = ws.sheet_format_pr.as_mut().unwrap();
477        if let Some(v) = opts.base_col_width {
478            sfp.base_col_width = Some(v);
479        }
480        if let Some(v) = opts.default_col_width {
481            sfp.default_col_width = Some(v);
482        }
483        if let Some(v) = opts.default_row_height {
484            sfp.default_row_height = v;
485        }
486        if let Some(v) = opts.custom_height {
487            sfp.custom_height = Some(v);
488        }
489        if let Some(v) = opts.zero_height {
490            sfp.zero_height = Some(v);
491        }
492        if let Some(v) = opts.thick_top {
493            sfp.thick_top = Some(v);
494        }
495        if let Some(v) = opts.thick_bottom {
496            sfp.thick_bottom = Some(v);
497        }
498        self.sheet.insert(path, ws);
499        Ok(())
500    }
501
502    /// Get worksheet properties.
503    pub fn get_sheet_props(&self, sheet: &str) -> Result<SheetPropsOptions> {
504        let ws = self.work_sheet_reader(sheet)?;
505        let mut opts = SheetPropsOptions {
506            enable_format_conditions_calculation: Some(true),
507            published: Some(true),
508            auto_page_breaks: Some(true),
509            outline_pr_summary_below: Some(true),
510            base_col_width: Some(8),
511            ..Default::default()
512        };
513        if let Some(pr) = ws.sheet_pr {
514            opts.code_name = pr.code_name;
515            if pr.enable_format_conditions_calculation.is_some() {
516                opts.enable_format_conditions_calculation = pr.enable_format_conditions_calculation;
517            }
518            if pr.published.is_some() {
519                opts.published = pr.published;
520            }
521            if let Some(tc) = pr.tab_color {
522                opts.tab_color_indexed = tc.indexed;
523                opts.tab_color_rgb = tc.rgb;
524                opts.tab_color_theme = tc.theme;
525                opts.tab_color_tint = tc.tint;
526            }
527            if let Some(o) = pr.outline_pr {
528                opts.outline_pr_apply_styles = o.apply_styles;
529                opts.outline_pr_summary_below = o.summary_below;
530                opts.outline_pr_summary_right = o.summary_right;
531                opts.outline_pr_show_outline_symbols = o.show_outline_symbols;
532            }
533            if let Some(pup) = pr.page_set_up_pr {
534                opts.auto_page_breaks = pup.auto_page_breaks;
535                opts.fit_to_page = pup.fit_to_page;
536            }
537        }
538        if let Some(sfp) = ws.sheet_format_pr {
539            opts.base_col_width = sfp.base_col_width.or(Some(8));
540            opts.default_col_width = sfp.default_col_width;
541            opts.default_row_height = Some(sfp.default_row_height);
542            opts.custom_height = sfp.custom_height;
543            opts.zero_height = sfp.zero_height;
544            opts.thick_top = sfp.thick_top;
545            opts.thick_bottom = sfp.thick_bottom;
546        }
547        Ok(opts)
548    }
549
550    /// Set worksheet view properties on the given sheet view. The view index may
551    /// be negative and counts backward from the last view.
552    pub fn set_sheet_view(
553        &self,
554        sheet: &str,
555        view_index: i32,
556        opts: &SheetViewOptions,
557    ) -> Result<()> {
558        let path = self
559            .get_sheet_xml_path(sheet)
560            .ok_or_else(|| ErrSheetNotExist {
561                sheet_name: sheet.to_string(),
562            })?;
563        let mut ws = self.work_sheet_reader(sheet)?;
564        if ws.sheet_views.is_none() {
565            ws.sheet_views = Some(XlsxSheetViews {
566                sheet_view: vec![XlsxSheetView::default()],
567            });
568        }
569        let views = ws.sheet_views.as_mut().unwrap();
570        let len = views.sheet_view.len();
571        let idx = if view_index < 0 {
572            if view_index < -(len as i32) {
573                return Err(crate::errors::new_view_idx_error(view_index).into());
574            }
575            (len as i32 + view_index) as usize
576        } else if view_index >= len as i32 {
577            return Err(crate::errors::new_view_idx_error(view_index).into());
578        } else {
579            view_index as usize
580        };
581        let view = &mut views.sheet_view[idx];
582        if opts.default_grid_color.is_some() {
583            view.default_grid_color = opts.default_grid_color;
584        }
585        if opts.right_to_left.is_some() {
586            view.right_to_left = opts.right_to_left;
587        }
588        if opts.show_formulas.is_some() {
589            view.show_formulas = opts.show_formulas;
590        }
591        if opts.show_grid_lines.is_some() {
592            view.show_grid_lines = opts.show_grid_lines;
593        }
594        if opts.show_outline_symbols.is_some() {
595            view.show_outline_symbols = opts.show_outline_symbols;
596        }
597        if opts.show_row_col_headers.is_some() {
598            view.show_row_col_headers = opts.show_row_col_headers;
599        }
600        if opts.show_ruler.is_some() {
601            view.show_ruler = opts.show_ruler;
602        }
603        if opts.show_white_space.is_some() {
604            view.show_white_space = opts.show_white_space;
605        }
606        if opts.show_zeros.is_some() {
607            view.show_zeros = opts.show_zeros;
608        }
609        if opts.tab_selected.is_some() {
610            view.tab_selected = opts.tab_selected;
611        }
612        if opts.top_left_cell.is_some() {
613            view.top_left_cell = opts.top_left_cell.clone();
614        }
615        if let Some(ref v) = opts.view {
616            if in_str_slice(&["normal", "pageLayout", "pageBreakPreview"], v, true) != -1 {
617                view.view = opts.view.clone();
618            }
619        }
620        if opts.window_protection.is_some() {
621            view.window_protection = opts.window_protection;
622        }
623        if let Some(zoom) = opts.zoom_scale {
624            if zoom >= 10.0 && zoom <= 400.0 {
625                view.zoom_scale = Some(zoom);
626            }
627        }
628        self.sheet.insert(path, ws);
629        Ok(())
630    }
631
632    /// Get worksheet view properties from the given sheet view. The view index may
633    /// be negative and counts backward from the last view.
634    pub fn get_sheet_view(&self, sheet: &str, view_index: i32) -> Result<SheetViewOptions> {
635        let ws = self.work_sheet_reader(sheet)?;
636        let views = ws.sheet_views.unwrap_or_else(|| XlsxSheetViews {
637            sheet_view: vec![XlsxSheetView::default()],
638        });
639        let len = views.sheet_view.len();
640        let idx = if view_index < 0 {
641            if view_index < -(len as i32) {
642                return Err(crate::errors::new_view_idx_error(view_index).into());
643            }
644            (len as i32 + view_index) as usize
645        } else if view_index >= len as i32 {
646            return Err(crate::errors::new_view_idx_error(view_index).into());
647        } else {
648            view_index as usize
649        };
650        let view = &views.sheet_view[idx];
651        let mut opts = SheetViewOptions {
652            default_grid_color: Some(true),
653            show_grid_lines: Some(true),
654            show_row_col_headers: Some(true),
655            show_ruler: Some(true),
656            show_zeros: Some(true),
657            view: Some("normal".to_string()),
658            zoom_scale: Some(100.0),
659            ..Default::default()
660        };
661        if let Some(v) = view.default_grid_color {
662            opts.default_grid_color = Some(v);
663        }
664        opts.right_to_left = Some(view.right_to_left.unwrap_or(false));
665        opts.show_formulas = Some(view.show_formulas.unwrap_or(false));
666        if let Some(v) = view.show_grid_lines {
667            opts.show_grid_lines = Some(v);
668        }
669        if let Some(v) = view.show_row_col_headers {
670            opts.show_row_col_headers = Some(v);
671        }
672        if let Some(v) = view.show_ruler {
673            opts.show_ruler = Some(v);
674        }
675        if let Some(v) = view.show_white_space {
676            opts.show_white_space = Some(v);
677        }
678        if let Some(v) = view.show_zeros {
679            opts.show_zeros = Some(v);
680        }
681        opts.show_outline_symbols = Some(view.show_outline_symbols.unwrap_or(false));
682        opts.tab_selected = Some(view.tab_selected.unwrap_or(false));
683        opts.top_left_cell = Some(view.top_left_cell.clone().unwrap_or_default());
684        opts.window_protection = Some(view.window_protection.unwrap_or(false));
685        if let Some(ref v) = view.view {
686            if !v.is_empty() {
687                opts.view = Some(v.clone());
688            }
689        }
690        if let Some(zoom) = view.zoom_scale {
691            if zoom >= 10.0 && zoom <= 400.0 {
692                opts.zoom_scale = Some(zoom);
693            }
694        }
695        Ok(opts)
696    }
697
698    /// Set page layout properties.
699    pub fn set_page_layout(&self, sheet: &str, opts: &PageLayoutOptions) -> Result<()> {
700        let path = self
701            .get_sheet_xml_path(sheet)
702            .ok_or_else(|| ErrSheetNotExist {
703                sheet_name: sheet.to_string(),
704            })?;
705        let mut ws = self.work_sheet_reader(sheet)?;
706        if ws.page_setup.is_none() {
707            ws.page_setup = Some(XlsxPageSetUp::default());
708        }
709        let ps = ws.page_setup.as_mut().unwrap();
710        if opts.black_and_white.is_some() {
711            ps.black_and_white = opts.black_and_white;
712        }
713        if opts.cell_comments.is_some() {
714            ps.cell_comments = opts.cell_comments.clone();
715        }
716        if opts.copies.is_some() {
717            ps.copies = opts.copies;
718        }
719        if opts.draft.is_some() {
720            ps.draft = opts.draft;
721        }
722        if opts.errors.is_some() {
723            ps.errors = opts.errors.clone();
724        }
725        if opts.first_page_number.is_some() {
726            ps.first_page_number = opts.first_page_number.clone();
727        }
728        if opts.fit_to_height.is_some() {
729            ps.fit_to_height = opts.fit_to_height;
730        }
731        if opts.fit_to_width.is_some() {
732            ps.fit_to_width = opts.fit_to_width;
733        }
734        if opts.horizontal_dpi.is_some() {
735            ps.horizontal_dpi = opts.horizontal_dpi.clone();
736        }
737        if opts.orientation.is_some() {
738            ps.orientation = opts.orientation.clone();
739        }
740        if opts.page_order.is_some() {
741            ps.page_order = opts.page_order.clone();
742        }
743        if opts.paper_size.is_some() {
744            ps.paper_size = opts.paper_size;
745        }
746        if opts.scale.is_some() {
747            ps.scale = opts.scale;
748        }
749        if opts.use_first_page_number.is_some() {
750            ps.use_first_page_number = opts.use_first_page_number;
751        }
752        if opts.use_printer_defaults.is_some() {
753            ps.use_printer_defaults = opts.use_printer_defaults;
754        }
755        if opts.vertical_dpi.is_some() {
756            ps.vertical_dpi = opts.vertical_dpi.clone();
757        }
758        self.sheet.insert(path, ws);
759        Ok(())
760    }
761
762    /// Get page layout properties.
763    pub fn get_page_layout(&self, sheet: &str) -> Result<PageLayoutOptions> {
764        let ws = self.work_sheet_reader(sheet)?;
765        let mut opts = PageLayoutOptions::default();
766        if let Some(ps) = ws.page_setup {
767            opts.black_and_white = ps.black_and_white;
768            opts.cell_comments = ps.cell_comments;
769            opts.copies = ps.copies;
770            opts.draft = ps.draft;
771            opts.errors = ps.errors;
772            opts.first_page_number = ps.first_page_number;
773            opts.fit_to_height = ps.fit_to_height;
774            opts.fit_to_width = ps.fit_to_width;
775            opts.horizontal_dpi = ps.horizontal_dpi;
776            opts.orientation = ps.orientation;
777            opts.page_order = ps.page_order;
778            opts.paper_size = ps.paper_size;
779            opts.scale = ps.scale;
780            opts.use_first_page_number = ps.use_first_page_number;
781            opts.use_printer_defaults = ps.use_printer_defaults;
782            opts.vertical_dpi = ps.vertical_dpi;
783        }
784        Ok(opts)
785    }
786
787    /// Set page margins.
788    pub fn set_page_margins(&self, sheet: &str, opts: &PageMarginsOptions) -> Result<()> {
789        let path = self
790            .get_sheet_xml_path(sheet)
791            .ok_or_else(|| ErrSheetNotExist {
792                sheet_name: sheet.to_string(),
793            })?;
794        let mut ws = self.work_sheet_reader(sheet)?;
795        let margins_needed = opts.left.is_some()
796            || opts.right.is_some()
797            || opts.top.is_some()
798            || opts.bottom.is_some()
799            || opts.header.is_some()
800            || opts.footer.is_some();
801        if margins_needed {
802            if ws.page_margins.is_none() {
803                ws.page_margins = Some(XlsxPageMargins::default());
804            }
805            let pm = ws.page_margins.as_mut().unwrap();
806            if let Some(v) = opts.left {
807                pm.left = v;
808            }
809            if let Some(v) = opts.right {
810                pm.right = v;
811            }
812            if let Some(v) = opts.top {
813                pm.top = v;
814            }
815            if let Some(v) = opts.bottom {
816                pm.bottom = v;
817            }
818            if let Some(v) = opts.header {
819                pm.header = v;
820            }
821            if let Some(v) = opts.footer {
822                pm.footer = v;
823            }
824        }
825        if opts.horizontally.is_some() || opts.vertically.is_some() {
826            if ws.print_options.is_none() {
827                ws.print_options = Some(XlsxPrintOptions::default());
828            }
829            let po = ws.print_options.as_mut().unwrap();
830            if let Some(v) = opts.horizontally {
831                po.horizontal_centered = Some(v);
832            }
833            if let Some(v) = opts.vertically {
834                po.vertical_centered = Some(v);
835            }
836        }
837        self.sheet.insert(path, ws);
838        Ok(())
839    }
840
841    /// Get page margins.
842    pub fn get_page_margins(&self, sheet: &str) -> Result<PageMarginsOptions> {
843        let ws = self.work_sheet_reader(sheet)?;
844        let mut opts = PageMarginsOptions {
845            left: Some(0.7),
846            right: Some(0.7),
847            top: Some(0.75),
848            bottom: Some(0.75),
849            header: Some(0.3),
850            footer: Some(0.3),
851            ..Default::default()
852        };
853        if let Some(pm) = ws.page_margins {
854            opts.left = Some(pm.left);
855            opts.right = Some(pm.right);
856            opts.top = Some(pm.top);
857            opts.bottom = Some(pm.bottom);
858            opts.header = Some(pm.header);
859            opts.footer = Some(pm.footer);
860        }
861        if let Some(po) = ws.print_options {
862            opts.horizontally = po.horizontal_centered;
863            opts.vertically = po.vertical_centered;
864        }
865        Ok(opts)
866    }
867
868    /// Set header/footer text.
869    pub fn set_header_footer(&self, sheet: &str, opts: &HeaderFooterOptions) -> Result<()> {
870        let path = self
871            .get_sheet_xml_path(sheet)
872            .ok_or_else(|| ErrSheetNotExist {
873                sheet_name: sheet.to_string(),
874            })?;
875        let mut ws = self.work_sheet_reader(sheet)?;
876        for (field, value) in [
877            ("OddHeader", opts.odd_header.as_deref()),
878            ("OddFooter", opts.odd_footer.as_deref()),
879            ("EvenHeader", opts.even_header.as_deref()),
880            ("EvenFooter", opts.even_footer.as_deref()),
881            ("FirstHeader", opts.first_header.as_deref()),
882            ("FirstFooter", opts.first_footer.as_deref()),
883        ] {
884            if let Some(v) = value {
885                if count_utf16_string(v) > MAX_FIELD_LENGTH {
886                    return Err(new_field_length_error(field).into());
887                }
888            }
889        }
890        if ws.header_footer.is_none() {
891            ws.header_footer = Some(XlsxHeaderFooter::default());
892        }
893        let hf = ws.header_footer.as_mut().unwrap();
894        if opts.different_first.is_some() {
895            hf.different_first = opts.different_first;
896        }
897        if opts.different_odd_even.is_some() {
898            hf.different_odd_even = opts.different_odd_even;
899        }
900        if opts.scale_with_doc.is_some() {
901            hf.scale_with_doc = opts.scale_with_doc;
902        }
903        if opts.align_with_margins.is_some() {
904            hf.align_with_margins = opts.align_with_margins;
905        }
906        if opts.odd_header.is_some() {
907            hf.odd_header = opts.odd_header.clone();
908        }
909        if opts.odd_footer.is_some() {
910            hf.odd_footer = opts.odd_footer.clone();
911        }
912        if opts.even_header.is_some() {
913            hf.even_header = opts.even_header.clone();
914        }
915        if opts.even_footer.is_some() {
916            hf.even_footer = opts.even_footer.clone();
917        }
918        if opts.first_header.is_some() {
919            hf.first_header = opts.first_header.clone();
920        }
921        if opts.first_footer.is_some() {
922            hf.first_footer = opts.first_footer.clone();
923        }
924        self.sheet.insert(path, ws);
925        Ok(())
926    }
927
928    /// Get header/footer text.
929    pub fn get_header_footer(&self, sheet: &str) -> Result<HeaderFooterOptions> {
930        let ws = self.work_sheet_reader(sheet)?;
931        let mut opts = HeaderFooterOptions::default();
932        if let Some(hf) = ws.header_footer {
933            opts.different_first = hf.different_first;
934            opts.different_odd_even = hf.different_odd_even;
935            opts.scale_with_doc = hf.scale_with_doc;
936            opts.align_with_margins = hf.align_with_margins;
937            opts.odd_header = hf.odd_header;
938            opts.odd_footer = hf.odd_footer;
939            opts.even_header = hf.even_header;
940            opts.even_footer = hf.even_footer;
941            opts.first_header = hf.first_header;
942            opts.first_footer = hf.first_footer;
943        }
944        Ok(opts)
945    }
946
947    /// Set the worksheet name by given source and target worksheet names.
948    pub fn set_sheet_name(&self, source: &str, target: &str) -> Result<()> {
949        crate::excelize::check_sheet_name(target)?;
950        if source.eq_ignore_ascii_case(target) {
951            return Ok(());
952        }
953        self.clear_calc_cache();
954        let mut wb = self.workbook_reader()?;
955        for k in 0..wb.sheets.sheet.len() {
956            if wb.sheets.sheet[k]
957                .name
958                .as_deref()
959                .map(|n| n.eq_ignore_ascii_case(source))
960                .unwrap_or(false)
961            {
962                wb.sheets.sheet[k].name = Some(target.to_string());
963                let mut map = self.sheet_map.lock().unwrap();
964                if let Some(path) = map.get(source).cloned() {
965                    map.remove(source);
966                    map.insert(target.to_string(), path);
967                }
968            }
969        }
970        if let Some(names) = wb.defined_names.as_mut() {
971            for dn in &mut names.defined_name {
972                dn.data = adjust_range_sheet_name(&dn.data, source, target);
973            }
974        }
975        *self.workbook.lock().unwrap() = Some(wb);
976        Ok(())
977    }
978
979    /// Set background picture by given worksheet name and file path.
980    pub fn set_sheet_background(&self, sheet: &str, picture: &str) -> Result<()> {
981        let ext = Path::new(picture)
982            .extension()
983            .and_then(|e| e.to_str())
984            .unwrap_or("")
985            .to_string();
986        let file = std::fs::read(picture)?;
987        self.set_sheet_background_from_bytes(sheet, &ext, &file)
988    }
989
990    /// Set background picture by given worksheet name, extension name and image data.
991    pub fn set_sheet_background_from_bytes(
992        &self,
993        sheet: &str,
994        extension: &str,
995        picture: &[u8],
996    ) -> Result<()> {
997        if picture.is_empty() {
998            return Err(Box::new(ErrParameterInvalid));
999        }
1000        let ext = if !extension.is_empty() {
1001            format!(".{extension}")
1002        } else {
1003            String::new()
1004        };
1005        let image_type = image_extension_map()
1006            .get(&ext.to_lowercase())
1007            .cloned()
1008            .ok_or(ErrImgExt)?;
1009        let name = self.add_media(picture, &image_type);
1010        let sheet_xml_path = self
1011            .get_sheet_xml_path(sheet)
1012            .ok_or_else(|| ErrSheetNotExist {
1013                sheet_name: sheet.to_string(),
1014            })?;
1015        let sheet_rels = format!(
1016            "xl/worksheets/_rels/{}.rels",
1017            sheet_xml_path.trim_start_matches("xl/worksheets/")
1018        );
1019        let target = name.replacen("xl", "..", 1);
1020        let r_id = self.add_rels(&sheet_rels, SOURCE_RELATIONSHIP_IMAGE, &target, "");
1021        self.add_sheet_picture(sheet, r_id)?;
1022        self.add_sheet_name_space(sheet, SOURCE_RELATIONSHIP);
1023        set_content_type_part_image_extensions(self)
1024    }
1025
1026    /// Move a sheet to a specified position in the workbook.
1027    /// Indices are 1-based and the source sheet is placed before the target sheet.
1028    pub fn move_sheet(&self, source: &str, target: &str) -> Result<()> {
1029        if source.eq_ignore_ascii_case(target) {
1030            return Ok(());
1031        }
1032        let mut wb = self.workbook_reader()?;
1033        let source_idx = self.get_sheet_index(source)?;
1034        let target_idx = self.get_sheet_index(target)?;
1035        if source_idx < 1 {
1036            return Err(Box::new(ErrSheetNotExist {
1037                sheet_name: source.to_string(),
1038            }));
1039        }
1040        if target_idx < 1 {
1041            return Err(Box::new(ErrSheetNotExist {
1042                sheet_name: target.to_string(),
1043            }));
1044        }
1045        let _ = self.ungroup_sheets();
1046        let active_sheet_name = self.get_sheet_name(self.get_active_sheet_index()?)?;
1047        let source_sheet = wb.sheets.sheet.remove(source_idx as usize - 1);
1048        let mut target_idx = target_idx;
1049        if target_idx > source_idx {
1050            target_idx -= 1;
1051        }
1052        wb.sheets
1053            .sheet
1054            .insert(target_idx as usize - 1, source_sheet);
1055        *self.workbook.lock().unwrap() = Some(wb);
1056        let active_idx = self.get_sheet_index(&active_sheet_name)?;
1057        self.set_active_sheet(active_idx)?;
1058        Ok(())
1059    }
1060
1061    /// Duplicate a worksheet by source and target 1-based sheet indices.
1062    pub fn copy_sheet(&self, from: i32, to: i32) -> Result<()> {
1063        if from < 1 || to < 1 || from == to {
1064            return Err(Box::new(ErrSheetIdx));
1065        }
1066        let from_name = self.get_sheet_name(from)?;
1067        let to_name = self.get_sheet_name(to)?;
1068        if from_name.is_empty() || to_name.is_empty() {
1069            return Err(Box::new(ErrSheetIdx));
1070        }
1071        self.clear_calc_cache();
1072        let ws = self.work_sheet_reader(&from_name)?;
1073        let mut worksheet = ws.clone();
1074        if let Some(views) = worksheet.sheet_views.as_mut() {
1075            if let Some(view) = views.sheet_view.first_mut() {
1076                view.tab_selected = Some(false);
1077            }
1078        }
1079        worksheet.drawing = None;
1080        worksheet.table_parts = None;
1081        worksheet.page_setup = None;
1082        let to_sheet_id = self.get_sheet_id(&to_name);
1083        let sheet_xml_path = format!("xl/worksheets/sheet{to_sheet_id}.xml");
1084        self.sheet.insert(sheet_xml_path.clone(), worksheet);
1085        let to_rels = format!("xl/worksheets/_rels/sheet{to_sheet_id}.xml.rels");
1086        let from_sheet_id = self.get_sheet_id(&from_name);
1087        let from_rels = format!("xl/worksheets/_rels/sheet{from_sheet_id}.xml.rels");
1088        if let Some(rels) = self.pkg.get(&from_rels) {
1089            self.pkg.insert(to_rels, rels.clone());
1090        }
1091        let from_sheet_xml_path = self.get_sheet_xml_path(&from_name).unwrap_or_default();
1092        if let Some(attrs) = self.xml_attr.get(&from_sheet_xml_path) {
1093            self.xml_attr.insert(sheet_xml_path, attrs.clone());
1094        }
1095        Ok(())
1096    }
1097
1098    /// Set worksheet visible by given worksheet name.
1099    pub fn set_sheet_visible(
1100        &self,
1101        sheet: &str,
1102        visible: bool,
1103        very_hidden: Option<bool>,
1104    ) -> Result<()> {
1105        crate::excelize::check_sheet_name(sheet)?;
1106        let mut wb = self.workbook_reader()?;
1107        if visible {
1108            for k in 0..wb.sheets.sheet.len() {
1109                if wb.sheets.sheet[k]
1110                    .name
1111                    .as_deref()
1112                    .map(|n| n.eq_ignore_ascii_case(sheet))
1113                    .unwrap_or(false)
1114                {
1115                    wb.sheets.sheet[k].state = None;
1116                }
1117            }
1118            *self.workbook.lock().unwrap() = Some(wb);
1119            return Ok(());
1120        }
1121        let state = get_sheet_state(false, very_hidden);
1122        let mut count = 0;
1123        for s in &wb.sheets.sheet {
1124            if s.state.as_deref().unwrap_or("") != state {
1125                count += 1;
1126            }
1127        }
1128        for k in 0..wb.sheets.sheet.len() {
1129            let name = wb.sheets.sheet[k].name.clone().unwrap_or_default();
1130            let ws = self.work_sheet_reader(&name)?;
1131            let mut tab_selected = false;
1132            if let Some(views) = ws.sheet_views.as_ref() {
1133                if let Some(view) = views.sheet_view.first() {
1134                    tab_selected = view.tab_selected.unwrap_or(false);
1135                }
1136            }
1137            if name.eq_ignore_ascii_case(sheet) && count > 1 && !tab_selected {
1138                wb.sheets.sheet[k].state = Some(state.to_string());
1139            }
1140        }
1141        *self.workbook.lock().unwrap() = Some(wb);
1142        Ok(())
1143    }
1144
1145    /// Get worksheet visible by given worksheet name.
1146    pub fn get_sheet_visible(&self, sheet: &str) -> Result<bool> {
1147        crate::excelize::check_sheet_name(sheet)?;
1148        let wb = self.workbook_reader()?;
1149        for s in &wb.sheets.sheet {
1150            if s.name
1151                .as_deref()
1152                .map(|n| n.eq_ignore_ascii_case(sheet))
1153                .unwrap_or(false)
1154            {
1155                let state = s.state.as_deref().unwrap_or("");
1156                return Ok(state.is_empty() || state.eq_ignore_ascii_case("visible"));
1157            }
1158        }
1159        Ok(false)
1160    }
1161
1162    /// Create and remove freeze panes and split panes by given worksheet name and panes options.
1163    pub fn set_panes(&self, sheet: &str, panes: &Panes) -> Result<()> {
1164        let path = self
1165            .get_sheet_xml_path(sheet)
1166            .ok_or_else(|| ErrSheetNotExist {
1167                sheet_name: sheet.to_string(),
1168            })?;
1169        let mut ws = self.work_sheet_reader(sheet)?;
1170        set_panes_ws(&mut ws, panes)?;
1171        self.sheet.insert(path, ws);
1172        Ok(())
1173    }
1174
1175    /// Get freeze panes, split panes, and worksheet views by given worksheet name.
1176    pub fn get_panes(&self, sheet: &str) -> Result<Panes> {
1177        let ws = self.work_sheet_reader(sheet)?;
1178        Ok(get_panes_ws(&ws))
1179    }
1180
1181    /// Search cell reference by given worksheet name, cell value, and regular expression.
1182    pub fn search_sheet(&self, sheet: &str, value: &str, reg: Option<bool>) -> Result<Vec<String>> {
1183        crate::excelize::check_sheet_name(sheet)?;
1184        let reg_search = reg.unwrap_or(false);
1185        let name = self
1186            .get_sheet_xml_path(sheet)
1187            .ok_or_else(|| ErrSheetNotExist {
1188                sheet_name: sheet.to_string(),
1189            })?;
1190        if let Some(ws) = self.sheet.get(&name) {
1191            let ws = ws.clone();
1192            self.save_file_list(&name, &worksheet_bytes(&ws));
1193        }
1194        search_sheet_impl(self, sheet, value, reg_search)
1195    }
1196
1197    /// Protect worksheet to prevent accidental or deliberate changes.
1198    pub fn protect_sheet(&self, sheet: &str, opts: &SheetProtectionOptions) -> Result<()> {
1199        let path = self
1200            .get_sheet_xml_path(sheet)
1201            .ok_or_else(|| ErrSheetNotExist {
1202                sheet_name: sheet.to_string(),
1203            })?;
1204        let mut ws = self.work_sheet_reader(sheet)?;
1205        let mut sp = XlsxSheetProtection {
1206            auto_filter: !opts.auto_filter,
1207            delete_columns: !opts.delete_columns,
1208            delete_rows: !opts.delete_rows,
1209            format_cells: !opts.format_cells,
1210            format_columns: !opts.format_columns,
1211            format_rows: !opts.format_rows,
1212            insert_columns: !opts.insert_columns,
1213            insert_hyperlinks: !opts.insert_hyperlinks,
1214            insert_rows: !opts.insert_rows,
1215            objects: !opts.edit_objects,
1216            pivot_tables: !opts.pivot_tables,
1217            scenarios: !opts.edit_scenarios,
1218            select_locked_cells: !opts.select_locked_cells,
1219            select_unlocked_cells: !opts.select_unlocked_cells,
1220            sheet: true,
1221            sort: !opts.sort,
1222            ..Default::default()
1223        };
1224        if !opts.password.is_empty() {
1225            if opts.algorithm_name.is_empty() {
1226                sp.password = Some(gen_sheet_passwd(&opts.password));
1227            } else {
1228                let (hash_value, salt_value) =
1229                    gen_iso_passwd_hash(&opts.password, &opts.algorithm_name, "", 100_000)?;
1230                sp.password = None;
1231                sp.algorithm_name = Some(opts.algorithm_name.clone());
1232                sp.salt_value = Some(salt_value);
1233                sp.hash_value = Some(hash_value);
1234                sp.spin_count = Some(100_000);
1235            }
1236        }
1237        ws.sheet_protection = Some(sp);
1238        self.sheet.insert(path, ws);
1239        Ok(())
1240    }
1241
1242    /// Remove protection for a sheet.
1243    pub fn unprotect_sheet(&self, sheet: &str, password: Option<&str>) -> Result<()> {
1244        let path = self
1245            .get_sheet_xml_path(sheet)
1246            .ok_or_else(|| ErrSheetNotExist {
1247                sheet_name: sheet.to_string(),
1248            })?;
1249        let mut ws = self.work_sheet_reader(sheet)?;
1250        if let Some(passwd) = password {
1251            let sp = ws.sheet_protection.as_ref().ok_or(ErrUnprotectSheet)?;
1252            if sp.algorithm_name.as_deref().unwrap_or("").is_empty() {
1253                if sp.password.as_deref().unwrap_or("") != gen_sheet_passwd(passwd) {
1254                    return Err(Box::new(ErrUnprotectSheetPassword));
1255                }
1256            } else {
1257                let (hash_value, _) = gen_iso_passwd_hash(
1258                    passwd,
1259                    sp.algorithm_name.as_deref().unwrap_or(""),
1260                    sp.salt_value.as_deref().unwrap_or(""),
1261                    sp.spin_count.unwrap_or(0) as i32,
1262                )?;
1263                if sp.hash_value.as_deref().unwrap_or("") != hash_value {
1264                    return Err(Box::new(ErrUnprotectSheetPassword));
1265                }
1266            }
1267        }
1268        ws.sheet_protection = None;
1269        self.sheet.insert(path, ws);
1270        Ok(())
1271    }
1272
1273    /// Get worksheet protection settings by given worksheet name.
1274    pub fn get_sheet_protection(&self, sheet: &str) -> Result<SheetProtectionOptions> {
1275        let ws = self.work_sheet_reader(sheet)?;
1276        let mut opts = SheetProtectionOptions::default();
1277        if let Some(sp) = ws.sheet_protection {
1278            opts.algorithm_name = sp.algorithm_name.unwrap_or_default();
1279            opts.auto_filter = !sp.auto_filter;
1280            opts.delete_columns = !sp.delete_columns;
1281            opts.delete_rows = !sp.delete_rows;
1282            opts.edit_objects = !sp.objects;
1283            opts.edit_scenarios = !sp.scenarios;
1284            opts.format_cells = !sp.format_cells;
1285            opts.format_columns = !sp.format_columns;
1286            opts.format_rows = !sp.format_rows;
1287            opts.insert_columns = !sp.insert_columns;
1288            opts.insert_hyperlinks = !sp.insert_hyperlinks;
1289            opts.insert_rows = !sp.insert_rows;
1290            opts.pivot_tables = !sp.pivot_tables;
1291            opts.select_locked_cells = !sp.select_locked_cells;
1292            opts.select_unlocked_cells = !sp.select_unlocked_cells;
1293            opts.sort = !sp.sort;
1294        }
1295        Ok(opts)
1296    }
1297
1298    /// Group worksheets by given worksheet names.
1299    pub fn group_sheets(&self, sheets: &[String]) -> Result<()> {
1300        let active_sheet = self.get_active_sheet_index()?;
1301        let sheet_list = self.get_sheet_list();
1302        let mut in_active_sheet = false;
1303        for (idx, sheet_name) in sheet_list.iter().enumerate() {
1304            for s in sheets {
1305                if s.eq_ignore_ascii_case(sheet_name) && (idx + 1) as i32 == active_sheet {
1306                    in_active_sheet = true;
1307                }
1308            }
1309        }
1310        if !in_active_sheet {
1311            return Err(Box::new(ErrGroupSheets));
1312        }
1313        for sheet in sheets {
1314            let mut ws = self.work_sheet_reader(sheet)?;
1315            let path = self.get_sheet_xml_path(sheet).unwrap_or_default();
1316            if let Some(views) = ws.sheet_views.as_mut() {
1317                for view in &mut views.sheet_view {
1318                    view.tab_selected = Some(true);
1319                }
1320            }
1321            self.sheet.insert(path, ws);
1322        }
1323        Ok(())
1324    }
1325
1326    /// Ungroup worksheets.
1327    pub fn ungroup_sheets(&self) -> Result<()> {
1328        let active_sheet = self.get_active_sheet_index()?;
1329        for (index, sheet) in self.get_sheet_list().iter().enumerate() {
1330            if active_sheet == (index + 1) as i32 {
1331                continue;
1332            }
1333            let mut ws = self.work_sheet_reader(sheet)?;
1334            let path = self.get_sheet_xml_path(sheet).unwrap_or_default();
1335            if let Some(views) = ws.sheet_views.as_mut() {
1336                for view in &mut views.sheet_view {
1337                    view.tab_selected = Some(false);
1338                }
1339            }
1340            self.sheet.insert(path, ws);
1341        }
1342        Ok(())
1343    }
1344
1345    /// Create a page break by given worksheet name and cell reference.
1346    pub fn insert_page_break(&self, sheet: &str, cell: &str) -> Result<()> {
1347        let path = self
1348            .get_sheet_xml_path(sheet)
1349            .ok_or_else(|| ErrSheetNotExist {
1350                sheet_name: sheet.to_string(),
1351            })?;
1352        let mut ws = self.work_sheet_reader(sheet)?;
1353        insert_page_break_ws(&mut ws, cell)?;
1354        self.sheet.insert(path, ws);
1355        Ok(())
1356    }
1357
1358    /// Remove a page break by given worksheet name and cell reference.
1359    pub fn remove_page_break(&self, sheet: &str, cell: &str) -> Result<()> {
1360        let path = self
1361            .get_sheet_xml_path(sheet)
1362            .ok_or_else(|| ErrSheetNotExist {
1363                sheet_name: sheet.to_string(),
1364            })?;
1365        let mut ws = self.work_sheet_reader(sheet)?;
1366        remove_page_break_ws(&mut ws, cell)?;
1367        self.sheet.insert(path, ws);
1368        Ok(())
1369    }
1370
1371    /// Set or remove the used range of the worksheet by a given range reference.
1372    pub fn set_sheet_dimension(&self, sheet: &str, range_ref: &str) -> Result<()> {
1373        let path = self
1374            .get_sheet_xml_path(sheet)
1375            .ok_or_else(|| ErrSheetNotExist {
1376                sheet_name: sheet.to_string(),
1377            })?;
1378        let mut ws = self.work_sheet_reader(sheet)?;
1379        if range_ref.is_empty() {
1380            ws.dimension = None;
1381            self.sheet.insert(path, ws);
1382            return Ok(());
1383        }
1384        let parts: Vec<&str> = range_ref.split(':').collect();
1385        if parts.len() == 1 {
1386            let _ = cell_name_to_coordinates(range_ref)?;
1387            ws.dimension = Some(XlsxDimension {
1388                r#ref: range_ref.to_uppercase(),
1389            });
1390            self.sheet.insert(path, ws);
1391            return Ok(());
1392        }
1393        if parts.len() != 2 {
1394            return Err(Box::new(ErrParameterInvalid));
1395        }
1396        let mut coordinates = range_ref_to_coordinates(range_ref)?;
1397        sort_coordinates(&mut coordinates)?;
1398        let r#ref = coordinates_to_range_ref(&coordinates, false)?;
1399        ws.dimension = Some(XlsxDimension { r#ref });
1400        self.sheet.insert(path, ws);
1401        Ok(())
1402    }
1403
1404    /// Get the used range of the worksheet.
1405    pub fn get_sheet_dimension(&self, sheet: &str) -> Result<String> {
1406        crate::excelize::check_sheet_name(sheet)?;
1407        let name = self
1408            .get_sheet_xml_path(sheet)
1409            .ok_or_else(|| ErrSheetNotExist {
1410                sheet_name: sheet.to_string(),
1411            })?;
1412        if let Some(ws) = self.sheet.get(&name) {
1413            return Ok(ws
1414                .dimension
1415                .as_ref()
1416                .map(|d| d.r#ref.clone())
1417                .unwrap_or_default());
1418        }
1419        let data = self.read_bytes(&name);
1420        let s = String::from_utf8_lossy(&data);
1421        if let Some(start) = s.find("<dimension") {
1422            if let Some(end) = s[start..].find('>') {
1423                let tag = &s[start..start + end + 1];
1424                if let Some(pos) = tag.find("ref=\"") {
1425                    let rest = &tag[pos + 6..];
1426                    if let Some(q) = rest.find('"') {
1427                        return Ok(rest[..q].to_string());
1428                    }
1429                }
1430            }
1431        }
1432        Ok(String::new())
1433    }
1434
1435    /// Ignore error for a range of cells.
1436    pub fn add_ignored_errors(
1437        &self,
1438        sheet: &str,
1439        range_ref: &str,
1440        ignored_errors_type: IgnoredErrorsType,
1441    ) -> Result<()> {
1442        if range_ref.is_empty() {
1443            return Err(Box::new(ErrParameterInvalid));
1444        }
1445        let path = self
1446            .get_sheet_xml_path(sheet)
1447            .ok_or_else(|| ErrSheetNotExist {
1448                sheet_name: sheet.to_string(),
1449            })?;
1450        let mut ws = self.work_sheet_reader(sheet)?;
1451        if ws.ignored_errors.is_none() {
1452            ws.ignored_errors = Some(XlsxIgnoredErrors::default());
1453        }
1454        let ie = ignored_error_for_type(range_ref, ignored_errors_type);
1455        let ignored = ws.ignored_errors.as_mut().unwrap();
1456        if ignored
1457            .ignored_error
1458            .iter()
1459            .any(|e| e.sqref == ie.sqref && same_ignored_error_flags(e, &ie))
1460        {
1461            self.sheet.insert(path, ws);
1462            return Ok(());
1463        }
1464        ignored.ignored_error.push(ie);
1465        self.sheet.insert(path, ws);
1466        Ok(())
1467    }
1468}
1469
1470// ------------------------------------------------------------------
1471// Internal helpers
1472// ------------------------------------------------------------------
1473
1474impl File {
1475    /// Build a map of worksheet name → XML path from the workbook and its rels.
1476    pub fn get_sheet_name_to_path_map(&self) -> Result<HashMap<String, String>> {
1477        let mut map = HashMap::new();
1478        let wb = self.workbook_reader()?;
1479        let rels = self.rels_reader(&self.get_workbook_rels_path())?;
1480        let Some(rels) = rels else {
1481            return Ok(map);
1482        };
1483        for sheet in &wb.sheets.sheet {
1484            let Some(name) = &sheet.name else { continue };
1485            let id = sheet.id.as_ref().or(sheet.plain_id.as_ref());
1486            let Some(id) = id else { continue };
1487            for rel in &rels.relationships {
1488                if &rel.id == id {
1489                    let sheet_xml = self.get_worksheet_path(&rel.target);
1490                    if self.pkg.contains_key(&sheet_xml) || self.temp_files.contains_key(&sheet_xml)
1491                    {
1492                        map.insert(name.clone(), sheet_xml);
1493                    }
1494                }
1495            }
1496        }
1497        Ok(map)
1498    }
1499
1500    /// Get worksheets, chart sheets and dialog sheets ID and name map of the workbook.
1501    pub fn get_sheet_map(&self) -> HashMap<i32, String> {
1502        let mut sheet_map = HashMap::new();
1503        if let Ok(wb) = self.workbook_reader() {
1504            for sheet in &wb.sheets.sheet {
1505                if let (Some(id), Some(name)) = (sheet.sheet_id, sheet.name.clone()) {
1506                    sheet_map.insert(id as i32, name);
1507                }
1508            }
1509        }
1510        sheet_map
1511    }
1512
1513    /// Convert a relationship id to a 0-based sheet index.
1514    pub fn rel_id_to_sheet_id(&self, r_id: &str) -> Result<i32> {
1515        let wb = self.workbook_reader()?;
1516        for (i, sheet) in wb.sheets.sheet.iter().enumerate() {
1517            let id = sheet.id.as_deref().or(sheet.plain_id.as_deref());
1518            if id == Some(r_id) {
1519                return Ok(i as i32);
1520            }
1521        }
1522        Err(Box::new(ErrSheetIdx))
1523    }
1524}
1525
1526fn new_worksheet() -> XlsxWorksheet {
1527    XlsxWorksheet {
1528        sheet_format_pr: Some(XlsxSheetFormatPr {
1529            default_row_height: 15.0,
1530            ..Default::default()
1531        }),
1532        ..Default::default()
1533    }
1534}
1535
1536fn worksheet_bytes(ws: &XlsxWorksheet) -> Vec<u8> {
1537    use quick_xml::se::to_string;
1538    let body = to_string(ws).unwrap_or_default().into_bytes();
1539    [crate::constants::XML_HEADER.as_bytes(), &body].concat()
1540}
1541
1542impl File {
1543    /// Set a picture reference on a worksheet for use as a background image.
1544    pub(crate) fn add_sheet_picture(&self, sheet: &str, r_id: i32) -> Result<()> {
1545        let path = self
1546            .get_sheet_xml_path(sheet)
1547            .ok_or_else(|| ErrSheetNotExist {
1548                sheet_name: sheet.to_string(),
1549            })?;
1550        let mut ws = self.work_sheet_reader(sheet)?;
1551        ws.picture = Some(XlsxPicture {
1552            rid: Some(format!("rId{r_id}")),
1553        });
1554        self.sheet.insert(path, ws);
1555        Ok(())
1556    }
1557}
1558
1559fn set_panes_ws(ws: &mut XlsxWorksheet, panes: &Panes) -> Result<()> {
1560    let mut p = XlsxPane {
1561        active_pane: Some(panes.active_pane.clone()),
1562        top_left_cell: Some(panes.top_left_cell.clone()),
1563        x_split: Some(panes.x_split as f64),
1564        y_split: Some(panes.y_split as f64),
1565        ..Default::default()
1566    };
1567    if panes.freeze {
1568        p.state = Some("frozen".to_string());
1569    }
1570    if ws.sheet_views.is_none() {
1571        ws.sheet_views = Some(XlsxSheetViews::default());
1572    }
1573    let views = ws.sheet_views.as_mut().unwrap();
1574    if views.sheet_view.is_empty() {
1575        views.sheet_view.push(XlsxSheetView::default());
1576    }
1577    let idx = views.sheet_view.len() - 1;
1578    if !panes.freeze && !panes.split {
1579        views.sheet_view[idx].pane = None;
1580    } else {
1581        views.sheet_view[idx].pane = Some(p);
1582    }
1583    let mut selection = Vec::new();
1584    for s in &panes.selection {
1585        selection.push(XlsxSelection {
1586            active_cell: Some(s.active_cell.clone()),
1587            pane: Some(s.pane.clone()),
1588            sqref: Some(s.sqref.clone()),
1589            ..Default::default()
1590        });
1591    }
1592    views.sheet_view[idx].selection = selection;
1593    Ok(())
1594}
1595
1596fn get_panes_ws(ws: &XlsxWorksheet) -> Panes {
1597    let mut panes = Panes::default();
1598    let Some(views) = ws.sheet_views.as_ref() else {
1599        return panes;
1600    };
1601    let Some(view) = views.sheet_view.last() else {
1602        return panes;
1603    };
1604    for s in &view.selection {
1605        panes.selection.push(Selection {
1606            sqref: s.sqref.clone().unwrap_or_default(),
1607            active_cell: s.active_cell.clone().unwrap_or_default(),
1608            pane: s.pane.clone().unwrap_or_default(),
1609        });
1610    }
1611    let Some(p) = view.pane.as_ref() else {
1612        return panes;
1613    };
1614    panes.active_pane = p.active_pane.clone().unwrap_or_default();
1615    if p.state.as_deref() == Some("frozen") {
1616        panes.freeze = true;
1617    }
1618    panes.top_left_cell = p.top_left_cell.clone().unwrap_or_default();
1619    panes.x_split = p.x_split.unwrap_or(0.0) as i64;
1620    panes.y_split = p.y_split.unwrap_or(0.0) as i64;
1621    panes
1622}
1623
1624fn search_sheet_impl(file: &File, sheet: &str, value: &str, reg: bool) -> Result<Vec<String>> {
1625    let name = file
1626        .get_sheet_xml_path(sheet)
1627        .ok_or_else(|| ErrSheetNotExist {
1628            sheet_name: sheet.to_string(),
1629        })?;
1630    let raw = {
1631        let raw = file.read_xml(&name);
1632        if !raw.is_empty() {
1633            raw
1634        } else if let Some(ws) = file.sheet.get(&name) {
1635            worksheet_bytes(&ws)
1636        } else {
1637            file.read_bytes(&name)
1638        }
1639    };
1640    let data = file.apply_charset_transcoder(&raw)?;
1641    let data = namespace_strict_to_transitional(&data);
1642    let sst = file.shared_strings_reader().ok();
1643
1644    let regex = if reg {
1645        Some(regex::Regex::new(value).map_err(|e| e.to_string())?)
1646    } else {
1647        None
1648    };
1649
1650    let mut result = Vec::new();
1651    let mut merge_cells = Vec::new();
1652
1653    let mut reader = Reader::from_reader(Cursor::new(data));
1654    let mut buf = Vec::new();
1655
1656    let mut _current_row = 0;
1657    let mut in_cell = false;
1658    let mut in_v = false;
1659    let mut in_is = false;
1660    let mut in_is_t = false;
1661    let mut cell_ref = String::new();
1662    let mut cell_type = None::<String>;
1663    let mut current_val = String::new();
1664
1665    loop {
1666        match reader.read_event_into(&mut buf) {
1667            Ok(Event::Start(e)) => {
1668                let local_name = e.local_name();
1669                let name = local_name.as_ref();
1670                if name == b"row" {
1671                    if let Ok(Some(attr)) = e.try_get_attribute("r") {
1672                        if let Ok(r_str) = attr.decode_and_unescape_value(reader.decoder()) {
1673                            _current_row = r_str.parse::<i32>().unwrap_or(0);
1674                        }
1675                    }
1676                } else if name == b"c" {
1677                    in_cell = true;
1678                    in_v = false;
1679                    in_is = false;
1680                    in_is_t = false;
1681                    cell_ref.clear();
1682                    cell_type = None;
1683                    current_val.clear();
1684                    if let Ok(Some(attr)) = e.try_get_attribute("r") {
1685                        if let Ok(r) = attr.decode_and_unescape_value(reader.decoder()) {
1686                            cell_ref = r.to_string();
1687                        }
1688                    }
1689                    if let Ok(Some(attr)) = e.try_get_attribute("t") {
1690                        if let Ok(t) = attr.decode_and_unescape_value(reader.decoder()) {
1691                            cell_type = Some(t.to_string());
1692                        }
1693                    }
1694                } else if in_cell && name == b"v" {
1695                    in_v = true;
1696                    current_val.clear();
1697                } else if in_cell && name == b"is" {
1698                    in_is = true;
1699                    current_val.clear();
1700                } else if in_is && name == b"t" {
1701                    in_is_t = true;
1702                }
1703            }
1704            Ok(Event::Empty(e)) => {
1705                let local_name = e.local_name();
1706                let name = local_name.as_ref();
1707                if name == b"c" {
1708                    cell_ref.clear();
1709                    cell_type = None;
1710                    current_val.clear();
1711                    if let Ok(Some(attr)) = e.try_get_attribute("r") {
1712                        if let Ok(r) = attr.decode_and_unescape_value(reader.decoder()) {
1713                            cell_ref = r.to_string();
1714                        }
1715                    }
1716                    if let Ok(Some(attr)) = e.try_get_attribute("t") {
1717                        if let Ok(t) = attr.decode_and_unescape_value(reader.decoder()) {
1718                            cell_type = Some(t.to_string());
1719                        }
1720                    }
1721                    evaluate_cell(
1722                        &mut result,
1723                        &cell_ref,
1724                        cell_type.as_deref(),
1725                        &current_val,
1726                        sst.as_ref(),
1727                        value,
1728                        reg,
1729                        regex.as_ref(),
1730                    );
1731                } else if name == b"mergeCell" {
1732                    if let Ok(Some(attr)) = e.try_get_attribute("ref") {
1733                        if let Ok(r) = attr.decode_and_unescape_value(reader.decoder()) {
1734                            merge_cells.push(r.to_string());
1735                        }
1736                    }
1737                }
1738            }
1739            Ok(Event::Text(e)) => {
1740                if in_v || in_is_t {
1741                    if let Ok(text) = e.unescape() {
1742                        current_val.push_str(&text);
1743                    }
1744                }
1745            }
1746            Ok(Event::End(e)) => {
1747                let local_name = e.local_name();
1748                let name = local_name.as_ref();
1749                if in_cell && name == b"c" {
1750                    evaluate_cell(
1751                        &mut result,
1752                        &cell_ref,
1753                        cell_type.as_deref(),
1754                        &current_val,
1755                        sst.as_ref(),
1756                        value,
1757                        reg,
1758                        regex.as_ref(),
1759                    );
1760                    in_cell = false;
1761                    in_v = false;
1762                    in_is = false;
1763                    in_is_t = false;
1764                    cell_ref.clear();
1765                    cell_type = None;
1766                    current_val.clear();
1767                } else if in_v && name == b"v" {
1768                    in_v = false;
1769                } else if in_is && name == b"is" {
1770                    in_is = false;
1771                    in_is_t = false;
1772                } else if in_is_t && name == b"t" {
1773                    in_is_t = false;
1774                }
1775            }
1776            Ok(Event::Eof) => break,
1777            Err(e) => return Err(Box::new(e)),
1778            _ => {}
1779        }
1780        buf.clear();
1781    }
1782
1783    if !merge_cells.is_empty() {
1784        for cell_ref in &mut result {
1785            for merge_ref in &merge_cells {
1786                if cell_in_range(cell_ref, merge_ref) {
1787                    if let Some(top_left) = merge_ref.split(':').next() {
1788                        *cell_ref = top_left.to_string();
1789                    }
1790                }
1791            }
1792        }
1793    }
1794
1795    Ok(result)
1796}
1797
1798fn evaluate_cell(
1799    result: &mut Vec<String>,
1800    cell_ref: &str,
1801    cell_type: Option<&str>,
1802    current_val: &str,
1803    sst: Option<&crate::xml::shared_strings::XlsxSst>,
1804    value: &str,
1805    reg: bool,
1806    regex: Option<&regex::Regex>,
1807) {
1808    if cell_ref.is_empty() {
1809        return;
1810    }
1811    let val = match cell_type {
1812        Some("s") => {
1813            if let Ok(idx) = current_val.parse::<usize>() {
1814                sst.and_then(|s| s.si.get(idx))
1815                    .map(|si| extract_si_text(si))
1816                    .unwrap_or_default()
1817            } else {
1818                String::new()
1819            }
1820        }
1821        Some("inlineStr") => current_val.to_string(),
1822        Some("str") => current_val.to_string(),
1823        _ => current_val.to_string(),
1824    };
1825    let matched = if reg {
1826        regex.unwrap().is_match(&val)
1827    } else {
1828        val == value
1829    };
1830    if matched {
1831        result.push(cell_ref.to_string());
1832    }
1833}
1834
1835fn extract_si_text(si: &crate::xml::shared_strings::XlsxSi) -> String {
1836    if let Some(t) = &si.t {
1837        return t.val.clone();
1838    }
1839    si.r.iter()
1840        .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
1841        .collect()
1842}
1843
1844fn cell_in_range(cell: &str, range: &str) -> bool {
1845    let parts: Vec<&str> = range.split(':').collect();
1846    if parts.len() != 2 {
1847        return false;
1848    }
1849    let (c, r) = cell_name_to_coordinates(cell).unwrap_or((0, 0));
1850    let (c1, r1) = cell_name_to_coordinates(parts[0]).unwrap_or((0, 0));
1851    let (c2, r2) = cell_name_to_coordinates(parts[1]).unwrap_or((0, 0));
1852    c >= c1 && c <= c2 && r >= r1 && r <= r2
1853}
1854
1855fn insert_page_break_ws(ws: &mut XlsxWorksheet, cell: &str) -> Result<()> {
1856    let (col, row) = cell_name_to_coordinates(cell).map_err(|e| e.to_string())?;
1857    let col = col - 1;
1858    let row = row - 1;
1859    if col == 0 && row == 0 {
1860        return Ok(());
1861    }
1862    if ws.row_breaks.is_none() {
1863        ws.row_breaks = Some(XlsxRowBreaks {
1864            breaks: XlsxBreaks::default(),
1865        });
1866    }
1867    if ws.col_breaks.is_none() {
1868        ws.col_breaks = Some(XlsxColBreaks {
1869            breaks: XlsxBreaks::default(),
1870        });
1871    }
1872    let row_breaks = ws.row_breaks.as_mut().unwrap();
1873    let col_breaks = ws.col_breaks.as_mut().unwrap();
1874    let mut row_brk = -1;
1875    let mut col_brk = -1;
1876    for (idx, brk) in row_breaks.breaks.brk.iter().enumerate() {
1877        if brk.id == Some(row as i64) {
1878            row_brk = idx as i32;
1879        }
1880    }
1881    for (idx, brk) in col_breaks.breaks.brk.iter().enumerate() {
1882        if brk.id == Some(col as i64) {
1883            col_brk = idx as i32;
1884        }
1885    }
1886    if row != 0 && row_brk == -1 {
1887        row_breaks.breaks.brk.push(XlsxBrk {
1888            id: Some(row as i64),
1889            max: Some((MAX_COLUMNS - 1) as i64),
1890            man: Some(true),
1891            ..Default::default()
1892        });
1893        row_breaks.breaks.manual_break_count =
1894            Some(row_breaks.breaks.manual_break_count.unwrap_or(0) + 1);
1895    }
1896    if col != 0 && col_brk == -1 {
1897        col_breaks.breaks.brk.push(XlsxBrk {
1898            id: Some(col as i64),
1899            max: Some((TOTAL_ROWS - 1) as i64),
1900            man: Some(true),
1901            ..Default::default()
1902        });
1903        col_breaks.breaks.manual_break_count =
1904            Some(col_breaks.breaks.manual_break_count.unwrap_or(0) + 1);
1905    }
1906    row_breaks.breaks.count = Some(row_breaks.breaks.brk.len() as i64);
1907    col_breaks.breaks.count = Some(col_breaks.breaks.brk.len() as i64);
1908    Ok(())
1909}
1910
1911fn remove_page_break_ws(ws: &mut XlsxWorksheet, cell: &str) -> Result<()> {
1912    let (col, row) = cell_name_to_coordinates(cell).map_err(|e| e.to_string())?;
1913    let col = col - 1;
1914    let row = row - 1;
1915    if col == 0 && row == 0 {
1916        return Ok(());
1917    }
1918    if ws.row_breaks.is_none() || ws.col_breaks.is_none() {
1919        return Ok(());
1920    }
1921    let row_breaks = ws.row_breaks.as_mut().unwrap();
1922    let col_breaks = ws.col_breaks.as_mut().unwrap();
1923    let row_brks = row_breaks.breaks.brk.len();
1924    let col_brks = col_breaks.breaks.brk.len();
1925    let mut remove_brk = |id: i64, brks: &mut Vec<XlsxBrk>| {
1926        let mut i = 0;
1927        while i < brks.len() {
1928            if brks[i].id == Some(id) {
1929                brks.remove(i);
1930            } else {
1931                i += 1;
1932            }
1933        }
1934    };
1935    if row_brks > 0 && row_brks == col_brks {
1936        remove_brk(row as i64, &mut row_breaks.breaks.brk);
1937        remove_brk(col as i64, &mut col_breaks.breaks.brk);
1938        row_breaks.breaks.count = Some(row_breaks.breaks.brk.len() as i64);
1939        col_breaks.breaks.count = Some(col_breaks.breaks.brk.len() as i64);
1940        row_breaks.breaks.manual_break_count =
1941            Some(row_breaks.breaks.manual_break_count.unwrap_or(1) - 1);
1942        col_breaks.breaks.manual_break_count =
1943            Some(col_breaks.breaks.manual_break_count.unwrap_or(1) - 1);
1944        return Ok(());
1945    }
1946    if row_brks > 0 && row_brks > col_brks {
1947        remove_brk(row as i64, &mut row_breaks.breaks.brk);
1948        row_breaks.breaks.count = Some(row_breaks.breaks.brk.len() as i64);
1949        row_breaks.breaks.manual_break_count =
1950            Some(row_breaks.breaks.manual_break_count.unwrap_or(1) - 1);
1951        return Ok(());
1952    }
1953    if col_brks > 0 && col_brks > row_brks {
1954        remove_brk(col as i64, &mut col_breaks.breaks.brk);
1955        col_breaks.breaks.count = Some(col_breaks.breaks.brk.len() as i64);
1956        col_breaks.breaks.manual_break_count =
1957            Some(col_breaks.breaks.manual_break_count.unwrap_or(1) - 1);
1958    }
1959    Ok(())
1960}
1961
1962fn get_sheet_state(visible: bool, very_hidden: Option<bool>) -> &'static str {
1963    if !visible && very_hidden == Some(true) {
1964        "veryHidden"
1965    } else {
1966        "hidden"
1967    }
1968}
1969
1970fn gen_sheet_passwd(plaintext: &str) -> String {
1971    let mut password: i64 = 0x0000;
1972    let mut char_pos: u32 = 1;
1973    for v in plaintext.chars() {
1974        let value = (v as i64) << char_pos;
1975        char_pos += 1;
1976        let rotated_bits = value >> 15;
1977        let value = value & 0x7fff;
1978        password ^= value | rotated_bits;
1979    }
1980    password ^= plaintext.chars().count() as i64;
1981    password ^= 0xCE4B;
1982    format!("{password:X}")
1983}
1984
1985fn image_extension_map() -> HashMap<String, String> {
1986    [
1987        (".bmp".to_string(), ".bmp".to_string()),
1988        (".emf".to_string(), ".emf".to_string()),
1989        (".emz".to_string(), ".emz".to_string()),
1990        (".gif".to_string(), ".gif".to_string()),
1991        (".ico".to_string(), ".ico".to_string()),
1992        (".jpg".to_string(), ".jpeg".to_string()),
1993        (".jpeg".to_string(), ".jpeg".to_string()),
1994        (".png".to_string(), ".png".to_string()),
1995        (".svg".to_string(), ".svg".to_string()),
1996        (".tif".to_string(), ".tiff".to_string()),
1997        (".tiff".to_string(), ".tiff".to_string()),
1998        (".wmf".to_string(), ".wmf".to_string()),
1999        (".wmz".to_string(), ".wmz".to_string()),
2000    ]
2001    .into_iter()
2002    .collect()
2003}
2004
2005pub(crate) fn set_content_type_part_image_extensions(file: &File) -> Result<()> {
2006    // Matches Go setContentTypePartImageExtensions: register image types by
2007    // file extension as Defaults in [Content_Types].xml.
2008    let image_types: HashMap<String, String> = [
2009        ("bmp".to_string(), "image/".to_string()),
2010        ("ico".to_string(), "image/x-".to_string()),
2011        ("jpeg".to_string(), "image/".to_string()),
2012        ("png".to_string(), "image/".to_string()),
2013        ("gif".to_string(), "image/".to_string()),
2014        ("svg".to_string(), "image/".to_string()),
2015        ("tiff".to_string(), "image/".to_string()),
2016        ("emf".to_string(), "image/x-".to_string()),
2017        ("wmf".to_string(), "image/x-".to_string()),
2018        ("emz".to_string(), "image/x-".to_string()),
2019        ("wmz".to_string(), "image/x-".to_string()),
2020    ]
2021    .into_iter()
2022    .collect();
2023    let mut ct = file.content_types_reader()?;
2024    for (extension, prefix) in image_types {
2025        let exists = ct.entries.iter().any(|e| {
2026            if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2027                d.extension == extension
2028            } else {
2029                false
2030            }
2031        });
2032        if !exists {
2033            let content_type = format!("{prefix}{extension}");
2034            ct.entries.push(XlsxContentTypeEntry::Default(XlsxDefault {
2035                extension: extension.clone(),
2036                content_type,
2037            }));
2038        }
2039    }
2040    *file.content_types.lock().unwrap() = Some(ct);
2041    Ok(())
2042}
2043
2044fn ignored_error_for_type(range_ref: &str, t: IgnoredErrorsType) -> XlsxIgnoredError {
2045    let base = XlsxIgnoredError {
2046        sqref: range_ref.to_string(),
2047        ..Default::default()
2048    };
2049    match t {
2050        IgnoredErrorsType::EvalError => XlsxIgnoredError {
2051            eval_error: Some(true),
2052            ..base
2053        },
2054        IgnoredErrorsType::TwoDigitTextYear => XlsxIgnoredError {
2055            two_digit_text_year: Some(true),
2056            ..base
2057        },
2058        IgnoredErrorsType::NumberStoredAsText => XlsxIgnoredError {
2059            number_stored_as_text: Some(true),
2060            ..base
2061        },
2062        IgnoredErrorsType::Formula => XlsxIgnoredError {
2063            formula: Some(true),
2064            ..base
2065        },
2066        IgnoredErrorsType::FormulaRange => XlsxIgnoredError {
2067            formula_range: Some(true),
2068            ..base
2069        },
2070        IgnoredErrorsType::UnlockedFormula => XlsxIgnoredError {
2071            unlocked_formula: Some(true),
2072            ..base
2073        },
2074        IgnoredErrorsType::EmptyCellReference => XlsxIgnoredError {
2075            empty_cell_reference: Some(true),
2076            ..base
2077        },
2078        IgnoredErrorsType::ListDataValidation => XlsxIgnoredError {
2079            list_data_validation: Some(true),
2080            ..base
2081        },
2082        IgnoredErrorsType::CalculatedColumn => XlsxIgnoredError {
2083            calculated_column: Some(true),
2084            ..base
2085        },
2086    }
2087}
2088
2089fn same_ignored_error_flags(a: &XlsxIgnoredError, b: &XlsxIgnoredError) -> bool {
2090    a.eval_error == b.eval_error
2091        && a.two_digit_text_year == b.two_digit_text_year
2092        && a.number_stored_as_text == b.number_stored_as_text
2093        && a.formula == b.formula
2094        && a.formula_range == b.formula_range
2095        && a.unlocked_formula == b.unlocked_formula
2096        && a.empty_cell_reference == b.empty_cell_reference
2097        && a.list_data_validation == b.list_data_validation
2098        && a.calculated_column == b.calculated_column
2099}
2100
2101fn adjust_range_sheet_name(data: &str, source: &str, target: &str) -> String {
2102    // Replace sheet name references in defined name formulas.
2103    let mut out = data.to_string();
2104    let patterns = vec![format!("{source}!"), format!("'{source}'!")];
2105    for p in patterns {
2106        out = out.replace(&p, &format!("{target}!"));
2107    }
2108    out
2109}
2110
2111pub(crate) fn ensure_shared_strings_rel(rels: &mut crate::xml::workbook::XlsxRelationships) {
2112    for rel in &rels.relationships {
2113        if rel.r#type == SOURCE_RELATIONSHIP_SHARED_STRINGS {
2114            return;
2115        }
2116    }
2117    let mut max_rid = 0i32;
2118    for rel in &rels.relationships {
2119        if let Ok(n) = rel.id.trim_start_matches("rId").parse::<i32>() {
2120            if n > max_rid {
2121                max_rid = n;
2122            }
2123        }
2124    }
2125    rels.relationships
2126        .push(crate::xml::workbook::XlsxRelationship {
2127            id: format!("rId{}", max_rid + 1),
2128            r#type: SOURCE_RELATIONSHIP_SHARED_STRINGS.to_string(),
2129            target: "sharedStrings.xml".to_string(),
2130            target_mode: None,
2131        });
2132}
2133
2134#[cfg(test)]
2135mod tests {
2136    use super::*;
2137
2138    #[test]
2139    fn new_sheet_round_trip() {
2140        let f = File::new_with_options(crate::options::Options::default());
2141        let idx = f.new_sheet("Sheet2").unwrap();
2142        assert_eq!(idx, 2);
2143        assert_eq!(f.get_sheet_name(idx).unwrap(), "Sheet2");
2144        assert_eq!(f.get_sheet_index("Sheet2").unwrap(), idx);
2145        assert!(f.get_sheet_list().contains(&"Sheet2".to_string()));
2146    }
2147
2148    #[test]
2149    fn sheet_name_and_map_round_trip() {
2150        let f = File::new_with_options(crate::options::Options::default());
2151        f.new_sheet("Sheet2").unwrap();
2152        f.set_sheet_name("Sheet2", "NewName").unwrap();
2153        assert_eq!(f.get_sheet_index("NewName").unwrap(), 2);
2154        let map = f.get_sheet_map();
2155        assert!(map.values().any(|n| n == "NewName"));
2156    }
2157
2158    #[test]
2159    fn sheet_visible_round_trip() {
2160        let f = File::new_with_options(crate::options::Options::default());
2161        f.new_sheet("Sheet2").unwrap();
2162        assert!(f.get_sheet_visible("Sheet2").unwrap());
2163        f.set_sheet_visible("Sheet2", false, None).unwrap();
2164        assert!(!f.get_sheet_visible("Sheet2").unwrap());
2165        f.set_sheet_visible("Sheet2", true, None).unwrap();
2166        assert!(f.get_sheet_visible("Sheet2").unwrap());
2167    }
2168
2169    #[test]
2170    fn panes_round_trip() {
2171        let f = File::new_with_options(crate::options::Options::default());
2172        let panes = Panes {
2173            freeze: true,
2174            x_split: 1,
2175            top_left_cell: "B1".to_string(),
2176            active_pane: "topRight".to_string(),
2177            selection: vec![Selection {
2178                sqref: "K16".to_string(),
2179                active_cell: "K16".to_string(),
2180                pane: "topRight".to_string(),
2181            }],
2182            ..Default::default()
2183        };
2184        f.set_panes("Sheet1", &panes).unwrap();
2185        let got = f.get_panes("Sheet1").unwrap();
2186        assert_eq!(got.freeze, true);
2187        assert_eq!(got.x_split, 1);
2188        assert_eq!(got.top_left_cell, "B1");
2189    }
2190
2191    #[test]
2192    fn sheet_protection_round_trip() {
2193        let f = File::new_with_options(crate::options::Options::default());
2194        let opts = SheetProtectionOptions {
2195            password: "password".to_string(),
2196            select_locked_cells: true,
2197            ..Default::default()
2198        };
2199        f.protect_sheet("Sheet1", &opts).unwrap();
2200        let got = f.get_sheet_protection("Sheet1").unwrap();
2201        assert!(got.select_locked_cells);
2202        f.unprotect_sheet("Sheet1", Some("password")).unwrap();
2203        let got = f.get_sheet_protection("Sheet1").unwrap();
2204        assert!(!got.select_locked_cells);
2205    }
2206
2207    #[test]
2208    fn page_break_round_trip() {
2209        let f = File::new_with_options(crate::options::Options::default());
2210        f.insert_page_break("Sheet1", "B2").unwrap();
2211        let ws = f.work_sheet_reader("Sheet1").unwrap();
2212        assert!(ws.row_breaks.is_some());
2213        assert!(ws.col_breaks.is_some());
2214        f.remove_page_break("Sheet1", "B2").unwrap();
2215        let ws = f.work_sheet_reader("Sheet1").unwrap();
2216        assert!(ws.row_breaks.as_ref().unwrap().breaks.brk.is_empty());
2217    }
2218
2219    #[test]
2220    fn sheet_dimension_round_trip() {
2221        let f = File::new_with_options(crate::options::Options::default());
2222        f.set_sheet_dimension("Sheet1", "A1:D5").unwrap();
2223        assert_eq!(f.get_sheet_dimension("Sheet1").unwrap(), "A1:D5");
2224        f.set_sheet_dimension("Sheet1", "").unwrap();
2225        assert!(f.get_sheet_dimension("Sheet1").unwrap().is_empty());
2226    }
2227
2228    #[test]
2229    fn search_sheet_basic() {
2230        let f = File::new_with_options(crate::options::Options::default());
2231        f.set_cell_str("Sheet1", "A1", "hello").unwrap();
2232        f.set_cell_str("Sheet1", "B2", "world").unwrap();
2233        let refs = f.search_sheet("Sheet1", "hello", None).unwrap();
2234        assert!(refs.contains(&"A1".to_string()));
2235    }
2236
2237    #[test]
2238    fn search_sheet_shared_string() {
2239        let f = File::new_with_options(crate::options::Options::default());
2240        f.set_cell_str("Sheet1", "A1", "shared_text").unwrap();
2241        let refs = f.search_sheet("Sheet1", "shared_text", None).unwrap();
2242        assert_eq!(refs, vec!["A1".to_string()]);
2243    }
2244
2245    #[test]
2246    fn search_sheet_regex() {
2247        let f = File::new_with_options(crate::options::Options::default());
2248        f.set_cell_str("Sheet1", "A1", "hello world").unwrap();
2249        f.set_cell_str("Sheet1", "B2", "hello rust").unwrap();
2250        let refs = f.search_sheet("Sheet1", r"hello \w+", Some(true)).unwrap();
2251        assert_eq!(refs.len(), 2);
2252        assert!(refs.contains(&"A1".to_string()));
2253        assert!(refs.contains(&"B2".to_string()));
2254    }
2255
2256    #[test]
2257    fn search_sheet_inline_string() {
2258        let f = File::new_with_options(crate::options::Options::default());
2259        f.set_cell_value(
2260            "Sheet1",
2261            "A1",
2262            vec![crate::xml::common::RichTextRun {
2263                text: "inline".to_string(),
2264                ..Default::default()
2265            }],
2266        )
2267        .unwrap();
2268        let refs = f.search_sheet("Sheet1", "inline", None).unwrap();
2269        assert_eq!(refs, vec!["A1".to_string()]);
2270    }
2271
2272    #[test]
2273    fn search_sheet_rich_text_shared_string() {
2274        let f = File::new_with_options(crate::options::Options::default());
2275        let runs = vec![
2276            crate::xml::common::RichTextRun {
2277                text: "bold".to_string(),
2278                ..Default::default()
2279            },
2280            crate::xml::common::RichTextRun {
2281                text: "text".to_string(),
2282                ..Default::default()
2283            },
2284        ];
2285        f.set_cell_rich_text("Sheet1", "A1", runs).unwrap();
2286        let refs = f.search_sheet("Sheet1", "boldtext", None).unwrap();
2287        assert_eq!(refs, vec!["A1".to_string()]);
2288    }
2289
2290    #[test]
2291    fn search_sheet_merge_cells() {
2292        let f = File::new_with_options(crate::options::Options::default());
2293        f.set_cell_str("Sheet1", "A1", "merged").unwrap();
2294        f.merge_cell("Sheet1", "A1", "B2").unwrap();
2295        let refs = f.search_sheet("Sheet1", "merged", None).unwrap();
2296        assert_eq!(refs, vec!["A1".to_string()]);
2297    }
2298
2299    #[test]
2300    fn search_sheet_after_save_and_reopen() {
2301        let f = File::new_with_options(crate::options::Options::default());
2302        f.set_cell_str("Sheet1", "A1", "persisted").unwrap();
2303        let buf = f.write_to_buffer().unwrap();
2304        let f2 = File::open_reader(
2305            std::io::Cursor::new(buf),
2306            0,
2307            crate::options::Options::default(),
2308        )
2309        .unwrap();
2310        let refs = f2.search_sheet("Sheet1", "persisted", None).unwrap();
2311        assert_eq!(refs, vec!["A1".to_string()]);
2312    }
2313
2314    #[test]
2315    fn ignored_errors_round_trip() {
2316        let f = File::new_with_options(crate::options::Options::default());
2317        f.add_ignored_errors("Sheet1", "A1:A2", IgnoredErrorsType::NumberStoredAsText)
2318            .unwrap();
2319        let ws = f.work_sheet_reader("Sheet1").unwrap();
2320        let errors = ws.ignored_errors.as_ref().unwrap();
2321        assert_eq!(errors.ignored_error.len(), 1);
2322        assert!(errors.ignored_error[0].number_stored_as_text.unwrap());
2323    }
2324
2325    #[test]
2326    fn move_and_copy_sheet() {
2327        let f = File::new_with_options(crate::options::Options::default());
2328        f.new_sheet("Sheet2").unwrap();
2329        f.new_sheet("Sheet3").unwrap();
2330        f.move_sheet("Sheet3", "Sheet1").unwrap();
2331        let list = f.get_sheet_list();
2332        assert_eq!(list[0], "Sheet3");
2333        f.copy_sheet(1, 2).unwrap();
2334    }
2335
2336    #[test]
2337    fn sheet_background_from_bytes() {
2338        let f = File::new_with_options(crate::options::Options::default());
2339        // 1x1 red PNG
2340        let png = vec![
2341            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
2342            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
2343            0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
2344            0x99, 0x63, 0xf8, 0x0f, 0x00, 0x00, 0x01, 0x01, 0x00, 0x05, 0x18, 0xd8, 0x4e, 0x00,
2345            0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
2346        ];
2347        f.set_sheet_background_from_bytes("Sheet1", "png", &png)
2348            .unwrap();
2349        let ws = f.work_sheet_reader("Sheet1").unwrap();
2350        assert!(ws.picture.is_some());
2351    }
2352
2353    #[test]
2354    fn sheet_props_round_trip() {
2355        let f = File::new_with_options(crate::options::Options::default());
2356        let expected = SheetPropsOptions {
2357            code_name: Some("code".to_string()),
2358            enable_format_conditions_calculation: Some(true),
2359            published: Some(true),
2360            auto_page_breaks: Some(true),
2361            fit_to_page: Some(true),
2362            tab_color_indexed: Some(1),
2363            tab_color_rgb: Some("FFFF00".to_string()),
2364            tab_color_theme: Some(1),
2365            tab_color_tint: Some(1.0),
2366            outline_pr_summary_below: Some(true),
2367            outline_pr_summary_right: Some(true),
2368            base_col_width: Some(8),
2369            default_col_width: Some(10.0),
2370            default_row_height: Some(10.0),
2371            custom_height: Some(true),
2372            zero_height: Some(true),
2373            thick_top: Some(true),
2374            thick_bottom: Some(true),
2375            ..Default::default()
2376        };
2377        f.set_sheet_props("Sheet1", &expected).unwrap();
2378        let got = f.get_sheet_props("Sheet1").unwrap();
2379        assert_eq!(expected, got);
2380    }
2381
2382    #[test]
2383    fn page_margins_round_trip() {
2384        let f = File::new_with_options(crate::options::Options::default());
2385        let expected = PageMarginsOptions {
2386            left: Some(1.0),
2387            right: Some(1.0),
2388            top: Some(1.0),
2389            bottom: Some(1.0),
2390            header: Some(1.0),
2391            footer: Some(1.0),
2392            horizontally: Some(true),
2393            vertically: Some(true),
2394        };
2395        f.set_page_margins("Sheet1", &expected).unwrap();
2396        let got = f.get_page_margins("Sheet1").unwrap();
2397        assert_eq!(expected, got);
2398    }
2399
2400    #[test]
2401    fn sheet_view_round_trip() {
2402        let f = File::new_with_options(crate::options::Options::default());
2403        let expected = SheetViewOptions {
2404            default_grid_color: Some(false),
2405            right_to_left: Some(false),
2406            show_formulas: Some(false),
2407            show_grid_lines: Some(false),
2408            show_row_col_headers: Some(false),
2409            show_ruler: Some(false),
2410            show_zeros: Some(false),
2411            top_left_cell: Some("A1".to_string()),
2412            view: Some("normal".to_string()),
2413            zoom_scale: Some(120.0),
2414            // The new file template has tabSelected="1", so this stays true.
2415            tab_selected: Some(true),
2416            // Value-type fields that are not present in the XML default to false.
2417            show_outline_symbols: Some(false),
2418            show_white_space: Some(false),
2419            window_protection: Some(false),
2420        };
2421        f.set_sheet_view("Sheet1", 0, &expected).unwrap();
2422        let got = f.get_sheet_view("Sheet1", 0).unwrap();
2423        assert_eq!(expected, got);
2424
2425        // Negative index points at the same single view.
2426        f.set_sheet_view("Sheet1", -1, &expected).unwrap();
2427        let got = f.get_sheet_view("Sheet1", -1).unwrap();
2428        assert_eq!(expected, got);
2429
2430        // Invalid view values are ignored.
2431        let invalid_view = SheetViewOptions {
2432            view: Some("invalid".to_string()),
2433            ..Default::default()
2434        };
2435        f.set_sheet_view("Sheet1", 0, &invalid_view).unwrap();
2436        let got = f.get_sheet_view("Sheet1", 0).unwrap();
2437        assert_eq!(Some("normal".to_string()), got.view);
2438
2439        // Invalid zoom values are ignored.
2440        let invalid_zoom = SheetViewOptions {
2441            zoom_scale: Some(5.0),
2442            ..Default::default()
2443        };
2444        f.set_sheet_view("Sheet1", 0, &invalid_zoom).unwrap();
2445        let got = f.get_sheet_view("Sheet1", 0).unwrap();
2446        assert_eq!(Some(120.0), got.zoom_scale);
2447
2448        // Out-of-range indices return an error.
2449        let empty = SheetViewOptions::default();
2450        assert_eq!(
2451            "view index 1 out of range",
2452            f.set_sheet_view("Sheet1", 1, &empty)
2453                .unwrap_err()
2454                .to_string()
2455        );
2456        assert_eq!(
2457            "view index -2 out of range",
2458            f.set_sheet_view("Sheet1", -2, &empty)
2459                .unwrap_err()
2460                .to_string()
2461        );
2462        assert!(f.get_sheet_view("Sheet1", 1).is_err());
2463        assert!(f.get_sheet_view("Sheet1", -2).is_err());
2464
2465        // Non-existent worksheet returns an error.
2466        assert!(f.set_sheet_view("SheetN", 0, &empty).is_err());
2467        assert!(f.get_sheet_view("SheetN", 0).is_err());
2468    }
2469
2470    #[test]
2471    fn sheet_view_page_break_preview() {
2472        let f = File::new_with_options(crate::options::Options::default());
2473        let opts = SheetViewOptions {
2474            view: Some("pageBreakPreview".to_string()),
2475            zoom_scale: Some(200.0),
2476            ..Default::default()
2477        };
2478        f.set_sheet_view("Sheet1", 0, &opts).unwrap();
2479        let got = f.get_sheet_view("Sheet1", 0).unwrap();
2480        assert_eq!(Some("pageBreakPreview".to_string()), got.view);
2481        assert_eq!(Some(200.0), got.zoom_scale);
2482    }
2483
2484    #[test]
2485    fn sheet_props_defaults() {
2486        let f = File::new_with_options(crate::options::Options::default());
2487        let opts = f.get_sheet_props("Sheet1").unwrap();
2488        assert_eq!(Some(true), opts.enable_format_conditions_calculation);
2489        assert_eq!(Some(true), opts.published);
2490        assert_eq!(Some(true), opts.auto_page_breaks);
2491        assert_eq!(Some(true), opts.outline_pr_summary_below);
2492        assert_eq!(Some(8), opts.base_col_width);
2493        assert_eq!(Some(15.0), opts.default_row_height);
2494    }
2495
2496    #[test]
2497    fn page_margins_defaults() {
2498        let f = File::new_with_options(crate::options::Options::default());
2499        let opts = f.get_page_margins("Sheet1").unwrap();
2500        assert_eq!(Some(0.7), opts.left);
2501        assert_eq!(Some(0.7), opts.right);
2502        assert_eq!(Some(0.75), opts.top);
2503        assert_eq!(Some(0.75), opts.bottom);
2504        assert_eq!(Some(0.3), opts.header);
2505        assert_eq!(Some(0.3), opts.footer);
2506    }
2507}