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 self.rebuild_sheet_index();
32 Ok(idx)
33 }
34
35 pub fn delete_sheet(&mut self, name: &str) -> Result<()> {
37 let idx = self.sheet_index(name)?;
38 crate::sheet::delete_sheet(
39 &mut self.workbook_xml,
40 &mut self.workbook_rels,
41 &mut self.content_types,
42 &mut self.worksheets,
43 name,
44 )?;
45
46 if idx < self.sheet_comments.len() {
47 self.sheet_comments.remove(idx);
48 }
49 if idx < self.sheet_sparklines.len() {
50 self.sheet_sparklines.remove(idx);
51 }
52 if idx < self.sheet_vml.len() {
53 self.sheet_vml.remove(idx);
54 }
55 self.reindex_sheet_maps_after_delete(idx);
56 self.rebuild_sheet_index();
57 Ok(())
58 }
59
60 pub fn set_sheet_name(&mut self, old_name: &str, new_name: &str) -> Result<()> {
62 crate::sheet::rename_sheet(
63 &mut self.workbook_xml,
64 &mut self.worksheets,
65 old_name,
66 new_name,
67 )?;
68 self.rebuild_sheet_index();
69 Ok(())
70 }
71
72 pub fn copy_sheet(&mut self, source: &str, target: &str) -> Result<usize> {
74 let idx = crate::sheet::copy_sheet(
75 &mut self.workbook_xml,
76 &mut self.workbook_rels,
77 &mut self.content_types,
78 &mut self.worksheets,
79 source,
80 target,
81 )?;
82 if self.sheet_comments.len() < self.worksheets.len() {
83 self.sheet_comments.push(None);
84 }
85 let source_sparklines = {
86 let src_idx = self.sheet_index(source).unwrap_or(0);
87 self.sheet_sparklines
88 .get(src_idx)
89 .cloned()
90 .unwrap_or_default()
91 };
92 if self.sheet_sparklines.len() < self.worksheets.len() {
93 self.sheet_sparklines.push(source_sparklines);
94 }
95 if self.sheet_vml.len() < self.worksheets.len() {
96 self.sheet_vml.push(None);
97 }
98 self.rebuild_sheet_index();
99 Ok(idx)
100 }
101
102 pub fn get_sheet_index(&self, name: &str) -> Option<usize> {
104 crate::sheet::find_sheet_index(&self.worksheets, name)
105 }
106
107 pub fn get_active_sheet(&self) -> &str {
109 let idx = crate::sheet::active_sheet_index(&self.workbook_xml);
110 self.worksheets
111 .get(idx)
112 .map(|(n, _)| n.as_str())
113 .unwrap_or_else(|| self.worksheets[0].0.as_str())
114 }
115
116 pub fn set_active_sheet(&mut self, name: &str) -> Result<()> {
118 let idx = crate::sheet::find_sheet_index(&self.worksheets, name).ok_or_else(|| {
119 Error::SheetNotFound {
120 name: name.to_string(),
121 }
122 })?;
123 crate::sheet::set_active_sheet_index(&mut self.workbook_xml, idx as u32);
124 Ok(())
125 }
126
127 pub fn new_stream_writer(&self, sheet_name: &str) -> Result<crate::stream::StreamWriter> {
132 crate::sheet::validate_sheet_name(sheet_name)?;
133 if self.worksheets.iter().any(|(n, _)| n == sheet_name) {
134 return Err(Error::SheetAlreadyExists {
135 name: sheet_name.to_string(),
136 });
137 }
138 Ok(crate::stream::StreamWriter::new(sheet_name))
139 }
140
141 pub fn apply_stream_writer(&mut self, writer: crate::stream::StreamWriter) -> Result<usize> {
146 let sheet_name = writer.sheet_name().to_string();
147 let (mut ws, sst) = writer.into_worksheet_parts()?;
148
149 let mut sst_remap: Vec<usize> = Vec::with_capacity(sst.len());
151 for i in 0..sst.len() {
152 if let Some(s) = sst.get(i) {
153 let new_idx = self.sst_runtime.add(s);
154 sst_remap.push(new_idx);
155 }
156 }
157
158 for row in &mut ws.sheet_data.rows {
160 for cell in &mut row.cells {
161 if cell.t == CellTypeTag::SharedString {
162 if let Some(ref v) = cell.v {
163 if let Ok(old_idx) = v.parse::<usize>() {
164 if let Some(&new_idx) = sst_remap.get(old_idx) {
165 cell.v = Some(new_idx.to_string());
166 }
167 }
168 }
169 }
170 }
171 }
172
173 let idx = crate::sheet::add_sheet(
177 &mut self.workbook_xml,
178 &mut self.workbook_rels,
179 &mut self.content_types,
180 &mut self.worksheets,
181 &sheet_name,
182 ws,
183 )?;
184 if self.sheet_comments.len() < self.worksheets.len() {
185 self.sheet_comments.push(None);
186 }
187 if self.sheet_sparklines.len() < self.worksheets.len() {
188 self.sheet_sparklines.push(vec![]);
189 }
190 if self.sheet_vml.len() < self.worksheets.len() {
191 self.sheet_vml.push(None);
192 }
193 self.rebuild_sheet_index();
194 Ok(idx)
195 }
196
197 pub fn insert_rows(&mut self, sheet: &str, start_row: u32, count: u32) -> Result<()> {
199 let sheet_idx = self.sheet_index(sheet)?;
200 {
201 let ws = &mut self.worksheets[sheet_idx].1;
202 crate::row::insert_rows(ws, start_row, count)?;
203 }
204 self.apply_reference_shift_for_sheet(sheet_idx, |col, row| {
205 if row >= start_row {
206 (col, row + count)
207 } else {
208 (col, row)
209 }
210 })
211 }
212
213 pub fn remove_row(&mut self, sheet: &str, row: u32) -> Result<()> {
215 let sheet_idx = self.sheet_index(sheet)?;
216 {
217 let ws = &mut self.worksheets[sheet_idx].1;
218 crate::row::remove_row(ws, row)?;
219 }
220 self.apply_reference_shift_for_sheet(sheet_idx, |col, r| {
221 if r > row {
222 (col, r - 1)
223 } else {
224 (col, r)
225 }
226 })
227 }
228
229 pub fn duplicate_row(&mut self, sheet: &str, row: u32) -> Result<()> {
231 let ws = self.worksheet_mut(sheet)?;
232 crate::row::duplicate_row(ws, row)
233 }
234
235 pub fn set_row_height(&mut self, sheet: &str, row: u32, height: f64) -> Result<()> {
237 let ws = self.worksheet_mut(sheet)?;
238 crate::row::set_row_height(ws, row, height)
239 }
240
241 pub fn get_row_height(&self, sheet: &str, row: u32) -> Result<Option<f64>> {
243 let ws = self.worksheet_ref(sheet)?;
244 Ok(crate::row::get_row_height(ws, row))
245 }
246
247 pub fn set_row_visible(&mut self, sheet: &str, row: u32, visible: bool) -> Result<()> {
249 let ws = self.worksheet_mut(sheet)?;
250 crate::row::set_row_visible(ws, row, visible)
251 }
252
253 pub fn get_row_visible(&self, sheet: &str, row: u32) -> Result<bool> {
255 let ws = self.worksheet_ref(sheet)?;
256 Ok(crate::row::get_row_visible(ws, row))
257 }
258
259 pub fn set_row_outline_level(&mut self, sheet: &str, row: u32, level: u8) -> Result<()> {
261 let ws = self.worksheet_mut(sheet)?;
262 crate::row::set_row_outline_level(ws, row, level)
263 }
264
265 pub fn get_row_outline_level(&self, sheet: &str, row: u32) -> Result<u8> {
267 let ws = self.worksheet_ref(sheet)?;
268 Ok(crate::row::get_row_outline_level(ws, row))
269 }
270
271 pub fn set_row_style(&mut self, sheet: &str, row: u32, style_id: u32) -> Result<()> {
275 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
276 return Err(Error::StyleNotFound { id: style_id });
277 }
278 let ws = self.worksheet_mut(sheet)?;
279 crate::row::set_row_style(ws, row, style_id)
280 }
281
282 pub fn get_row_style(&self, sheet: &str, row: u32) -> Result<u32> {
284 let ws = self.worksheet_ref(sheet)?;
285 Ok(crate::row::get_row_style(ws, row))
286 }
287
288 #[allow(clippy::type_complexity)]
294 pub fn get_rows(&self, sheet: &str) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
295 let ws = self.worksheet_ref(sheet)?;
296 crate::row::get_rows(ws, &self.sst_runtime)
297 }
298
299 #[allow(clippy::type_complexity)]
304 pub fn get_cols(&self, sheet: &str) -> Result<Vec<(String, Vec<(u32, CellValue)>)>> {
305 let ws = self.worksheet_ref(sheet)?;
306 crate::col::get_cols(ws, &self.sst_runtime)
307 }
308
309 pub fn set_col_width(&mut self, sheet: &str, col: &str, width: f64) -> Result<()> {
311 let ws = self.worksheet_mut(sheet)?;
312 crate::col::set_col_width(ws, col, width)
313 }
314
315 pub fn get_col_width(&self, sheet: &str, col: &str) -> Result<Option<f64>> {
317 let ws = self.worksheet_ref(sheet)?;
318 Ok(crate::col::get_col_width(ws, col))
319 }
320
321 pub fn set_col_visible(&mut self, sheet: &str, col: &str, visible: bool) -> Result<()> {
323 let ws = self.worksheet_mut(sheet)?;
324 crate::col::set_col_visible(ws, col, visible)
325 }
326
327 pub fn get_col_visible(&self, sheet: &str, col: &str) -> Result<bool> {
329 let ws = self.worksheet_ref(sheet)?;
330 crate::col::get_col_visible(ws, col)
331 }
332
333 pub fn set_col_outline_level(&mut self, sheet: &str, col: &str, level: u8) -> Result<()> {
335 let ws = self.worksheet_mut(sheet)?;
336 crate::col::set_col_outline_level(ws, col, level)
337 }
338
339 pub fn get_col_outline_level(&self, sheet: &str, col: &str) -> Result<u8> {
341 let ws = self.worksheet_ref(sheet)?;
342 crate::col::get_col_outline_level(ws, col)
343 }
344
345 pub fn set_col_style(&mut self, sheet: &str, col: &str, style_id: u32) -> Result<()> {
349 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
350 return Err(Error::StyleNotFound { id: style_id });
351 }
352 let ws = self.worksheet_mut(sheet)?;
353 crate::col::set_col_style(ws, col, style_id)
354 }
355
356 pub fn get_col_style(&self, sheet: &str, col: &str) -> Result<u32> {
358 let ws = self.worksheet_ref(sheet)?;
359 crate::col::get_col_style(ws, col)
360 }
361
362 pub fn insert_cols(&mut self, sheet: &str, col: &str, count: u32) -> Result<()> {
364 let sheet_idx = self.sheet_index(sheet)?;
365 let start_col = column_name_to_number(col)?;
366 {
367 let ws = &mut self.worksheets[sheet_idx].1;
368 crate::col::insert_cols(ws, col, count)?;
369 }
370 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
371 if c >= start_col {
372 (c + count, row)
373 } else {
374 (c, row)
375 }
376 })
377 }
378
379 pub fn remove_col(&mut self, sheet: &str, col: &str) -> Result<()> {
381 let sheet_idx = self.sheet_index(sheet)?;
382 let col_num = column_name_to_number(col)?;
383 {
384 let ws = &mut self.worksheets[sheet_idx].1;
385 crate::col::remove_col(ws, col)?;
386 }
387 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
388 if c > col_num {
389 (c - 1, row)
390 } else {
391 (c, row)
392 }
393 })
394 }
395
396 pub(crate) fn reindex_sheet_maps_after_delete(&mut self, removed_idx: usize) {
398 self.worksheet_rels = self
399 .worksheet_rels
400 .iter()
401 .filter_map(|(idx, rels)| {
402 if *idx == removed_idx {
403 None
404 } else if *idx > removed_idx {
405 Some((idx - 1, rels.clone()))
406 } else {
407 Some((*idx, rels.clone()))
408 }
409 })
410 .collect();
411
412 self.worksheet_drawings = self
413 .worksheet_drawings
414 .iter()
415 .filter_map(|(idx, drawing_idx)| {
416 if *idx == removed_idx {
417 None
418 } else if *idx > removed_idx {
419 Some((idx - 1, *drawing_idx))
420 } else {
421 Some((*idx, *drawing_idx))
422 }
423 })
424 .collect();
425 }
426
427 pub(crate) fn apply_reference_shift_for_sheet<F>(
429 &mut self,
430 sheet_idx: usize,
431 shift_cell: F,
432 ) -> Result<()>
433 where
434 F: Fn(u32, u32) -> (u32, u32) + Copy,
435 {
436 {
437 let ws = &mut self.worksheets[sheet_idx].1;
438
439 for row in &mut ws.sheet_data.rows {
441 for cell in &mut row.cells {
442 if let Some(ref mut f) = cell.f {
443 if let Some(ref mut expr) = f.value {
444 *expr = shift_cell_references_in_text(expr, shift_cell)?;
445 }
446 }
447 }
448 }
449
450 if let Some(ref mut merges) = ws.merge_cells {
452 for mc in &mut merges.merge_cells {
453 mc.reference = shift_cell_references_in_text(&mc.reference, shift_cell)?;
454 }
455 }
456
457 if let Some(ref mut af) = ws.auto_filter {
459 af.reference = shift_cell_references_in_text(&af.reference, shift_cell)?;
460 }
461
462 if let Some(ref mut dvs) = ws.data_validations {
464 for dv in &mut dvs.data_validations {
465 dv.sqref = shift_cell_references_in_text(&dv.sqref, shift_cell)?;
466 if let Some(ref mut f1) = dv.formula1 {
467 *f1 = shift_cell_references_in_text(f1, shift_cell)?;
468 }
469 if let Some(ref mut f2) = dv.formula2 {
470 *f2 = shift_cell_references_in_text(f2, shift_cell)?;
471 }
472 }
473 }
474
475 for cf in &mut ws.conditional_formatting {
477 cf.sqref = shift_cell_references_in_text(&cf.sqref, shift_cell)?;
478 for rule in &mut cf.cf_rules {
479 for f in &mut rule.formulas {
480 *f = shift_cell_references_in_text(f, shift_cell)?;
481 }
482 }
483 }
484
485 if let Some(ref mut hyperlinks) = ws.hyperlinks {
487 for hl in &mut hyperlinks.hyperlinks {
488 hl.reference = shift_cell_references_in_text(&hl.reference, shift_cell)?;
489 if let Some(ref mut loc) = hl.location {
490 *loc = shift_cell_references_in_text(loc, shift_cell)?;
491 }
492 }
493 }
494
495 if let Some(ref mut views) = ws.sheet_views {
497 for view in &mut views.sheet_views {
498 if let Some(ref mut pane) = view.pane {
499 if let Some(ref mut top_left) = pane.top_left_cell {
500 *top_left = shift_cell_references_in_text(top_left, shift_cell)?;
501 }
502 }
503 for sel in &mut view.selection {
504 if let Some(ref mut ac) = sel.active_cell {
505 *ac = shift_cell_references_in_text(ac, shift_cell)?;
506 }
507 if let Some(ref mut sqref) = sel.sqref {
508 *sqref = shift_cell_references_in_text(sqref, shift_cell)?;
509 }
510 }
511 }
512 }
513 }
514
515 if let Some(&drawing_idx) = self.worksheet_drawings.get(&sheet_idx) {
517 if let Some((_, drawing)) = self.drawings.get_mut(drawing_idx) {
518 for anchor in &mut drawing.one_cell_anchors {
519 let (new_col, new_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
520 anchor.from.col = new_col - 1;
521 anchor.from.row = new_row - 1;
522 }
523 for anchor in &mut drawing.two_cell_anchors {
524 let (from_col, from_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
525 anchor.from.col = from_col - 1;
526 anchor.from.row = from_row - 1;
527 let (to_col, to_row) = shift_cell(anchor.to.col + 1, anchor.to.row + 1);
528 anchor.to.col = to_col - 1;
529 anchor.to.row = to_row - 1;
530 }
531 }
532 }
533
534 Ok(())
535 }
536
537 pub(crate) fn ensure_drawing_for_sheet(&mut self, sheet_idx: usize) -> usize {
540 if let Some(&idx) = self.worksheet_drawings.get(&sheet_idx) {
541 return idx;
542 }
543
544 let idx = self.drawings.len();
545 let drawing_path = format!("xl/drawings/drawing{}.xml", idx + 1);
546 self.drawings.push((drawing_path, WsDr::default()));
547 self.worksheet_drawings.insert(sheet_idx, idx);
548
549 let ws_rid = self.next_worksheet_rid(sheet_idx);
551 self.worksheets[sheet_idx].1.drawing = Some(DrawingRef {
552 r_id: ws_rid.clone(),
553 });
554
555 let drawing_rel_target = format!("../drawings/drawing{}.xml", idx + 1);
557 let ws_rels = self
558 .worksheet_rels
559 .entry(sheet_idx)
560 .or_insert_with(|| Relationships {
561 xmlns: sheetkit_xml::namespaces::PACKAGE_RELATIONSHIPS.to_string(),
562 relationships: vec![],
563 });
564 ws_rels.relationships.push(Relationship {
565 id: ws_rid,
566 rel_type: rel_types::DRAWING.to_string(),
567 target: drawing_rel_target,
568 target_mode: None,
569 });
570
571 self.content_types.overrides.push(ContentTypeOverride {
573 part_name: format!("/xl/drawings/drawing{}.xml", idx + 1),
574 content_type: mime_types::DRAWING.to_string(),
575 });
576
577 idx
578 }
579
580 pub(crate) fn next_worksheet_rid(&self, sheet_idx: usize) -> String {
582 let existing = self
583 .worksheet_rels
584 .get(&sheet_idx)
585 .map(|r| r.relationships.as_slice())
586 .unwrap_or(&[]);
587 crate::sheet::next_rid(existing)
588 }
589
590 pub(crate) fn next_drawing_rid(&self, drawing_idx: usize) -> String {
592 let existing = self
593 .drawing_rels
594 .get(&drawing_idx)
595 .map(|r| r.relationships.as_slice())
596 .unwrap_or(&[]);
597 crate::sheet::next_rid(existing)
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use tempfile::TempDir;
605
606 #[test]
607 fn test_new_sheet_basic() {
608 let mut wb = Workbook::new();
609 let idx = wb.new_sheet("Sheet2").unwrap();
610 assert_eq!(idx, 1);
611 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet2"]);
612 }
613
614 #[test]
615 fn test_new_sheet_duplicate_returns_error() {
616 let mut wb = Workbook::new();
617 let result = wb.new_sheet("Sheet1");
618 assert!(result.is_err());
619 assert!(matches!(
620 result.unwrap_err(),
621 Error::SheetAlreadyExists { .. }
622 ));
623 }
624
625 #[test]
626 fn test_new_sheet_invalid_name_returns_error() {
627 let mut wb = Workbook::new();
628 let result = wb.new_sheet("Bad/Name");
629 assert!(result.is_err());
630 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
631 }
632
633 #[test]
634 fn test_delete_sheet_basic() {
635 let mut wb = Workbook::new();
636 wb.new_sheet("Sheet2").unwrap();
637 wb.delete_sheet("Sheet1").unwrap();
638 assert_eq!(wb.sheet_names(), vec!["Sheet2"]);
639 }
640
641 #[test]
642 fn test_delete_last_sheet_returns_error() {
643 let mut wb = Workbook::new();
644 let result = wb.delete_sheet("Sheet1");
645 assert!(result.is_err());
646 }
647
648 #[test]
649 fn test_delete_nonexistent_sheet_returns_error() {
650 let mut wb = Workbook::new();
651 let result = wb.delete_sheet("NoSuchSheet");
652 assert!(result.is_err());
653 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
654 }
655
656 #[test]
657 fn test_set_sheet_name_basic() {
658 let mut wb = Workbook::new();
659 wb.set_sheet_name("Sheet1", "Renamed").unwrap();
660 assert_eq!(wb.sheet_names(), vec!["Renamed"]);
661 }
662
663 #[test]
664 fn test_set_sheet_name_to_existing_returns_error() {
665 let mut wb = Workbook::new();
666 wb.new_sheet("Sheet2").unwrap();
667 let result = wb.set_sheet_name("Sheet1", "Sheet2");
668 assert!(result.is_err());
669 assert!(matches!(
670 result.unwrap_err(),
671 Error::SheetAlreadyExists { .. }
672 ));
673 }
674
675 #[test]
676 fn test_copy_sheet_basic() {
677 let mut wb = Workbook::new();
678 let idx = wb.copy_sheet("Sheet1", "Sheet1 Copy").unwrap();
679 assert_eq!(idx, 1);
680 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet1 Copy"]);
681 }
682
683 #[test]
684 fn test_get_sheet_index() {
685 let mut wb = Workbook::new();
686 wb.new_sheet("Sheet2").unwrap();
687 assert_eq!(wb.get_sheet_index("Sheet1"), Some(0));
688 assert_eq!(wb.get_sheet_index("Sheet2"), Some(1));
689 assert_eq!(wb.get_sheet_index("Nonexistent"), None);
690 }
691
692 #[test]
693 fn test_get_active_sheet_default() {
694 let wb = Workbook::new();
695 assert_eq!(wb.get_active_sheet(), "Sheet1");
696 }
697
698 #[test]
699 fn test_set_active_sheet() {
700 let mut wb = Workbook::new();
701 wb.new_sheet("Sheet2").unwrap();
702 wb.set_active_sheet("Sheet2").unwrap();
703 assert_eq!(wb.get_active_sheet(), "Sheet2");
704 }
705
706 #[test]
707 fn test_set_active_sheet_not_found() {
708 let mut wb = Workbook::new();
709 let result = wb.set_active_sheet("NoSuchSheet");
710 assert!(result.is_err());
711 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
712 }
713
714 #[test]
715 fn test_sheet_management_roundtrip_save_open() {
716 let dir = TempDir::new().unwrap();
717 let path = dir.path().join("sheet_mgmt.xlsx");
718
719 let mut wb = Workbook::new();
720 wb.new_sheet("Data").unwrap();
721 wb.new_sheet("Summary").unwrap();
722 wb.set_sheet_name("Sheet1", "Overview").unwrap();
723 wb.save(&path).unwrap();
724
725 let wb2 = Workbook::open(&path).unwrap();
726 assert_eq!(wb2.sheet_names(), vec!["Overview", "Data", "Summary"]);
727 }
728
729 #[test]
730 fn test_workbook_insert_rows() {
731 let mut wb = Workbook::new();
732 wb.set_cell_value("Sheet1", "A1", "stay").unwrap();
733 wb.set_cell_value("Sheet1", "A2", "shift").unwrap();
734 wb.insert_rows("Sheet1", 2, 1).unwrap();
735
736 assert_eq!(
737 wb.get_cell_value("Sheet1", "A1").unwrap(),
738 CellValue::String("stay".to_string())
739 );
740 assert_eq!(
741 wb.get_cell_value("Sheet1", "A3").unwrap(),
742 CellValue::String("shift".to_string())
743 );
744 assert_eq!(wb.get_cell_value("Sheet1", "A2").unwrap(), CellValue::Empty);
745 }
746
747 #[test]
748 fn test_workbook_insert_rows_updates_formula_and_ranges() {
749 let mut wb = Workbook::new();
750 wb.set_cell_value(
751 "Sheet1",
752 "C1",
753 CellValue::Formula {
754 expr: "SUM(A2:B2)".to_string(),
755 result: None,
756 },
757 )
758 .unwrap();
759 wb.add_data_validation(
760 "Sheet1",
761 &crate::validation::DataValidationConfig::whole_number("A2:A5", 1, 9),
762 )
763 .unwrap();
764 wb.set_auto_filter("Sheet1", "A2:B10").unwrap();
765 wb.merge_cells("Sheet1", "A2", "B3").unwrap();
766
767 wb.insert_rows("Sheet1", 2, 1).unwrap();
768
769 match wb.get_cell_value("Sheet1", "C1").unwrap() {
770 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:B3)"),
771 other => panic!("expected formula, got {other:?}"),
772 }
773
774 let validations = wb.get_data_validations("Sheet1").unwrap();
775 assert_eq!(validations.len(), 1);
776 assert_eq!(validations[0].sqref, "A3:A6");
777
778 let merges = wb.get_merge_cells("Sheet1").unwrap();
779 assert_eq!(merges, vec!["A3:B4".to_string()]);
780
781 let ws = wb.worksheet_ref("Sheet1").unwrap();
782 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A3:B11");
783 }
784
785 #[test]
786 fn test_workbook_insert_rows_sheet_not_found() {
787 let mut wb = Workbook::new();
788 let result = wb.insert_rows("NoSheet", 1, 1);
789 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
790 }
791
792 #[test]
793 fn test_workbook_remove_row() {
794 let mut wb = Workbook::new();
795 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
796 wb.set_cell_value("Sheet1", "A2", "second").unwrap();
797 wb.set_cell_value("Sheet1", "A3", "third").unwrap();
798 wb.remove_row("Sheet1", 2).unwrap();
799
800 assert_eq!(
801 wb.get_cell_value("Sheet1", "A1").unwrap(),
802 CellValue::String("first".to_string())
803 );
804 assert_eq!(
805 wb.get_cell_value("Sheet1", "A2").unwrap(),
806 CellValue::String("third".to_string())
807 );
808 }
809
810 #[test]
811 fn test_workbook_remove_row_sheet_not_found() {
812 let mut wb = Workbook::new();
813 let result = wb.remove_row("NoSheet", 1);
814 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
815 }
816
817 #[test]
818 fn test_workbook_duplicate_row() {
819 let mut wb = Workbook::new();
820 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
821 wb.duplicate_row("Sheet1", 1).unwrap();
822
823 assert_eq!(
824 wb.get_cell_value("Sheet1", "A1").unwrap(),
825 CellValue::String("original".to_string())
826 );
827 assert_eq!(
829 wb.get_cell_value("Sheet1", "A2").unwrap(),
830 CellValue::String("original".to_string())
831 );
832 }
833
834 #[test]
835 fn test_workbook_set_and_get_row_height() {
836 let mut wb = Workbook::new();
837 wb.set_row_height("Sheet1", 3, 25.0).unwrap();
838 assert_eq!(wb.get_row_height("Sheet1", 3).unwrap(), Some(25.0));
839 }
840
841 #[test]
842 fn test_workbook_get_row_height_sheet_not_found() {
843 let wb = Workbook::new();
844 let result = wb.get_row_height("NoSheet", 1);
845 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
846 }
847
848 #[test]
849 fn test_workbook_set_row_visible() {
850 let mut wb = Workbook::new();
851 wb.set_row_visible("Sheet1", 1, false).unwrap();
852 }
853
854 #[test]
855 fn test_workbook_set_row_visible_sheet_not_found() {
856 let mut wb = Workbook::new();
857 let result = wb.set_row_visible("NoSheet", 1, false);
858 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
859 }
860
861 #[test]
862 fn test_workbook_set_and_get_col_width() {
863 let mut wb = Workbook::new();
864 wb.set_col_width("Sheet1", "A", 18.0).unwrap();
865 assert_eq!(wb.get_col_width("Sheet1", "A").unwrap(), Some(18.0));
866 }
867
868 #[test]
869 fn test_workbook_get_col_width_sheet_not_found() {
870 let wb = Workbook::new();
871 let result = wb.get_col_width("NoSheet", "A");
872 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
873 }
874
875 #[test]
876 fn test_workbook_set_col_visible() {
877 let mut wb = Workbook::new();
878 wb.set_col_visible("Sheet1", "B", false).unwrap();
879 }
880
881 #[test]
882 fn test_workbook_set_col_visible_sheet_not_found() {
883 let mut wb = Workbook::new();
884 let result = wb.set_col_visible("NoSheet", "A", false);
885 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
886 }
887
888 #[test]
889 fn test_workbook_insert_cols() {
890 let mut wb = Workbook::new();
891 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
892 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
893 wb.insert_cols("Sheet1", "B", 1).unwrap();
894
895 assert_eq!(
896 wb.get_cell_value("Sheet1", "A1").unwrap(),
897 CellValue::String("a".to_string())
898 );
899 assert_eq!(wb.get_cell_value("Sheet1", "B1").unwrap(), CellValue::Empty);
900 assert_eq!(
901 wb.get_cell_value("Sheet1", "C1").unwrap(),
902 CellValue::String("b".to_string())
903 );
904 }
905
906 #[test]
907 fn test_workbook_insert_cols_updates_formula_and_ranges() {
908 let mut wb = Workbook::new();
909 wb.set_cell_value(
910 "Sheet1",
911 "D1",
912 CellValue::Formula {
913 expr: "SUM(A1:B1)".to_string(),
914 result: None,
915 },
916 )
917 .unwrap();
918 wb.add_data_validation(
919 "Sheet1",
920 &crate::validation::DataValidationConfig::whole_number("B2:C3", 1, 9),
921 )
922 .unwrap();
923 wb.set_auto_filter("Sheet1", "A1:C10").unwrap();
924 wb.merge_cells("Sheet1", "B3", "C4").unwrap();
925
926 wb.insert_cols("Sheet1", "B", 2).unwrap();
927
928 match wb.get_cell_value("Sheet1", "F1").unwrap() {
929 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A1:D1)"),
930 other => panic!("expected formula, got {other:?}"),
931 }
932
933 let validations = wb.get_data_validations("Sheet1").unwrap();
934 assert_eq!(validations.len(), 1);
935 assert_eq!(validations[0].sqref, "D2:E3");
936
937 let merges = wb.get_merge_cells("Sheet1").unwrap();
938 assert_eq!(merges, vec!["D3:E4".to_string()]);
939
940 let ws = wb.worksheet_ref("Sheet1").unwrap();
941 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A1:E10");
942 }
943
944 #[test]
945 fn test_workbook_insert_cols_sheet_not_found() {
946 let mut wb = Workbook::new();
947 let result = wb.insert_cols("NoSheet", "A", 1);
948 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
949 }
950
951 #[test]
952 fn test_workbook_remove_col() {
953 let mut wb = Workbook::new();
954 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
955 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
956 wb.set_cell_value("Sheet1", "C1", "c").unwrap();
957 wb.remove_col("Sheet1", "B").unwrap();
958
959 assert_eq!(
960 wb.get_cell_value("Sheet1", "A1").unwrap(),
961 CellValue::String("a".to_string())
962 );
963 assert_eq!(
964 wb.get_cell_value("Sheet1", "B1").unwrap(),
965 CellValue::String("c".to_string())
966 );
967 }
968
969 #[test]
970 fn test_workbook_remove_col_sheet_not_found() {
971 let mut wb = Workbook::new();
972 let result = wb.remove_col("NoSheet", "A");
973 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
974 }
975
976 #[test]
977 fn test_new_stream_writer_validates_name() {
978 let wb = Workbook::new();
979 let result = wb.new_stream_writer("Bad[Name");
980 assert!(result.is_err());
981 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
982 }
983
984 #[test]
985 fn test_new_stream_writer_rejects_duplicate() {
986 let wb = Workbook::new();
987 let result = wb.new_stream_writer("Sheet1");
988 assert!(result.is_err());
989 assert!(matches!(
990 result.unwrap_err(),
991 Error::SheetAlreadyExists { .. }
992 ));
993 }
994
995 #[test]
996 fn test_new_stream_writer_valid_name() {
997 let wb = Workbook::new();
998 let sw = wb.new_stream_writer("StreamSheet").unwrap();
999 assert_eq!(sw.sheet_name(), "StreamSheet");
1000 }
1001
1002 #[test]
1003 fn test_apply_stream_writer_adds_sheet() {
1004 let mut wb = Workbook::new();
1005 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1006 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1007 .unwrap();
1008 let idx = wb.apply_stream_writer(sw).unwrap();
1009 assert_eq!(idx, 1);
1010 assert_eq!(wb.sheet_names(), vec!["Sheet1", "StreamSheet"]);
1011 }
1012
1013 #[test]
1014 fn test_apply_stream_writer_merges_sst() {
1015 let mut wb = Workbook::new();
1016 wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1017
1018 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1019 sw.write_row(1, &[CellValue::from("New"), CellValue::from("Existing")])
1020 .unwrap();
1021 wb.apply_stream_writer(sw).unwrap();
1022
1023 assert!(wb.sst_runtime.len() >= 2);
1024 }
1025
1026 #[test]
1027 fn test_stream_writer_save_and_reopen() {
1028 let dir = TempDir::new().unwrap();
1029 let path = dir.path().join("stream_test.xlsx");
1030
1031 let mut wb = Workbook::new();
1032 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1033
1034 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1035 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1036 .unwrap();
1037 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1038 .unwrap();
1039 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1040 .unwrap();
1041 wb.apply_stream_writer(sw).unwrap();
1042
1043 wb.save(&path).unwrap();
1044
1045 let wb2 = Workbook::open(&path).unwrap();
1046 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Streamed"]);
1047 assert_eq!(
1048 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1049 CellValue::String("Normal".to_string())
1050 );
1051 assert_eq!(
1052 wb2.get_cell_value("Streamed", "A1").unwrap(),
1053 CellValue::String("Name".to_string())
1054 );
1055 assert_eq!(
1056 wb2.get_cell_value("Streamed", "B2").unwrap(),
1057 CellValue::Number(100.0)
1058 );
1059 assert_eq!(
1060 wb2.get_cell_value("Streamed", "A3").unwrap(),
1061 CellValue::String("Bob".to_string())
1062 );
1063 }
1064
1065 #[test]
1066 fn test_workbook_get_rows_empty_sheet() {
1067 let wb = Workbook::new();
1068 let rows = wb.get_rows("Sheet1").unwrap();
1069 assert!(rows.is_empty());
1070 }
1071
1072 #[test]
1073 fn test_workbook_get_rows_with_data() {
1074 let mut wb = Workbook::new();
1075 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1076 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1077 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1078 wb.set_cell_value("Sheet1", "B2", true).unwrap();
1079
1080 let rows = wb.get_rows("Sheet1").unwrap();
1081 assert_eq!(rows.len(), 2);
1082 assert_eq!(rows[0].0, 1);
1083 assert_eq!(rows[0].1.len(), 2);
1084 assert_eq!(rows[0].1[0].0, 1);
1085 assert_eq!(rows[0].1[0].1, CellValue::String("Name".to_string()));
1086 assert_eq!(rows[0].1[1].0, 2);
1087 assert_eq!(rows[0].1[1].1, CellValue::Number(42.0));
1088 assert_eq!(rows[1].0, 2);
1089 assert_eq!(rows[1].1[0].1, CellValue::String("Alice".to_string()));
1090 assert_eq!(rows[1].1[1].1, CellValue::Bool(true));
1091 }
1092
1093 #[test]
1094 fn test_workbook_get_rows_sheet_not_found() {
1095 let wb = Workbook::new();
1096 assert!(wb.get_rows("NoSheet").is_err());
1097 }
1098
1099 #[test]
1100 fn test_workbook_get_cols_empty_sheet() {
1101 let wb = Workbook::new();
1102 let cols = wb.get_cols("Sheet1").unwrap();
1103 assert!(cols.is_empty());
1104 }
1105
1106 #[test]
1107 fn test_workbook_get_cols_with_data() {
1108 let mut wb = Workbook::new();
1109 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1110 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1111 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1112 wb.set_cell_value("Sheet1", "B2", 30.0).unwrap();
1113
1114 let cols = wb.get_cols("Sheet1").unwrap();
1115 assert_eq!(cols.len(), 2);
1116 assert_eq!(cols[0].0, "A");
1117 assert_eq!(cols[0].1.len(), 2);
1118 assert_eq!(cols[0].1[0], (1, CellValue::String("Name".to_string())));
1119 assert_eq!(cols[0].1[1], (2, CellValue::String("Alice".to_string())));
1120 assert_eq!(cols[1].0, "B");
1121 assert_eq!(cols[1].1[0], (1, CellValue::Number(42.0)));
1122 assert_eq!(cols[1].1[1], (2, CellValue::Number(30.0)));
1123 }
1124
1125 #[test]
1126 fn test_workbook_get_cols_sheet_not_found() {
1127 let wb = Workbook::new();
1128 assert!(wb.get_cols("NoSheet").is_err());
1129 }
1130
1131 #[test]
1132 fn test_apply_stream_writer_cells_readable_without_reopen() {
1133 let mut wb = Workbook::new();
1134 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1135 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1136 .unwrap();
1137 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
1138 .unwrap();
1139 wb.apply_stream_writer(sw).unwrap();
1140
1141 assert_eq!(
1144 wb.get_cell_value("Streamed", "A1").unwrap(),
1145 CellValue::String("Name".to_string())
1146 );
1147 assert_eq!(
1148 wb.get_cell_value("Streamed", "B1").unwrap(),
1149 CellValue::String("Age".to_string())
1150 );
1151 assert_eq!(
1152 wb.get_cell_value("Streamed", "A2").unwrap(),
1153 CellValue::String("Alice".to_string())
1154 );
1155 assert_eq!(
1156 wb.get_cell_value("Streamed", "B2").unwrap(),
1157 CellValue::Number(30.0)
1158 );
1159 }
1160
1161 #[test]
1162 fn test_workbook_get_rows_roundtrip_save_open() {
1163 let mut wb = Workbook::new();
1164 wb.set_cell_value("Sheet1", "A1", "hello").unwrap();
1165 wb.set_cell_value("Sheet1", "B1", 99.0).unwrap();
1166 wb.set_cell_value("Sheet1", "A2", true).unwrap();
1167
1168 let tmp = std::env::temp_dir().join("test_get_rows_roundtrip.xlsx");
1169 wb.save(&tmp).unwrap();
1170
1171 let wb2 = Workbook::open(&tmp).unwrap();
1172 let rows = wb2.get_rows("Sheet1").unwrap();
1173 assert_eq!(rows.len(), 2);
1174 assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
1175 assert_eq!(rows[0].1[1].1, CellValue::Number(99.0));
1176 assert_eq!(rows[1].1[0].1, CellValue::Bool(true));
1177
1178 let _ = std::fs::remove_file(&tmp);
1179 }
1180
1181 #[test]
1182 fn test_apply_stream_optimized_basic() {
1183 let mut wb = Workbook::new();
1184 let mut sw = wb.new_stream_writer("Optimized").unwrap();
1185 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1186 .unwrap();
1187 sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1188 .unwrap();
1189 let idx = wb.apply_stream_writer(sw).unwrap();
1190 assert_eq!(idx, 1);
1191
1192 assert_eq!(
1193 wb.get_cell_value("Optimized", "A1").unwrap(),
1194 CellValue::String("Hello".to_string())
1195 );
1196 assert_eq!(
1197 wb.get_cell_value("Optimized", "B1").unwrap(),
1198 CellValue::Number(42.0)
1199 );
1200 assert_eq!(
1201 wb.get_cell_value("Optimized", "A2").unwrap(),
1202 CellValue::String("World".to_string())
1203 );
1204 assert_eq!(
1205 wb.get_cell_value("Optimized", "B2").unwrap(),
1206 CellValue::Number(99.0)
1207 );
1208 }
1209
1210 #[test]
1211 fn test_apply_stream_optimized_sst_merge() {
1212 let mut wb = Workbook::new();
1213 wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1214
1215 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1216 sw.write_row(
1217 1,
1218 &[
1219 CellValue::from("New"),
1220 CellValue::from("Existing"),
1221 CellValue::from("New"),
1222 ],
1223 )
1224 .unwrap();
1225 wb.apply_stream_writer(sw).unwrap();
1226
1227 assert_eq!(
1228 wb.get_cell_value("Streamed", "A1").unwrap(),
1229 CellValue::String("New".to_string())
1230 );
1231 assert_eq!(
1232 wb.get_cell_value("Streamed", "B1").unwrap(),
1233 CellValue::String("Existing".to_string())
1234 );
1235 assert!(wb.sst_runtime.len() >= 2);
1236 }
1237
1238 #[test]
1239 fn test_apply_stream_optimized_all_types() {
1240 let mut wb = Workbook::new();
1241 let mut sw = wb.new_stream_writer("Types").unwrap();
1242 sw.write_row(
1243 1,
1244 &[
1245 CellValue::from("text"),
1246 CellValue::from(42),
1247 CellValue::from(3.14),
1248 CellValue::from(true),
1249 CellValue::Formula {
1250 expr: "SUM(B1:C1)".to_string(),
1251 result: None,
1252 },
1253 CellValue::Error("#N/A".to_string()),
1254 CellValue::Empty,
1255 ],
1256 )
1257 .unwrap();
1258 wb.apply_stream_writer(sw).unwrap();
1259
1260 assert_eq!(
1261 wb.get_cell_value("Types", "A1").unwrap(),
1262 CellValue::String("text".to_string())
1263 );
1264 assert_eq!(
1265 wb.get_cell_value("Types", "B1").unwrap(),
1266 CellValue::Number(42.0)
1267 );
1268 assert_eq!(
1269 wb.get_cell_value("Types", "D1").unwrap(),
1270 CellValue::Bool(true)
1271 );
1272 match wb.get_cell_value("Types", "E1").unwrap() {
1273 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:C1)"),
1274 other => panic!("expected formula, got {other:?}"),
1275 }
1276 assert_eq!(
1277 wb.get_cell_value("Types", "F1").unwrap(),
1278 CellValue::Error("#N/A".to_string())
1279 );
1280 assert_eq!(wb.get_cell_value("Types", "G1").unwrap(), CellValue::Empty);
1281 }
1282
1283 #[test]
1284 fn test_apply_stream_optimized_save_reopen() {
1285 let dir = TempDir::new().unwrap();
1286 let path = dir.path().join("stream_optimized.xlsx");
1287
1288 let mut wb = Workbook::new();
1289 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1290
1291 let mut sw = wb.new_stream_writer("Fast").unwrap();
1292 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1293 .unwrap();
1294 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1295 .unwrap();
1296 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1297 .unwrap();
1298 wb.apply_stream_writer(sw).unwrap();
1299
1300 wb.save(&path).unwrap();
1301
1302 let wb2 = Workbook::open(&path).unwrap();
1303 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Fast"]);
1304 assert_eq!(
1305 wb2.get_cell_value("Fast", "A1").unwrap(),
1306 CellValue::String("Name".to_string())
1307 );
1308 assert_eq!(
1309 wb2.get_cell_value("Fast", "B2").unwrap(),
1310 CellValue::Number(100.0)
1311 );
1312 assert_eq!(
1313 wb2.get_cell_value("Fast", "A3").unwrap(),
1314 CellValue::String("Bob".to_string())
1315 );
1316 }
1317}