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_rich_text(
350 &mut self,
351 sheet: &str,
352 cell: &str,
353 runs: Vec<crate::rich_text::RichTextRun>,
354 ) -> Result<()> {
355 self.set_cell_value(sheet, cell, CellValue::RichString(runs))
356 }
357
358 pub fn get_cell_rich_text(
363 &self,
364 sheet: &str,
365 cell: &str,
366 ) -> Result<Option<Vec<crate::rich_text::RichTextRun>>> {
367 let (col, row) = cell_name_to_coordinates(cell)?;
368 let sheet_idx = self
369 .worksheets
370 .iter()
371 .position(|(name, _)| name == sheet)
372 .ok_or_else(|| Error::SheetNotFound {
373 name: sheet.to_string(),
374 })?;
375 let ws = &self.worksheets[sheet_idx].1;
376
377 for xml_row in &ws.sheet_data.rows {
378 if xml_row.r == row {
379 for xml_cell in &xml_row.cells {
380 let (cc, cr) = cell_name_to_coordinates(&xml_cell.r)?;
381 if cc == col && cr == row {
382 if xml_cell.t.as_deref() == Some("s") {
383 if let Some(ref v) = xml_cell.v {
384 if let Ok(idx) = v.parse::<usize>() {
385 return Ok(self.sst_runtime.get_rich_text(idx));
386 }
387 }
388 }
389 return Ok(None);
390 }
391 }
392 }
393 }
394 Ok(None)
395 }
396}
397
398pub(crate) fn value_to_xml_cell(
400 sst: &mut SharedStringTable,
401 xml_cell: &mut Cell,
402 value: CellValue,
403) {
404 xml_cell.t = None;
406 xml_cell.v = None;
407 xml_cell.f = None;
408 xml_cell.is = None;
409
410 match value {
411 CellValue::String(s) => {
412 let idx = sst.add(&s);
413 xml_cell.t = Some("s".to_string());
414 xml_cell.v = Some(idx.to_string());
415 }
416 CellValue::Number(n) => {
417 xml_cell.v = Some(n.to_string());
418 }
419 CellValue::Date(serial) => {
420 xml_cell.v = Some(serial.to_string());
423 }
424 CellValue::Bool(b) => {
425 xml_cell.t = Some("b".to_string());
426 xml_cell.v = Some(if b { "1" } else { "0" }.to_string());
427 }
428 CellValue::Formula { expr, .. } => {
429 xml_cell.f = Some(CellFormula {
430 t: None,
431 reference: None,
432 si: None,
433 value: Some(expr),
434 });
435 }
436 CellValue::Error(e) => {
437 xml_cell.t = Some("e".to_string());
438 xml_cell.v = Some(e);
439 }
440 CellValue::Empty => {
441 }
443 CellValue::RichString(runs) => {
444 let idx = sst.add_rich_text(&runs);
445 xml_cell.t = Some("s".to_string());
446 xml_cell.v = Some(idx.to_string());
447 }
448 }
449}
450
451pub(crate) fn new_row(row_num: u32) -> Row {
453 Row {
454 r: row_num,
455 spans: None,
456 s: None,
457 custom_format: None,
458 ht: None,
459 hidden: None,
460 custom_height: None,
461 outline_level: None,
462 cells: vec![],
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use tempfile::TempDir;
470
471 #[test]
472 fn test_set_and_get_string_value() {
473 let mut wb = Workbook::new();
474 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
475 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
476 assert_eq!(val, CellValue::String("Hello".to_string()));
477 }
478
479 #[test]
480 fn test_set_and_get_number_value() {
481 let mut wb = Workbook::new();
482 wb.set_cell_value("Sheet1", "B2", 42.5f64).unwrap();
483 let val = wb.get_cell_value("Sheet1", "B2").unwrap();
484 assert_eq!(val, CellValue::Number(42.5));
485 }
486
487 #[test]
488 fn test_set_and_get_bool_value() {
489 let mut wb = Workbook::new();
490 wb.set_cell_value("Sheet1", "C3", true).unwrap();
491 let val = wb.get_cell_value("Sheet1", "C3").unwrap();
492 assert_eq!(val, CellValue::Bool(true));
493
494 wb.set_cell_value("Sheet1", "D4", false).unwrap();
495 let val = wb.get_cell_value("Sheet1", "D4").unwrap();
496 assert_eq!(val, CellValue::Bool(false));
497 }
498
499 #[test]
500 fn test_set_value_sheet_not_found() {
501 let mut wb = Workbook::new();
502 let result = wb.set_cell_value("NoSuchSheet", "A1", "test");
503 assert!(result.is_err());
504 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
505 }
506
507 #[test]
508 fn test_get_value_sheet_not_found() {
509 let wb = Workbook::new();
510 let result = wb.get_cell_value("NoSuchSheet", "A1");
511 assert!(result.is_err());
512 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
513 }
514
515 #[test]
516 fn test_get_empty_cell_returns_empty() {
517 let wb = Workbook::new();
518 let val = wb.get_cell_value("Sheet1", "Z99").unwrap();
519 assert_eq!(val, CellValue::Empty);
520 }
521
522 #[test]
523 fn test_cell_value_roundtrip_save_open() {
524 let dir = TempDir::new().unwrap();
525 let path = dir.path().join("cell_roundtrip.xlsx");
526
527 let mut wb = Workbook::new();
528 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
529 wb.set_cell_value("Sheet1", "B1", 42.0f64).unwrap();
530 wb.set_cell_value("Sheet1", "C1", true).unwrap();
531 wb.save(&path).unwrap();
532
533 let wb2 = Workbook::open(&path).unwrap();
534 assert_eq!(
535 wb2.get_cell_value("Sheet1", "A1").unwrap(),
536 CellValue::String("Hello".to_string())
537 );
538 assert_eq!(
539 wb2.get_cell_value("Sheet1", "B1").unwrap(),
540 CellValue::Number(42.0)
541 );
542 assert_eq!(
543 wb2.get_cell_value("Sheet1", "C1").unwrap(),
544 CellValue::Bool(true)
545 );
546 }
547
548 #[test]
549 fn test_set_empty_value_clears_cell() {
550 let mut wb = Workbook::new();
551 wb.set_cell_value("Sheet1", "A1", "test").unwrap();
552 assert_eq!(
553 wb.get_cell_value("Sheet1", "A1").unwrap(),
554 CellValue::String("test".to_string())
555 );
556
557 wb.set_cell_value("Sheet1", "A1", CellValue::Empty).unwrap();
558 assert_eq!(wb.get_cell_value("Sheet1", "A1").unwrap(), CellValue::Empty);
559 }
560
561 #[test]
562 fn test_string_too_long_returns_error() {
563 let mut wb = Workbook::new();
564 let long_string = "x".repeat(MAX_CELL_CHARS + 1);
565 let result = wb.set_cell_value("Sheet1", "A1", long_string.as_str());
566 assert!(result.is_err());
567 assert!(matches!(
568 result.unwrap_err(),
569 Error::CellValueTooLong { .. }
570 ));
571 }
572
573 #[test]
574 fn test_set_multiple_cells_same_row() {
575 let mut wb = Workbook::new();
576 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
577 wb.set_cell_value("Sheet1", "B1", "second").unwrap();
578 wb.set_cell_value("Sheet1", "C1", "third").unwrap();
579
580 assert_eq!(
581 wb.get_cell_value("Sheet1", "A1").unwrap(),
582 CellValue::String("first".to_string())
583 );
584 assert_eq!(
585 wb.get_cell_value("Sheet1", "B1").unwrap(),
586 CellValue::String("second".to_string())
587 );
588 assert_eq!(
589 wb.get_cell_value("Sheet1", "C1").unwrap(),
590 CellValue::String("third".to_string())
591 );
592 }
593
594 #[test]
595 fn test_overwrite_cell_value() {
596 let mut wb = Workbook::new();
597 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
598 wb.set_cell_value("Sheet1", "A1", "updated").unwrap();
599
600 assert_eq!(
601 wb.get_cell_value("Sheet1", "A1").unwrap(),
602 CellValue::String("updated".to_string())
603 );
604 }
605
606 #[test]
607 fn test_set_and_get_error_value() {
608 let mut wb = Workbook::new();
609 wb.set_cell_value("Sheet1", "A1", CellValue::Error("#DIV/0!".to_string()))
610 .unwrap();
611 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
612 assert_eq!(val, CellValue::Error("#DIV/0!".to_string()));
613 }
614
615 #[test]
616 fn test_set_and_get_date_value() {
617 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
618
619 let mut wb = Workbook::new();
620 let style_id = wb
622 .add_style(&Style {
623 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
624 ..Style::default()
625 })
626 .unwrap();
627
628 let date_serial =
630 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
631 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
632 .unwrap();
633 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
634
635 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
637 assert_eq!(val, CellValue::Date(date_serial));
638 }
639
640 #[test]
641 fn test_date_value_without_style_returns_number() {
642 let mut wb = Workbook::new();
643 let date_serial =
645 crate::cell::date_to_serial(chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap());
646 wb.set_cell_value("Sheet1", "A1", CellValue::Date(date_serial))
647 .unwrap();
648
649 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
651 assert_eq!(val, CellValue::Number(date_serial));
652 }
653
654 #[test]
655 fn test_date_value_roundtrip_through_save() {
656 use crate::style::{builtin_num_fmts, NumFmtStyle, Style};
657
658 let mut wb = Workbook::new();
659 let style_id = wb
660 .add_style(&Style {
661 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATETIME)),
662 ..Style::default()
663 })
664 .unwrap();
665
666 let dt = chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
667 .unwrap()
668 .and_hms_opt(14, 30, 0)
669 .unwrap();
670 let serial = crate::cell::datetime_to_serial(dt);
671 wb.set_cell_value("Sheet1", "A1", CellValue::Date(serial))
672 .unwrap();
673 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
674
675 let tmp = tempfile::NamedTempFile::new().unwrap();
676 let path = tmp.path().to_str().unwrap();
677 wb.save(path).unwrap();
678
679 let wb2 = Workbook::open(path).unwrap();
680 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
681 assert_eq!(val, CellValue::Date(serial));
682 }
683
684 #[test]
685 fn test_date_from_naive_date_conversion() {
686 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
687 let cv: CellValue = date.into();
688 match cv {
689 CellValue::Date(s) => {
690 let roundtripped = crate::cell::serial_to_date(s).unwrap();
691 assert_eq!(roundtripped, date);
692 }
693 _ => panic!("expected Date variant"),
694 }
695 }
696
697 #[test]
698 fn test_set_and_get_formula_value() {
699 let mut wb = Workbook::new();
700 wb.set_cell_value(
701 "Sheet1",
702 "A1",
703 CellValue::Formula {
704 expr: "SUM(B1:B10)".to_string(),
705 result: None,
706 },
707 )
708 .unwrap();
709 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
710 match val {
711 CellValue::Formula { expr, .. } => {
712 assert_eq!(expr, "SUM(B1:B10)");
713 }
714 other => panic!("expected Formula, got {:?}", other),
715 }
716 }
717
718 #[test]
719 fn test_set_i32_value() {
720 let mut wb = Workbook::new();
721 wb.set_cell_value("Sheet1", "A1", 100i32).unwrap();
722 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
723 assert_eq!(val, CellValue::Number(100.0));
724 }
725
726 #[test]
727 fn test_set_string_at_max_length() {
728 let mut wb = Workbook::new();
729 let max_string = "x".repeat(MAX_CELL_CHARS);
730 wb.set_cell_value("Sheet1", "A1", max_string.as_str())
731 .unwrap();
732 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
733 assert_eq!(val, CellValue::String(max_string));
734 }
735
736 #[test]
737 fn test_set_cells_different_rows() {
738 let mut wb = Workbook::new();
739 wb.set_cell_value("Sheet1", "A1", "row1").unwrap();
740 wb.set_cell_value("Sheet1", "A3", "row3").unwrap();
741 wb.set_cell_value("Sheet1", "A2", "row2").unwrap(); assert_eq!(
744 wb.get_cell_value("Sheet1", "A1").unwrap(),
745 CellValue::String("row1".to_string())
746 );
747 assert_eq!(
748 wb.get_cell_value("Sheet1", "A2").unwrap(),
749 CellValue::String("row2".to_string())
750 );
751 assert_eq!(
752 wb.get_cell_value("Sheet1", "A3").unwrap(),
753 CellValue::String("row3".to_string())
754 );
755 }
756
757 #[test]
758 fn test_string_deduplication_in_sst() {
759 let mut wb = Workbook::new();
760 wb.set_cell_value("Sheet1", "A1", "same").unwrap();
761 wb.set_cell_value("Sheet1", "A2", "same").unwrap();
762 wb.set_cell_value("Sheet1", "A3", "different").unwrap();
763
764 assert_eq!(wb.sst_runtime.len(), 2);
766 assert_eq!(
767 wb.get_cell_value("Sheet1", "A1").unwrap(),
768 CellValue::String("same".to_string())
769 );
770 assert_eq!(
771 wb.get_cell_value("Sheet1", "A2").unwrap(),
772 CellValue::String("same".to_string())
773 );
774 }
775
776 #[test]
777 fn test_add_style_returns_id() {
778 use crate::style::{FontStyle, Style};
779
780 let mut wb = Workbook::new();
781 let style = Style {
782 font: Some(FontStyle {
783 bold: true,
784 ..FontStyle::default()
785 }),
786 ..Style::default()
787 };
788 let id = wb.add_style(&style).unwrap();
789 assert!(id > 0);
790 }
791
792 #[test]
793 fn test_get_cell_style_unstyled_cell_returns_none() {
794 let wb = Workbook::new();
795 let result = wb.get_cell_style("Sheet1", "A1").unwrap();
796 assert!(result.is_none());
797 }
798
799 #[test]
800 fn test_set_cell_style_on_existing_value() {
801 use crate::style::{FontStyle, Style};
802
803 let mut wb = Workbook::new();
804 wb.set_cell_value("Sheet1", "A1", "Hello").unwrap();
805
806 let style = Style {
807 font: Some(FontStyle {
808 bold: true,
809 ..FontStyle::default()
810 }),
811 ..Style::default()
812 };
813 let style_id = wb.add_style(&style).unwrap();
814 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
815
816 let retrieved_id = wb.get_cell_style("Sheet1", "A1").unwrap();
817 assert_eq!(retrieved_id, Some(style_id));
818
819 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
821 assert_eq!(val, CellValue::String("Hello".to_string()));
822 }
823
824 #[test]
825 fn test_set_cell_style_on_empty_cell_creates_cell() {
826 use crate::style::{FontStyle, Style};
827
828 let mut wb = Workbook::new();
829 let style = Style {
830 font: Some(FontStyle {
831 bold: true,
832 ..FontStyle::default()
833 }),
834 ..Style::default()
835 };
836 let style_id = wb.add_style(&style).unwrap();
837
838 wb.set_cell_style("Sheet1", "B5", style_id).unwrap();
840
841 let retrieved_id = wb.get_cell_style("Sheet1", "B5").unwrap();
842 assert_eq!(retrieved_id, Some(style_id));
843
844 let val = wb.get_cell_value("Sheet1", "B5").unwrap();
846 assert_eq!(val, CellValue::Empty);
847 }
848
849 #[test]
850 fn test_set_cell_style_invalid_id() {
851 let mut wb = Workbook::new();
852 let result = wb.set_cell_style("Sheet1", "A1", 999);
853 assert!(result.is_err());
854 assert!(matches!(result.unwrap_err(), Error::StyleNotFound { .. }));
855 }
856
857 #[test]
858 fn test_set_cell_style_sheet_not_found() {
859 let mut wb = Workbook::new();
860 let style = crate::style::Style::default();
861 let style_id = wb.add_style(&style).unwrap();
862 let result = wb.set_cell_style("NoSuchSheet", "A1", style_id);
863 assert!(result.is_err());
864 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
865 }
866
867 #[test]
868 fn test_get_cell_style_sheet_not_found() {
869 let wb = Workbook::new();
870 let result = wb.get_cell_style("NoSuchSheet", "A1");
871 assert!(result.is_err());
872 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
873 }
874
875 #[test]
876 fn test_style_roundtrip_save_open() {
877 use crate::style::{
878 AlignmentStyle, BorderLineStyle, BorderSideStyle, BorderStyle, FillStyle, FontStyle,
879 HorizontalAlign, NumFmtStyle, PatternType, Style, StyleColor, VerticalAlign,
880 };
881
882 let dir = TempDir::new().unwrap();
883 let path = dir.path().join("style_roundtrip.xlsx");
884
885 let mut wb = Workbook::new();
886 wb.set_cell_value("Sheet1", "A1", "Styled").unwrap();
887
888 let style = Style {
889 font: Some(FontStyle {
890 name: Some("Arial".to_string()),
891 size: Some(14.0),
892 bold: true,
893 italic: true,
894 color: Some(StyleColor::Rgb("FFFF0000".to_string())),
895 ..FontStyle::default()
896 }),
897 fill: Some(FillStyle {
898 pattern: PatternType::Solid,
899 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
900 bg_color: None,
901 gradient: None,
902 }),
903 border: Some(BorderStyle {
904 left: Some(BorderSideStyle {
905 style: BorderLineStyle::Thin,
906 color: None,
907 }),
908 right: Some(BorderSideStyle {
909 style: BorderLineStyle::Thin,
910 color: None,
911 }),
912 top: Some(BorderSideStyle {
913 style: BorderLineStyle::Thin,
914 color: None,
915 }),
916 bottom: Some(BorderSideStyle {
917 style: BorderLineStyle::Thin,
918 color: None,
919 }),
920 diagonal: None,
921 }),
922 alignment: Some(AlignmentStyle {
923 horizontal: Some(HorizontalAlign::Center),
924 vertical: Some(VerticalAlign::Center),
925 wrap_text: true,
926 ..AlignmentStyle::default()
927 }),
928 num_fmt: Some(NumFmtStyle::Custom("#,##0.00".to_string())),
929 protection: None,
930 };
931 let style_id = wb.add_style(&style).unwrap();
932 wb.set_cell_style("Sheet1", "A1", style_id).unwrap();
933 wb.save(&path).unwrap();
934
935 let wb2 = Workbook::open(&path).unwrap();
937 let retrieved_id = wb2.get_cell_style("Sheet1", "A1").unwrap();
938 assert_eq!(retrieved_id, Some(style_id));
939
940 let val = wb2.get_cell_value("Sheet1", "A1").unwrap();
942 assert_eq!(val, CellValue::String("Styled".to_string()));
943
944 let retrieved_style = crate::style::get_style(&wb2.stylesheet, style_id).unwrap();
946 assert!(retrieved_style.font.is_some());
947 let font = retrieved_style.font.unwrap();
948 assert!(font.bold);
949 assert!(font.italic);
950 assert_eq!(font.name, Some("Arial".to_string()));
951
952 assert!(retrieved_style.fill.is_some());
953 let fill = retrieved_style.fill.unwrap();
954 assert_eq!(fill.pattern, PatternType::Solid);
955
956 assert!(retrieved_style.alignment.is_some());
957 let align = retrieved_style.alignment.unwrap();
958 assert_eq!(align.horizontal, Some(HorizontalAlign::Center));
959 assert_eq!(align.vertical, Some(VerticalAlign::Center));
960 assert!(align.wrap_text);
961 }
962
963 #[test]
964 fn test_set_and_get_cell_rich_text() {
965 use crate::rich_text::RichTextRun;
966
967 let mut wb = Workbook::new();
968 let runs = vec![
969 RichTextRun {
970 text: "Bold".to_string(),
971 font: None,
972 size: None,
973 bold: true,
974 italic: false,
975 color: None,
976 },
977 RichTextRun {
978 text: " Normal".to_string(),
979 font: None,
980 size: None,
981 bold: false,
982 italic: false,
983 color: None,
984 },
985 ];
986 wb.set_cell_rich_text("Sheet1", "A1", runs.clone()).unwrap();
987
988 let val = wb.get_cell_value("Sheet1", "A1").unwrap();
990 assert_eq!(val.to_string(), "Bold Normal");
991
992 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
994 assert!(got.is_some());
995 let got_runs = got.unwrap();
996 assert_eq!(got_runs.len(), 2);
997 assert_eq!(got_runs[0].text, "Bold");
998 assert!(got_runs[0].bold);
999 assert_eq!(got_runs[1].text, " Normal");
1000 assert!(!got_runs[1].bold);
1001 }
1002
1003 #[test]
1004 fn test_get_cell_rich_text_returns_none_for_plain() {
1005 let mut wb = Workbook::new();
1006 wb.set_cell_value("Sheet1", "A1", CellValue::String("plain".to_string()))
1007 .unwrap();
1008 let got = wb.get_cell_rich_text("Sheet1", "A1").unwrap();
1009 assert!(got.is_none());
1010 }
1011
1012 #[test]
1013 fn test_rich_text_roundtrip_save_open() {
1014 use crate::rich_text::RichTextRun;
1015
1016 let dir = TempDir::new().unwrap();
1017 let path = dir.path().join("rich_text.xlsx");
1018
1019 let mut wb = Workbook::new();
1023 let runs = vec![
1024 RichTextRun {
1025 text: "Hello".to_string(),
1026 font: Some("Arial".to_string()),
1027 size: Some(14.0),
1028 bold: true,
1029 italic: false,
1030 color: Some("#FF0000".to_string()),
1031 },
1032 RichTextRun {
1033 text: "World".to_string(),
1034 font: None,
1035 size: None,
1036 bold: false,
1037 italic: true,
1038 color: None,
1039 },
1040 ];
1041 wb.set_cell_rich_text("Sheet1", "B2", runs).unwrap();
1042 wb.save(&path).unwrap();
1043
1044 let wb2 = Workbook::open(&path).unwrap();
1045 let val = wb2.get_cell_value("Sheet1", "B2").unwrap();
1046 assert_eq!(val.to_string(), "HelloWorld");
1047
1048 let got = wb2.get_cell_rich_text("Sheet1", "B2").unwrap();
1049 assert!(got.is_some());
1050 let got_runs = got.unwrap();
1051 assert_eq!(got_runs.len(), 2);
1052 assert_eq!(got_runs[0].text, "Hello");
1053 assert!(got_runs[0].bold);
1054 assert_eq!(got_runs[0].font.as_deref(), Some("Arial"));
1055 assert_eq!(got_runs[0].size, Some(14.0));
1056 assert_eq!(got_runs[0].color.as_deref(), Some("#FF0000"));
1057 assert_eq!(got_runs[1].text, "World");
1058 assert!(got_runs[1].italic);
1059 assert!(!got_runs[1].bold);
1060 }
1061}