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