1use super::*;
2
3impl Workbook {
4 pub fn get_cell_value(&self, sheet: &str, cell: &str) -> Result<CellValue> {
9 let ws = self
10 .worksheets
11 .iter()
12 .find(|(name, _)| name == sheet)
13 .map(|(_, ws)| ws)
14 .ok_or_else(|| Error::SheetNotFound {
15 name: sheet.to_string(),
16 })?;
17
18 let (col, row) = cell_name_to_coordinates(cell)?;
19 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row)?;
20
21 let xml_row = ws.sheet_data.rows.iter().find(|r| r.r == row);
23 let xml_row = match xml_row {
24 Some(r) => r,
25 None => return Ok(CellValue::Empty),
26 };
27
28 let xml_cell = xml_row.cells.iter().find(|c| c.r == cell_ref);
30 let xml_cell = match xml_cell {
31 Some(c) => c,
32 None => return Ok(CellValue::Empty),
33 };
34
35 self.xml_cell_to_value(xml_cell)
36 }
37
38 pub fn set_cell_value(
45 &mut self,
46 sheet: &str,
47 cell: &str,
48 value: impl Into<CellValue>,
49 ) -> Result<()> {
50 let value = value.into();
51
52 if let CellValue::String(ref s) = value {
54 if s.len() > MAX_CELL_CHARS {
55 return Err(Error::CellValueTooLong {
56 length: s.len(),
57 max: MAX_CELL_CHARS,
58 });
59 }
60 }
61
62 let ws = self
63 .worksheets
64 .iter_mut()
65 .find(|(name, _)| name == sheet)
66 .map(|(_, ws)| ws)
67 .ok_or_else(|| Error::SheetNotFound {
68 name: sheet.to_string(),
69 })?;
70
71 let (col, row_num) = cell_name_to_coordinates(cell)?;
72 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
73
74 let row_idx = match ws.sheet_data.rows.iter().position(|r| r.r >= row_num) {
76 Some(idx) if ws.sheet_data.rows[idx].r == row_num => idx,
77 Some(idx) => {
78 ws.sheet_data.rows.insert(idx, new_row(row_num));
79 idx
80 }
81 None => {
82 ws.sheet_data.rows.push(new_row(row_num));
83 ws.sheet_data.rows.len() - 1
84 }
85 };
86
87 let row = &mut ws.sheet_data.rows[row_idx];
88
89 if value == CellValue::Empty {
91 row.cells.retain(|c| c.r != cell_ref);
92 return Ok(());
93 }
94
95 let cell_idx = match row.cells.iter().position(|c| c.r == cell_ref) {
97 Some(idx) => idx,
98 None => {
99 let insert_pos = row
101 .cells
102 .iter()
103 .position(|c| {
104 cell_name_to_coordinates(&c.r)
105 .map(|(c_col, _)| c_col > col)
106 .unwrap_or(false)
107 })
108 .unwrap_or(row.cells.len());
109 row.cells.insert(
110 insert_pos,
111 Cell {
112 r: cell_ref,
113 s: None,
114 t: None,
115 v: None,
116 f: None,
117 is: None,
118 },
119 );
120 insert_pos
121 }
122 };
123
124 let xml_cell = &mut row.cells[cell_idx];
125 value_to_xml_cell(&mut self.sst_runtime, xml_cell, value);
126
127 Ok(())
128 }
129
130 pub(crate) fn xml_cell_to_value(&self, xml_cell: &Cell) -> Result<CellValue> {
132 if let Some(ref formula) = xml_cell.f {
134 let expr = formula.value.clone().unwrap_or_default();
135 let result = match (&xml_cell.t, &xml_cell.v) {
136 (Some(t), Some(v)) if t == "b" => Some(Box::new(CellValue::Bool(v == "1"))),
137 (Some(t), Some(v)) if t == "e" => Some(Box::new(CellValue::Error(v.clone()))),
138 (_, Some(v)) => v
139 .parse::<f64>()
140 .ok()
141 .map(|n| Box::new(CellValue::Number(n))),
142 _ => None,
143 };
144 return Ok(CellValue::Formula { expr, result });
145 }
146
147 let cell_type = xml_cell.t.as_deref();
148 let cell_value = xml_cell.v.as_deref();
149
150 match (cell_type, cell_value) {
151 (Some("s"), Some(v)) => {
153 let idx: usize = v
154 .parse()
155 .map_err(|_| Error::Internal(format!("invalid SST index: {v}")))?;
156 let s = self.sst_runtime.get(idx).unwrap_or("").to_string();
157 Ok(CellValue::String(s))
158 }
159 (Some("b"), Some(v)) => Ok(CellValue::Bool(v == "1")),
161 (Some("e"), Some(v)) => Ok(CellValue::Error(v.to_string())),
163 (Some("inlineStr"), _) => {
165 let s = xml_cell
166 .is
167 .as_ref()
168 .and_then(|is| is.t.clone())
169 .unwrap_or_default();
170 Ok(CellValue::String(s))
171 }
172 (Some("str"), Some(v)) => Ok(CellValue::String(v.to_string())),
174 (None | Some("n"), Some(v)) => {
176 let n: f64 = v
177 .parse()
178 .map_err(|_| Error::Internal(format!("invalid number: {v}")))?;
179 if self.is_date_styled_cell(xml_cell) {
181 return Ok(CellValue::Date(n));
182 }
183 Ok(CellValue::Number(n))
184 }
185 _ => Ok(CellValue::Empty),
187 }
188 }
189
190 pub(crate) fn is_date_styled_cell(&self, xml_cell: &Cell) -> bool {
192 let style_idx = match xml_cell.s {
193 Some(idx) => idx as usize,
194 None => return false,
195 };
196 let xf = match self.stylesheet.cell_xfs.xfs.get(style_idx) {
197 Some(xf) => xf,
198 None => return false,
199 };
200 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
201 if crate::cell::is_date_num_fmt(num_fmt_id) {
203 return true;
204 }
205 if num_fmt_id >= 164 {
207 if let Some(ref num_fmts) = self.stylesheet.num_fmts {
208 if let Some(nf) = num_fmts
209 .num_fmts
210 .iter()
211 .find(|nf| nf.num_fmt_id == num_fmt_id)
212 {
213 return crate::cell::is_date_format_code(&nf.format_code);
214 }
215 }
216 }
217 false
218 }
219
220 pub fn add_style(&mut self, style: &crate::style::Style) -> Result<u32> {
225 crate::style::add_style(&mut self.stylesheet, style)
226 }
227
228 pub fn get_cell_style(&self, sheet: &str, cell: &str) -> Result<Option<u32>> {
233 let ws = self.worksheet_ref(sheet)?;
234
235 let (col, row) = cell_name_to_coordinates(cell)?;
236 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row)?;
237
238 let xml_row = match ws.sheet_data.rows.iter().find(|r| r.r == row) {
240 Some(r) => r,
241 None => return Ok(None),
242 };
243
244 let xml_cell = match xml_row.cells.iter().find(|c| c.r == cell_ref) {
246 Some(c) => c,
247 None => return Ok(None),
248 };
249
250 Ok(xml_cell.s)
251 }
252
253 pub fn set_cell_style(&mut self, sheet: &str, cell: &str, style_id: u32) -> Result<()> {
258 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
260 return Err(Error::StyleNotFound { id: style_id });
261 }
262
263 let ws = self
264 .worksheets
265 .iter_mut()
266 .find(|(name, _)| name == sheet)
267 .map(|(_, ws)| ws)
268 .ok_or_else(|| Error::SheetNotFound {
269 name: sheet.to_string(),
270 })?;
271
272 let (col, row_num) = cell_name_to_coordinates(cell)?;
273 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row_num)?;
274
275 let row_idx = match ws.sheet_data.rows.iter().position(|r| r.r >= row_num) {
277 Some(idx) if ws.sheet_data.rows[idx].r == row_num => idx,
278 Some(idx) => {
279 ws.sheet_data.rows.insert(idx, new_row(row_num));
280 idx
281 }
282 None => {
283 ws.sheet_data.rows.push(new_row(row_num));
284 ws.sheet_data.rows.len() - 1
285 }
286 };
287
288 let row = &mut ws.sheet_data.rows[row_idx];
289
290 let cell_idx = match row.cells.iter().position(|c| c.r == cell_ref) {
292 Some(idx) => idx,
293 None => {
294 let insert_pos = row
296 .cells
297 .iter()
298 .position(|c| {
299 cell_name_to_coordinates(&c.r)
300 .map(|(c_col, _)| c_col > col)
301 .unwrap_or(false)
302 })
303 .unwrap_or(row.cells.len());
304 row.cells.insert(
305 insert_pos,
306 Cell {
307 r: cell_ref,
308 s: None,
309 t: None,
310 v: None,
311 f: None,
312 is: None,
313 },
314 );
315 insert_pos
316 }
317 };
318
319 row.cells[cell_idx].s = Some(style_id);
320 Ok(())
321 }
322
323 pub fn merge_cells(&mut self, sheet: &str, top_left: &str, bottom_right: &str) -> Result<()> {
328 let ws = self.worksheet_mut(sheet)?;
329 crate::merge::merge_cells(ws, top_left, bottom_right)
330 }
331
332 pub fn unmerge_cell(&mut self, sheet: &str, reference: &str) -> Result<()> {
336 let ws = self.worksheet_mut(sheet)?;
337 crate::merge::unmerge_cell(ws, reference)
338 }
339
340 pub fn get_merge_cells(&self, sheet: &str) -> Result<Vec<String>> {
344 let ws = self.worksheet_ref(sheet)?;
345 Ok(crate::merge::get_merge_cells(ws))
346 }
347
348 pub fn set_cell_formula(&mut self, sheet: &str, cell: &str, formula: &str) -> Result<()> {
353 self.set_cell_value(
354 sheet,
355 cell,
356 CellValue::Formula {
357 expr: formula.to_string(),
358 result: None,
359 },
360 )
361 }
362
363 pub fn fill_formula(&mut self, sheet: &str, range: &str, formula: &str) -> Result<()> {
371 let parts: Vec<&str> = range.split(':').collect();
372 if parts.len() != 2 {
373 return Err(Error::InvalidCellReference(format!(
374 "invalid range: {range}"
375 )));
376 }
377 let (start_col, start_row) = cell_name_to_coordinates(parts[0])?;
378 let (end_col, end_row) = cell_name_to_coordinates(parts[1])?;
379
380 if start_col != end_col {
381 return Err(Error::InvalidCellReference(
382 "fill_formula only supports single-column ranges".to_string(),
383 ));
384 }
385
386 for row in start_row..=end_row {
387 let row_offset = row as i32 - start_row as i32;
388 let adjusted = if row_offset == 0 {
389 formula.to_string()
390 } else {
391 crate::cell_ref_shift::shift_cell_references_with_abs(
392 formula,
393 |col, r, _abs_col, abs_row| {
394 if abs_row {
395 (col, r)
396 } else {
397 (col, (r as i32 + row_offset) as u32)
398 }
399 },
400 )?
401 };
402 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(start_col, row)?;
403 self.set_cell_formula(sheet, &cell_ref, &adjusted)?;
404 }
405 Ok(())
406 }
407
408 pub fn set_cell_rich_text(
410 &mut self,
411 sheet: &str,
412 cell: &str,
413 runs: Vec<crate::rich_text::RichTextRun>,
414 ) -> Result<()> {
415 self.set_cell_value(sheet, cell, CellValue::RichString(runs))
416 }
417
418 pub fn get_cell_rich_text(
423 &self,
424 sheet: &str,
425 cell: &str,
426 ) -> Result<Option<Vec<crate::rich_text::RichTextRun>>> {
427 let (col, row) = cell_name_to_coordinates(cell)?;
428 let sheet_idx = self
429 .worksheets
430 .iter()
431 .position(|(name, _)| name == sheet)
432 .ok_or_else(|| Error::SheetNotFound {
433 name: sheet.to_string(),
434 })?;
435 let ws = &self.worksheets[sheet_idx].1;
436
437 for xml_row in &ws.sheet_data.rows {
438 if xml_row.r == row {
439 for xml_cell in &xml_row.cells {
440 let (cc, cr) = cell_name_to_coordinates(&xml_cell.r)?;
441 if cc == col && cr == row {
442 if xml_cell.t.as_deref() == Some("s") {
443 if let Some(ref v) = xml_cell.v {
444 if let Ok(idx) = v.parse::<usize>() {
445 return Ok(self.sst_runtime.get_rich_text(idx));
446 }
447 }
448 }
449 return Ok(None);
450 }
451 }
452 }
453 }
454 Ok(None)
455 }
456}
457
458pub(crate) fn value_to_xml_cell(
460 sst: &mut SharedStringTable,
461 xml_cell: &mut Cell,
462 value: CellValue,
463) {
464 xml_cell.t = None;
466 xml_cell.v = None;
467 xml_cell.f = None;
468 xml_cell.is = None;
469
470 match value {
471 CellValue::String(s) => {
472 let idx = sst.add(&s);
473 xml_cell.t = Some("s".to_string());
474 xml_cell.v = Some(idx.to_string());
475 }
476 CellValue::Number(n) => {
477 xml_cell.v = Some(n.to_string());
478 }
479 CellValue::Date(serial) => {
480 xml_cell.v = Some(serial.to_string());
483 }
484 CellValue::Bool(b) => {
485 xml_cell.t = Some("b".to_string());
486 xml_cell.v = Some(if b { "1" } else { "0" }.to_string());
487 }
488 CellValue::Formula { expr, .. } => {
489 xml_cell.f = Some(CellFormula {
490 t: None,
491 reference: None,
492 si: None,
493 value: Some(expr),
494 });
495 }
496 CellValue::Error(e) => {
497 xml_cell.t = Some("e".to_string());
498 xml_cell.v = Some(e);
499 }
500 CellValue::Empty => {
501 }
503 CellValue::RichString(runs) => {
504 let idx = sst.add_rich_text(&runs);
505 xml_cell.t = Some("s".to_string());
506 xml_cell.v = Some(idx.to_string());
507 }
508 }
509}
510
511pub(crate) fn new_row(row_num: u32) -> Row {
513 Row {
514 r: row_num,
515 spans: None,
516 s: None,
517 custom_format: None,
518 ht: None,
519 hidden: None,
520 custom_height: None,
521 outline_level: None,
522 cells: vec![],
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529 use tempfile::TempDir;
530
531 #[test]
532 fn test_set_and_get_string_value() {
533 let mut wb = Workbook::new();
534 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
535 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
536 assert_eq!(val, CellValue::String("Hello".to_string()));
537 }
538
539 #[test]
540 fn test_set_and_get_number_value() {
541 let mut wb = Workbook::new();
542 wb.set_cell_value("Sheet1", "B2", 42.5f64).unwrap();
543 let val = wb.get_cell_value("Sheet1", "B2").unwrap();
544 assert_eq!(val, CellValue::Number(42.5));
545 }
546
547 #[test]
548 fn test_set_and_get_bool_value() {
549 let mut wb = Workbook::new();
550 wb.set_cell_value("Sheet1", "C3", true).unwrap();
551 let val = wb.get_cell_value("Sheet1", "C3").unwrap();
552 assert_eq!(val, CellValue::Bool(true));
553
554 wb.set_cell_value("Sheet1", "D4", false).unwrap();
555 let val = wb.get_cell_value("Sheet1", "D4").unwrap();
556 assert_eq!(val, CellValue::Bool(false));
557 }
558
559 #[test]
560 fn test_set_value_sheet_not_found() {
561 let mut wb = Workbook::new();
562 let result = wb.set_cell_value("NoSuchSheet", "A1", "test");
563 assert!(result.is_err());
564 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
565 }
566
567 #[test]
568 fn test_get_value_sheet_not_found() {
569 let wb = Workbook::new();
570 let result = wb.get_cell_value("NoSuchSheet", "A1");
571 assert!(result.is_err());
572 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
573 }
574
575 #[test]
576 fn test_get_empty_cell_returns_empty() {
577 let wb = Workbook::new();
578 let val = wb.get_cell_value("Sheet1", "Z99").unwrap();
579 assert_eq!(val, CellValue::Empty);
580 }
581
582 #[test]
583 fn test_cell_value_roundtrip_save_open() {
584 let dir = TempDir::new().unwrap();
585 let path = dir.path().join("cell_roundtrip.xlsx");
586
587 let mut wb = Workbook::new();
588 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
589 wb.set_cell_value("Sheet1", "B1", 42.0f64).unwrap();
590 wb.set_cell_value("Sheet1", "C1", true).unwrap();
591 wb.save(&path).unwrap();
592
593 let wb2 = Workbook::open(&path).unwrap();
594 assert_eq!(
595 wb2.get_cell_value("Sheet1", "A1").unwrap(),
596 CellValue::String("Hello".to_string())
597 );
598 assert_eq!(
599 wb2.get_cell_value("Sheet1", "B1").unwrap(),
600 CellValue::Number(42.0)
601 );
602 assert_eq!(
603 wb2.get_cell_value("Sheet1", "C1").unwrap(),
604 CellValue::Bool(true)
605 );
606 }
607
608 #[test]
609 fn test_set_empty_value_clears_cell() {
610 let mut wb = Workbook::new();
611 wb.set_cell_value("Sheet1", "A1", "test").unwrap();
612 assert_eq!(
613 wb.get_cell_value("Sheet1", "A1").unwrap(),
614 CellValue::String("test".to_string())
615 );
616
617 wb.set_cell_value("Sheet1", "A1", CellValue::Empty).unwrap();
618 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
619 }
620
621 #[test]
622 fn test_string_too_long_returns_error() {
623 let mut wb = Workbook::new();
624 let long_string = "x".repeat(MAX_CELL_CHARS + 1);
625 let result = wb.set_cell_value("Sheet1", "A1", long_string.as_str());
626 assert!(result.is_err());
627 assert!(matches!(
628 result.unwrap_err(),
629 Error::CellValueTooLong { .. }
630 ));
631 }
632
633 #[test]
634 fn test_set_multiple_cells_same_row() {
635 let mut wb = Workbook::new();
636 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
637 wb.set_cell_value("Sheet1", "B1", "second").unwrap();
638 wb.set_cell_value("Sheet1", "C1", "third").unwrap();
639
640 assert_eq!(
641 wb.get_cell_value("Sheet1", "A1").unwrap(),
642 CellValue::String("first".to_string())
643 );
644 assert_eq!(
645 wb.get_cell_value("Sheet1", "B1").unwrap(),
646 CellValue::String("second".to_string())
647 );
648 assert_eq!(
649 wb.get_cell_value("Sheet1", "C1").unwrap(),
650 CellValue::String("third".to_string())
651 );
652 }
653
654 #[test]
655 fn test_overwrite_cell_value() {
656 let mut wb = Workbook::new();
657 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
658 wb.set_cell_value("Sheet1", "A1", "updated").unwrap();
659
660 assert_eq!(
661 wb.get_cell_value("Sheet1", "A1").unwrap(),
662 CellValue::String("updated".to_string())
663 );
664 }
665
666 #[test]
667 fn test_set_and_get_error_value() {
668 let mut wb = Workbook::new();
669 wb.set_cell_value("Sheet1", "A1", CellValue::Error("#DIV/0!".to_string()))
670 .unwrap();
671 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
672 assert_eq!(val, CellValue::Error("#DIV/0!".to_string()));
673 }
674
675 #[test]
676 fn test_set_and_get_date_value() {
677 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
678
679 let mut wb = Workbook::new();
680 let style_id = wb
682 .add_style(&Style {
683 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
684 ..Style::default()
685 })
686 .unwrap();
687
688 let date_serial =
690 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
691 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
692 .unwrap();
693 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
694
695 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
697 assert_eq!(val, CellValue::Date(date_serial));
698 }
699
700 #[test]
701 fn test_date_value_without_style_returns_number() {
702 let mut wb = Workbook::new();
703 let date_serial =
705 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
706 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
707 .unwrap();
708
709 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
711 assert_eq!(val, CellValue::Number(date_serial));
712 }
713
714 #[test]
715 fn test_date_value_roundtrip_through_save() {
716 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
717
718 let mut wb = Workbook::new();
719 let style_id = wb
720 .add_style(&Style {
721 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATETIME)),
722 ..Style::default()
723 })
724 .unwrap();
725
726 let dt = chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
727 .unwrap()
728 .and_hms_opt(14, 30, 0)
729 .unwrap();
730 let serial = crate::cell::datetime_to_serial(dt);
731 wb.set_cell_value("Sheet1", "A1", CellValue::Date(serial))
732 .unwrap();
733 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
734
735 let tmp = tempfile::NamedTempFile::new().unwrap();
736 let path = tmp.path().to_str().unwrap();
737 wb.save(path).unwrap();
738
739 let wb2 = Workbook::open(path).unwrap();
740 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
741 assert_eq!(val, CellValue::Date(serial));
742 }
743
744 #[test]
745 fn test_date_from_naive_date_conversion() {
746 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
747 let cv: CellValue = date.into();
748 match cv {
749 CellValue::Date(s) => {
750 let roundtripped = crate::cell::serial_to_date(s).unwrap();
751 assert_eq!(roundtripped, date);
752 }
753 _ => panic!("expected Date variant"),
754 }
755 }
756
757 #[test]
758 fn test_set_and_get_formula_value() {
759 let mut wb = Workbook::new();
760 wb.set_cell_value(
761 "Sheet1",
762 "A1",
763 CellValue::Formula {
764 expr: "SUM(B1:B10)".to_string(),
765 result: None,
766 },
767 )
768 .unwrap();
769 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
770 match val {
771 CellValue::Formula { expr, .. } => {
772 assert_eq!(expr, "SUM(B1:B10)");
773 }
774 other => panic!("expected Formula, got {:?}", other),
775 }
776 }
777
778 #[test]
779 fn test_set_i32_value() {
780 let mut wb = Workbook::new();
781 wb.set_cell_value("Sheet1", "A1", 100i32).unwrap();
782 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
783 assert_eq!(val, CellValue::Number(100.0));
784 }
785
786 #[test]
787 fn test_set_string_at_max_length() {
788 let mut wb = Workbook::new();
789 let max_string = "x".repeat(MAX_CELL_CHARS);
790 wb.set_cell_value("Sheet1", "A1", max_string.as_str())
791 .unwrap();
792 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
793 assert_eq!(val, CellValue::String(max_string));
794 }
795
796 #[test]
797 fn test_set_cells_different_rows() {
798 let mut wb = Workbook::new();
799 wb.set_cell_value("Sheet1", "A1", "row1").unwrap();
800 wb.set_cell_value("Sheet1", "A3", "row3").unwrap();
801 wb.set_cell_value("Sheet1", "A2", "row2").unwrap(); assert_eq!(
804 wb.get_cell_value("Sheet1", "A1").unwrap(),
805 CellValue::String("row1".to_string())
806 );
807 assert_eq!(
808 wb.get_cell_value("Sheet1", "A2").unwrap(),
809 CellValue::String("row2".to_string())
810 );
811 assert_eq!(
812 wb.get_cell_value("Sheet1", "A3").unwrap(),
813 CellValue::String("row3".to_string())
814 );
815 }
816
817 #[test]
818 fn test_string_deduplication_in_sst() {
819 let mut wb = Workbook::new();
820 wb.set_cell_value("Sheet1", "A1", "same").unwrap();
821 wb.set_cell_value("Sheet1", "A2", "same").unwrap();
822 wb.set_cell_value("Sheet1", "A3", "different").unwrap();
823
824 assert_eq!(wb.sst_runtime.len(), 2);
826 assert_eq!(
827 wb.get_cell_value("Sheet1", "A1").unwrap(),
828 CellValue::String("same".to_string())
829 );
830 assert_eq!(
831 wb.get_cell_value("Sheet1", "A2").unwrap(),
832 CellValue::String("same".to_string())
833 );
834 }
835
836 #[test]
837 fn test_add_style_returns_id() {
838 use crate::style::{FontStyle, Style};
839
840 let mut wb = Workbook::new();
841 let style = Style {
842 font: Some(FontStyle {
843 bold: true,
844 ..FontStyle::default()
845 }),
846 ..Style::default()
847 };
848 let id = wb.add_style(&style).unwrap();
849 assert!(id > 0);
850 }
851
852 #[test]
853 fn test_get_cell_style_unstyled_cell_returns_none() {
854 let wb = Workbook::new();
855 let result = wb.get_cell_style("Sheet1", "A1").unwrap();
856 assert!(result.is_none());
857 }
858
859 #[test]
860 fn test_set_cell_style_on_existing_value() {
861 use crate::style::{FontStyle, Style};
862
863 let mut wb = Workbook::new();
864 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
865
866 let style = Style {
867 font: Some(FontStyle {
868 bold: true,
869 ..FontStyle::default()
870 }),
871 ..Style::default()
872 };
873 let style_id = wb.add_style(&style).unwrap();
874 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
875
876 let retrieved_id = wb.get_cell_style("Sheet1", "A1").unwrap();
877 assert_eq!(retrieved_id, Some(style_id));
878
879 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
881 assert_eq!(val, CellValue::String("Hello".to_string()));
882 }
883
884 #[test]
885 fn test_set_cell_style_on_empty_cell_creates_cell() {
886 use crate::style::{FontStyle, Style};
887
888 let mut wb = Workbook::new();
889 let style = Style {
890 font: Some(FontStyle {
891 bold: true,
892 ..FontStyle::default()
893 }),
894 ..Style::default()
895 };
896 let style_id = wb.add_style(&style).unwrap();
897
898 wb.set_cell_style("Sheet1", "B5", style_id).unwrap();
900
901 let retrieved_id = wb.get_cell_style("Sheet1", "B5").unwrap();
902 assert_eq!(retrieved_id, Some(style_id));
903
904 let val = wb.get_cell_value("Sheet1", "B5").unwrap();
906 assert_eq!(val, CellValue::Empty);
907 }
908
909 #[test]
910 fn test_set_cell_style_invalid_id() {
911 let mut wb = Workbook::new();
912 let result = wb.set_cell_style("Sheet1", "A1", 999);
913 assert!(result.is_err());
914 assert!(matches!(result.unwrap_err(), Error::StyleNotFound { .. }));
915 }
916
917 #[test]
918 fn test_set_cell_style_sheet_not_found() {
919 let mut wb = Workbook::new();
920 let style = crate::style::Style::default();
921 let style_id = wb.add_style(&style).unwrap();
922 let result = wb.set_cell_style("NoSuchSheet", "A1", style_id);
923 assert!(result.is_err());
924 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
925 }
926
927 #[test]
928 fn test_get_cell_style_sheet_not_found() {
929 let wb = Workbook::new();
930 let result = wb.get_cell_style("NoSuchSheet", "A1");
931 assert!(result.is_err());
932 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
933 }
934
935 #[test]
936 fn test_style_roundtrip_save_open() {
937 use crate::style::{
938 AlignmentStyle, BorderLineStyle, BorderSideStyle, BorderStyle, FillStyle, FontStyle,
939 HorizontalAlign, NumFmtStyle, PatternType, Style, StyleColor, VerticalAlign,
940 };
941
942 let dir = TempDir::new().unwrap();
943 let path = dir.path().join("style_roundtrip.xlsx");
944
945 let mut wb = Workbook::new();
946 wb.set_cell_value("Sheet1", "A1", "Styled").unwrap();
947
948 let style = Style {
949 font: Some(FontStyle {
950 name: Some("Arial".to_string()),
951 size: Some(14.0),
952 bold: true,
953 italic: true,
954 color: Some(StyleColor::Rgb("FFFF0000".to_string())),
955 ..FontStyle::default()
956 }),
957 fill: Some(FillStyle {
958 pattern: PatternType::Solid,
959 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
960 bg_color: None,
961 gradient: None,
962 }),
963 border: Some(BorderStyle {
964 left: Some(BorderSideStyle {
965 style: BorderLineStyle::Thin,
966 color: None,
967 }),
968 right: Some(BorderSideStyle {
969 style: BorderLineStyle::Thin,
970 color: None,
971 }),
972 top: Some(BorderSideStyle {
973 style: BorderLineStyle::Thin,
974 color: None,
975 }),
976 bottom: Some(BorderSideStyle {
977 style: BorderLineStyle::Thin,
978 color: None,
979 }),
980 diagonal: None,
981 }),
982 alignment: Some(AlignmentStyle {
983 horizontal: Some(HorizontalAlign::Center),
984 vertical: Some(VerticalAlign::Center),
985 wrap_text: true,
986 ..AlignmentStyle::default()
987 }),
988 num_fmt: Some(NumFmtStyle::Custom("#,##0.00".to_string())),
989 protection: None,
990 };
991 let style_id = wb.add_style(&style).unwrap();
992 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
993 wb.save(&path).unwrap();
994
995 let wb2 = Workbook::open(&path).unwrap();
997 let retrieved_id = wb2.get_cell_style("Sheet1", "A1").unwrap();
998 assert_eq!(retrieved_id, Some(style_id));
999
1000 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
1002 assert_eq!(val, CellValue::String("Styled".to_string()));
1003
1004 let retrieved_style = crate::style::get_style(&wb2.stylesheet, style_id).unwrap();
1006 assert!(retrieved_style.font.is_some());
1007 let font = retrieved_style.font.unwrap();
1008 assert!(font.bold);
1009 assert!(font.italic);
1010 assert_eq!(font.name, Some("Arial".to_string()));
1011
1012 assert!(retrieved_style.fill.is_some());
1013 let fill = retrieved_style.fill.unwrap();
1014 assert_eq!(fill.pattern, PatternType::Solid);
1015
1016 assert!(retrieved_style.alignment.is_some());
1017 let align = retrieved_style.alignment.unwrap();
1018 assert_eq!(align.horizontal, Some(HorizontalAlign::Center));
1019 assert_eq!(align.vertical, Some(VerticalAlign::Center));
1020 assert!(align.wrap_text);
1021 }
1022
1023 #[test]
1024 fn test_set_and_get_cell_rich_text() {
1025 use crate::rich_text::RichTextRun;
1026
1027 let mut wb = Workbook::new();
1028 let runs = vec![
1029 RichTextRun {
1030 text: "Bold".to_string(),
1031 font: None,
1032 size: None,
1033 bold: true,
1034 italic: false,
1035 color: None,
1036 },
1037 RichTextRun {
1038 text: " Normal".to_string(),
1039 font: None,
1040 size: None,
1041 bold: false,
1042 italic: false,
1043 color: None,
1044 },
1045 ];
1046 wb.set_cell_rich_text("Sheet1", "A1", runs.clone()).unwrap();
1047
1048 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1050 assert_eq!(val.to_string(), "Bold Normal");
1051
1052 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1054 assert!(got.is_some());
1055 let got_runs = got.unwrap();
1056 assert_eq!(got_runs.len(), 2);
1057 assert_eq!(got_runs[0].text, "Bold");
1058 assert!(got_runs[0].bold);
1059 assert_eq!(got_runs[1].text, " Normal");
1060 assert!(!got_runs[1].bold);
1061 }
1062
1063 #[test]
1064 fn test_get_cell_rich_text_returns_none_for_plain() {
1065 let mut wb = Workbook::new();
1066 wb.set_cell_value("Sheet1", "A1", CellValue::String("plain".to_string()))
1067 .unwrap();
1068 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1069 assert!(got.is_none());
1070 }
1071
1072 #[test]
1073 fn test_rich_text_roundtrip_save_open() {
1074 use crate::rich_text::RichTextRun;
1075
1076 let dir = TempDir::new().unwrap();
1077 let path = dir.path().join("rich_text.xlsx");
1078
1079 let mut wb = Workbook::new();
1083 let runs = vec![
1084 RichTextRun {
1085 text: "Hello".to_string(),
1086 font: Some("Arial".to_string()),
1087 size: Some(14.0),
1088 bold: true,
1089 italic: false,
1090 color: Some("#FF0000".to_string()),
1091 },
1092 RichTextRun {
1093 text: "World".to_string(),
1094 font: None,
1095 size: None,
1096 bold: false,
1097 italic: true,
1098 color: None,
1099 },
1100 ];
1101 wb.set_cell_rich_text("Sheet1", "B2", runs).unwrap();
1102 wb.save(&path).unwrap();
1103
1104 let wb2 = Workbook::open(&path).unwrap();
1105 let val = wb2.get_cell_value("Sheet1", "B2").unwrap();
1106 assert_eq!(val.to_string(), "HelloWorld");
1107
1108 let got = wb2.get_cell_rich_text("Sheet1", "B2").unwrap();
1109 assert!(got.is_some());
1110 let got_runs = got.unwrap();
1111 assert_eq!(got_runs.len(), 2);
1112 assert_eq!(got_runs[0].text, "Hello");
1113 assert!(got_runs[0].bold);
1114 assert_eq!(got_runs[0].font.as_deref(), Some("Arial"));
1115 assert_eq!(got_runs[0].size, Some(14.0));
1116 assert_eq!(got_runs[0].color.as_deref(), Some("#FF0000"));
1117 assert_eq!(got_runs[1].text, "World");
1118 assert!(got_runs[1].italic);
1119 assert!(!got_runs[1].bold);
1120 }
1121
1122 #[test]
1123 fn test_set_cell_formula() {
1124 let mut wb = Workbook::new();
1125 wb.set_cell_formula("Sheet1", "A1", "SUM(B1:B10)").unwrap();
1126 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
1127 match val {
1128 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:B10)"),
1129 other => panic!("expected Formula, got {:?}", other),
1130 }
1131 }
1132
1133 #[test]
1134 fn test_fill_formula_basic() {
1135 let mut wb = Workbook::new();
1136 wb.fill_formula("Sheet1", "D2:D5", "SUM(A2:C2)").unwrap();
1137
1138 match wb.get_cell_value("Sheet1", "D2").unwrap() {
1140 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A2:C2)"),
1141 other => panic!("D2: expected Formula, got {:?}", other),
1142 }
1143 match wb.get_cell_value("Sheet1", "D3").unwrap() {
1145 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:C3)"),
1146 other => panic!("D3: expected Formula, got {:?}", other),
1147 }
1148 match wb.get_cell_value("Sheet1", "D4").unwrap() {
1150 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A4:C4)"),
1151 other => panic!("D4: expected Formula, got {:?}", other),
1152 }
1153 match wb.get_cell_value("Sheet1", "D5").unwrap() {
1155 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A5:C5)"),
1156 other => panic!("D5: expected Formula, got {:?}", other),
1157 }
1158 }
1159
1160 #[test]
1161 fn test_fill_formula_preserves_absolute_refs() {
1162 let mut wb = Workbook::new();
1163 wb.fill_formula("Sheet1", "B1:B3", "$A$1*A1").unwrap();
1164
1165 match wb.get_cell_value("Sheet1", "B1").unwrap() {
1166 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A1"),
1167 other => panic!("B1: expected Formula, got {:?}", other),
1168 }
1169 match wb.get_cell_value("Sheet1", "B2").unwrap() {
1170 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A2"),
1171 other => panic!("B2: expected Formula, got {:?}", other),
1172 }
1173 match wb.get_cell_value("Sheet1", "B3").unwrap() {
1174 CellValue::Formula { expr, .. } => assert_eq!(expr, "$A$1*A3"),
1175 other => panic!("B3: expected Formula, got {:?}", other),
1176 }
1177 }
1178
1179 #[test]
1180 fn test_fill_formula_single_cell() {
1181 let mut wb = Workbook::new();
1182 wb.fill_formula("Sheet1", "A1:A1", "B1+C1").unwrap();
1183 match wb.get_cell_value("Sheet1", "A1").unwrap() {
1184 CellValue::Formula { expr, .. } => assert_eq!(expr, "B1+C1"),
1185 other => panic!("expected Formula, got {:?}", other),
1186 }
1187 }
1188
1189 #[test]
1190 fn test_fill_formula_invalid_range() {
1191 let mut wb = Workbook::new();
1192 assert!(wb.fill_formula("Sheet1", "INVALID", "A1").is_err());
1193 }
1194
1195 #[test]
1196 fn test_fill_formula_multi_column_range_rejected() {
1197 let mut wb = Workbook::new();
1198 assert!(wb.fill_formula("Sheet1", "A1:B5", "C1").is_err());
1199 }
1200}