1use super::*;
2
3impl Workbook {
4 pub fn sheet_names(&self) -> Vec<&str> {
6 self.worksheets
7 .iter()
8 .map(|(name, _)| name.as_str())
9 .collect()
10 }
11
12 pub fn new_sheet(&mut self, name: &str) -> Result<usize> {
14 let idx = crate::sheet::add_sheet(
15 &mut self.workbook_xml,
16 &mut self.workbook_rels,
17 &mut self.content_types,
18 &mut self.worksheets,
19 name,
20 WorksheetXml::default(),
21 )?;
22 if self.sheet_comments.len() < self.worksheets.len() {
23 self.sheet_comments.push(None);
24 }
25 if self.sheet_sparklines.len() < self.worksheets.len() {
26 self.sheet_sparklines.push(vec![]);
27 }
28 if self.sheet_vml.len() < self.worksheets.len() {
29 self.sheet_vml.push(None);
30 }
31 if self.raw_sheet_xml.len() < self.worksheets.len() {
32 self.raw_sheet_xml.push(None);
33 }
34 if self.sheet_dirty.len() < self.worksheets.len() {
35 self.sheet_dirty.push(true);
36 }
37 if self.sheet_threaded_comments.len() < self.worksheets.len() {
38 self.sheet_threaded_comments.push(None);
39 }
40 if self.sheet_form_controls.len() < self.worksheets.len() {
41 self.sheet_form_controls.push(vec![]);
42 }
43 self.rebuild_sheet_index();
44 Ok(idx)
45 }
46
47 pub fn delete_sheet(&mut self, name: &str) -> Result<()> {
49 let idx = self.sheet_index(name)?;
50 self.assert_parallel_vecs_in_sync();
51
52 crate::sheet::delete_sheet(
53 &mut self.workbook_xml,
54 &mut self.workbook_rels,
55 &mut self.content_types,
56 &mut self.worksheets,
57 name,
58 )?;
59
60 self.sheet_comments.remove(idx);
64 self.sheet_sparklines.remove(idx);
65 self.sheet_vml.remove(idx);
66 self.raw_sheet_xml.remove(idx);
67 self.sheet_dirty.remove(idx);
68 self.sheet_threaded_comments.remove(idx);
69 self.sheet_form_controls.remove(idx);
70
71 self.tables.retain(|(_, _, si)| *si != idx);
73 for (_, _, si) in &mut self.tables {
74 if *si > idx {
75 *si -= 1;
76 }
77 }
78
79 self.streamed_sheets.remove(&idx);
81 self.streamed_sheets = self
82 .streamed_sheets
83 .drain()
84 .map(|(i, data)| if i > idx { (i - 1, data) } else { (i, data) })
85 .collect();
86
87 self.reindex_sheet_maps_after_delete(idx);
88 self.rebuild_sheet_index();
89 Ok(())
90 }
91
92 fn assert_parallel_vecs_in_sync(&self) {
96 let n = self.worksheets.len();
97 debug_assert_eq!(self.sheet_comments.len(), n, "sheet_comments desync");
98 debug_assert_eq!(self.sheet_sparklines.len(), n, "sheet_sparklines desync");
99 debug_assert_eq!(self.sheet_vml.len(), n, "sheet_vml desync");
100 debug_assert_eq!(self.raw_sheet_xml.len(), n, "raw_sheet_xml desync");
101 debug_assert_eq!(self.sheet_dirty.len(), n, "sheet_dirty desync");
102 debug_assert_eq!(
103 self.sheet_threaded_comments.len(),
104 n,
105 "sheet_threaded_comments desync"
106 );
107 debug_assert_eq!(
108 self.sheet_form_controls.len(),
109 n,
110 "sheet_form_controls desync"
111 );
112 }
113
114 pub fn set_sheet_name(&mut self, old_name: &str, new_name: &str) -> Result<()> {
116 crate::sheet::rename_sheet(
117 &mut self.workbook_xml,
118 &mut self.worksheets,
119 old_name,
120 new_name,
121 )?;
122 self.rebuild_sheet_index();
123 Ok(())
124 }
125
126 pub fn copy_sheet(&mut self, source: &str, target: &str) -> Result<usize> {
128 let src_idx = self.sheet_index(source)?;
130 self.ensure_hydrated(src_idx)?;
133 let idx = crate::sheet::copy_sheet(
134 &mut self.workbook_xml,
135 &mut self.workbook_rels,
136 &mut self.content_types,
137 &mut self.worksheets,
138 source,
139 target,
140 )?;
141 if self.sheet_comments.len() < self.worksheets.len() {
142 self.sheet_comments.push(None);
143 }
144 let source_sparklines = self
145 .sheet_sparklines
146 .get(src_idx)
147 .cloned()
148 .unwrap_or_default();
149 if self.sheet_sparklines.len() < self.worksheets.len() {
150 self.sheet_sparklines.push(source_sparklines);
151 }
152 if self.sheet_vml.len() < self.worksheets.len() {
153 self.sheet_vml.push(None);
154 }
155 if self.raw_sheet_xml.len() < self.worksheets.len() {
156 self.raw_sheet_xml.push(None);
157 }
158 if self.sheet_dirty.len() < self.worksheets.len() {
159 self.sheet_dirty.push(true);
160 }
161 if self.sheet_threaded_comments.len() < self.worksheets.len() {
162 self.sheet_threaded_comments.push(None);
163 }
164 if self.sheet_form_controls.len() < self.worksheets.len() {
165 self.sheet_form_controls.push(vec![]);
166 }
167 if let Some(src_streamed) = self.streamed_sheets.get(&src_idx) {
169 let cloned = src_streamed.try_clone()?;
170 self.streamed_sheets.insert(idx, cloned);
171 }
172 self.rebuild_sheet_index();
173 Ok(idx)
174 }
175
176 pub fn get_sheet_index(&self, name: &str) -> Option<usize> {
178 crate::sheet::find_sheet_index(&self.worksheets, name)
179 }
180
181 pub fn get_active_sheet(&self) -> &str {
183 let idx = crate::sheet::active_sheet_index(&self.workbook_xml);
184 self.worksheets
185 .get(idx)
186 .map(|(n, _)| n.as_str())
187 .unwrap_or_else(|| self.worksheets[0].0.as_str())
188 }
189
190 pub fn set_active_sheet(&mut self, name: &str) -> Result<()> {
192 let idx = crate::sheet::find_sheet_index(&self.worksheets, name).ok_or_else(|| {
193 Error::SheetNotFound {
194 name: name.to_string(),
195 }
196 })?;
197 crate::sheet::set_active_sheet_index(&mut self.workbook_xml, idx as u32);
198 Ok(())
199 }
200
201 pub fn new_stream_writer(&self, sheet_name: &str) -> Result<crate::stream::StreamWriter> {
206 crate::sheet::validate_sheet_name(sheet_name)?;
207 if self.worksheets.iter().any(|(n, _)| n == sheet_name) {
208 return Err(Error::SheetAlreadyExists {
209 name: sheet_name.to_string(),
210 });
211 }
212 Ok(crate::stream::StreamWriter::new(sheet_name))
213 }
214
215 pub fn apply_stream_writer(&mut self, writer: crate::stream::StreamWriter) -> Result<usize> {
228 let (sheet_name, streamed_data) = writer.into_streamed_data()?;
229
230 let idx = crate::sheet::add_sheet(
234 &mut self.workbook_xml,
235 &mut self.workbook_rels,
236 &mut self.content_types,
237 &mut self.worksheets,
238 &sheet_name,
239 WorksheetXml::default(),
240 )?;
241 if self.sheet_comments.len() < self.worksheets.len() {
242 self.sheet_comments.push(None);
243 }
244 if self.sheet_sparklines.len() < self.worksheets.len() {
245 self.sheet_sparklines.push(vec![]);
246 }
247 if self.sheet_vml.len() < self.worksheets.len() {
248 self.sheet_vml.push(None);
249 }
250 if self.raw_sheet_xml.len() < self.worksheets.len() {
251 self.raw_sheet_xml.push(None);
252 }
253 if self.sheet_dirty.len() < self.worksheets.len() {
254 self.sheet_dirty.push(true);
255 }
256 if self.sheet_threaded_comments.len() < self.worksheets.len() {
257 self.sheet_threaded_comments.push(None);
258 }
259 if self.sheet_form_controls.len() < self.worksheets.len() {
260 self.sheet_form_controls.push(vec![]);
261 }
262
263 self.streamed_sheets.insert(idx, streamed_data);
265
266 self.rebuild_sheet_index();
267 Ok(idx)
268 }
269
270 pub fn insert_rows(&mut self, sheet: &str, start_row: u32, count: u32) -> Result<()> {
272 let sheet_idx = self.sheet_index(sheet)?;
273 {
274 let ws = self.worksheet_mut_by_index(sheet_idx)?;
275 crate::row::insert_rows(ws, start_row, count)?;
276 }
277 self.apply_reference_shift_for_sheet(sheet_idx, |col, row| {
278 if row >= start_row {
279 (col, row + count)
280 } else {
281 (col, row)
282 }
283 })
284 }
285
286 pub fn remove_row(&mut self, sheet: &str, row: u32) -> Result<()> {
288 let sheet_idx = self.sheet_index(sheet)?;
289 {
290 let ws = self.worksheet_mut_by_index(sheet_idx)?;
291 crate::row::remove_row(ws, row)?;
292 }
293 self.apply_reference_shift_for_sheet(sheet_idx, |col, r| {
294 if r > row {
295 (col, r - 1)
296 } else {
297 (col, r)
298 }
299 })
300 }
301
302 pub fn duplicate_row(&mut self, sheet: &str, row: u32) -> Result<()> {
304 let ws = self.worksheet_mut(sheet)?;
305 crate::row::duplicate_row(ws, row)
306 }
307
308 pub fn set_row_height(&mut self, sheet: &str, row: u32, height: f64) -> Result<()> {
310 let ws = self.worksheet_mut(sheet)?;
311 crate::row::set_row_height(ws, row, height)
312 }
313
314 pub fn get_row_height(&self, sheet: &str, row: u32) -> Result<Option<f64>> {
316 let ws = self.worksheet_ref(sheet)?;
317 Ok(crate::row::get_row_height(ws, row))
318 }
319
320 pub fn set_row_visible(&mut self, sheet: &str, row: u32, visible: bool) -> Result<()> {
322 let ws = self.worksheet_mut(sheet)?;
323 crate::row::set_row_visible(ws, row, visible)
324 }
325
326 pub fn get_row_visible(&self, sheet: &str, row: u32) -> Result<bool> {
328 let ws = self.worksheet_ref(sheet)?;
329 Ok(crate::row::get_row_visible(ws, row))
330 }
331
332 pub fn set_row_outline_level(&mut self, sheet: &str, row: u32, level: u8) -> Result<()> {
334 let ws = self.worksheet_mut(sheet)?;
335 crate::row::set_row_outline_level(ws, row, level)
336 }
337
338 pub fn get_row_outline_level(&self, sheet: &str, row: u32) -> Result<u8> {
340 let ws = self.worksheet_ref(sheet)?;
341 Ok(crate::row::get_row_outline_level(ws, row))
342 }
343
344 pub fn set_row_style(&mut self, sheet: &str, row: u32, style_id: u32) -> Result<()> {
348 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
349 return Err(Error::StyleNotFound { id: style_id });
350 }
351 let ws = self.worksheet_mut(sheet)?;
352 crate::row::set_row_style(ws, row, style_id)
353 }
354
355 pub fn get_row_style(&self, sheet: &str, row: u32) -> Result<u32> {
357 let ws = self.worksheet_ref(sheet)?;
358 Ok(crate::row::get_row_style(ws, row))
359 }
360
361 #[allow(clippy::type_complexity)]
367 pub fn get_rows(&self, sheet: &str) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
368 let ws = self.worksheet_ref(sheet)?;
369 crate::row::get_rows(ws, &self.sst_runtime)
370 }
371
372 #[allow(clippy::type_complexity)]
377 pub fn get_cols(&self, sheet: &str) -> Result<Vec<(String, Vec<(u32, CellValue)>)>> {
378 let ws = self.worksheet_ref(sheet)?;
379 crate::col::get_cols(ws, &self.sst_runtime)
380 }
381
382 pub fn set_col_width(&mut self, sheet: &str, col: &str, width: f64) -> Result<()> {
384 let ws = self.worksheet_mut(sheet)?;
385 crate::col::set_col_width(ws, col, width)
386 }
387
388 pub fn get_col_width(&self, sheet: &str, col: &str) -> Result<Option<f64>> {
390 let ws = self.worksheet_ref(sheet)?;
391 Ok(crate::col::get_col_width(ws, col))
392 }
393
394 pub fn set_col_visible(&mut self, sheet: &str, col: &str, visible: bool) -> Result<()> {
396 let ws = self.worksheet_mut(sheet)?;
397 crate::col::set_col_visible(ws, col, visible)
398 }
399
400 pub fn get_col_visible(&self, sheet: &str, col: &str) -> Result<bool> {
402 let ws = self.worksheet_ref(sheet)?;
403 crate::col::get_col_visible(ws, col)
404 }
405
406 pub fn set_col_outline_level(&mut self, sheet: &str, col: &str, level: u8) -> Result<()> {
408 let ws = self.worksheet_mut(sheet)?;
409 crate::col::set_col_outline_level(ws, col, level)
410 }
411
412 pub fn get_col_outline_level(&self, sheet: &str, col: &str) -> Result<u8> {
414 let ws = self.worksheet_ref(sheet)?;
415 crate::col::get_col_outline_level(ws, col)
416 }
417
418 pub fn set_col_style(&mut self, sheet: &str, col: &str, style_id: u32) -> Result<()> {
422 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
423 return Err(Error::StyleNotFound { id: style_id });
424 }
425 let ws = self.worksheet_mut(sheet)?;
426 crate::col::set_col_style(ws, col, style_id)
427 }
428
429 pub fn get_col_style(&self, sheet: &str, col: &str) -> Result<u32> {
431 let ws = self.worksheet_ref(sheet)?;
432 crate::col::get_col_style(ws, col)
433 }
434
435 pub fn insert_cols(&mut self, sheet: &str, col: &str, count: u32) -> Result<()> {
437 let sheet_idx = self.sheet_index(sheet)?;
438 let start_col = column_name_to_number(col)?;
439 {
440 let ws = self.worksheet_mut_by_index(sheet_idx)?;
441 crate::col::insert_cols(ws, col, count)?;
442 }
443 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
444 if c >= start_col {
445 (c + count, row)
446 } else {
447 (c, row)
448 }
449 })
450 }
451
452 pub fn remove_col(&mut self, sheet: &str, col: &str) -> Result<()> {
454 let sheet_idx = self.sheet_index(sheet)?;
455 let col_num = column_name_to_number(col)?;
456 {
457 let ws = self.worksheet_mut_by_index(sheet_idx)?;
458 crate::col::remove_col(ws, col)?;
459 }
460 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
461 if c > col_num {
462 (c - 1, row)
463 } else {
464 (c, row)
465 }
466 })
467 }
468
469 pub(crate) fn reindex_sheet_maps_after_delete(&mut self, removed_idx: usize) {
471 self.worksheet_rels = self
472 .worksheet_rels
473 .iter()
474 .filter_map(|(idx, rels)| {
475 if *idx == removed_idx {
476 None
477 } else if *idx > removed_idx {
478 Some((idx - 1, rels.clone()))
479 } else {
480 Some((*idx, rels.clone()))
481 }
482 })
483 .collect();
484
485 self.worksheet_drawings = self
486 .worksheet_drawings
487 .iter()
488 .filter_map(|(idx, drawing_idx)| {
489 if *idx == removed_idx {
490 None
491 } else if *idx > removed_idx {
492 Some((idx - 1, *drawing_idx))
493 } else {
494 Some((*idx, *drawing_idx))
495 }
496 })
497 .collect();
498 }
499
500 pub(crate) fn apply_reference_shift_for_sheet<F>(
502 &mut self,
503 sheet_idx: usize,
504 shift_cell: F,
505 ) -> Result<()>
506 where
507 F: Fn(u32, u32) -> (u32, u32) + Copy,
508 {
509 {
510 let ws = self.worksheet_mut_by_index(sheet_idx)?;
511
512 for row in &mut ws.sheet_data.rows {
514 for cell in &mut row.cells {
515 if let Some(ref mut f) = cell.f {
516 if let Some(ref mut expr) = f.value {
517 *expr = shift_cell_references_in_text(expr, shift_cell)?;
518 }
519 }
520 }
521 }
522
523 if let Some(ref mut merges) = ws.merge_cells {
525 for mc in &mut merges.merge_cells {
526 mc.reference = shift_cell_references_in_text(&mc.reference, shift_cell)?;
527 }
528 merges.cached_coords.clear();
530 }
531
532 if let Some(ref mut af) = ws.auto_filter {
534 af.reference = shift_cell_references_in_text(&af.reference, shift_cell)?;
535 }
536
537 if let Some(ref mut dvs) = ws.data_validations {
539 for dv in &mut dvs.data_validations {
540 dv.sqref = shift_cell_references_in_text(&dv.sqref, shift_cell)?;
541 if let Some(ref mut f1) = dv.formula1 {
542 *f1 = shift_cell_references_in_text(f1, shift_cell)?;
543 }
544 if let Some(ref mut f2) = dv.formula2 {
545 *f2 = shift_cell_references_in_text(f2, shift_cell)?;
546 }
547 }
548 }
549
550 for cf in &mut ws.conditional_formatting {
552 cf.sqref = shift_cell_references_in_text(&cf.sqref, shift_cell)?;
553 for rule in &mut cf.cf_rules {
554 for f in &mut rule.formulas {
555 *f = shift_cell_references_in_text(f, shift_cell)?;
556 }
557 }
558 }
559
560 if let Some(ref mut hyperlinks) = ws.hyperlinks {
562 for hl in &mut hyperlinks.hyperlinks {
563 hl.reference = shift_cell_references_in_text(&hl.reference, shift_cell)?;
564 if let Some(ref mut loc) = hl.location {
565 *loc = shift_cell_references_in_text(loc, shift_cell)?;
566 }
567 }
568 }
569
570 if let Some(ref mut views) = ws.sheet_views {
572 for view in &mut views.sheet_views {
573 if let Some(ref mut pane) = view.pane {
574 if let Some(ref mut top_left) = pane.top_left_cell {
575 *top_left = shift_cell_references_in_text(top_left, shift_cell)?;
576 }
577 }
578 for sel in &mut view.selection {
579 if let Some(ref mut ac) = sel.active_cell {
580 *ac = shift_cell_references_in_text(ac, shift_cell)?;
581 }
582 if let Some(ref mut sqref) = sel.sqref {
583 *sqref = shift_cell_references_in_text(sqref, shift_cell)?;
584 }
585 }
586 }
587 }
588 }
589
590 if let Some(&drawing_idx) = self.worksheet_drawings.get(&sheet_idx) {
592 if let Some((_, drawing)) = self.drawings.get_mut(drawing_idx) {
593 for anchor in &mut drawing.one_cell_anchors {
594 let (new_col, new_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
595 anchor.from.col = new_col - 1;
596 anchor.from.row = new_row - 1;
597 }
598 for anchor in &mut drawing.two_cell_anchors {
599 let (from_col, from_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
600 anchor.from.col = from_col - 1;
601 anchor.from.row = from_row - 1;
602 let (to_col, to_row) = shift_cell(anchor.to.col + 1, anchor.to.row + 1);
603 anchor.to.col = to_col - 1;
604 anchor.to.row = to_row - 1;
605 }
606 }
607 }
608
609 Ok(())
610 }
611
612 pub(crate) fn ensure_drawing_for_sheet(&mut self, sheet_idx: usize) -> usize {
615 if let Some(&idx) = self.worksheet_drawings.get(&sheet_idx) {
616 return idx;
617 }
618
619 let idx = self.drawings.len();
620 let drawing_path = format!("xl/drawings/drawing{}.xml", idx + 1);
621 self.drawings.push((drawing_path, WsDr::default()));
622 self.worksheet_drawings.insert(sheet_idx, idx);
623
624 let ws_rid = self.next_worksheet_rid(sheet_idx);
626 self.ensure_hydrated(sheet_idx)
630 .expect("sheet must be hydrated before attaching a drawing");
631 self.mark_sheet_dirty(sheet_idx);
632 self.worksheets[sheet_idx].1.get_mut().unwrap().drawing = Some(DrawingRef {
633 r_id: ws_rid.clone(),
634 });
635
636 let drawing_rel_target = format!("../drawings/drawing{}.xml", idx + 1);
638 let ws_rels = self
639 .worksheet_rels
640 .entry(sheet_idx)
641 .or_insert_with(|| Relationships {
642 xmlns: sheetkit_xml::namespaces::PACKAGE_RELATIONSHIPS.to_string(),
643 relationships: vec![],
644 });
645 ws_rels.relationships.push(Relationship {
646 id: ws_rid,
647 rel_type: rel_types::DRAWING.to_string(),
648 target: drawing_rel_target,
649 target_mode: None,
650 });
651
652 self.content_types.overrides.push(ContentTypeOverride {
654 part_name: format!("/xl/drawings/drawing{}.xml", idx + 1),
655 content_type: mime_types::DRAWING.to_string(),
656 });
657
658 idx
659 }
660
661 pub(crate) fn next_worksheet_rid(&self, sheet_idx: usize) -> String {
663 let existing = self
664 .worksheet_rels
665 .get(&sheet_idx)
666 .map(|r| r.relationships.as_slice())
667 .unwrap_or(&[]);
668 crate::sheet::next_rid(existing)
669 }
670
671 pub(crate) fn next_drawing_rid(&self, drawing_idx: usize) -> String {
673 let existing = self
674 .drawing_rels
675 .get(&drawing_idx)
676 .map(|r| r.relationships.as_slice())
677 .unwrap_or(&[]);
678 crate::sheet::next_rid(existing)
679 }
680}
681
682#[cfg(test)]
683#[allow(clippy::approx_constant)]
684mod tests {
685 use super::*;
686 use tempfile::TempDir;
687
688 #[test]
689 fn test_new_sheet_basic() {
690 let mut wb = Workbook::new();
691 let idx = wb.new_sheet("Sheet2").unwrap();
692 assert_eq!(idx, 1);
693 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet2"]);
694 }
695
696 #[test]
697 fn test_new_sheet_duplicate_returns_error() {
698 let mut wb = Workbook::new();
699 let result = wb.new_sheet("Sheet1");
700 assert!(result.is_err());
701 assert!(matches!(
702 result.unwrap_err(),
703 Error::SheetAlreadyExists { .. }
704 ));
705 }
706
707 #[test]
708 fn test_new_sheet_invalid_name_returns_error() {
709 let mut wb = Workbook::new();
710 let result = wb.new_sheet("Bad/Name");
711 assert!(result.is_err());
712 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
713 }
714
715 #[test]
716 fn test_delete_sheet_basic() {
717 let mut wb = Workbook::new();
718 wb.new_sheet("Sheet2").unwrap();
719 wb.delete_sheet("Sheet1").unwrap();
720 assert_eq!(wb.sheet_names(), vec!["Sheet2"]);
721 }
722
723 #[test]
724 fn test_delete_sheet_keeps_parallel_vecs_in_sync() {
725 let mut wb = Workbook::new();
726 wb.new_sheet("Sheet2").unwrap();
727 wb.new_sheet("Sheet3").unwrap();
728
729 wb.add_comment(
731 "Sheet2",
732 &crate::comment::CommentConfig {
733 cell: "A1".to_string(),
734 author: "Test".to_string(),
735 text: "note".to_string(),
736 },
737 )
738 .unwrap();
739
740 wb.delete_sheet("Sheet2").unwrap();
742 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet3"]);
743
744 wb.add_comment(
747 "Sheet3",
748 &crate::comment::CommentConfig {
749 cell: "B2".to_string(),
750 author: "Test".to_string(),
751 text: "note2".to_string(),
752 },
753 )
754 .unwrap();
755 }
756
757 #[test]
758 fn test_delete_last_sheet_returns_error() {
759 let mut wb = Workbook::new();
760 let result = wb.delete_sheet("Sheet1");
761 assert!(result.is_err());
762 }
763
764 #[test]
765 fn test_delete_nonexistent_sheet_returns_error() {
766 let mut wb = Workbook::new();
767 let result = wb.delete_sheet("NoSuchSheet");
768 assert!(result.is_err());
769 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
770 }
771
772 #[test]
773 fn test_set_sheet_name_basic() {
774 let mut wb = Workbook::new();
775 wb.set_sheet_name("Sheet1", "Renamed").unwrap();
776 assert_eq!(wb.sheet_names(), vec!["Renamed"]);
777 }
778
779 #[test]
780 fn test_set_sheet_name_to_existing_returns_error() {
781 let mut wb = Workbook::new();
782 wb.new_sheet("Sheet2").unwrap();
783 let result = wb.set_sheet_name("Sheet1", "Sheet2");
784 assert!(result.is_err());
785 assert!(matches!(
786 result.unwrap_err(),
787 Error::SheetAlreadyExists { .. }
788 ));
789 }
790
791 #[test]
792 fn test_copy_sheet_basic() {
793 let mut wb = Workbook::new();
794 let idx = wb.copy_sheet("Sheet1", "Sheet1 Copy").unwrap();
795 assert_eq!(idx, 1);
796 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet1 Copy"]);
797 }
798
799 #[test]
800 fn test_get_sheet_index() {
801 let mut wb = Workbook::new();
802 wb.new_sheet("Sheet2").unwrap();
803 assert_eq!(wb.get_sheet_index("Sheet1"), Some(0));
804 assert_eq!(wb.get_sheet_index("Sheet2"), Some(1));
805 assert_eq!(wb.get_sheet_index("Nonexistent"), None);
806 }
807
808 #[test]
809 fn test_get_active_sheet_default() {
810 let wb = Workbook::new();
811 assert_eq!(wb.get_active_sheet(), "Sheet1");
812 }
813
814 #[test]
815 fn test_set_active_sheet() {
816 let mut wb = Workbook::new();
817 wb.new_sheet("Sheet2").unwrap();
818 wb.set_active_sheet("Sheet2").unwrap();
819 assert_eq!(wb.get_active_sheet(), "Sheet2");
820 }
821
822 #[test]
823 fn test_set_active_sheet_not_found() {
824 let mut wb = Workbook::new();
825 let result = wb.set_active_sheet("NoSuchSheet");
826 assert!(result.is_err());
827 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
828 }
829
830 #[test]
831 fn test_sheet_management_roundtrip_save_open() {
832 let dir = TempDir::new().unwrap();
833 let path = dir.path().join("sheet_mgmt.xlsx");
834
835 let mut wb = Workbook::new();
836 wb.new_sheet("Data").unwrap();
837 wb.new_sheet("Summary").unwrap();
838 wb.set_sheet_name("Sheet1", "Overview").unwrap();
839 wb.save(&path).unwrap();
840
841 let wb2 = Workbook::open(&path).unwrap();
842 assert_eq!(wb2.sheet_names(), vec!["Overview", "Data", "Summary"]);
843 }
844
845 #[test]
846 fn test_workbook_insert_rows() {
847 let mut wb = Workbook::new();
848 wb.set_cell_value("Sheet1", "A1", "stay").unwrap();
849 wb.set_cell_value("Sheet1", "A2", "shift").unwrap();
850 wb.insert_rows("Sheet1", 2, 1).unwrap();
851
852 assert_eq!(
853 wb.get_cell_value("Sheet1", "A1").unwrap(),
854 CellValue::String("stay".to_string())
855 );
856 assert_eq!(
857 wb.get_cell_value("Sheet1", "A3").unwrap(),
858 CellValue::String("shift".to_string())
859 );
860 assert_eq!(wb.get_cell_value("Sheet1", "A2").unwrap(), CellValue::Empty);
861 }
862
863 #[test]
864 fn test_workbook_insert_rows_updates_formula_and_ranges() {
865 let mut wb = Workbook::new();
866 wb.set_cell_value(
867 "Sheet1",
868 "C1",
869 CellValue::Formula {
870 expr: "SUM(A2:B2)".to_string(),
871 result: None,
872 },
873 )
874 .unwrap();
875 wb.add_data_validation(
876 "Sheet1",
877 &crate::validation::DataValidationConfig::whole_number("A2:A5", 1, 9),
878 )
879 .unwrap();
880 wb.set_auto_filter("Sheet1", "A2:B10").unwrap();
881 wb.merge_cells("Sheet1", "A2", "B3").unwrap();
882
883 wb.insert_rows("Sheet1", 2, 1).unwrap();
884
885 match wb.get_cell_value("Sheet1", "C1").unwrap() {
886 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:B3)"),
887 other => panic!("expected formula, got {other:?}"),
888 }
889
890 let validations = wb.get_data_validations("Sheet1").unwrap();
891 assert_eq!(validations.len(), 1);
892 assert_eq!(validations[0].sqref, "A3:A6");
893
894 let merges = wb.get_merge_cells("Sheet1").unwrap();
895 assert_eq!(merges, vec!["A3:B4".to_string()]);
896
897 let ws = wb.worksheet_ref("Sheet1").unwrap();
898 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A3:B11");
899 }
900
901 #[test]
902 fn test_workbook_insert_rows_sheet_not_found() {
903 let mut wb = Workbook::new();
904 let result = wb.insert_rows("NoSheet", 1, 1);
905 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
906 }
907
908 #[test]
909 fn test_workbook_remove_row() {
910 let mut wb = Workbook::new();
911 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
912 wb.set_cell_value("Sheet1", "A2", "second").unwrap();
913 wb.set_cell_value("Sheet1", "A3", "third").unwrap();
914 wb.remove_row("Sheet1", 2).unwrap();
915
916 assert_eq!(
917 wb.get_cell_value("Sheet1", "A1").unwrap(),
918 CellValue::String("first".to_string())
919 );
920 assert_eq!(
921 wb.get_cell_value("Sheet1", "A2").unwrap(),
922 CellValue::String("third".to_string())
923 );
924 }
925
926 #[test]
927 fn test_workbook_remove_row_sheet_not_found() {
928 let mut wb = Workbook::new();
929 let result = wb.remove_row("NoSheet", 1);
930 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
931 }
932
933 #[test]
934 fn test_workbook_duplicate_row() {
935 let mut wb = Workbook::new();
936 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
937 wb.duplicate_row("Sheet1", 1).unwrap();
938
939 assert_eq!(
940 wb.get_cell_value("Sheet1", "A1").unwrap(),
941 CellValue::String("original".to_string())
942 );
943 assert_eq!(
945 wb.get_cell_value("Sheet1", "A2").unwrap(),
946 CellValue::String("original".to_string())
947 );
948 }
949
950 #[test]
951 fn test_workbook_set_and_get_row_height() {
952 let mut wb = Workbook::new();
953 wb.set_row_height("Sheet1", 3, 25.0).unwrap();
954 assert_eq!(wb.get_row_height("Sheet1", 3).unwrap(), Some(25.0));
955 }
956
957 #[test]
958 fn test_workbook_get_row_height_sheet_not_found() {
959 let wb = Workbook::new();
960 let result = wb.get_row_height("NoSheet", 1);
961 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
962 }
963
964 #[test]
965 fn test_workbook_set_row_visible() {
966 let mut wb = Workbook::new();
967 wb.set_row_visible("Sheet1", 1, false).unwrap();
968 }
969
970 #[test]
971 fn test_workbook_set_row_visible_sheet_not_found() {
972 let mut wb = Workbook::new();
973 let result = wb.set_row_visible("NoSheet", 1, false);
974 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
975 }
976
977 #[test]
978 fn test_workbook_set_and_get_col_width() {
979 let mut wb = Workbook::new();
980 wb.set_col_width("Sheet1", "A", 18.0).unwrap();
981 assert_eq!(wb.get_col_width("Sheet1", "A").unwrap(), Some(18.0));
982 }
983
984 #[test]
985 fn test_workbook_get_col_width_sheet_not_found() {
986 let wb = Workbook::new();
987 let result = wb.get_col_width("NoSheet", "A");
988 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
989 }
990
991 #[test]
992 fn test_workbook_set_col_visible() {
993 let mut wb = Workbook::new();
994 wb.set_col_visible("Sheet1", "B", false).unwrap();
995 }
996
997 #[test]
998 fn test_workbook_set_col_visible_sheet_not_found() {
999 let mut wb = Workbook::new();
1000 let result = wb.set_col_visible("NoSheet", "A", false);
1001 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1002 }
1003
1004 #[test]
1005 fn test_workbook_insert_cols() {
1006 let mut wb = Workbook::new();
1007 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
1008 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
1009 wb.insert_cols("Sheet1", "B", 1).unwrap();
1010
1011 assert_eq!(
1012 wb.get_cell_value("Sheet1", "A1").unwrap(),
1013 CellValue::String("a".to_string())
1014 );
1015 assert_eq!(wb.get_cell_value("Sheet1", "B1").unwrap(), CellValue::Empty);
1016 assert_eq!(
1017 wb.get_cell_value("Sheet1", "C1").unwrap(),
1018 CellValue::String("b".to_string())
1019 );
1020 }
1021
1022 #[test]
1023 fn test_workbook_insert_cols_updates_formula_and_ranges() {
1024 let mut wb = Workbook::new();
1025 wb.set_cell_value(
1026 "Sheet1",
1027 "D1",
1028 CellValue::Formula {
1029 expr: "SUM(A1:B1)".to_string(),
1030 result: None,
1031 },
1032 )
1033 .unwrap();
1034 wb.add_data_validation(
1035 "Sheet1",
1036 &crate::validation::DataValidationConfig::whole_number("B2:C3", 1, 9),
1037 )
1038 .unwrap();
1039 wb.set_auto_filter("Sheet1", "A1:C10").unwrap();
1040 wb.merge_cells("Sheet1", "B3", "C4").unwrap();
1041
1042 wb.insert_cols("Sheet1", "B", 2).unwrap();
1043
1044 match wb.get_cell_value("Sheet1", "F1").unwrap() {
1045 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A1:D1)"),
1046 other => panic!("expected formula, got {other:?}"),
1047 }
1048
1049 let validations = wb.get_data_validations("Sheet1").unwrap();
1050 assert_eq!(validations.len(), 1);
1051 assert_eq!(validations[0].sqref, "D2:E3");
1052
1053 let merges = wb.get_merge_cells("Sheet1").unwrap();
1054 assert_eq!(merges, vec!["D3:E4".to_string()]);
1055
1056 let ws = wb.worksheet_ref("Sheet1").unwrap();
1057 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A1:E10");
1058 }
1059
1060 #[test]
1061 fn test_workbook_insert_cols_sheet_not_found() {
1062 let mut wb = Workbook::new();
1063 let result = wb.insert_cols("NoSheet", "A", 1);
1064 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1065 }
1066
1067 #[test]
1068 fn test_workbook_remove_col() {
1069 let mut wb = Workbook::new();
1070 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
1071 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
1072 wb.set_cell_value("Sheet1", "C1", "c").unwrap();
1073 wb.remove_col("Sheet1", "B").unwrap();
1074
1075 assert_eq!(
1076 wb.get_cell_value("Sheet1", "A1").unwrap(),
1077 CellValue::String("a".to_string())
1078 );
1079 assert_eq!(
1080 wb.get_cell_value("Sheet1", "B1").unwrap(),
1081 CellValue::String("c".to_string())
1082 );
1083 }
1084
1085 #[test]
1086 fn test_workbook_remove_col_sheet_not_found() {
1087 let mut wb = Workbook::new();
1088 let result = wb.remove_col("NoSheet", "A");
1089 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1090 }
1091
1092 #[test]
1093 fn test_new_stream_writer_validates_name() {
1094 let wb = Workbook::new();
1095 let result = wb.new_stream_writer("Bad[Name");
1096 assert!(result.is_err());
1097 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
1098 }
1099
1100 #[test]
1101 fn test_new_stream_writer_rejects_duplicate() {
1102 let wb = Workbook::new();
1103 let result = wb.new_stream_writer("Sheet1");
1104 assert!(result.is_err());
1105 assert!(matches!(
1106 result.unwrap_err(),
1107 Error::SheetAlreadyExists { .. }
1108 ));
1109 }
1110
1111 #[test]
1112 fn test_new_stream_writer_valid_name() {
1113 let wb = Workbook::new();
1114 let sw = wb.new_stream_writer("StreamSheet").unwrap();
1115 assert_eq!(sw.sheet_name(), "StreamSheet");
1116 }
1117
1118 #[test]
1119 fn test_apply_stream_writer_adds_sheet() {
1120 let mut wb = Workbook::new();
1121 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1122 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1123 .unwrap();
1124 let idx = wb.apply_stream_writer(sw).unwrap();
1125 assert_eq!(idx, 1);
1126 assert_eq!(wb.sheet_names(), vec!["Sheet1", "StreamSheet"]);
1127 }
1128
1129 #[test]
1130 fn test_apply_stream_writer_uses_inline_strings() {
1131 let mut wb = Workbook::new();
1133 wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1134 let sst_before = wb.sst_runtime.len();
1135
1136 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1137 sw.write_row(1, &[CellValue::from("New"), CellValue::from("Existing")])
1138 .unwrap();
1139 wb.apply_stream_writer(sw).unwrap();
1140
1141 assert_eq!(wb.sst_runtime.len(), sst_before);
1143 }
1144
1145 #[test]
1146 fn test_stream_writer_save_and_reopen() {
1147 let dir = TempDir::new().unwrap();
1148 let path = dir.path().join("stream_test.xlsx");
1149
1150 let mut wb = Workbook::new();
1151 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1152
1153 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1154 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1155 .unwrap();
1156 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1157 .unwrap();
1158 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1159 .unwrap();
1160 wb.apply_stream_writer(sw).unwrap();
1161
1162 wb.save(&path).unwrap();
1163
1164 let wb2 = Workbook::open(&path).unwrap();
1165 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Streamed"]);
1166 assert_eq!(
1167 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1168 CellValue::String("Normal".to_string())
1169 );
1170 assert_eq!(
1171 wb2.get_cell_value("Streamed", "A1").unwrap(),
1172 CellValue::String("Name".to_string())
1173 );
1174 assert_eq!(
1175 wb2.get_cell_value("Streamed", "B2").unwrap(),
1176 CellValue::Number(100.0)
1177 );
1178 assert_eq!(
1179 wb2.get_cell_value("Streamed", "A3").unwrap(),
1180 CellValue::String("Bob".to_string())
1181 );
1182 }
1183
1184 #[test]
1185 fn test_workbook_get_rows_empty_sheet() {
1186 let wb = Workbook::new();
1187 let rows = wb.get_rows("Sheet1").unwrap();
1188 assert!(rows.is_empty());
1189 }
1190
1191 #[test]
1192 fn test_workbook_get_rows_with_data() {
1193 let mut wb = Workbook::new();
1194 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1195 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1196 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1197 wb.set_cell_value("Sheet1", "B2", true).unwrap();
1198
1199 let rows = wb.get_rows("Sheet1").unwrap();
1200 assert_eq!(rows.len(), 2);
1201 assert_eq!(rows[0].0, 1);
1202 assert_eq!(rows[0].1.len(), 2);
1203 assert_eq!(rows[0].1[0].0, 1);
1204 assert_eq!(rows[0].1[0].1, CellValue::String("Name".to_string()));
1205 assert_eq!(rows[0].1[1].0, 2);
1206 assert_eq!(rows[0].1[1].1, CellValue::Number(42.0));
1207 assert_eq!(rows[1].0, 2);
1208 assert_eq!(rows[1].1[0].1, CellValue::String("Alice".to_string()));
1209 assert_eq!(rows[1].1[1].1, CellValue::Bool(true));
1210 }
1211
1212 #[test]
1213 fn test_workbook_get_rows_sheet_not_found() {
1214 let wb = Workbook::new();
1215 assert!(wb.get_rows("NoSheet").is_err());
1216 }
1217
1218 #[test]
1219 fn test_workbook_get_cols_empty_sheet() {
1220 let wb = Workbook::new();
1221 let cols = wb.get_cols("Sheet1").unwrap();
1222 assert!(cols.is_empty());
1223 }
1224
1225 #[test]
1226 fn test_workbook_get_cols_with_data() {
1227 let mut wb = Workbook::new();
1228 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1229 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1230 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1231 wb.set_cell_value("Sheet1", "B2", 30.0).unwrap();
1232
1233 let cols = wb.get_cols("Sheet1").unwrap();
1234 assert_eq!(cols.len(), 2);
1235 assert_eq!(cols[0].0, "A");
1236 assert_eq!(cols[0].1.len(), 2);
1237 assert_eq!(cols[0].1[0], (1, CellValue::String("Name".to_string())));
1238 assert_eq!(cols[0].1[1], (2, CellValue::String("Alice".to_string())));
1239 assert_eq!(cols[1].0, "B");
1240 assert_eq!(cols[1].1[0], (1, CellValue::Number(42.0)));
1241 assert_eq!(cols[1].1[1], (2, CellValue::Number(30.0)));
1242 }
1243
1244 #[test]
1245 fn test_workbook_get_cols_sheet_not_found() {
1246 let wb = Workbook::new();
1247 assert!(wb.get_cols("NoSheet").is_err());
1248 }
1249
1250 #[test]
1251 fn test_streamed_sheet_cells_empty_before_save() {
1252 let mut wb = Workbook::new();
1255 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1256 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1257 .unwrap();
1258 wb.apply_stream_writer(sw).unwrap();
1259
1260 assert_eq!(
1261 wb.get_cell_value("Streamed", "A1").unwrap(),
1262 CellValue::Empty
1263 );
1264 assert_eq!(
1265 wb.get_cell_value("Streamed", "B1").unwrap(),
1266 CellValue::Empty
1267 );
1268 }
1269
1270 #[test]
1271 fn test_streamed_sheet_readable_after_save_reopen() {
1272 let dir = TempDir::new().unwrap();
1273 let path = dir.path().join("stream_reopen.xlsx");
1274
1275 let mut wb = Workbook::new();
1276 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1277 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1278 .unwrap();
1279 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
1280 .unwrap();
1281 wb.apply_stream_writer(sw).unwrap();
1282 wb.save(&path).unwrap();
1283
1284 let wb2 = Workbook::open(&path).unwrap();
1285 assert_eq!(
1286 wb2.get_cell_value("Streamed", "A1").unwrap(),
1287 CellValue::String("Name".to_string())
1288 );
1289 assert_eq!(
1290 wb2.get_cell_value("Streamed", "B1").unwrap(),
1291 CellValue::String("Age".to_string())
1292 );
1293 assert_eq!(
1294 wb2.get_cell_value("Streamed", "A2").unwrap(),
1295 CellValue::String("Alice".to_string())
1296 );
1297 assert_eq!(
1298 wb2.get_cell_value("Streamed", "B2").unwrap(),
1299 CellValue::Number(30.0)
1300 );
1301 }
1302
1303 #[test]
1304 fn test_workbook_get_rows_roundtrip_save_open() {
1305 let mut wb = Workbook::new();
1306 wb.set_cell_value("Sheet1", "A1", "hello").unwrap();
1307 wb.set_cell_value("Sheet1", "B1", 99.0).unwrap();
1308 wb.set_cell_value("Sheet1", "A2", true).unwrap();
1309
1310 let tmp = std::env::temp_dir().join("test_get_rows_roundtrip.xlsx");
1311 wb.save(&tmp).unwrap();
1312
1313 let wb2 = Workbook::open(&tmp).unwrap();
1314 let rows = wb2.get_rows("Sheet1").unwrap();
1315 assert_eq!(rows.len(), 2);
1316 assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
1317 assert_eq!(rows[0].1[1].1, CellValue::Number(99.0));
1318 assert_eq!(rows[1].1[0].1, CellValue::Bool(true));
1319
1320 let _ = std::fs::remove_file(&tmp);
1321 }
1322
1323 #[test]
1324 fn test_stream_save_reopen_basic() {
1325 let dir = TempDir::new().unwrap();
1326 let path = dir.path().join("stream_basic.xlsx");
1327
1328 let mut wb = Workbook::new();
1329 let mut sw = wb.new_stream_writer("Optimized").unwrap();
1330 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1331 .unwrap();
1332 sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1333 .unwrap();
1334 let idx = wb.apply_stream_writer(sw).unwrap();
1335 assert_eq!(idx, 1);
1336
1337 wb.save(&path).unwrap();
1338 let wb2 = Workbook::open(&path).unwrap();
1339 assert_eq!(
1340 wb2.get_cell_value("Optimized", "A1").unwrap(),
1341 CellValue::String("Hello".to_string())
1342 );
1343 assert_eq!(
1344 wb2.get_cell_value("Optimized", "B1").unwrap(),
1345 CellValue::Number(42.0)
1346 );
1347 assert_eq!(
1348 wb2.get_cell_value("Optimized", "A2").unwrap(),
1349 CellValue::String("World".to_string())
1350 );
1351 assert_eq!(
1352 wb2.get_cell_value("Optimized", "B2").unwrap(),
1353 CellValue::Number(99.0)
1354 );
1355 }
1356
1357 #[test]
1358 fn test_stream_save_reopen_all_types() {
1359 let dir = TempDir::new().unwrap();
1360 let path = dir.path().join("stream_types.xlsx");
1361
1362 let mut wb = Workbook::new();
1363 let mut sw = wb.new_stream_writer("Types").unwrap();
1364 sw.write_row(
1365 1,
1366 &[
1367 CellValue::from("text"),
1368 CellValue::from(42),
1369 CellValue::from(3.14),
1370 CellValue::from(true),
1371 CellValue::Formula {
1372 expr: "SUM(B1:C1)".to_string(),
1373 result: None,
1374 },
1375 CellValue::Error("#N/A".to_string()),
1376 CellValue::Empty,
1377 ],
1378 )
1379 .unwrap();
1380 wb.apply_stream_writer(sw).unwrap();
1381
1382 wb.save(&path).unwrap();
1383 let wb2 = Workbook::open(&path).unwrap();
1384 assert_eq!(
1385 wb2.get_cell_value("Types", "A1").unwrap(),
1386 CellValue::String("text".to_string())
1387 );
1388 assert_eq!(
1389 wb2.get_cell_value("Types", "B1").unwrap(),
1390 CellValue::Number(42.0)
1391 );
1392 assert_eq!(
1393 wb2.get_cell_value("Types", "D1").unwrap(),
1394 CellValue::Bool(true)
1395 );
1396 match wb2.get_cell_value("Types", "E1").unwrap() {
1397 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:C1)"),
1398 other => panic!("expected formula, got {other:?}"),
1399 }
1400 assert_eq!(
1401 wb2.get_cell_value("Types", "F1").unwrap(),
1402 CellValue::Error("#N/A".to_string())
1403 );
1404 assert_eq!(wb2.get_cell_value("Types", "G1").unwrap(), CellValue::Empty);
1405 }
1406
1407 #[test]
1408 fn test_apply_stream_optimized_save_reopen() {
1409 let dir = TempDir::new().unwrap();
1410 let path = dir.path().join("stream_optimized.xlsx");
1411
1412 let mut wb = Workbook::new();
1413 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1414
1415 let mut sw = wb.new_stream_writer("Fast").unwrap();
1416 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1417 .unwrap();
1418 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1419 .unwrap();
1420 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1421 .unwrap();
1422 wb.apply_stream_writer(sw).unwrap();
1423
1424 wb.save(&path).unwrap();
1425
1426 let wb2 = Workbook::open(&path).unwrap();
1427 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Fast"]);
1428 assert_eq!(
1429 wb2.get_cell_value("Fast", "A1").unwrap(),
1430 CellValue::String("Name".to_string())
1431 );
1432 assert_eq!(
1433 wb2.get_cell_value("Fast", "B2").unwrap(),
1434 CellValue::Number(100.0)
1435 );
1436 assert_eq!(
1437 wb2.get_cell_value("Fast", "A3").unwrap(),
1438 CellValue::String("Bob".to_string())
1439 );
1440 }
1441
1442 #[test]
1443 fn test_stream_freeze_panes_roundtrip() {
1444 let dir = TempDir::new().unwrap();
1445 let path = dir.path().join("stream_freeze.xlsx");
1446
1447 let mut wb = Workbook::new();
1448 let mut sw = wb.new_stream_writer("FreezeSheet").unwrap();
1449 sw.set_freeze_panes("B3").unwrap();
1450 sw.write_row(1, &[CellValue::from("A"), CellValue::from("B")])
1451 .unwrap();
1452 sw.write_row(2, &[CellValue::from("C"), CellValue::from("D")])
1453 .unwrap();
1454 wb.apply_stream_writer(sw).unwrap();
1455 wb.save(&path).unwrap();
1456
1457 let wb2 = Workbook::open(&path).unwrap();
1458 assert_eq!(
1459 wb2.get_panes("FreezeSheet").unwrap(),
1460 Some("B3".to_string())
1461 );
1462 assert_eq!(
1463 wb2.get_cell_value("FreezeSheet", "A1").unwrap(),
1464 CellValue::String("A".to_string())
1465 );
1466 }
1467
1468 #[test]
1469 fn test_stream_merge_cells_roundtrip() {
1470 let dir = TempDir::new().unwrap();
1471 let path = dir.path().join("stream_merge.xlsx");
1472
1473 let mut wb = Workbook::new();
1474 let mut sw = wb.new_stream_writer("MergeSheet").unwrap();
1475 sw.add_merge_cell("A1:C1").unwrap();
1476 sw.add_merge_cell("A3:B4").unwrap();
1477 sw.write_row(1, &[CellValue::from("Header")]).unwrap();
1478 sw.write_row(2, &[CellValue::from("Data")]).unwrap();
1479 wb.apply_stream_writer(sw).unwrap();
1480 wb.save(&path).unwrap();
1481
1482 let wb2 = Workbook::open(&path).unwrap();
1483 let merges = wb2.get_merge_cells("MergeSheet").unwrap();
1484 assert!(merges.contains(&"A1:C1".to_string()));
1485 assert!(merges.contains(&"A3:B4".to_string()));
1486 assert_eq!(
1487 wb2.get_cell_value("MergeSheet", "A1").unwrap(),
1488 CellValue::String("Header".to_string())
1489 );
1490 }
1491
1492 #[test]
1493 fn test_stream_col_widths_roundtrip() {
1494 let dir = TempDir::new().unwrap();
1495 let path = dir.path().join("stream_colw.xlsx");
1496
1497 let mut wb = Workbook::new();
1498 let mut sw = wb.new_stream_writer("ColSheet").unwrap();
1499 sw.set_col_width(1, 25.0).unwrap();
1500 sw.set_col_width(2, 12.5).unwrap();
1501 sw.write_row(1, &[CellValue::from("Wide"), CellValue::from("Narrow")])
1502 .unwrap();
1503 wb.apply_stream_writer(sw).unwrap();
1504 wb.save(&path).unwrap();
1505
1506 let wb2 = Workbook::open(&path).unwrap();
1507 let w1 = wb2.get_col_width("ColSheet", "A").unwrap().unwrap();
1508 let w2 = wb2.get_col_width("ColSheet", "B").unwrap().unwrap();
1509 assert!((w1 - 25.0).abs() < 0.01);
1510 assert!((w2 - 12.5).abs() < 0.01);
1511 }
1512
1513 #[test]
1514 fn test_stream_multiple_sheets() {
1515 let dir = TempDir::new().unwrap();
1516 let path = dir.path().join("stream_multi.xlsx");
1517
1518 let mut wb = Workbook::new();
1519 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1520
1521 let mut sw1 = wb.new_stream_writer("Stream1").unwrap();
1522 sw1.write_row(1, &[CellValue::from("S1R1")]).unwrap();
1523 sw1.write_row(2, &[CellValue::from("S1R2")]).unwrap();
1524 wb.apply_stream_writer(sw1).unwrap();
1525
1526 let mut sw2 = wb.new_stream_writer("Stream2").unwrap();
1527 sw2.write_row(1, &[CellValue::from("S2R1")]).unwrap();
1528 wb.apply_stream_writer(sw2).unwrap();
1529
1530 wb.save(&path).unwrap();
1531
1532 let wb2 = Workbook::open(&path).unwrap();
1533 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Stream1", "Stream2"]);
1534 assert_eq!(
1535 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1536 CellValue::String("Normal".to_string())
1537 );
1538 assert_eq!(
1539 wb2.get_cell_value("Stream1", "A1").unwrap(),
1540 CellValue::String("S1R1".to_string())
1541 );
1542 assert_eq!(
1543 wb2.get_cell_value("Stream1", "A2").unwrap(),
1544 CellValue::String("S1R2".to_string())
1545 );
1546 assert_eq!(
1547 wb2.get_cell_value("Stream2", "A1").unwrap(),
1548 CellValue::String("S2R1".to_string())
1549 );
1550 }
1551
1552 #[test]
1553 fn test_stream_delete_sheet() {
1554 let dir = TempDir::new().unwrap();
1555 let path = dir.path().join("stream_delete.xlsx");
1556
1557 let mut wb = Workbook::new();
1558 let mut sw = wb.new_stream_writer("ToDelete").unwrap();
1559 sw.write_row(1, &[CellValue::from("Gone")]).unwrap();
1560 wb.apply_stream_writer(sw).unwrap();
1561
1562 let mut sw2 = wb.new_stream_writer("Kept").unwrap();
1563 sw2.write_row(1, &[CellValue::from("Stays")]).unwrap();
1564 wb.apply_stream_writer(sw2).unwrap();
1565
1566 wb.delete_sheet("ToDelete").unwrap();
1567 wb.save(&path).unwrap();
1568
1569 let wb2 = Workbook::open(&path).unwrap();
1570 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Kept"]);
1571 assert_eq!(
1572 wb2.get_cell_value("Kept", "A1").unwrap(),
1573 CellValue::String("Stays".to_string())
1574 );
1575 }
1576
1577 #[test]
1578 fn test_stream_combined_features_roundtrip() {
1579 let dir = TempDir::new().unwrap();
1580 let path = dir.path().join("stream_combined.xlsx");
1581
1582 let mut wb = Workbook::new();
1583 let mut sw = wb.new_stream_writer("Combined").unwrap();
1584 sw.set_freeze_panes("A2").unwrap();
1585 sw.set_col_width(1, 30.0).unwrap();
1586 sw.set_col_width_range(2, 3, 15.0).unwrap();
1587 sw.add_merge_cell("B1:C1").unwrap();
1588 sw.write_row(
1589 1,
1590 &[
1591 CellValue::from("Name"),
1592 CellValue::from("Merged Header"),
1593 CellValue::Empty,
1594 ],
1595 )
1596 .unwrap();
1597 sw.write_row(
1598 2,
1599 &[
1600 CellValue::from("Alice"),
1601 CellValue::from(100),
1602 CellValue::from(true),
1603 ],
1604 )
1605 .unwrap();
1606 wb.apply_stream_writer(sw).unwrap();
1607 wb.save(&path).unwrap();
1608
1609 let wb2 = Workbook::open(&path).unwrap();
1610 assert_eq!(wb2.get_panes("Combined").unwrap(), Some("A2".to_string()));
1611 let merges = wb2.get_merge_cells("Combined").unwrap();
1612 assert!(merges.contains(&"B1:C1".to_string()));
1613 let w1 = wb2.get_col_width("Combined", "A").unwrap().unwrap();
1614 assert!((w1 - 30.0).abs() < 0.01);
1615 assert_eq!(
1616 wb2.get_cell_value("Combined", "A1").unwrap(),
1617 CellValue::String("Name".to_string())
1618 );
1619 assert_eq!(
1620 wb2.get_cell_value("Combined", "B2").unwrap(),
1621 CellValue::Number(100.0)
1622 );
1623 assert_eq!(
1624 wb2.get_cell_value("Combined", "C2").unwrap(),
1625 CellValue::Bool(true)
1626 );
1627 }
1628
1629 #[test]
1632 fn test_stream_formula_result_types_roundtrip() {
1633 let dir = TempDir::new().unwrap();
1637 let path = dir.path().join("stream_formula_types.xlsx");
1638
1639 let mut wb = Workbook::new();
1640 let mut sw = wb.new_stream_writer("Formulas").unwrap();
1641 sw.write_row(
1642 1,
1643 &[
1644 CellValue::Formula {
1645 expr: "A2&B2".to_string(),
1646 result: Some(Box::new(CellValue::String("hello".to_string()))),
1647 },
1648 CellValue::Formula {
1649 expr: "A2>0".to_string(),
1650 result: Some(Box::new(CellValue::Bool(true))),
1651 },
1652 CellValue::Formula {
1653 expr: "1/0".to_string(),
1654 result: Some(Box::new(CellValue::Error("#DIV/0!".to_string()))),
1655 },
1656 CellValue::Formula {
1657 expr: "SUM(A2:A10)".to_string(),
1658 result: Some(Box::new(CellValue::Number(55.0))),
1659 },
1660 ],
1661 )
1662 .unwrap();
1663 wb.apply_stream_writer(sw).unwrap();
1664 wb.save(&path).unwrap();
1665
1666 let wb2 = Workbook::open(&path).unwrap();
1667 assert_eq!(
1669 wb2.get_cell_value("Formulas", "A1").unwrap(),
1670 CellValue::Formula {
1671 expr: "A2&B2".to_string(),
1672 result: Some(Box::new(CellValue::String("hello".to_string()))),
1673 }
1674 );
1675 assert_eq!(
1677 wb2.get_cell_value("Formulas", "B1").unwrap(),
1678 CellValue::Formula {
1679 expr: "A2>0".to_string(),
1680 result: Some(Box::new(CellValue::Bool(true))),
1681 }
1682 );
1683 assert_eq!(
1685 wb2.get_cell_value("Formulas", "C1").unwrap(),
1686 CellValue::Formula {
1687 expr: "1/0".to_string(),
1688 result: Some(Box::new(CellValue::Error("#DIV/0!".to_string()))),
1689 }
1690 );
1691 assert_eq!(
1693 wb2.get_cell_value("Formulas", "D1").unwrap(),
1694 CellValue::Formula {
1695 expr: "SUM(A2:A10)".to_string(),
1696 result: Some(Box::new(CellValue::Number(55.0))),
1697 }
1698 );
1699 }
1700
1701 #[test]
1702 fn test_stream_edit_after_apply_takes_effect() {
1703 let dir = TempDir::new().unwrap();
1707 let path = dir.path().join("stream_edit_after.xlsx");
1708
1709 let mut wb = Workbook::new();
1710 let mut sw = wb.new_stream_writer("S").unwrap();
1711 sw.write_row(1, &[CellValue::from("old")]).unwrap();
1712 wb.apply_stream_writer(sw).unwrap();
1713
1714 wb.set_cell_value("S", "A1", "new").unwrap();
1716 wb.save(&path).unwrap();
1717
1718 let wb2 = Workbook::open(&path).unwrap();
1719 assert_eq!(
1720 wb2.get_cell_value("S", "A1").unwrap(),
1721 CellValue::String("new".to_string())
1722 );
1723 }
1724
1725 #[test]
1726 fn test_stream_copy_sheet_preserves_data() {
1727 let dir = TempDir::new().unwrap();
1730 let path = dir.path().join("stream_copy.xlsx");
1731
1732 let mut wb = Workbook::new();
1733 let mut sw = wb.new_stream_writer("Src").unwrap();
1734 sw.write_row(1, &[CellValue::from("x")]).unwrap();
1735 sw.write_row(2, &[CellValue::from("y")]).unwrap();
1736 wb.apply_stream_writer(sw).unwrap();
1737
1738 wb.copy_sheet("Src", "Dst").unwrap();
1739 wb.save(&path).unwrap();
1740
1741 let wb2 = Workbook::open(&path).unwrap();
1742 assert_eq!(
1743 wb2.get_cell_value("Src", "A1").unwrap(),
1744 CellValue::String("x".to_string())
1745 );
1746 assert_eq!(
1747 wb2.get_cell_value("Src", "A2").unwrap(),
1748 CellValue::String("y".to_string())
1749 );
1750 assert_eq!(
1751 wb2.get_cell_value("Dst", "A1").unwrap(),
1752 CellValue::String("x".to_string())
1753 );
1754 assert_eq!(
1755 wb2.get_cell_value("Dst", "A2").unwrap(),
1756 CellValue::String("y".to_string())
1757 );
1758 }
1759}