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)) => {
159 let n: f64 = v
160 .parse()
161 .map_err(|_| Error::Internal(format!("invalid number: {v}")))?;
162 if matches!(
163 self.date_interpretation,
164 super::open_options::DateInterpretation::NumFmt
165 ) && self.is_date_styled_cell(xml_cell)
166 {
167 return Ok(CellValue::Date(n));
168 }
169 Ok(CellValue::Number(n))
170 }
171 _ => Ok(CellValue::Empty),
173 }
174 }
175
176 pub(crate) fn is_date_styled_cell(&self, xml_cell: &Cell) -> bool {
178 let style_idx = match xml_cell.s {
179 Some(idx) => idx as usize,
180 None => return false,
181 };
182 let xf = match self.stylesheet.cell_xfs.xfs.get(style_idx) {
183 Some(xf) => xf,
184 None => return false,
185 };
186 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
187 if crate::cell::is_date_num_fmt(num_fmt_id) {
189 return true;
190 }
191 if num_fmt_id >= 164 {
193 if let Some(ref num_fmts) = self.stylesheet.num_fmts {
194 if let Some(nf) = num_fmts
195 .num_fmts
196 .iter()
197 .find(|nf| nf.num_fmt_id == num_fmt_id)
198 {
199 return crate::cell::is_date_format_code(&nf.format_code);
200 }
201 }
202 }
203 false
204 }
205
206 pub fn get_cell_formatted_value(&self, sheet: &str, cell: &str) -> Result<String> {
212 let ws = self.worksheet_ref(sheet)?;
213 let (col, row) = cell_name_to_coordinates(cell)?;
214
215 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
216 Ok(idx) => &ws.sheet_data.rows[idx],
217 Err(_) => return Ok(String::new()),
218 };
219
220 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
221 Ok(idx) => &xml_row.cells[idx],
222 Err(_) => return Ok(String::new()),
223 };
224
225 let cell_value = self.xml_cell_to_value(xml_cell)?;
226
227 let numeric_val = match &cell_value {
228 CellValue::Number(n) => Some(*n),
229 CellValue::Date(n) => Some(*n),
230 CellValue::Formula {
231 result: Some(boxed),
232 ..
233 } => match boxed.as_ref() {
234 CellValue::Number(n) => Some(*n),
235 CellValue::Date(n) => Some(*n),
236 _ => None,
237 },
238 _ => None,
239 };
240
241 if let Some(val) = numeric_val {
242 if let Some(format_code) = self.cell_format_code(xml_cell) {
243 return Ok(crate::numfmt::format_number(val, &format_code));
244 }
245 }
246
247 Ok(cell_value.to_string())
248 }
249
250 pub(crate) fn cell_format_code(&self, xml_cell: &Cell) -> Option<String> {
253 let style_idx = xml_cell.s? as usize;
254 let xf = self.stylesheet.cell_xfs.xfs.get(style_idx)?;
255 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
256
257 if num_fmt_id == 0 {
258 return None;
259 }
260
261 if let Some(code) = crate::numfmt::builtin_format_code(num_fmt_id) {
263 return Some(code.to_string());
264 }
265
266 if let Some(ref num_fmts) = self.stylesheet.num_fmts {
268 if let Some(nf) = num_fmts
269 .num_fmts
270 .iter()
271 .find(|nf| nf.num_fmt_id == num_fmt_id)
272 {
273 return Some(nf.format_code.clone());
274 }
275 }
276
277 None
278 }
279
280 pub fn add_style(&mut self, style: &crate::style::Style) -> Result<u32> {
285 crate::style::add_style(&mut self.stylesheet, style)
286 }
287
288 pub fn get_cell_style(&self, sheet: &str, cell: &str) -> Result<Option<u32>> {
293 let ws = self.worksheet_ref(sheet)?;
294
295 let (col, row) = cell_name_to_coordinates(cell)?;
296
297 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
299 Ok(idx) => &ws.sheet_data.rows[idx],
300 Err(_) => return Ok(None),
301 };
302
303 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
305 Ok(idx) => &xml_row.cells[idx],
306 Err(_) => return Ok(None),
307 };
308
309 Ok(xml_cell.s)
310 }
311
312 pub fn set_cell_style(&mut self, sheet: &str, cell: &str, style_id: u32) -> Result<()> {
317 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
319 return Err(Error::StyleNotFound { id: style_id });
320 }
321
322 let sheet_idx = self.sheet_index(sheet)?;
323 let ws = self.worksheet_mut_by_index(sheet_idx)?;
324
325 let (col, row_num) = cell_name_to_coordinates(cell)?;
326 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
327
328 let row_idx = match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
330 Ok(idx) => idx,
331 Err(idx) => {
332 ws.sheet_data.rows.insert(idx, new_row(row_num));
333 idx
334 }
335 };
336
337 let row = &mut ws.sheet_data.rows[row_idx];
338
339 let cell_idx = match row.cells.binary_search_by_key(&col, |c| c.col) {
341 Ok(idx) => idx,
342 Err(insert_pos) => {
343 row.cells.insert(
344 insert_pos,
345 Cell {
346 r: cell_ref.into(),
347 col,
348 s: None,
349 t: CellTypeTag::None,
350 v: None,
351 f: None,
352 is: None,
353 },
354 );
355 insert_pos
356 }
357 };
358
359 row.cells[cell_idx].s = Some(style_id);
360 Ok(())
361 }
362
363 pub fn merge_cells(&mut self, sheet: &str, top_left: &str, bottom_right: &str) -> Result<()> {
368 let ws = self.worksheet_mut(sheet)?;
369 crate::merge::merge_cells(ws, top_left, bottom_right)
370 }
371
372 pub fn unmerge_cell(&mut self, sheet: &str, reference: &str) -> Result<()> {
376 let ws = self.worksheet_mut(sheet)?;
377 crate::merge::unmerge_cell(ws, reference)
378 }
379
380 pub fn get_merge_cells(&self, sheet: &str) -> Result<Vec<String>> {
384 let ws = self.worksheet_ref(sheet)?;
385 Ok(crate::merge::get_merge_cells(ws))
386 }
387
388 pub fn set_cell_formula(&mut self, sheet: &str, cell: &str, formula: &str) -> Result<()> {
393 self.set_cell_value(
394 sheet,
395 cell,
396 CellValue::Formula {
397 expr: formula.to_string(),
398 result: None,
399 },
400 )
401 }
402
403 pub fn fill_formula(&mut self, sheet: &str, range: &str, formula: &str) -> Result<()> {
411 let parts: Vec<&str> = range.split(':').collect();
412 if parts.len() != 2 {
413 return Err(Error::InvalidCellReference(format!(
414 "invalid range: {range}"
415 )));
416 }
417 let (start_col, start_row) = cell_name_to_coordinates(parts[0])?;
418 let (end_col, end_row) = cell_name_to_coordinates(parts[1])?;
419
420 if start_col != end_col {
421 return Err(Error::InvalidCellReference(
422 "fill_formula only supports single-column ranges".to_string(),
423 ));
424 }
425
426 for row in start_row..=end_row {
427 let row_offset = row as i32 - start_row as i32;
428 let adjusted = if row_offset == 0 {
429 formula.to_string()
430 } else {
431 crate::cell_ref_shift::shift_cell_references_with_abs(
432 formula,
433 |col, r, _abs_col, abs_row| {
434 if abs_row {
435 (col, r)
436 } else {
437 (col, (r as i32 + row_offset) as u32)
438 }
439 },
440 )?
441 };
442 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(start_col, row)?;
443 self.set_cell_formula(sheet, &cell_ref, &adjusted)?;
444 }
445 Ok(())
446 }
447
448 pub fn set_cell_rich_text(
450 &mut self,
451 sheet: &str,
452 cell: &str,
453 runs: Vec<crate::rich_text::RichTextRun>,
454 ) -> Result<()> {
455 self.set_cell_value(sheet, cell, CellValue::RichString(runs))
456 }
457
458 pub fn get_cell_rich_text(
463 &self,
464 sheet: &str,
465 cell: &str,
466 ) -> Result<Option<Vec<crate::rich_text::RichTextRun>>> {
467 let (col, row) = cell_name_to_coordinates(cell)?;
468 let ws = self.worksheet_ref(sheet)?;
469
470 let xml_row = match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
472 Ok(idx) => &ws.sheet_data.rows[idx],
473 Err(_) => return Ok(None),
474 };
475
476 let xml_cell = match xml_row.cells.binary_search_by_key(&col, |c| c.col) {
478 Ok(idx) => &xml_row.cells[idx],
479 Err(_) => return Ok(None),
480 };
481
482 if xml_cell.t == CellTypeTag::SharedString {
483 if let Some(ref v) = xml_cell.v {
484 if let Ok(idx) = v.parse::<usize>() {
485 return Ok(self.sst_runtime.get_rich_text(idx));
486 }
487 }
488 }
489 Ok(None)
490 }
491
492 pub fn set_cell_values(
497 &mut self,
498 sheet: &str,
499 entries: Vec<(String, CellValue)>,
500 ) -> Result<()> {
501 let sheet_idx = self.sheet_index(sheet)?;
502 self.invalidate_streamed(sheet_idx);
503 self.ensure_hydrated(sheet_idx)?;
504 self.mark_sheet_dirty(sheet_idx);
505
506 for (cell, value) in entries {
507 if let CellValue::String(ref s) = value {
508 if s.len() > MAX_CELL_CHARS {
509 return Err(Error::CellValueTooLong {
510 length: s.len(),
511 max: MAX_CELL_CHARS,
512 });
513 }
514 }
515
516 let (col, row_num) = cell_name_to_coordinates(&cell)?;
517 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
518
519 let row_idx = {
520 let ws = self.worksheets[sheet_idx].1.get_mut().unwrap();
521 match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
522 Ok(idx) => idx,
523 Err(idx) => {
524 ws.sheet_data.rows.insert(idx, new_row(row_num));
525 idx
526 }
527 }
528 };
529
530 if value == CellValue::Empty {
531 let row = &mut self.worksheets[sheet_idx]
532 .1
533 .get_mut()
534 .unwrap()
535 .sheet_data
536 .rows[row_idx];
537 if let Ok(idx) = row.cells.binary_search_by_key(&col, |c| c.col) {
538 row.cells.remove(idx);
539 }
540 continue;
541 }
542
543 let cell_idx = {
544 let row = &mut self.worksheets[sheet_idx]
545 .1
546 .get_mut()
547 .unwrap()
548 .sheet_data
549 .rows[row_idx];
550 match row.cells.binary_search_by_key(&col, |c| c.col) {
551 Ok(idx) => idx,
552 Err(pos) => {
553 row.cells.insert(
554 pos,
555 Cell {
556 r: cell_ref.into(),
557 col,
558 s: None,
559 t: CellTypeTag::None,
560 v: None,
561 f: None,
562 is: None,
563 },
564 );
565 pos
566 }
567 }
568 };
569
570 let xml_cell = &mut self.worksheets[sheet_idx]
571 .1
572 .get_mut()
573 .unwrap()
574 .sheet_data
575 .rows[row_idx]
576 .cells[cell_idx];
577 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
578 }
579
580 Ok(())
581 }
582
583 pub fn set_sheet_data(
592 &mut self,
593 sheet: &str,
594 data: Vec<Vec<CellValue>>,
595 start_row: u32,
596 start_col: u32,
597 ) -> Result<()> {
598 let sheet_idx = self.sheet_index(sheet)?;
599 self.ensure_hydrated(sheet_idx)?;
600 self.mark_sheet_dirty(sheet_idx);
601
602 let max_cols = data.iter().map(|r| r.len()).max().unwrap_or(0) as u32;
604 let col_names: Vec<String> = (0..max_cols)
605 .map(|i| crate::utils::cell_ref::column_number_to_name(start_col + i))
606 .collect::<Result<Vec<_>>>()?;
607
608 for (row_offset, row_values) in data.into_iter().enumerate() {
609 let row_num = start_row + row_offset as u32;
610
611 let row_idx = {
612 let ws = self.worksheets[sheet_idx].1.get_mut().unwrap();
613 match ws.sheet_data.rows.binary_search_by_key(&row_num, |r| r.r) {
614 Ok(idx) => idx,
615 Err(idx) => {
616 ws.sheet_data.rows.insert(idx, new_row(row_num));
617 idx
618 }
619 }
620 };
621
622 for (col_offset, value) in row_values.into_iter().enumerate() {
623 let col = start_col + col_offset as u32;
624
625 if let CellValue::String(ref s) = value {
626 if s.len() > MAX_CELL_CHARS {
627 return Err(Error::CellValueTooLong {
628 length: s.len(),
629 max: MAX_CELL_CHARS,
630 });
631 }
632 }
633
634 if value == CellValue::Empty {
635 let row = &mut self.worksheets[sheet_idx]
636 .1
637 .get_mut()
638 .unwrap()
639 .sheet_data
640 .rows[row_idx];
641 if let Ok(idx) = row.cells.binary_search_by_key(&col, |c| c.col) {
642 row.cells.remove(idx);
643 }
644 continue;
645 }
646
647 let cell_ref = format!("{}{}", col_names[col_offset], row_num);
648
649 let cell_idx = {
650 let row = &mut self.worksheets[sheet_idx]
651 .1
652 .get_mut()
653 .unwrap()
654 .sheet_data
655 .rows[row_idx];
656 match row.cells.binary_search_by_key(&col, |c| c.col) {
657 Ok(idx) => idx,
658 Err(pos) => {
659 row.cells.insert(
660 pos,
661 Cell {
662 r: cell_ref.into(),
663 col,
664 s: None,
665 t: CellTypeTag::None,
666 v: None,
667 f: None,
668 is: None,
669 },
670 );
671 pos
672 }
673 }
674 };
675
676 let xml_cell = &mut self.worksheets[sheet_idx]
677 .1
678 .get_mut()
679 .unwrap()
680 .sheet_data
681 .rows[row_idx]
682 .cells[cell_idx];
683 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
684 }
685 }
686
687 Ok(())
688 }
689
690 pub fn set_row_values(
695 &mut self,
696 sheet: &str,
697 row_num: u32,
698 start_col: u32,
699 values: Vec<CellValue>,
700 ) -> Result<()> {
701 self.set_sheet_data(sheet, vec![values], row_num, start_col)
702 }
703}
704
705pub(crate) fn value_to_xml_cell(
707 sst: &mut SharedStringTable,
708 xml_cell: &mut Cell,
709 value: CellValue,
710) {
711 xml_cell.t = CellTypeTag::None;
713 xml_cell.v = None;
714 xml_cell.f = None;
715 xml_cell.is = None;
716
717 match value {
718 CellValue::String(s) => {
719 let idx = sst.add_owned(s);
720 xml_cell.t = CellTypeTag::SharedString;
721 xml_cell.v = Some(idx.to_string());
722 }
723 CellValue::Number(n) => {
724 xml_cell.v = Some(n.to_string());
725 }
726 CellValue::Date(serial) => {
727 xml_cell.v = Some(serial.to_string());
730 }
731 CellValue::Bool(b) => {
732 xml_cell.t = CellTypeTag::Boolean;
733 xml_cell.v = Some(if b { "1" } else { "0" }.to_string());
734 }
735 CellValue::Formula { expr, .. } => {
736 xml_cell.f = Some(Box::new(CellFormula {
737 t: None,
738 reference: None,
739 si: None,
740 value: Some(expr),
741 }));
742 }
743 CellValue::Error(e) => {
744 xml_cell.t = CellTypeTag::Error;
745 xml_cell.v = Some(e);
746 }
747 CellValue::Empty => {
748 }
750 CellValue::RichString(runs) => {
751 let idx = sst.add_rich_text(&runs);
752 xml_cell.t = CellTypeTag::SharedString;
753 xml_cell.v = Some(idx.to_string());
754 }
755 }
756}
757
758pub(crate) fn new_row(row_num: u32) -> Row {
760 Row {
761 r: row_num,
762 spans: None,
763 s: None,
764 custom_format: None,
765 ht: None,
766 hidden: None,
767 custom_height: None,
768 outline_level: None,
769 cells: vec![],
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776 use tempfile::TempDir;
777
778 #[test]
779 fn test_set_and_get_string_value() {
780 let mut wb = Workbook::new();
781 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
782 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
783 assert_eq!(val, CellValue::String("Hello".to_string()));
784 }
785
786 #[test]
787 fn test_set_and_get_number_value() {
788 let mut wb = Workbook::new();
789 wb.set_cell_value("Sheet1", "B2", 42.5f64).unwrap();
790 let val = wb.get_cell_value("Sheet1", "B2").unwrap();
791 assert_eq!(val, CellValue::Number(42.5));
792 }
793
794 #[test]
795 fn test_set_and_get_bool_value() {
796 let mut wb = Workbook::new();
797 wb.set_cell_value("Sheet1", "C3", true).unwrap();
798 let val = wb.get_cell_value("Sheet1", "C3").unwrap();
799 assert_eq!(val, CellValue::Bool(true));
800
801 wb.set_cell_value("Sheet1", "D4", false).unwrap();
802 let val = wb.get_cell_value("Sheet1", "D4").unwrap();
803 assert_eq!(val, CellValue::Bool(false));
804 }
805
806 #[test]
807 fn test_set_value_sheet_not_found() {
808 let mut wb = Workbook::new();
809 let result = wb.set_cell_value("NoSuchSheet", "A1", "test");
810 assert!(result.is_err());
811 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
812 }
813
814 #[test]
815 fn test_get_value_sheet_not_found() {
816 let wb = Workbook::new();
817 let result = wb.get_cell_value("NoSuchSheet", "A1");
818 assert!(result.is_err());
819 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
820 }
821
822 #[test]
823 fn test_get_empty_cell_returns_empty() {
824 let wb = Workbook::new();
825 let val = wb.get_cell_value("Sheet1", "Z99").unwrap();
826 assert_eq!(val, CellValue::Empty);
827 }
828
829 #[test]
830 fn test_cell_value_roundtrip_save_open() {
831 let dir = TempDir::new().unwrap();
832 let path = dir.path().join("cell_roundtrip.xlsx");
833
834 let mut wb = Workbook::new();
835 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
836 wb.set_cell_value("Sheet1", "B1", 42.0f64).unwrap();
837 wb.set_cell_value("Sheet1", "C1", true).unwrap();
838 wb.save(&path).unwrap();
839
840 let wb2 = Workbook::open(&path).unwrap();
841 assert_eq!(
842 wb2.get_cell_value("Sheet1", "A1").unwrap(),
843 CellValue::String("Hello".to_string())
844 );
845 assert_eq!(
846 wb2.get_cell_value("Sheet1", "B1").unwrap(),
847 CellValue::Number(42.0)
848 );
849 assert_eq!(
850 wb2.get_cell_value("Sheet1", "C1").unwrap(),
851 CellValue::Bool(true)
852 );
853 }
854
855 #[test]
856 fn test_set_empty_value_clears_cell() {
857 let mut wb = Workbook::new();
858 wb.set_cell_value("Sheet1", "A1", "test").unwrap();
859 assert_eq!(
860 wb.get_cell_value("Sheet1", "A1").unwrap(),
861 CellValue::String("test".to_string())
862 );
863
864 wb.set_cell_value("Sheet1", "A1", CellValue::Empty).unwrap();
865 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
866 }
867
868 #[test]
869 fn test_string_too_long_returns_error() {
870 let mut wb = Workbook::new();
871 let long_string = "x".repeat(MAX_CELL_CHARS + 1);
872 let result = wb.set_cell_value("Sheet1", "A1", long_string.as_str());
873 assert!(result.is_err());
874 assert!(matches!(
875 result.unwrap_err(),
876 Error::CellValueTooLong { .. }
877 ));
878 }
879
880 #[test]
881 fn test_set_multiple_cells_same_row() {
882 let mut wb = Workbook::new();
883 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
884 wb.set_cell_value("Sheet1", "B1", "second").unwrap();
885 wb.set_cell_value("Sheet1", "C1", "third").unwrap();
886
887 assert_eq!(
888 wb.get_cell_value("Sheet1", "A1").unwrap(),
889 CellValue::String("first".to_string())
890 );
891 assert_eq!(
892 wb.get_cell_value("Sheet1", "B1").unwrap(),
893 CellValue::String("second".to_string())
894 );
895 assert_eq!(
896 wb.get_cell_value("Sheet1", "C1").unwrap(),
897 CellValue::String("third".to_string())
898 );
899 }
900
901 #[test]
902 fn test_overwrite_cell_value() {
903 let mut wb = Workbook::new();
904 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
905 wb.set_cell_value("Sheet1", "A1", "updated").unwrap();
906
907 assert_eq!(
908 wb.get_cell_value("Sheet1", "A1").unwrap(),
909 CellValue::String("updated".to_string())
910 );
911 }
912
913 #[test]
914 fn test_set_and_get_error_value() {
915 let mut wb = Workbook::new();
916 wb.set_cell_value("Sheet1", "A1", CellValue::Error("#DIV/0!".to_string()))
917 .unwrap();
918 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
919 assert_eq!(val, CellValue::Error("#DIV/0!".to_string()));
920 }
921
922 #[test]
923 fn test_set_and_get_date_value() {
924 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
925
926 let mut wb = Workbook::new();
927 let style_id = wb
929 .add_style(&Style {
930 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
931 ..Style::default()
932 })
933 .unwrap();
934
935 let date_serial =
937 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
938 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
939 .unwrap();
940 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
941
942 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
944 assert_eq!(val, CellValue::Date(date_serial));
945 }
946
947 #[test]
948 fn test_date_value_without_style_returns_number() {
949 let mut wb = Workbook::new();
950 let date_serial =
952 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
953 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
954 .unwrap();
955
956 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
958 assert_eq!(val, CellValue::Number(date_serial));
959 }
960
961 #[test]
962 fn test_date_value_roundtrip_through_save() {
963 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
964
965 let mut wb = Workbook::new();
966 let style_id = wb
967 .add_style(&Style {
968 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATETIME)),
969 ..Style::default()
970 })
971 .unwrap();
972
973 let dt = chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
974 .unwrap()
975 .and_hms_opt(14, 30, 0)
976 .unwrap();
977 let serial = crate::cell::datetime_to_serial(dt);
978 wb.set_cell_value("Sheet1", "A1", CellValue::Date(serial))
979 .unwrap();
980 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
981
982 let dir = tempfile::TempDir::new().unwrap();
983 let path = dir.path().join("date_test.xlsx");
984 wb.save(&path).unwrap();
985
986 let wb2 = Workbook::open(&path).unwrap();
987 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
988 assert_eq!(val, CellValue::Date(serial));
989 }
990
991 #[test]
992 fn test_date_from_naive_date_conversion() {
993 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
994 let cv: CellValue = date.into();
995 match cv {
996 CellValue::Date(s) => {
997 let roundtripped = crate::cell::serial_to_date(s).unwrap();
998 assert_eq!(roundtripped, date);
999 }
1000 _ => panic!("expected Date variant"),
1001 }
1002 }
1003
1004 #[test]
1005 fn test_set_and_get_formula_value() {
1006 let mut wb = Workbook::new();
1007 wb.set_cell_value(
1008 "Sheet1",
1009 "A1",
1010 CellValue::Formula {
1011 expr: "SUM(B1:B10)".to_string(),
1012 result: None,
1013 },
1014 )
1015 .unwrap();
1016 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1017 match val {
1018 CellValue::Formula { expr, .. } => {
1019 assert_eq!(expr, "SUM(B1:B10)");
1020 }
1021 other => panic!("expected Formula, got {:?}", other),
1022 }
1023 }
1024
1025 #[test]
1026 fn test_set_i32_value() {
1027 let mut wb = Workbook::new();
1028 wb.set_cell_value("Sheet1", "A1", 100i32).unwrap();
1029 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1030 assert_eq!(val, CellValue::Number(100.0));
1031 }
1032
1033 #[test]
1034 fn test_set_string_at_max_length() {
1035 let mut wb = Workbook::new();
1036 let max_string = "x".repeat(MAX_CELL_CHARS);
1037 wb.set_cell_value("Sheet1", "A1", max_string.as_str())
1038 .unwrap();
1039 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1040 assert_eq!(val, CellValue::String(max_string));
1041 }
1042
1043 #[test]
1044 fn test_set_cells_different_rows() {
1045 let mut wb = Workbook::new();
1046 wb.set_cell_value("Sheet1", "A1", "row1").unwrap();
1047 wb.set_cell_value("Sheet1", "A3", "row3").unwrap();
1048 wb.set_cell_value("Sheet1", "A2", "row2").unwrap(); assert_eq!(
1051 wb.get_cell_value("Sheet1", "A1").unwrap(),
1052 CellValue::String("row1".to_string())
1053 );
1054 assert_eq!(
1055 wb.get_cell_value("Sheet1", "A2").unwrap(),
1056 CellValue::String("row2".to_string())
1057 );
1058 assert_eq!(
1059 wb.get_cell_value("Sheet1", "A3").unwrap(),
1060 CellValue::String("row3".to_string())
1061 );
1062 }
1063
1064 #[test]
1065 fn test_string_deduplication_in_sst() {
1066 let mut wb = Workbook::new();
1067 wb.set_cell_value("Sheet1", "A1", "same").unwrap();
1068 wb.set_cell_value("Sheet1", "A2", "same").unwrap();
1069 wb.set_cell_value("Sheet1", "A3", "different").unwrap();
1070
1071 assert_eq!(wb.sst_runtime.len(), 2);
1073 assert_eq!(
1074 wb.get_cell_value("Sheet1", "A1").unwrap(),
1075 CellValue::String("same".to_string())
1076 );
1077 assert_eq!(
1078 wb.get_cell_value("Sheet1", "A2").unwrap(),
1079 CellValue::String("same".to_string())
1080 );
1081 }
1082
1083 #[test]
1084 fn test_add_style_returns_id() {
1085 use crate::style::{FontStyle, Style};
1086
1087 let mut wb = Workbook::new();
1088 let style = Style {
1089 font: Some(FontStyle {
1090 bold: true,
1091 ..FontStyle::default()
1092 }),
1093 ..Style::default()
1094 };
1095 let id = wb.add_style(&style).unwrap();
1096 assert!(id > 0);
1097 }
1098
1099 #[test]
1100 fn test_get_cell_style_unstyled_cell_returns_none() {
1101 let wb = Workbook::new();
1102 let result = wb.get_cell_style("Sheet1", "A1").unwrap();
1103 assert!(result.is_none());
1104 }
1105
1106 #[test]
1107 fn test_set_cell_style_on_existing_value() {
1108 use crate::style::{FontStyle, Style};
1109
1110 let mut wb = Workbook::new();
1111 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
1112
1113 let style = Style {
1114 font: Some(FontStyle {
1115 bold: true,
1116 ..FontStyle::default()
1117 }),
1118 ..Style::default()
1119 };
1120 let style_id = wb.add_style(&style).unwrap();
1121 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
1122
1123 let retrieved_id = wb.get_cell_style("Sheet1", "A1").unwrap();
1124 assert_eq!(retrieved_id, Some(style_id));
1125
1126 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1128 assert_eq!(val, CellValue::String("Hello".to_string()));
1129 }
1130
1131 #[test]
1132 fn test_set_cell_style_on_empty_cell_creates_cell() {
1133 use crate::style::{FontStyle, Style};
1134
1135 let mut wb = Workbook::new();
1136 let style = Style {
1137 font: Some(FontStyle {
1138 bold: true,
1139 ..FontStyle::default()
1140 }),
1141 ..Style::default()
1142 };
1143 let style_id = wb.add_style(&style).unwrap();
1144
1145 wb.set_cell_style("Sheet1", "B5", style_id).unwrap();
1147
1148 let retrieved_id = wb.get_cell_style("Sheet1", "B5").unwrap();
1149 assert_eq!(retrieved_id, Some(style_id));
1150
1151 let val = wb.get_cell_value("Sheet1", "B5").unwrap();
1153 assert_eq!(val, CellValue::Empty);
1154 }
1155
1156 #[test]
1157 fn test_set_cell_style_invalid_id() {
1158 let mut wb = Workbook::new();
1159 let result = wb.set_cell_style("Sheet1", "A1", 999);
1160 assert!(result.is_err());
1161 assert!(matches!(result.unwrap_err(), Error::StyleNotFound { .. }));
1162 }
1163
1164 #[test]
1165 fn test_set_cell_style_sheet_not_found() {
1166 let mut wb = Workbook::new();
1167 let style = crate::style::Style::default();
1168 let style_id = wb.add_style(&style).unwrap();
1169 let result = wb.set_cell_style("NoSuchSheet", "A1", style_id);
1170 assert!(result.is_err());
1171 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1172 }
1173
1174 #[test]
1175 fn test_get_cell_style_sheet_not_found() {
1176 let wb = Workbook::new();
1177 let result = wb.get_cell_style("NoSuchSheet", "A1");
1178 assert!(result.is_err());
1179 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1180 }
1181
1182 #[test]
1183 fn test_style_roundtrip_save_open() {
1184 use crate::style::{
1185 AlignmentStyle, BorderLineStyle, BorderSideStyle, BorderStyle, FillStyle, FontStyle,
1186 HorizontalAlign, NumFmtStyle, PatternType, Style, StyleColor, VerticalAlign,
1187 };
1188
1189 let dir = TempDir::new().unwrap();
1190 let path = dir.path().join("style_roundtrip.xlsx");
1191
1192 let mut wb = Workbook::new();
1193 wb.set_cell_value("Sheet1", "A1", "Styled").unwrap();
1194
1195 let style = Style {
1196 font: Some(FontStyle {
1197 name: Some("Arial".to_string()),
1198 size: Some(14.0),
1199 bold: true,
1200 italic: true,
1201 color: Some(StyleColor::Rgb("FFFF0000".to_string())),
1202 ..FontStyle::default()
1203 }),
1204 fill: Some(FillStyle {
1205 pattern: PatternType::Solid,
1206 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
1207 bg_color: None,
1208 gradient: None,
1209 }),
1210 border: Some(BorderStyle {
1211 left: Some(BorderSideStyle {
1212 style: BorderLineStyle::Thin,
1213 color: None,
1214 }),
1215 right: Some(BorderSideStyle {
1216 style: BorderLineStyle::Thin,
1217 color: None,
1218 }),
1219 top: Some(BorderSideStyle {
1220 style: BorderLineStyle::Thin,
1221 color: None,
1222 }),
1223 bottom: Some(BorderSideStyle {
1224 style: BorderLineStyle::Thin,
1225 color: None,
1226 }),
1227 diagonal: None,
1228 }),
1229 alignment: Some(AlignmentStyle {
1230 horizontal: Some(HorizontalAlign::Center),
1231 vertical: Some(VerticalAlign::Center),
1232 wrap_text: true,
1233 ..AlignmentStyle::default()
1234 }),
1235 num_fmt: Some(NumFmtStyle::Custom("#,##0.00".to_string())),
1236 protection: None,
1237 };
1238 let style_id = wb.add_style(&style).unwrap();
1239 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
1240 wb.save(&path).unwrap();
1241
1242 let wb2 = Workbook::open(&path).unwrap();
1244 let retrieved_id = wb2.get_cell_style("Sheet1", "A1").unwrap();
1245 assert_eq!(retrieved_id, Some(style_id));
1246
1247 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
1249 assert_eq!(val, CellValue::String("Styled".to_string()));
1250
1251 let retrieved_style = crate::style::get_style(&wb2.stylesheet, style_id).unwrap();
1253 assert!(retrieved_style.font.is_some());
1254 let font = retrieved_style.font.unwrap();
1255 assert!(font.bold);
1256 assert!(font.italic);
1257 assert_eq!(font.name, Some("Arial".to_string()));
1258
1259 assert!(retrieved_style.fill.is_some());
1260 let fill = retrieved_style.fill.unwrap();
1261 assert_eq!(fill.pattern, PatternType::Solid);
1262
1263 assert!(retrieved_style.alignment.is_some());
1264 let align = retrieved_style.alignment.unwrap();
1265 assert_eq!(align.horizontal, Some(HorizontalAlign::Center));
1266 assert_eq!(align.vertical, Some(VerticalAlign::Center));
1267 assert!(align.wrap_text);
1268 }
1269
1270 #[test]
1271 fn test_set_and_get_cell_rich_text() {
1272 use crate::rich_text::RichTextRun;
1273
1274 let mut wb = Workbook::new();
1275 let runs = vec![
1276 RichTextRun {
1277 text: "Bold".to_string(),
1278 font: None,
1279 size: None,
1280 bold: true,
1281 italic: false,
1282 color: None,
1283 },
1284 RichTextRun {
1285 text: " Normal".to_string(),
1286 font: None,
1287 size: None,
1288 bold: false,
1289 italic: false,
1290 color: None,
1291 },
1292 ];
1293 wb.set_cell_rich_text("Sheet1", "A1", runs.clone()).unwrap();
1294
1295 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1297 assert_eq!(val.to_string(), "Bold Normal");
1298
1299 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1301 assert!(got.is_some());
1302 let got_runs = got.unwrap();
1303 assert_eq!(got_runs.len(), 2);
1304 assert_eq!(got_runs[0].text, "Bold");
1305 assert!(got_runs[0].bold);
1306 assert_eq!(got_runs[1].text, " Normal");
1307 assert!(!got_runs[1].bold);
1308 }
1309
1310 #[test]
1311 fn test_get_cell_rich_text_returns_none_for_plain() {
1312 let mut wb = Workbook::new();
1313 wb.set_cell_value("Sheet1", "A1", CellValue::String("plain".to_string()))
1314 .unwrap();
1315 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1316 assert!(got.is_none());
1317 }
1318
1319 #[test]
1320 fn test_rich_text_roundtrip_save_open() {
1321 use crate::rich_text::RichTextRun;
1322
1323 let dir = TempDir::new().unwrap();
1324 let path = dir.path().join("rich_text.xlsx");
1325
1326 let mut wb = Workbook::new();
1330 let runs = vec![
1331 RichTextRun {
1332 text: "Hello".to_string(),
1333 font: Some("Arial".to_string()),
1334 size: Some(14.0),
1335 bold: true,
1336 italic: false,
1337 color: Some("#FF0000".to_string()),
1338 },
1339 RichTextRun {
1340 text: "World".to_string(),
1341 font: None,
1342 size: None,
1343 bold: false,
1344 italic: true,
1345 color: None,
1346 },
1347 ];
1348 wb.set_cell_rich_text("Sheet1", "B2", runs).unwrap();
1349 wb.save(&path).unwrap();
1350
1351 let wb2 = Workbook::open(&path).unwrap();
1352 let val = wb2.get_cell_value("Sheet1", "B2").unwrap();
1353 assert_eq!(val.to_string(), "HelloWorld");
1354
1355 let got = wb2.get_cell_rich_text("Sheet1", "B2").unwrap();
1356 assert!(got.is_some());
1357 let got_runs = got.unwrap();
1358 assert_eq!(got_runs.len(), 2);
1359 assert_eq!(got_runs[0].text, "Hello");
1360 assert!(got_runs[0].bold);
1361 assert_eq!(got_runs[0].font.as_deref(), Some("Arial"));
1362 assert_eq!(got_runs[0].size, Some(14.0));
1363 assert_eq!(got_runs[0].color.as_deref(), Some("#FF0000"));
1364 assert_eq!(got_runs[1].text, "World");
1365 assert!(got_runs[1].italic);
1366 assert!(!got_runs[1].bold);
1367 }
1368
1369 #[test]
1370 fn test_set_cell_formula() {
1371 let mut wb = Workbook::new();
1372 wb.set_cell_formula("Sheet1", "A1", "SUM(B1:B10)").unwrap();
1373 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1374 match val {
1375 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:B10)"),
1376 other => panic!("expected Formula, got {:?}", other),
1377 }
1378 }
1379
1380 #[test]
1381 fn test_fill_formula_basic() {
1382 let mut wb = Workbook::new();
1383 wb.fill_formula("Sheet1", "D2:D5", "SUM(A2:C2)").unwrap();
1384
1385 match wb.get_cell_value("Sheet1", "D2").unwrap() {
1387 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A2:C2)"),
1388 other => panic!("D2: expected Formula, got {:?}", other),
1389 }
1390 match wb.get_cell_value("Sheet1", "D3").unwrap() {
1392 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:C3)"),
1393 other => panic!("D3: expected Formula, got {:?}", other),
1394 }
1395 match wb.get_cell_value("Sheet1", "D4").unwrap() {
1397 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A4:C4)"),
1398 other => panic!("D4: expected Formula, got {:?}", other),
1399 }
1400 match wb.get_cell_value("Sheet1", "D5").unwrap() {
1402 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A5:C5)"),
1403 other => panic!("D5: expected Formula, got {:?}", other),
1404 }
1405 }
1406
1407 #[test]
1408 fn test_fill_formula_preserves_absolute_refs() {
1409 let mut wb = Workbook::new();
1410 wb.fill_formula("Sheet1", "B1:B3", "$A$1*A1").unwrap();
1411
1412 match wb.get_cell_value("Sheet1", "B1").unwrap() {
1413 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A1"),
1414 other => panic!("B1: expected Formula, got {:?}", other),
1415 }
1416 match wb.get_cell_value("Sheet1", "B2").unwrap() {
1417 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A2"),
1418 other => panic!("B2: expected Formula, got {:?}", other),
1419 }
1420 match wb.get_cell_value("Sheet1", "B3").unwrap() {
1421 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A3"),
1422 other => panic!("B3: expected Formula, got {:?}", other),
1423 }
1424 }
1425
1426 #[test]
1427 fn test_fill_formula_single_cell() {
1428 let mut wb = Workbook::new();
1429 wb.fill_formula("Sheet1", "A1:A1", "B1+C1").unwrap();
1430 match wb.get_cell_value("Sheet1", "A1").unwrap() {
1431 CellValue::Formula { expr, .. } => assert_eq!(expr, "B1+C1"),
1432 other => panic!("expected Formula, got {:?}", other),
1433 }
1434 }
1435
1436 #[test]
1437 fn test_fill_formula_invalid_range() {
1438 let mut wb = Workbook::new();
1439 assert!(wb.fill_formula("Sheet1", "INVALID", "A1").is_err());
1440 }
1441
1442 #[test]
1443 fn test_fill_formula_multi_column_range_rejected() {
1444 let mut wb = Workbook::new();
1445 assert!(wb.fill_formula("Sheet1", "A1:B5", "C1").is_err());
1446 }
1447
1448 #[test]
1449 fn test_set_cell_values_batch() {
1450 let mut wb = Workbook::new();
1451 wb.set_cell_values(
1452 "Sheet1",
1453 vec![
1454 ("A1".to_string(), CellValue::String("hello".to_string())),
1455 ("B1".to_string(), CellValue::Number(42.0)),
1456 ("C1".to_string(), CellValue::Bool(true)),
1457 ("A2".to_string(), CellValue::String("world".to_string())),
1458 ],
1459 )
1460 .unwrap();
1461
1462 assert_eq!(
1463 wb.get_cell_value("Sheet1", "A1").unwrap(),
1464 CellValue::String("hello".to_string())
1465 );
1466 assert_eq!(
1467 wb.get_cell_value("Sheet1", "B1").unwrap(),
1468 CellValue::Number(42.0)
1469 );
1470 assert_eq!(
1471 wb.get_cell_value("Sheet1", "C1").unwrap(),
1472 CellValue::Bool(true)
1473 );
1474 assert_eq!(
1475 wb.get_cell_value("Sheet1", "A2").unwrap(),
1476 CellValue::String("world".to_string())
1477 );
1478 }
1479
1480 #[test]
1481 fn test_set_cell_values_empty_removes_cell() {
1482 let mut wb = Workbook::new();
1483 wb.set_cell_value("Sheet1", "A1", "existing").unwrap();
1484 wb.set_cell_values("Sheet1", vec![("A1".to_string(), CellValue::Empty)])
1485 .unwrap();
1486 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1487 }
1488
1489 #[test]
1490 fn test_set_sheet_data_basic() {
1491 let mut wb = Workbook::new();
1492 wb.set_sheet_data(
1493 "Sheet1",
1494 vec![
1495 vec![
1496 CellValue::String("Name".to_string()),
1497 CellValue::String("Age".to_string()),
1498 ],
1499 vec![
1500 CellValue::String("Alice".to_string()),
1501 CellValue::Number(30.0),
1502 ],
1503 vec![
1504 CellValue::String("Bob".to_string()),
1505 CellValue::Number(25.0),
1506 ],
1507 ],
1508 1,
1509 1,
1510 )
1511 .unwrap();
1512
1513 assert_eq!(
1514 wb.get_cell_value("Sheet1", "A1").unwrap(),
1515 CellValue::String("Name".to_string())
1516 );
1517 assert_eq!(
1518 wb.get_cell_value("Sheet1", "B1").unwrap(),
1519 CellValue::String("Age".to_string())
1520 );
1521 assert_eq!(
1522 wb.get_cell_value("Sheet1", "A2").unwrap(),
1523 CellValue::String("Alice".to_string())
1524 );
1525 assert_eq!(
1526 wb.get_cell_value("Sheet1", "B2").unwrap(),
1527 CellValue::Number(30.0)
1528 );
1529 assert_eq!(
1530 wb.get_cell_value("Sheet1", "A3").unwrap(),
1531 CellValue::String("Bob".to_string())
1532 );
1533 assert_eq!(
1534 wb.get_cell_value("Sheet1", "B3").unwrap(),
1535 CellValue::Number(25.0)
1536 );
1537 }
1538
1539 #[test]
1540 fn test_set_sheet_data_with_offset() {
1541 let mut wb = Workbook::new();
1542 wb.set_sheet_data(
1544 "Sheet1",
1545 vec![
1546 vec![CellValue::Number(1.0), CellValue::Number(2.0)],
1547 vec![CellValue::Number(3.0), CellValue::Number(4.0)],
1548 ],
1549 3,
1550 3,
1551 )
1552 .unwrap();
1553
1554 assert_eq!(
1555 wb.get_cell_value("Sheet1", "C3").unwrap(),
1556 CellValue::Number(1.0)
1557 );
1558 assert_eq!(
1559 wb.get_cell_value("Sheet1", "D3").unwrap(),
1560 CellValue::Number(2.0)
1561 );
1562 assert_eq!(
1563 wb.get_cell_value("Sheet1", "C4").unwrap(),
1564 CellValue::Number(3.0)
1565 );
1566 assert_eq!(
1567 wb.get_cell_value("Sheet1", "D4").unwrap(),
1568 CellValue::Number(4.0)
1569 );
1570 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1572 }
1573
1574 #[test]
1575 fn test_set_sheet_data_roundtrip() {
1576 let dir = TempDir::new().unwrap();
1577 let path = dir.path().join("batch_roundtrip.xlsx");
1578
1579 let mut wb = Workbook::new();
1580 wb.set_sheet_data(
1581 "Sheet1",
1582 vec![
1583 vec![
1584 CellValue::String("Header1".to_string()),
1585 CellValue::String("Header2".to_string()),
1586 ],
1587 vec![CellValue::Number(100.0), CellValue::Bool(true)],
1588 ],
1589 1,
1590 1,
1591 )
1592 .unwrap();
1593 wb.save(&path).unwrap();
1594
1595 let wb2 = Workbook::open(&path).unwrap();
1596 assert_eq!(
1597 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1598 CellValue::String("Header1".to_string())
1599 );
1600 assert_eq!(
1601 wb2.get_cell_value("Sheet1", "B1").unwrap(),
1602 CellValue::String("Header2".to_string())
1603 );
1604 assert_eq!(
1605 wb2.get_cell_value("Sheet1", "A2").unwrap(),
1606 CellValue::Number(100.0)
1607 );
1608 assert_eq!(
1609 wb2.get_cell_value("Sheet1", "B2").unwrap(),
1610 CellValue::Bool(true)
1611 );
1612 }
1613
1614 #[test]
1615 fn test_set_row_values() {
1616 let mut wb = Workbook::new();
1617 wb.set_row_values(
1618 "Sheet1",
1619 1,
1620 1,
1621 vec![
1622 CellValue::String("A".to_string()),
1623 CellValue::String("B".to_string()),
1624 CellValue::String("C".to_string()),
1625 ],
1626 )
1627 .unwrap();
1628
1629 assert_eq!(
1630 wb.get_cell_value("Sheet1", "A1").unwrap(),
1631 CellValue::String("A".to_string())
1632 );
1633 assert_eq!(
1634 wb.get_cell_value("Sheet1", "B1").unwrap(),
1635 CellValue::String("B".to_string())
1636 );
1637 assert_eq!(
1638 wb.get_cell_value("Sheet1", "C1").unwrap(),
1639 CellValue::String("C".to_string())
1640 );
1641 }
1642
1643 #[test]
1644 fn test_set_row_values_with_offset() {
1645 let mut wb = Workbook::new();
1646 wb.set_row_values(
1648 "Sheet1",
1649 2,
1650 4,
1651 vec![CellValue::Number(10.0), CellValue::Number(20.0)],
1652 )
1653 .unwrap();
1654
1655 assert_eq!(
1656 wb.get_cell_value("Sheet1", "D2").unwrap(),
1657 CellValue::Number(10.0)
1658 );
1659 assert_eq!(
1660 wb.get_cell_value("Sheet1", "E2").unwrap(),
1661 CellValue::Number(20.0)
1662 );
1663 }
1664
1665 #[test]
1666 fn test_set_sheet_data_merges_with_existing() {
1667 let mut wb = Workbook::new();
1668 wb.set_cell_value("Sheet1", "A1", "existing").unwrap();
1669 wb.set_sheet_data(
1670 "Sheet1",
1671 vec![vec![CellValue::Empty, CellValue::String("new".to_string())]],
1672 1,
1673 1,
1674 )
1675 .unwrap();
1676
1677 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
1679 assert_eq!(
1681 wb.get_cell_value("Sheet1", "B1").unwrap(),
1682 CellValue::String("new".to_string())
1683 );
1684 }
1685
1686 fn write_workbook_with_date_style(path: &std::path::Path) {
1689 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
1690 let mut wb = Workbook::new();
1691 let date_style = wb
1692 .add_style(&Style {
1693 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
1694 ..Style::default()
1695 })
1696 .unwrap();
1697 wb.set_cell_value("Sheet1", "A1", 46127.0_f64).unwrap();
1698 wb.set_cell_style("Sheet1", "A1", date_style).unwrap();
1699 wb.set_cell_value("Sheet1", "B1", 42.0_f64).unwrap();
1700 wb.save(path).unwrap();
1701 }
1702
1703 #[test]
1704 fn test_get_cell_value_num_fmt_default_promotes_date_styled_cells() {
1705 let dir = tempfile::TempDir::new().unwrap();
1706 let path = dir.path().join("get_cell_value_default.xlsx");
1707 write_workbook_with_date_style(&path);
1708
1709 let wb = Workbook::open(&path).unwrap();
1712 assert_eq!(
1713 wb.get_cell_value("Sheet1", "A1").unwrap(),
1714 CellValue::Date(46127.0)
1715 );
1716 assert_eq!(
1717 wb.get_cell_value("Sheet1", "B1").unwrap(),
1718 CellValue::Number(42.0)
1719 );
1720 }
1721
1722 #[test]
1723 fn test_get_cell_value_cell_type_keeps_numbers() {
1724 let dir = tempfile::TempDir::new().unwrap();
1725 let path = dir.path().join("get_cell_value_cell_type.xlsx");
1726 write_workbook_with_date_style(&path);
1727
1728 let wb = Workbook::open_with_options(
1731 &path,
1732 &crate::workbook::OpenOptions::new()
1733 .date_interpretation(crate::workbook::DateInterpretation::CellType),
1734 )
1735 .unwrap();
1736 assert_eq!(
1737 wb.get_cell_value("Sheet1", "A1").unwrap(),
1738 CellValue::Number(46127.0)
1739 );
1740 assert_eq!(
1741 wb.get_cell_value("Sheet1", "B1").unwrap(),
1742 CellValue::Number(42.0)
1743 );
1744 }
1745
1746 #[test]
1747 fn test_get_rows_num_fmt_default_promotes_date_styled_cells() {
1748 let dir = tempfile::TempDir::new().unwrap();
1749 let path = dir.path().join("get_rows_default.xlsx");
1750 write_workbook_with_date_style(&path);
1751
1752 let wb = Workbook::open(&path).unwrap();
1755 let rows = wb.get_rows("Sheet1").unwrap();
1756 assert_eq!(rows.len(), 1);
1757 let cells = &rows[0].1;
1758 assert_eq!(cells[0].1, CellValue::Date(46127.0));
1759 assert_eq!(cells[1].1, CellValue::Number(42.0));
1760 }
1761
1762 #[test]
1763 fn test_get_rows_cell_type_keeps_numbers() {
1764 let dir = tempfile::TempDir::new().unwrap();
1765 let path = dir.path().join("get_rows_cell_type.xlsx");
1766 write_workbook_with_date_style(&path);
1767
1768 let wb = Workbook::open_with_options(
1769 &path,
1770 &crate::workbook::OpenOptions::new()
1771 .date_interpretation(crate::workbook::DateInterpretation::CellType),
1772 )
1773 .unwrap();
1774 let rows = wb.get_rows("Sheet1").unwrap();
1775 assert_eq!(rows.len(), 1);
1776 let cells = &rows[0].1;
1777 assert_eq!(cells[0].1, CellValue::Number(46127.0));
1778 assert_eq!(cells[1].1, CellValue::Number(42.0));
1779 }
1780}