1use super::*;
2
3impl Workbook {
4 pub fn get_cell_value(&self, sheet: &str, cell: &str) -> Result<CellValue> {
9 let ws = self.worksheet_ref(sheet)?;
10
11 let (col, row) = cell_name_to_coordinates(cell)?;
12
13 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
15 Ok(idx) => &ws.sheet_data.rows[idx],
16 Err(_) => return Ok(CellValue::Empty),
17 };
18
19 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
21 Ok(idx) => &xml_row.cells[idx],
22 Err(_) => return Ok(CellValue::Empty),
23 };
24
25 self.xml_cell_to_value(xml_cell)
26 }
27
28 pub fn set_cell_value(
35 &mut self,
36 sheet: &str,
37 cell: &str,
38 value: impl Into<CellValue>,
39 ) -> Result<()> {
40 let value = value.into();
41
42 if let CellValue::String(ref s) = value {
44 if s.len() > MAX_CELL_CHARS {
45 return Err(Error::CellValueTooLong {
46 length: s.len(),
47 max: MAX_CELL_CHARS,
48 });
49 }
50 }
51
52 let sheet_idx = self.sheet_index(sheet)?;
53 self.invalidate_streamed(sheet_idx);
54 self.ensure_hydrated(sheet_idx)?;
55 self.mark_sheet_dirty(sheet_idx);
56
57 let (col, row_num) = cell_name_to_coordinates(cell)?;
58 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
59
60 let ws = self.worksheets[sheet_idx].1.get_mut().unwrap();
61
62 let row_idx = match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
64 Ok(idx) => idx,
65 Err(idx) => {
66 ws.sheet_data.rows.insert(idx, new_row(row_num));
67 idx
68 }
69 };
70
71 let row = &mut ws.sheet_data.rows[row_idx];
72
73 if value == CellValue::Empty {
75 if let Ok(idx) = row.cells.binary_search_by_key(&col, |c| c.col) {
76 row.cells.remove(idx);
77 }
78 return Ok(());
79 }
80
81 let cell_idx = match row.cells.binary_search_by_key(&col, |c| c.col) {
83 Ok(idx) => idx,
84 Err(insert_pos) => {
85 row.cells.insert(
86 insert_pos,
87 Cell {
88 r: cell_ref.into(),
89 col,
90 s: None,
91 t: CellTypeTag::None,
92 v: None,
93 f: None,
94 is: None,
95 },
96 );
97 insert_pos
98 }
99 };
100
101 let xml_cell = &mut row.cells[cell_idx];
102 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
103
104 Ok(())
105 }
106
107 pub(crate) fn xml_cell_to_value(&self, xml_cell: &Cell) -> Result<CellValue> {
109 if let Some(ref formula) = xml_cell.f {
111 let expr = formula.value.clone().unwrap_or_default();
112 let result = match (xml_cell.t, &xml_cell.v) {
113 (CellTypeTag::Boolean, Some(v)) => Some(Box::new(CellValue::Bool(v == "1"))),
114 (CellTypeTag::Error, Some(v)) => Some(Box::new(CellValue::Error(v.clone()))),
115 (CellTypeTag::FormulaString, Some(v)) => {
116 Some(Box::new(CellValue::String(v.clone())))
117 }
118 (_, Some(v)) => v
119 .parse::<f64>()
120 .ok()
121 .map(|n| Box::new(CellValue::Number(n))),
122 _ => None,
123 };
124 return Ok(CellValue::Formula { expr, result });
125 }
126
127 let cell_value = xml_cell.v.as_deref();
128
129 match (xml_cell.t, cell_value) {
130 (CellTypeTag::SharedString, Some(v)) => {
132 let idx: usize = v
133 .parse()
134 .map_err(|_| Error::Internal(format!("invalid SST index: {v}")))?;
135 let s = self
136 .sst_runtime
137 .get(idx)
138 .ok_or_else(|| Error::Internal(format!("SST index {idx} out of bounds")))?;
139 Ok(CellValue::String(s.to_string()))
140 }
141 (CellTypeTag::Boolean, Some(v)) => Ok(CellValue::Bool(v == "1")),
143 (CellTypeTag::Error, Some(v)) => Ok(CellValue::Error(v.to_string())),
145 (CellTypeTag::InlineString, _) => {
147 let s = xml_cell
148 .is
149 .as_ref()
150 .and_then(|is| is.t.clone())
151 .unwrap_or_default();
152 Ok(CellValue::String(s))
153 }
154 (CellTypeTag::FormulaString, Some(v)) => Ok(CellValue::String(v.to_string())),
156 (CellTypeTag::None | CellTypeTag::Number, Some(v)) => {
158 let n: f64 = v
159 .parse()
160 .map_err(|_| Error::Internal(format!("invalid number: {v}")))?;
161 if self.is_date_styled_cell(xml_cell) {
163 return Ok(CellValue::Date(n));
164 }
165 Ok(CellValue::Number(n))
166 }
167 _ => Ok(CellValue::Empty),
169 }
170 }
171
172 pub(crate) fn is_date_styled_cell(&self, xml_cell: &Cell) -> bool {
174 let style_idx = match xml_cell.s {
175 Some(idx) => idx as usize,
176 None => return false,
177 };
178 let xf = match self.stylesheet.cell_xfs.xfs.get(style_idx) {
179 Some(xf) => xf,
180 None => return false,
181 };
182 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
183 if crate::cell::is_date_num_fmt(num_fmt_id) {
185 return true;
186 }
187 if num_fmt_id >= 164 {
189 if let Some(ref num_fmts) = self.stylesheet.num_fmts {
190 if let Some(nf) = num_fmts
191 .num_fmts
192 .iter()
193 .find(|nf| nf.num_fmt_id == num_fmt_id)
194 {
195 return crate::cell::is_date_format_code(&nf.format_code);
196 }
197 }
198 }
199 false
200 }
201
202 pub fn get_cell_formatted_value(&self, sheet: &str, cell: &str) -> Result<String> {
208 let ws = self.worksheet_ref(sheet)?;
209 let (col, row) = cell_name_to_coordinates(cell)?;
210
211 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
212 Ok(idx) => &ws.sheet_data.rows[idx],
213 Err(_) => return Ok(String::new()),
214 };
215
216 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
217 Ok(idx) => &xml_row.cells[idx],
218 Err(_) => return Ok(String::new()),
219 };
220
221 let cell_value = self.xml_cell_to_value(xml_cell)?;
222
223 let numeric_val = match &cell_value {
224 CellValue::Number(n) => Some(*n),
225 CellValue::Date(n) => Some(*n),
226 CellValue::Formula {
227 result: Some(boxed),
228 ..
229 } => match boxed.as_ref() {
230 CellValue::Number(n) => Some(*n),
231 CellValue::Date(n) => Some(*n),
232 _ => None,
233 },
234 _ => None,
235 };
236
237 if let Some(val) = numeric_val {
238 if let Some(format_code) = self.cell_format_code(xml_cell) {
239 return Ok(crate::numfmt::format_number(val, &format_code));
240 }
241 }
242
243 Ok(cell_value.to_string())
244 }
245
246 pub(crate) fn cell_format_code(&self, xml_cell: &Cell) -> Option<String> {
249 let style_idx = xml_cell.s? as usize;
250 let xf = self.stylesheet.cell_xfs.xfs.get(style_idx)?;
251 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
252
253 if num_fmt_id == 0 {
254 return None;
255 }
256
257 if let Some(code) = crate::numfmt::builtin_format_code(num_fmt_id) {
259 return Some(code.to_string());
260 }
261
262 if let Some(ref num_fmts) = self.stylesheet.num_fmts {
264 if let Some(nf) = num_fmts
265 .num_fmts
266 .iter()
267 .find(|nf| nf.num_fmt_id == num_fmt_id)
268 {
269 return Some(nf.format_code.clone());
270 }
271 }
272
273 None
274 }
275
276 pub fn add_style(&mut self, style: &crate::style::Style) -> Result<u32> {
281 crate::style::add_style(&mut self.stylesheet, style)
282 }
283
284 pub fn get_cell_style(&self, sheet: &str, cell: &str) -> Result<Option<u32>> {
289 let ws = self.worksheet_ref(sheet)?;
290
291 let (col, row) = cell_name_to_coordinates(cell)?;
292
293 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
295 Ok(idx) => &ws.sheet_data.rows[idx],
296 Err(_) => return Ok(None),
297 };
298
299 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
301 Ok(idx) => &xml_row.cells[idx],
302 Err(_) => return Ok(None),
303 };
304
305 Ok(xml_cell.s)
306 }
307
308 pub fn set_cell_style(&mut self, sheet: &str, cell: &str, style_id: u32) -> Result<()> {
313 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
315 return Err(Error::StyleNotFound { id: style_id });
316 }
317
318 let sheet_idx = self.sheet_index(sheet)?;
319 let ws = self.worksheet_mut_by_index(sheet_idx)?;
320
321 let (col, row_num) = cell_name_to_coordinates(cell)?;
322 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
323
324 let row_idx = match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
326 Ok(idx) => idx,
327 Err(idx) => {
328 ws.sheet_data.rows.insert(idx, new_row(row_num));
329 idx
330 }
331 };
332
333 let row = &mut ws.sheet_data.rows[row_idx];
334
335 let cell_idx = match row.cells.binary_search_by_key(&col, |c| c.col) {
337 Ok(idx) => idx,
338 Err(insert_pos) => {
339 row.cells.insert(
340 insert_pos,
341 Cell {
342 r: cell_ref.into(),
343 col,
344 s: None,
345 t: CellTypeTag::None,
346 v: None,
347 f: None,
348 is: None,
349 },
350 );
351 insert_pos
352 }
353 };
354
355 row.cells[cell_idx].s = Some(style_id);
356 Ok(())
357 }
358
359 pub fn merge_cells(&mut self, sheet: &str, top_left: &str, bottom_right: &str) -> Result<()> {
364 let ws = self.worksheet_mut(sheet)?;
365 crate::merge::merge_cells(ws, top_left, bottom_right)
366 }
367
368 pub fn unmerge_cell(&mut self, sheet: &str, reference: &str) -> Result<()> {
372 let ws = self.worksheet_mut(sheet)?;
373 crate::merge::unmerge_cell(ws, reference)
374 }
375
376 pub fn get_merge_cells(&self, sheet: &str) -> Result<Vec<String>> {
380 let ws = self.worksheet_ref(sheet)?;
381 Ok(crate::merge::get_merge_cells(ws))
382 }
383
384 pub fn set_cell_formula(&mut self, sheet: &str, cell: &str, formula: &str) -> Result<()> {
389 self.set_cell_value(
390 sheet,
391 cell,
392 CellValue::Formula {
393 expr: formula.to_string(),
394 result: None,
395 },
396 )
397 }
398
399 pub fn fill_formula(&mut self, sheet: &str, range: &str, formula: &str) -> Result<()> {
407 let parts: Vec<&str> = range.split(':').collect();
408 if parts.len() != 2 {
409 return Err(Error::InvalidCellReference(format!(
410 "invalid range: {range}"
411 )));
412 }
413 let (start_col, start_row) = cell_name_to_coordinates(parts[0])?;
414 let (end_col, end_row) = cell_name_to_coordinates(parts[1])?;
415
416 if start_col != end_col {
417 return Err(Error::InvalidCellReference(
418 "fill_formula only supports single-column ranges".to_string(),
419 ));
420 }
421
422 for row in start_row..=end_row {
423 let row_offset = row as i32 - start_row as i32;
424 let adjusted = if row_offset == 0 {
425 formula.to_string()
426 } else {
427 crate::cell_ref_shift::shift_cell_references_with_abs(
428 formula,
429 |col, r, _abs_col, abs_row| {
430 if abs_row {
431 (col, r)
432 } else {
433 (col, (r as i32 + row_offset) as u32)
434 }
435 },
436 )?
437 };
438 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(start_col, row)?;
439 self.set_cell_formula(sheet, &cell_ref, &adjusted)?;
440 }
441 Ok(())
442 }
443
444 pub fn set_cell_rich_text(
446 &mut self,
447 sheet: &str,
448 cell: &str,
449 runs: Vec<crate::rich_text::RichTextRun>,
450 ) -> Result<()> {
451 self.set_cell_value(sheet, cell, CellValue::RichString(runs))
452 }
453
454 pub fn get_cell_rich_text(
459 &self,
460 sheet: &str,
461 cell: &str,
462 ) -> Result<Option<Vec<crate::rich_text::RichTextRun>>> {
463 let (col, row) = cell_name_to_coordinates(cell)?;
464 let ws = self.worksheet_ref(sheet)?;
465
466 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
468 Ok(idx) => &ws.sheet_data.rows[idx],
469 Err(_) => return Ok(None),
470 };
471
472 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
474 Ok(idx) => &xml_row.cells[idx],
475 Err(_) => return Ok(None),
476 };
477
478 if xml_cell.t == CellTypeTag::SharedString {
479 if let Some(ref v) = xml_cell.v {
480 if let Ok(idx) = v.parse::<usize>() {
481 return Ok(self.sst_runtime.get_rich_text(idx));
482 }
483 }
484 }
485 Ok(None)
486 }
487
488 pub fn set_cell_values(
493 &mut self,
494 sheet: &str,
495 entries: Vec<(String, CellValue)>,
496 ) -> Result<()> {
497 let sheet_idx = self.sheet_index(sheet)?;
498 self.invalidate_streamed(sheet_idx);
499 self.ensure_hydrated(sheet_idx)?;
500 self.mark_sheet_dirty(sheet_idx);
501
502 for (cell, value) in entries {
503 if let CellValue::String(ref s) = value {
504 if s.len() > MAX_CELL_CHARS {
505 return Err(Error::CellValueTooLong {
506 length: s.len(),
507 max: MAX_CELL_CHARS,
508 });
509 }
510 }
511
512 let (col, row_num) = cell_name_to_coordinates(&cell)?;
513 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
514
515 let row_idx = {
516 let ws = self.worksheets[sheet_idx].1.get_mut().unwrap();
517 match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
518 Ok(idx) => idx,
519 Err(idx) => {
520 ws.sheet_data.rows.insert(idx, new_row(row_num));
521 idx
522 }
523 }
524 };
525
526 if value == CellValue::Empty {
527 let row = &mut self.worksheets[sheet_idx]
528 .1
529 .get_mut()
530 .unwrap()
531 .sheet_data
532 .rows[row_idx];
533 if let Ok(idx) = row.cells.binary_search_by_key(&col, |c| c.col) {
534 row.cells.remove(idx);
535 }
536 continue;
537 }
538
539 let cell_idx = {
540 let row = &mut self.worksheets[sheet_idx]
541 .1
542 .get_mut()
543 .unwrap()
544 .sheet_data
545 .rows[row_idx];
546 match row.cells.binary_search_by_key(&col, |c| c.col) {
547 Ok(idx) => idx,
548 Err(pos) => {
549 row.cells.insert(
550 pos,
551 Cell {
552 r: cell_ref.into(),
553 col,
554 s: None,
555 t: CellTypeTag::None,
556 v: None,
557 f: None,
558 is: None,
559 },
560 );
561 pos
562 }
563 }
564 };
565
566 let xml_cell = &mut self.worksheets[sheet_idx]
567 .1
568 .get_mut()
569 .unwrap()
570 .sheet_data
571 .rows[row_idx]
572 .cells[cell_idx];
573 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
574 }
575
576 Ok(())
577 }
578
579 pub fn set_sheet_data(
588 &mut self,
589 sheet: &str,
590 data: Vec<Vec<CellValue>>,
591 start_row: u32,
592 start_col: u32,
593 ) -> Result<()> {
594 let sheet_idx = self.sheet_index(sheet)?;
595 self.ensure_hydrated(sheet_idx)?;
596 self.mark_sheet_dirty(sheet_idx);
597
598 let max_cols = data.iter().map(|r| r.len()).max().unwrap_or(0) as u32;
600 let col_names: Vec<String> = (0..max_cols)
601 .map(|i| crate::utils::cell_ref::column_number_to_name(start_col + i))
602 .collect::<Result<Vec<_>>>()?;
603
604 for (row_offset, row_values) in data.into_iter().enumerate() {
605 let row_num = start_row + row_offset as u32;
606
607 let row_idx = {
608 let ws = self.worksheets[sheet_idx].1.get_mut().unwrap();
609 match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
610 Ok(idx) => idx,
611 Err(idx) => {
612 ws.sheet_data.rows.insert(idx, new_row(row_num));
613 idx
614 }
615 }
616 };
617
618 for (col_offset, value) in row_values.into_iter().enumerate() {
619 let col = start_col + col_offset as u32;
620
621 if let CellValue::String(ref s) = value {
622 if s.len() > MAX_CELL_CHARS {
623 return Err(Error::CellValueTooLong {
624 length: s.len(),
625 max: MAX_CELL_CHARS,
626 });
627 }
628 }
629
630 if value == CellValue::Empty {
631 let row = &mut self.worksheets[sheet_idx]
632 .1
633 .get_mut()
634 .unwrap()
635 .sheet_data
636 .rows[row_idx];
637 if let Ok(idx) = row.cells.binary_search_by_key(&col, |c| c.col) {
638 row.cells.remove(idx);
639 }
640 continue;
641 }
642
643 let cell_ref = format!("{}{}", col_names[col_offset], row_num);
644
645 let cell_idx = {
646 let row = &mut self.worksheets[sheet_idx]
647 .1
648 .get_mut()
649 .unwrap()
650 .sheet_data
651 .rows[row_idx];
652 match row.cells.binary_search_by_key(&col, |c| c.col) {
653 Ok(idx) => idx,
654 Err(pos) => {
655 row.cells.insert(
656 pos,
657 Cell {
658 r: cell_ref.into(),
659 col,
660 s: None,
661 t: CellTypeTag::None,
662 v: None,
663 f: None,
664 is: None,
665 },
666 );
667 pos
668 }
669 }
670 };
671
672 let xml_cell = &mut self.worksheets[sheet_idx]
673 .1
674 .get_mut()
675 .unwrap()
676 .sheet_data
677 .rows[row_idx]
678 .cells[cell_idx];
679 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
680 }
681 }
682
683 Ok(())
684 }
685
686 pub fn set_row_values(
691 &mut self,
692 sheet: &str,
693 row_num: u32,
694 start_col: u32,
695 values: Vec<CellValue>,
696 ) -> Result<()> {
697 self.set_sheet_data(sheet, vec![values], row_num, start_col)
698 }
699}
700
701pub(crate) fn value_to_xml_cell(
703 sst: &mut SharedStringTable,
704 xml_cell: &mut Cell,
705 value: CellValue,
706) {
707 xml_cell.t = CellTypeTag::None;
709 xml_cell.v = None;
710 xml_cell.f = None;
711 xml_cell.is = None;
712
713 match value {
714 CellValue::String(s) => {
715 let idx = sst.add_owned(s);
716 xml_cell.t = CellTypeTag::SharedString;
717 xml_cell.v = Some(idx.to_string());
718 }
719 CellValue::Number(n) => {
720 xml_cell.v = Some(n.to_string());
721 }
722 CellValue::Date(serial) => {
723 xml_cell.v = Some(serial.to_string());
726 }
727 CellValue::Bool(b) => {
728 xml_cell.t = CellTypeTag::Boolean;
729 xml_cell.v = Some(if b { "1" } else { "0" }.to_string());
730 }
731 CellValue::Formula { expr, .. } => {
732 xml_cell.f = Some(Box::new(CellFormula {
733 t: None,
734 reference: None,
735 si: None,
736 value: Some(expr),
737 }));
738 }
739 CellValue::Error(e) => {
740 xml_cell.t = CellTypeTag::Error;
741 xml_cell.v = Some(e);
742 }
743 CellValue::Empty => {
744 }
746 CellValue::RichString(runs) => {
747 let idx = sst.add_rich_text(&runs);
748 xml_cell.t = CellTypeTag::SharedString;
749 xml_cell.v = Some(idx.to_string());
750 }
751 }
752}
753
754pub(crate) fn new_row(row_num: u32) -> Row {
756 Row {
757 r: row_num,
758 spans: None,
759 s: None,
760 custom_format: None,
761 ht: None,
762 hidden: None,
763 custom_height: None,
764 outline_level: None,
765 cells: vec![],
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772 use tempfile::TempDir;
773
774 #[test]
775 fn test_set_and_get_string_value() {
776 let mut wb = Workbook::new();
777 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
778 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
779 assert_eq!(val, CellValue::String("Hello".to_string()));
780 }
781
782 #[test]
783 fn test_set_and_get_number_value() {
784 let mut wb = Workbook::new();
785 wb.set_cell_value("Sheet1", "B2", 42.5f64).unwrap();
786 let val = wb.get_cell_value("Sheet1", "B2").unwrap();
787 assert_eq!(val, CellValue::Number(42.5));
788 }
789
790 #[test]
791 fn test_set_and_get_bool_value() {
792 let mut wb = Workbook::new();
793 wb.set_cell_value("Sheet1", "C3", true).unwrap();
794 let val = wb.get_cell_value("Sheet1", "C3").unwrap();
795 assert_eq!(val, CellValue::Bool(true));
796
797 wb.set_cell_value("Sheet1", "D4", false).unwrap();
798 let val = wb.get_cell_value("Sheet1", "D4").unwrap();
799 assert_eq!(val, CellValue::Bool(false));
800 }
801
802 #[test]
803 fn test_set_value_sheet_not_found() {
804 let mut wb = Workbook::new();
805 let result = wb.set_cell_value("NoSuchSheet", "A1", "test");
806 assert!(result.is_err());
807 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
808 }
809
810 #[test]
811 fn test_get_value_sheet_not_found() {
812 let wb = Workbook::new();
813 let result = wb.get_cell_value("NoSuchSheet", "A1");
814 assert!(result.is_err());
815 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
816 }
817
818 #[test]
819 fn test_get_empty_cell_returns_empty() {
820 let wb = Workbook::new();
821 let val = wb.get_cell_value("Sheet1", "Z99").unwrap();
822 assert_eq!(val, CellValue::Empty);
823 }
824
825 #[test]
826 fn test_cell_value_roundtrip_save_open() {
827 let dir = TempDir::new().unwrap();
828 let path = dir.path().join("cell_roundtrip.xlsx");
829
830 let mut wb = Workbook::new();
831 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
832 wb.set_cell_value("Sheet1", "B1", 42.0f64).unwrap();
833 wb.set_cell_value("Sheet1", "C1", true).unwrap();
834 wb.save(&path).unwrap();
835
836 let wb2 = Workbook::open(&path).unwrap();
837 assert_eq!(
838 wb2.get_cell_value("Sheet1", "A1").unwrap(),
839 CellValue::String("Hello".to_string())
840 );
841 assert_eq!(
842 wb2.get_cell_value("Sheet1", "B1").unwrap(),
843 CellValue::Number(42.0)
844 );
845 assert_eq!(
846 wb2.get_cell_value("Sheet1", "C1").unwrap(),
847 CellValue::Bool(true)
848 );
849 }
850
851 #[test]
852 fn test_set_empty_value_clears_cell() {
853 let mut wb = Workbook::new();
854 wb.set_cell_value("Sheet1", "A1", "test").unwrap();
855 assert_eq!(
856 wb.get_cell_value("Sheet1", "A1").unwrap(),
857 CellValue::String("test".to_string())
858 );
859
860 wb.set_cell_value("Sheet1", "A1", CellValue::Empty).unwrap();
861 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
862 }
863
864 #[test]
865 fn test_string_too_long_returns_error() {
866 let mut wb = Workbook::new();
867 let long_string = "x".repeat(MAX_CELL_CHARS + 1);
868 let result = wb.set_cell_value("Sheet1", "A1", long_string.as_str());
869 assert!(result.is_err());
870 assert!(matches!(
871 result.unwrap_err(),
872 Error::CellValueTooLong { .. }
873 ));
874 }
875
876 #[test]
877 fn test_set_multiple_cells_same_row() {
878 let mut wb = Workbook::new();
879 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
880 wb.set_cell_value("Sheet1", "B1", "second").unwrap();
881 wb.set_cell_value("Sheet1", "C1", "third").unwrap();
882
883 assert_eq!(
884 wb.get_cell_value("Sheet1", "A1").unwrap(),
885 CellValue::String("first".to_string())
886 );
887 assert_eq!(
888 wb.get_cell_value("Sheet1", "B1").unwrap(),
889 CellValue::String("second".to_string())
890 );
891 assert_eq!(
892 wb.get_cell_value("Sheet1", "C1").unwrap(),
893 CellValue::String("third".to_string())
894 );
895 }
896
897 #[test]
898 fn test_overwrite_cell_value() {
899 let mut wb = Workbook::new();
900 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
901 wb.set_cell_value("Sheet1", "A1", "updated").unwrap();
902
903 assert_eq!(
904 wb.get_cell_value("Sheet1", "A1").unwrap(),
905 CellValue::String("updated".to_string())
906 );
907 }
908
909 #[test]
910 fn test_set_and_get_error_value() {
911 let mut wb = Workbook::new();
912 wb.set_cell_value("Sheet1", "A1", CellValue::Error("#DIV/0!".to_string()))
913 .unwrap();
914 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
915 assert_eq!(val, CellValue::Error("#DIV/0!".to_string()));
916 }
917
918 #[test]
919 fn test_set_and_get_date_value() {
920 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
921
922 let mut wb = Workbook::new();
923 let style_id = wb
925 .add_style(&Style {
926 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
927 ..Style::default()
928 })
929 .unwrap();
930
931 let date_serial =
933 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
934 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
935 .unwrap();
936 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
937
938 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
940 assert_eq!(val, CellValue::Date(date_serial));
941 }
942
943 #[test]
944 fn test_date_value_without_style_returns_number() {
945 let mut wb = Workbook::new();
946 let date_serial =
948 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
949 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
950 .unwrap();
951
952 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
954 assert_eq!(val, CellValue::Number(date_serial));
955 }
956
957 #[test]
958 fn test_date_value_roundtrip_through_save() {
959 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
960
961 let mut wb = Workbook::new();
962 let style_id = wb
963 .add_style(&Style {
964 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATETIME)),
965 ..Style::default()
966 })
967 .unwrap();
968
969 let dt = chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
970 .unwrap()
971 .and_hms_opt(14, 30, 0)
972 .unwrap();
973 let serial = crate::cell::datetime_to_serial(dt);
974 wb.set_cell_value("Sheet1", "A1", CellValue::Date(serial))
975 .unwrap();
976 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
977
978 let dir = tempfile::TempDir::new().unwrap();
979 let path = dir.path().join("date_test.xlsx");
980 wb.save(&path).unwrap();
981
982 let wb2 = Workbook::open(&path).unwrap();
983 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
984 assert_eq!(val, CellValue::Date(serial));
985 }
986
987 #[test]
988 fn test_date_from_naive_date_conversion() {
989 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
990 let cv: CellValue = date.into();
991 match cv {
992 CellValue::Date(s) => {
993 let roundtripped = crate::cell::serial_to_date(s).unwrap();
994 assert_eq!(roundtripped, date);
995 }
996 _ => panic!("expected Date variant"),
997 }
998 }
999
1000 #[test]
1001 fn test_set_and_get_formula_value() {
1002 let mut wb = Workbook::new();
1003 wb.set_cell_value(
1004 "Sheet1",
1005 "A1",
1006 CellValue::Formula {
1007 expr: "SUM(B1:B10)".to_string(),
1008 result: None,
1009 },
1010 )
1011 .unwrap();
1012 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1013 match val {
1014 CellValue::Formula { expr, .. } => {
1015 assert_eq!(expr, "SUM(B1:B10)");
1016 }
1017 other => panic!("expected Formula, got {:?}", other),
1018 }
1019 }
1020
1021 #[test]
1022 fn test_set_i32_value() {
1023 let mut wb = Workbook::new();
1024 wb.set_cell_value("Sheet1", "A1", 100i32).unwrap();
1025 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1026 assert_eq!(val, CellValue::Number(100.0));
1027 }
1028
1029 #[test]
1030 fn test_set_string_at_max_length() {
1031 let mut wb = Workbook::new();
1032 let max_string = "x".repeat(MAX_CELL_CHARS);
1033 wb.set_cell_value("Sheet1", "A1", max_string.as_str())
1034 .unwrap();
1035 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1036 assert_eq!(val, CellValue::String(max_string));
1037 }
1038
1039 #[test]
1040 fn test_set_cells_different_rows() {
1041 let mut wb = Workbook::new();
1042 wb.set_cell_value("Sheet1", "A1", "row1").unwrap();
1043 wb.set_cell_value("Sheet1", "A3", "row3").unwrap();
1044 wb.set_cell_value("Sheet1", "A2", "row2").unwrap(); assert_eq!(
1047 wb.get_cell_value("Sheet1", "A1").unwrap(),
1048 CellValue::String("row1".to_string())
1049 );
1050 assert_eq!(
1051 wb.get_cell_value("Sheet1", "A2").unwrap(),
1052 CellValue::String("row2".to_string())
1053 );
1054 assert_eq!(
1055 wb.get_cell_value("Sheet1", "A3").unwrap(),
1056 CellValue::String("row3".to_string())
1057 );
1058 }
1059
1060 #[test]
1061 fn test_string_deduplication_in_sst() {
1062 let mut wb = Workbook::new();
1063 wb.set_cell_value("Sheet1", "A1", "same").unwrap();
1064 wb.set_cell_value("Sheet1", "A2", "same").unwrap();
1065 wb.set_cell_value("Sheet1", "A3", "different").unwrap();
1066
1067 assert_eq!(wb.sst_runtime.len(), 2);
1069 assert_eq!(
1070 wb.get_cell_value("Sheet1", "A1").unwrap(),
1071 CellValue::String("same".to_string())
1072 );
1073 assert_eq!(
1074 wb.get_cell_value("Sheet1", "A2").unwrap(),
1075 CellValue::String("same".to_string())
1076 );
1077 }
1078
1079 #[test]
1080 fn test_add_style_returns_id() {
1081 use crate::style::{FontStyle, Style};
1082
1083 let mut wb = Workbook::new();
1084 let style = Style {
1085 font: Some(FontStyle {
1086 bold: true,
1087 ..FontStyle::default()
1088 }),
1089 ..Style::default()
1090 };
1091 let id = wb.add_style(&style).unwrap();
1092 assert!(id > 0);
1093 }
1094
1095 #[test]
1096 fn test_get_cell_style_unstyled_cell_returns_none() {
1097 let wb = Workbook::new();
1098 let result = wb.get_cell_style("Sheet1", "A1").unwrap();
1099 assert!(result.is_none());
1100 }
1101
1102 #[test]
1103 fn test_set_cell_style_on_existing_value() {
1104 use crate::style::{FontStyle, Style};
1105
1106 let mut wb = Workbook::new();
1107 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
1108
1109 let style = Style {
1110 font: Some(FontStyle {
1111 bold: true,
1112 ..FontStyle::default()
1113 }),
1114 ..Style::default()
1115 };
1116 let style_id = wb.add_style(&style).unwrap();
1117 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
1118
1119 let retrieved_id = wb.get_cell_style("Sheet1", "A1").unwrap();
1120 assert_eq!(retrieved_id, Some(style_id));
1121
1122 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1124 assert_eq!(val, CellValue::String("Hello".to_string()));
1125 }
1126
1127 #[test]
1128 fn test_set_cell_style_on_empty_cell_creates_cell() {
1129 use crate::style::{FontStyle, Style};
1130
1131 let mut wb = Workbook::new();
1132 let style = Style {
1133 font: Some(FontStyle {
1134 bold: true,
1135 ..FontStyle::default()
1136 }),
1137 ..Style::default()
1138 };
1139 let style_id = wb.add_style(&style).unwrap();
1140
1141 wb.set_cell_style("Sheet1", "B5", style_id).unwrap();
1143
1144 let retrieved_id = wb.get_cell_style("Sheet1", "B5").unwrap();
1145 assert_eq!(retrieved_id, Some(style_id));
1146
1147 let val = wb.get_cell_value("Sheet1", "B5").unwrap();
1149 assert_eq!(val, CellValue::Empty);
1150 }
1151
1152 #[test]
1153 fn test_set_cell_style_invalid_id() {
1154 let mut wb = Workbook::new();
1155 let result = wb.set_cell_style("Sheet1", "A1", 999);
1156 assert!(result.is_err());
1157 assert!(matches!(result.unwrap_err(), Error::StyleNotFound { .. }));
1158 }
1159
1160 #[test]
1161 fn test_set_cell_style_sheet_not_found() {
1162 let mut wb = Workbook::new();
1163 let style = crate::style::Style::default();
1164 let style_id = wb.add_style(&style).unwrap();
1165 let result = wb.set_cell_style("NoSuchSheet", "A1", style_id);
1166 assert!(result.is_err());
1167 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1168 }
1169
1170 #[test]
1171 fn test_get_cell_style_sheet_not_found() {
1172 let wb = Workbook::new();
1173 let result = wb.get_cell_style("NoSuchSheet", "A1");
1174 assert!(result.is_err());
1175 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1176 }
1177
1178 #[test]
1179 fn test_style_roundtrip_save_open() {
1180 use crate::style::{
1181 AlignmentStyle, BorderLineStyle, BorderSideStyle, BorderStyle, FillStyle, FontStyle,
1182 HorizontalAlign, NumFmtStyle, PatternType, Style, StyleColor, VerticalAlign,
1183 };
1184
1185 let dir = TempDir::new().unwrap();
1186 let path = dir.path().join("style_roundtrip.xlsx");
1187
1188 let mut wb = Workbook::new();
1189 wb.set_cell_value("Sheet1", "A1", "Styled").unwrap();
1190
1191 let style = Style {
1192 font: Some(FontStyle {
1193 name: Some("Arial".to_string()),
1194 size: Some(14.0),
1195 bold: true,
1196 italic: true,
1197 color: Some(StyleColor::Rgb("FFFF0000".to_string())),
1198 ..FontStyle::default()
1199 }),
1200 fill: Some(FillStyle {
1201 pattern: PatternType::Solid,
1202 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
1203 bg_color: None,
1204 gradient: None,
1205 }),
1206 border: Some(BorderStyle {
1207 left: Some(BorderSideStyle {
1208 style: BorderLineStyle::Thin,
1209 color: None,
1210 }),
1211 right: Some(BorderSideStyle {
1212 style: BorderLineStyle::Thin,
1213 color: None,
1214 }),
1215 top: Some(BorderSideStyle {
1216 style: BorderLineStyle::Thin,
1217 color: None,
1218 }),
1219 bottom: Some(BorderSideStyle {
1220 style: BorderLineStyle::Thin,
1221 color: None,
1222 }),
1223 diagonal: None,
1224 }),
1225 alignment: Some(AlignmentStyle {
1226 horizontal: Some(HorizontalAlign::Center),
1227 vertical: Some(VerticalAlign::Center),
1228 wrap_text: true,
1229 ..AlignmentStyle::default()
1230 }),
1231 num_fmt: Some(NumFmtStyle::Custom("#,##0.00".to_string())),
1232 protection: None,
1233 };
1234 let style_id = wb.add_style(&style).unwrap();
1235 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
1236 wb.save(&path).unwrap();
1237
1238 let wb2 = Workbook::open(&path).unwrap();
1240 let retrieved_id = wb2.get_cell_style("Sheet1", "A1").unwrap();
1241 assert_eq!(retrieved_id, Some(style_id));
1242
1243 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
1245 assert_eq!(val, CellValue::String("Styled".to_string()));
1246
1247 let retrieved_style = crate::style::get_style(&wb2.stylesheet, style_id).unwrap();
1249 assert!(retrieved_style.font.is_some());
1250 let font = retrieved_style.font.unwrap();
1251 assert!(font.bold);
1252 assert!(font.italic);
1253 assert_eq!(font.name, Some("Arial".to_string()));
1254
1255 assert!(retrieved_style.fill.is_some());
1256 let fill = retrieved_style.fill.unwrap();
1257 assert_eq!(fill.pattern, PatternType::Solid);
1258
1259 assert!(retrieved_style.alignment.is_some());
1260 let align = retrieved_style.alignment.unwrap();
1261 assert_eq!(align.horizontal, Some(HorizontalAlign::Center));
1262 assert_eq!(align.vertical, Some(VerticalAlign::Center));
1263 assert!(align.wrap_text);
1264 }
1265
1266 #[test]
1267 fn test_set_and_get_cell_rich_text() {
1268 use crate::rich_text::RichTextRun;
1269
1270 let mut wb = Workbook::new();
1271 let runs = vec![
1272 RichTextRun {
1273 text: "Bold".to_string(),
1274 font: None,
1275 size: None,
1276 bold: true,
1277 italic: false,
1278 color: None,
1279 },
1280 RichTextRun {
1281 text: " Normal".to_string(),
1282 font: None,
1283 size: None,
1284 bold: false,
1285 italic: false,
1286 color: None,
1287 },
1288 ];
1289 wb.set_cell_rich_text("Sheet1", "A1", runs.clone()).unwrap();
1290
1291 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1293 assert_eq!(val.to_string(), "Bold Normal");
1294
1295 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1297 assert!(got.is_some());
1298 let got_runs = got.unwrap();
1299 assert_eq!(got_runs.len(), 2);
1300 assert_eq!(got_runs[0].text, "Bold");
1301 assert!(got_runs[0].bold);
1302 assert_eq!(got_runs[1].text, " Normal");
1303 assert!(!got_runs[1].bold);
1304 }
1305
1306 #[test]
1307 fn test_get_cell_rich_text_returns_none_for_plain() {
1308 let mut wb = Workbook::new();
1309 wb.set_cell_value("Sheet1", "A1", CellValue::String("plain".to_string()))
1310 .unwrap();
1311 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1312 assert!(got.is_none());
1313 }
1314
1315 #[test]
1316 fn test_rich_text_roundtrip_save_open() {
1317 use crate::rich_text::RichTextRun;
1318
1319 let dir = TempDir::new().unwrap();
1320 let path = dir.path().join("rich_text.xlsx");
1321
1322 let mut wb = Workbook::new();
1326 let runs = vec![
1327 RichTextRun {
1328 text: "Hello".to_string(),
1329 font: Some("Arial".to_string()),
1330 size: Some(14.0),
1331 bold: true,
1332 italic: false,
1333 color: Some("#FF0000".to_string()),
1334 },
1335 RichTextRun {
1336 text: "World".to_string(),
1337 font: None,
1338 size: None,
1339 bold: false,
1340 italic: true,
1341 color: None,
1342 },
1343 ];
1344 wb.set_cell_rich_text("Sheet1", "B2", runs).unwrap();
1345 wb.save(&path).unwrap();
1346
1347 let wb2 = Workbook::open(&path).unwrap();
1348 let val = wb2.get_cell_value("Sheet1", "B2").unwrap();
1349 assert_eq!(val.to_string(), "HelloWorld");
1350
1351 let got = wb2.get_cell_rich_text("Sheet1", "B2").unwrap();
1352 assert!(got.is_some());
1353 let got_runs = got.unwrap();
1354 assert_eq!(got_runs.len(), 2);
1355 assert_eq!(got_runs[0].text, "Hello");
1356 assert!(got_runs[0].bold);
1357 assert_eq!(got_runs[0].font.as_deref(), Some("Arial"));
1358 assert_eq!(got_runs[0].size, Some(14.0));
1359 assert_eq!(got_runs[0].color.as_deref(), Some("#FF0000"));
1360 assert_eq!(got_runs[1].text, "World");
1361 assert!(got_runs[1].italic);
1362 assert!(!got_runs[1].bold);
1363 }
1364
1365 #[test]
1366 fn test_set_cell_formula() {
1367 let mut wb = Workbook::new();
1368 wb.set_cell_formula("Sheet1", "A1", "SUM(B1:B10)").unwrap();
1369 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1370 match val {
1371 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:B10)"),
1372 other => panic!("expected Formula, got {:?}", other),
1373 }
1374 }
1375
1376 #[test]
1377 fn test_fill_formula_basic() {
1378 let mut wb = Workbook::new();
1379 wb.fill_formula("Sheet1", "D2:D5", "SUM(A2:C2)").unwrap();
1380
1381 match wb.get_cell_value("Sheet1", "D2").unwrap() {
1383 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A2:C2)"),
1384 other => panic!("D2: expected Formula, got {:?}", other),
1385 }
1386 match wb.get_cell_value("Sheet1", "D3").unwrap() {
1388 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:C3)"),
1389 other => panic!("D3: expected Formula, got {:?}", other),
1390 }
1391 match wb.get_cell_value("Sheet1", "D4").unwrap() {
1393 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A4:C4)"),
1394 other => panic!("D4: expected Formula, got {:?}", other),
1395 }
1396 match wb.get_cell_value("Sheet1", "D5").unwrap() {
1398 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A5:C5)"),
1399 other => panic!("D5: expected Formula, got {:?}", other),
1400 }
1401 }
1402
1403 #[test]
1404 fn test_fill_formula_preserves_absolute_refs() {
1405 let mut wb = Workbook::new();
1406 wb.fill_formula("Sheet1", "B1:B3", "$A$1*A1").unwrap();
1407
1408 match wb.get_cell_value("Sheet1", "B1").unwrap() {
1409 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A1"),
1410 other => panic!("B1: expected Formula, got {:?}", other),
1411 }
1412 match wb.get_cell_value("Sheet1", "B2").unwrap() {
1413 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A2"),
1414 other => panic!("B2: expected Formula, got {:?}", other),
1415 }
1416 match wb.get_cell_value("Sheet1", "B3").unwrap() {
1417 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A3"),
1418 other => panic!("B3: expected Formula, got {:?}", other),
1419 }
1420 }
1421
1422 #[test]
1423 fn test_fill_formula_single_cell() {
1424 let mut wb = Workbook::new();
1425 wb.fill_formula("Sheet1", "A1:A1", "B1+C1").unwrap();
1426 match wb.get_cell_value("Sheet1", "A1").unwrap() {
1427 CellValue::Formula { expr, .. } => assert_eq!(expr, "B1+C1"),
1428 other => panic!("expected Formula, got {:?}", other),
1429 }
1430 }
1431
1432 #[test]
1433 fn test_fill_formula_invalid_range() {
1434 let mut wb = Workbook::new();
1435 assert!(wb.fill_formula("Sheet1", "INVALID", "A1").is_err());
1436 }
1437
1438 #[test]
1439 fn test_fill_formula_multi_column_range_rejected() {
1440 let mut wb = Workbook::new();
1441 assert!(wb.fill_formula("Sheet1", "A1:B5", "C1").is_err());
1442 }
1443
1444 #[test]
1445 fn test_set_cell_values_batch() {
1446 let mut wb = Workbook::new();
1447 wb.set_cell_values(
1448 "Sheet1",
1449 vec![
1450 ("A1".to_string(), CellValue::String("hello".to_string())),
1451 ("B1".to_string(), CellValue::Number(42.0)),
1452 ("C1".to_string(), CellValue::Bool(true)),
1453 ("A2".to_string(), CellValue::String("world".to_string())),
1454 ],
1455 )
1456 .unwrap();
1457
1458 assert_eq!(
1459 wb.get_cell_value("Sheet1", "A1").unwrap(),
1460 CellValue::String("hello".to_string())
1461 );
1462 assert_eq!(
1463 wb.get_cell_value("Sheet1", "B1").unwrap(),
1464 CellValue::Number(42.0)
1465 );
1466 assert_eq!(
1467 wb.get_cell_value("Sheet1", "C1").unwrap(),
1468 CellValue::Bool(true)
1469 );
1470 assert_eq!(
1471 wb.get_cell_value("Sheet1", "A2").unwrap(),
1472 CellValue::String("world".to_string())
1473 );
1474 }
1475
1476 #[test]
1477 fn test_set_cell_values_empty_removes_cell() {
1478 let mut wb = Workbook::new();
1479 wb.set_cell_value("Sheet1", "A1", "existing").unwrap();
1480 wb.set_cell_values("Sheet1", vec![("A1".to_string(), CellValue::Empty)])
1481 .unwrap();
1482 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1483 }
1484
1485 #[test]
1486 fn test_set_sheet_data_basic() {
1487 let mut wb = Workbook::new();
1488 wb.set_sheet_data(
1489 "Sheet1",
1490 vec![
1491 vec![
1492 CellValue::String("Name".to_string()),
1493 CellValue::String("Age".to_string()),
1494 ],
1495 vec![
1496 CellValue::String("Alice".to_string()),
1497 CellValue::Number(30.0),
1498 ],
1499 vec![
1500 CellValue::String("Bob".to_string()),
1501 CellValue::Number(25.0),
1502 ],
1503 ],
1504 1,
1505 1,
1506 )
1507 .unwrap();
1508
1509 assert_eq!(
1510 wb.get_cell_value("Sheet1", "A1").unwrap(),
1511 CellValue::String("Name".to_string())
1512 );
1513 assert_eq!(
1514 wb.get_cell_value("Sheet1", "B1").unwrap(),
1515 CellValue::String("Age".to_string())
1516 );
1517 assert_eq!(
1518 wb.get_cell_value("Sheet1", "A2").unwrap(),
1519 CellValue::String("Alice".to_string())
1520 );
1521 assert_eq!(
1522 wb.get_cell_value("Sheet1", "B2").unwrap(),
1523 CellValue::Number(30.0)
1524 );
1525 assert_eq!(
1526 wb.get_cell_value("Sheet1", "A3").unwrap(),
1527 CellValue::String("Bob".to_string())
1528 );
1529 assert_eq!(
1530 wb.get_cell_value("Sheet1", "B3").unwrap(),
1531 CellValue::Number(25.0)
1532 );
1533 }
1534
1535 #[test]
1536 fn test_set_sheet_data_with_offset() {
1537 let mut wb = Workbook::new();
1538 wb.set_sheet_data(
1540 "Sheet1",
1541 vec![
1542 vec![CellValue::Number(1.0), CellValue::Number(2.0)],
1543 vec![CellValue::Number(3.0), CellValue::Number(4.0)],
1544 ],
1545 3,
1546 3,
1547 )
1548 .unwrap();
1549
1550 assert_eq!(
1551 wb.get_cell_value("Sheet1", "C3").unwrap(),
1552 CellValue::Number(1.0)
1553 );
1554 assert_eq!(
1555 wb.get_cell_value("Sheet1", "D3").unwrap(),
1556 CellValue::Number(2.0)
1557 );
1558 assert_eq!(
1559 wb.get_cell_value("Sheet1", "C4").unwrap(),
1560 CellValue::Number(3.0)
1561 );
1562 assert_eq!(
1563 wb.get_cell_value("Sheet1", "D4").unwrap(),
1564 CellValue::Number(4.0)
1565 );
1566 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1568 }
1569
1570 #[test]
1571 fn test_set_sheet_data_roundtrip() {
1572 let dir = TempDir::new().unwrap();
1573 let path = dir.path().join("batch_roundtrip.xlsx");
1574
1575 let mut wb = Workbook::new();
1576 wb.set_sheet_data(
1577 "Sheet1",
1578 vec![
1579 vec![
1580 CellValue::String("Header1".to_string()),
1581 CellValue::String("Header2".to_string()),
1582 ],
1583 vec![CellValue::Number(100.0), CellValue::Bool(true)],
1584 ],
1585 1,
1586 1,
1587 )
1588 .unwrap();
1589 wb.save(&path).unwrap();
1590
1591 let wb2 = Workbook::open(&path).unwrap();
1592 assert_eq!(
1593 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1594 CellValue::String("Header1".to_string())
1595 );
1596 assert_eq!(
1597 wb2.get_cell_value("Sheet1", "B1").unwrap(),
1598 CellValue::String("Header2".to_string())
1599 );
1600 assert_eq!(
1601 wb2.get_cell_value("Sheet1", "A2").unwrap(),
1602 CellValue::Number(100.0)
1603 );
1604 assert_eq!(
1605 wb2.get_cell_value("Sheet1", "B2").unwrap(),
1606 CellValue::Bool(true)
1607 );
1608 }
1609
1610 #[test]
1611 fn test_set_row_values() {
1612 let mut wb = Workbook::new();
1613 wb.set_row_values(
1614 "Sheet1",
1615 1,
1616 1,
1617 vec![
1618 CellValue::String("A".to_string()),
1619 CellValue::String("B".to_string()),
1620 CellValue::String("C".to_string()),
1621 ],
1622 )
1623 .unwrap();
1624
1625 assert_eq!(
1626 wb.get_cell_value("Sheet1", "A1").unwrap(),
1627 CellValue::String("A".to_string())
1628 );
1629 assert_eq!(
1630 wb.get_cell_value("Sheet1", "B1").unwrap(),
1631 CellValue::String("B".to_string())
1632 );
1633 assert_eq!(
1634 wb.get_cell_value("Sheet1", "C1").unwrap(),
1635 CellValue::String("C".to_string())
1636 );
1637 }
1638
1639 #[test]
1640 fn test_set_row_values_with_offset() {
1641 let mut wb = Workbook::new();
1642 wb.set_row_values(
1644 "Sheet1",
1645 2,
1646 4,
1647 vec![CellValue::Number(10.0), CellValue::Number(20.0)],
1648 )
1649 .unwrap();
1650
1651 assert_eq!(
1652 wb.get_cell_value("Sheet1", "D2").unwrap(),
1653 CellValue::Number(10.0)
1654 );
1655 assert_eq!(
1656 wb.get_cell_value("Sheet1", "E2").unwrap(),
1657 CellValue::Number(20.0)
1658 );
1659 }
1660
1661 #[test]
1662 fn test_set_sheet_data_merges_with_existing() {
1663 let mut wb = Workbook::new();
1664 wb.set_cell_value("Sheet1", "A1", "existing").unwrap();
1665 wb.set_sheet_data(
1666 "Sheet1",
1667 vec![vec![CellValue::Empty, CellValue::String("new".to_string())]],
1668 1,
1669 1,
1670 )
1671 .unwrap();
1672
1673 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1675 assert_eq!(
1677 wb.get_cell_value("Sheet1", "B1").unwrap(),
1678 CellValue::String("new".to_string())
1679 );
1680 }
1681}