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