1use std::time::Duration;
6
7use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
8
9use crate::constants::{
10 SOURCE_RELATIONSHIP, SOURCE_RELATIONSHIP_HYPER_LINK, TOTAL_SHEET_HYPERLINKS,
11};
12use crate::date;
13use crate::errors::Result;
14use crate::errors::{
15 ErrCellCharsLength, ErrCoordinates, ErrInvalidFormula, ErrParameterInvalid,
16 ErrTotalSheetHyperlinks,
17};
18use crate::file::File;
19use crate::lib_util::{
20 cell_name_to_coordinates, coordinates_to_cell_name, range_ref_to_coordinates, sort_coordinates,
21 split_cell_name,
22};
23use crate::numfmt;
24use crate::xml::common::{RichTextRun, XlsxR, XlsxT};
25use crate::xml::shared_strings::XlsxSi;
26use crate::xml::styles::{XlsxNumFmt, XlsxXf};
27use crate::xml::worksheet::{XlsxC, XlsxF, XlsxHyperlink, XlsxHyperlinks, XlsxWorksheet};
28
29#[derive(Debug, Clone, PartialEq)]
35pub enum CellValue {
36 String(String),
38 Int(i64),
40 Float(f64),
42 Bool(bool),
44 Formula(String),
46 DateTime(NaiveDateTime),
48 Date(NaiveDate),
50 Time(NaiveTime),
52 Duration(Duration),
54 RichText(Vec<RichTextRun>),
56}
57
58impl From<&str> for CellValue {
59 fn from(v: &str) -> Self {
60 CellValue::String(v.to_string())
61 }
62}
63
64impl From<String> for CellValue {
65 fn from(v: String) -> Self {
66 CellValue::String(v)
67 }
68}
69
70impl From<i64> for CellValue {
71 fn from(v: i64) -> Self {
72 CellValue::Int(v)
73 }
74}
75
76impl From<i32> for CellValue {
77 fn from(v: i32) -> Self {
78 CellValue::Int(v as i64)
79 }
80}
81
82impl From<u64> for CellValue {
83 fn from(v: u64) -> Self {
84 CellValue::Int(v as i64)
85 }
86}
87
88impl From<f64> for CellValue {
89 fn from(v: f64) -> Self {
90 CellValue::Float(v)
91 }
92}
93
94impl From<bool> for CellValue {
95 fn from(v: bool) -> Self {
96 CellValue::Bool(v)
97 }
98}
99
100impl From<NaiveDateTime> for CellValue {
101 fn from(v: NaiveDateTime) -> Self {
102 CellValue::DateTime(v)
103 }
104}
105
106impl From<NaiveDate> for CellValue {
107 fn from(v: NaiveDate) -> Self {
108 CellValue::Date(v)
109 }
110}
111
112impl From<NaiveTime> for CellValue {
113 fn from(v: NaiveTime) -> Self {
114 CellValue::Time(v)
115 }
116}
117
118impl From<Duration> for CellValue {
119 fn from(v: Duration) -> Self {
120 CellValue::Duration(v)
121 }
122}
123
124impl From<Vec<RichTextRun>> for CellValue {
125 fn from(v: Vec<RichTextRun>) -> Self {
126 CellValue::RichText(v)
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum CellType {
137 Unset,
139 Bool,
141 Date,
143 Error,
145 Formula,
147 InlineString,
149 Number,
151 SharedString,
153}
154
155impl Default for CellType {
156 fn default() -> Self {
157 CellType::Unset
158 }
159}
160
161impl CellType {
162 fn from_type_attr(t: Option<&str>) -> Self {
163 match t {
164 Some("b") => CellType::Bool,
165 Some("d") => CellType::Date,
166 Some("e") => CellType::Error,
167 Some("str") => CellType::Formula,
168 Some("inlineStr") => CellType::InlineString,
169 Some("s") => CellType::SharedString,
170 Some(_) => CellType::Number,
171 None => CellType::Unset,
172 }
173 }
174}
175
176#[derive(Debug, Default, Clone, PartialEq, Eq)]
182pub struct FormulaOpts {
183 pub r#type: Option<String>,
185 pub r#ref: Option<String>,
187}
188
189#[derive(Debug, Default, Clone, PartialEq, Eq)]
191pub struct HyperlinkOpts {
192 pub display: Option<String>,
194 pub tooltip: Option<String>,
196}
197
198impl File {
203 pub fn set_cell_value(
205 &self,
206 sheet: &str,
207 cell: &str,
208 value: impl Into<CellValue>,
209 ) -> Result<()> {
210 let value = value.into();
211 self.set_cell_value_internal(sheet, cell, value)
212 }
213
214 pub fn get_cell_value(&self, sheet: &str, cell: &str) -> Result<String> {
216 let raw = self.options.lock().unwrap().raw_cell_value;
217 self.get_cell_value_with_options(sheet, cell, raw)
218 }
219
220 pub fn get_cell_value_with_options(
222 &self,
223 sheet: &str,
224 cell: &str,
225 raw: bool,
226 ) -> Result<String> {
227 let ws = self.work_sheet_reader(sheet)?;
228 let cell = merge_cells_parser(&ws, cell);
229 let Some(c) = find_cell(&ws, &cell) else {
230 return Ok(String::new());
231 };
232 Ok(read_cell_value(self, c, raw))
233 }
234
235 pub fn get_cell_type(&self, sheet: &str, cell: &str) -> Result<CellType> {
237 let ws = self.work_sheet_reader(sheet)?;
238 let cell = merge_cells_parser(&ws, cell);
239 let Some(c) = find_cell(&ws, &cell) else {
240 return Ok(CellType::Unset);
241 };
242 if c.f.is_some() {
243 return Ok(CellType::Formula);
244 }
245 Ok(CellType::from_type_attr(c.t.as_deref()))
246 }
247
248 pub fn set_cell_str(&self, sheet: &str, cell: &str, value: &str) -> Result<()> {
250 self.set_cell_value(sheet, cell, CellValue::String(value.to_string()))
251 }
252
253 pub fn set_cell_int(&self, sheet: &str, cell: &str, value: i64) -> Result<()> {
255 self.set_cell_value(sheet, cell, CellValue::Int(value))
256 }
257
258 pub fn set_cell_uint(&self, sheet: &str, cell: &str, value: u64) -> Result<()> {
260 self.set_cell_value(sheet, cell, CellValue::Int(value as i64))
261 }
262
263 pub fn set_cell_float(&self, sheet: &str, cell: &str, value: f64) -> Result<()> {
265 self.set_cell_float_with_precision(sheet, cell, value, -1, 64)
266 }
267
268 pub fn set_cell_float_with_precision(
273 &self,
274 sheet: &str,
275 cell: &str,
276 value: f64,
277 precision: i32,
278 bit_size: i32,
279 ) -> Result<()> {
280 if value.is_nan() || value.is_infinite() {
281 return self.set_cell_str(sheet, cell, &value.to_string());
282 }
283 let path =
284 self.get_sheet_xml_path(sheet)
285 .ok_or_else(|| crate::errors::ErrSheetNotExist {
286 sheet_name: sheet.to_string(),
287 })?;
288 let mut ws = self.work_sheet_reader(sheet)?;
289 let c = get_or_make_cell(&mut ws, cell);
290 let v = if precision < 0 {
291 if bit_size <= 32 {
292 format!("{}", value as f32)
293 } else {
294 format!("{}", value)
295 }
296 } else if bit_size <= 32 {
297 format!("{:.*}", precision as usize, value as f32)
298 } else {
299 format!("{:.*}", precision as usize, value)
300 };
301 c.t = None;
302 c.v = Some(v);
303 c.f = None;
304 c.is = None;
305 update_dimension(&mut ws)?;
306 self.sheet.insert(path, ws);
307 Ok(())
308 }
309
310 pub fn set_cell_bool(&self, sheet: &str, cell: &str, value: bool) -> Result<()> {
312 self.set_cell_value(sheet, cell, CellValue::Bool(value))
313 }
314
315 pub fn set_cell_default(&self, sheet: &str, cell: &str, value: &str) -> Result<()> {
317 let path =
318 self.get_sheet_xml_path(sheet)
319 .ok_or_else(|| crate::errors::ErrSheetNotExist {
320 sheet_name: sheet.to_string(),
321 })?;
322 let mut ws = self.work_sheet_reader(sheet)?;
323 let c = get_or_make_cell(&mut ws, cell);
324 set_cell_default_value(c, value);
325 update_dimension(&mut ws)?;
326 self.sheet.insert(path, ws);
327 Ok(())
328 }
329
330 pub fn set_cell_formula(&self, sheet: &str, cell: &str, formula: &str) -> Result<()> {
332 self.set_cell_formula_with_opts(sheet, cell, formula, &[])
333 }
334
335 pub fn set_cell_formula_with_opts(
337 &self,
338 sheet: &str,
339 cell: &str,
340 formula: &str,
341 opts: &[FormulaOpts],
342 ) -> Result<()> {
343 let path =
344 self.get_sheet_xml_path(sheet)
345 .ok_or_else(|| crate::errors::ErrSheetNotExist {
346 sheet_name: sheet.to_string(),
347 })?;
348 let mut ws = self.work_sheet_reader(sheet)?;
349 self.clear_calc_cache();
350
351 if formula.is_empty() {
352 if let Some(c) = find_cell_mut(&mut ws, cell) {
353 c.f = None;
354 }
355 self.delete_calc_chain(self.get_sheet_id(sheet), cell)?;
356 update_dimension(&mut ws)?;
357 self.sheet.insert(path, ws);
358 return Ok(());
359 }
360
361 if formula.starts_with('=') {
362 return Err(Box::new(ErrInvalidFormula));
363 }
364
365 let c = get_or_make_cell(&mut ws, cell);
366 let mut f = XlsxF {
367 content: formula.to_string(),
368 ..Default::default()
369 };
370 for opt in opts {
371 if let Some(t) = &opt.r#type {
372 f.t = Some(t.clone());
373 }
374 if let Some(r#ref) = &opt.r#ref {
375 f.r#ref = Some(r#ref.clone());
376 }
377 }
378 c.f = Some(f);
379 c.is = None;
380 update_dimension(&mut ws)?;
381 self.sheet.insert(path, ws);
382 Ok(())
383 }
384
385 pub fn get_cell_formula(&self, sheet: &str, cell: &str) -> Result<String> {
387 let ws = self.work_sheet_reader(sheet)?;
388 let cell = merge_cells_parser(&ws, cell);
389 let Some(c) = find_cell(&ws, &cell) else {
390 return Ok(String::new());
391 };
392 let Some(f) = &c.f else {
393 return Ok(String::new());
394 };
395 if f.t.as_deref() == Some("shared") {
396 if let Some(si) = f.si {
397 if let Some(master) = find_shared_formula_master(&ws, si) {
398 return Ok(master.content.clone());
399 }
400 }
401 }
402 Ok(f.content.clone())
403 }
404
405 pub fn get_cell_style(&self, sheet: &str, cell: &str) -> Result<i32> {
410 let ws = self.work_sheet_reader(sheet)?;
411 let (col, row) =
412 cell_name_to_coordinates(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
413 let mut style = 0_i64;
414 if let Some(c) = find_cell(&ws, cell) {
415 style = c.s.unwrap_or(0);
416 }
417 if style == 0 {
418 if let Some(r) = ws.sheet_data.row.iter().find(|r| r.r == Some(row as i64)) {
419 style = r.s.unwrap_or(0);
420 }
421 }
422 if style == 0 {
423 if let Some(cols) = &ws.cols {
424 for c in &cols.col {
425 if c.min <= col as i64 && col as i64 <= c.max {
426 style = c.style.unwrap_or(0);
427 if style != 0 {
428 break;
429 }
430 }
431 }
432 }
433 }
434 Ok(style as i32)
435 }
436
437 pub fn set_cell_style(
443 &self,
444 sheet: &str,
445 top_left_cell: &str,
446 bottom_right_cell: &str,
447 style_id: i32,
448 ) -> Result<()> {
449 if style_id < 0 {
450 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
451 }
452 let style_count = self
453 .styles_reader()?
454 .cell_xfs
455 .as_ref()
456 .map(|x| x.xf.len())
457 .unwrap_or(1) as i32;
458 if style_id >= style_count {
459 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
460 }
461 let (mut h_col, mut h_row) =
462 cell_name_to_coordinates(top_left_cell).map_err(|_| Box::new(ErrParameterInvalid))?;
463 let (mut v_col, mut v_row) = cell_name_to_coordinates(bottom_right_cell)
464 .map_err(|_| Box::new(ErrParameterInvalid))?;
465 if v_col < h_col {
466 std::mem::swap(&mut v_col, &mut h_col);
467 }
468 if v_row < h_row {
469 std::mem::swap(&mut v_row, &mut h_row);
470 }
471 let path =
472 self.get_sheet_xml_path(sheet)
473 .ok_or_else(|| crate::errors::ErrSheetNotExist {
474 sheet_name: sheet.to_string(),
475 })?;
476 let mut ws = self.work_sheet_reader(sheet)?;
477 for row in h_row..=v_row {
478 for col in h_col..=v_col {
479 let cell = coordinates_to_cell_name(col, row, false)
480 .map_err(|_| Box::new(ErrParameterInvalid))?;
481 let c = get_or_make_cell(&mut ws, &cell);
482 c.s = Some(style_id as i64);
483 }
484 }
485 self.sheet.insert(path, ws);
486 Ok(())
487 }
488
489 pub fn set_cell_rich_text(
491 &self,
492 sheet: &str,
493 cell: &str,
494 runs: Vec<RichTextRun>,
495 ) -> Result<()> {
496 if runs.is_empty() {
497 return self.set_cell_str(sheet, cell, "");
498 }
499 let path =
500 self.get_sheet_xml_path(sheet)
501 .ok_or_else(|| crate::errors::ErrSheetNotExist {
502 sheet_name: sheet.to_string(),
503 })?;
504 let mut ws = self.work_sheet_reader(sheet)?;
505 let (col, row) = cell_name_to_coordinates(cell).unwrap_or((1, 1));
506 let current_s = {
507 let c = get_or_make_cell(&mut ws, cell);
508 c.s.unwrap_or(0)
509 };
510 let style_id = prepare_cell_style(&ws, col as i64, row as i64, current_s);
511 let c = get_or_make_cell(&mut ws, cell);
512 c.s = Some(style_id);
513
514 let si = runs_to_xlsx_si(&runs);
515 let mut sst = self.shared_strings_reader().unwrap_or_default();
516 let idx = sst
517 .si
518 .iter()
519 .position(|existing| existing == &si)
520 .unwrap_or_else(|| {
521 sst.si.push(si);
522 sst.unique_count += 1;
523 sst.si.len() - 1
524 });
525 sst.count += 1;
526 *self.shared_strings.lock().unwrap() = Some(sst);
527
528 c.t = Some("s".to_string());
529 c.v = Some(idx.to_string());
530 c.f = None;
531 c.is = None;
532 update_dimension(&mut ws)?;
533 self.sheet.insert(path, ws);
534 Ok(())
535 }
536
537 pub fn get_cell_rich_text(&self, sheet: &str, cell: &str) -> Result<Vec<RichTextRun>> {
539 let ws = self.work_sheet_reader(sheet)?;
540 let cell = merge_cells_parser(&ws, cell);
541 let Some(c) = find_cell(&ws, &cell) else {
542 return Ok(Vec::new());
543 };
544 if c.t.as_deref() == Some("inlineStr") {
545 if let Some(is) = &c.is {
546 return Ok(runs_from_xlsx_si(is));
547 }
548 }
549 if c.t.as_deref() == Some("s") {
550 if let Some(v) = &c.v {
551 if let Ok(idx) = v.parse::<usize>() {
552 let sst = self.shared_strings_reader()?;
553 if let Some(si) = sst.si.get(idx) {
554 return Ok(runs_from_xlsx_si(si));
555 }
556 }
557 }
558 }
559 Ok(Vec::new())
560 }
561
562 pub fn get_cell_hyperlink(&self, sheet: &str, cell: &str) -> Result<(bool, String)> {
567 split_cell_name(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
568 let ws = self.work_sheet_reader(sheet)?;
569 let cell = merge_cells_parser(&ws, cell);
570 if let Some(links) = &ws.hyperlinks {
571 for link in &links.hyperlink {
572 if link.r#ref == cell {
573 if let Some(rid) = &link.rid {
574 return Ok((true, self.get_sheet_relationships_target_by_id(sheet, rid)));
575 }
576 return Ok((true, link.location.clone().unwrap_or_default()));
577 }
578 let ok = check_cell_in_range_ref(&cell, &link.r#ref).unwrap_or(false);
579 if ok {
580 if let Some(rid) = &link.rid {
581 return Ok((true, self.get_sheet_relationships_target_by_id(sheet, rid)));
582 }
583 return Ok((true, link.location.clone().unwrap_or_default()));
584 }
585 }
586 }
587 Ok((false, String::new()))
588 }
589
590 pub fn get_hyperlink_cells(&self, sheet: &str, link_type: &str) -> Result<Vec<String>> {
594 let ws = self.work_sheet_reader(sheet)?;
595 let mut out = Vec::new();
596 if let Some(links) = &ws.hyperlinks {
597 for link in &links.hyperlink {
598 match link_type {
599 "External" => {
600 if link.rid.is_some() {
601 out.push(link.r#ref.clone());
602 }
603 }
604 "Location" => {
605 if link.location.as_deref().unwrap_or("").is_empty() {
606 continue;
607 }
608 out.push(link.r#ref.clone());
609 }
610 "None" => return Ok(out),
611 "" => out.push(link.r#ref.clone()),
612 _ => return Err(crate::errors::new_invalid_link_type_error(link_type).into()),
613 }
614 }
615 }
616 Ok(out)
617 }
618
619 pub fn set_cell_hyperlink(
621 &self,
622 sheet: &str,
623 cell: &str,
624 link: &str,
625 link_type: &str,
626 opts: &[HyperlinkOpts],
627 ) -> Result<()> {
628 split_cell_name(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
629 let path =
630 self.get_sheet_xml_path(sheet)
631 .ok_or_else(|| crate::errors::ErrSheetNotExist {
632 sheet_name: sheet.to_string(),
633 })?;
634 let mut ws = self.work_sheet_reader(sheet)?;
635 let cell = merge_cells_parser(&ws, cell);
636
637 if link_type == "None" {
638 remove_hyperlink(self, &mut ws, sheet, &cell)?;
639 update_dimension(&mut ws)?;
640 self.sheet.insert(path, ws);
641 return Ok(());
642 }
643
644 if ws.hyperlinks.is_none() {
645 ws.hyperlinks = Some(XlsxHyperlinks::default());
646 }
647 let links = ws.hyperlinks.as_mut().unwrap();
648
649 let existing_idx = links.hyperlink.iter().position(|h| h.r#ref == cell);
650 let mut link_data = existing_idx
651 .and_then(|i| links.hyperlink.get(i).cloned())
652 .unwrap_or_default();
653 link_data.r#ref = cell.clone();
654
655 if links.hyperlink.len() as i32 > TOTAL_SHEET_HYPERLINKS {
656 return Err(Box::new(ErrTotalSheetHyperlinks));
657 }
658
659 match link_type {
660 "External" => {
661 let sheet_path = self.get_sheet_xml_path(sheet).unwrap_or_default();
662 let sheet_rels = format!(
663 "xl/worksheets/_rels/{}.rels",
664 sheet_path.trim_start_matches("xl/worksheets/")
665 );
666 let rid_num = self.set_rels(
667 link_data.rid.as_deref().unwrap_or(""),
668 &sheet_rels,
669 SOURCE_RELATIONSHIP_HYPER_LINK,
670 link,
671 "External",
672 );
673 link_data = XlsxHyperlink {
674 r#ref: cell,
675 rid: Some(format!("rId{rid_num}")),
676 ..Default::default()
677 };
678 self.add_sheet_name_space(sheet, SOURCE_RELATIONSHIP);
679 }
680 "Location" => {
681 link_data.location = Some(link.to_string());
682 link_data.rid = None;
683 }
684 _ => return Err(crate::errors::new_invalid_link_type_error(link_type).into()),
685 }
686
687 for opt in opts {
688 if let Some(display) = &opt.display {
689 link_data.display = Some(display.clone());
690 }
691 if let Some(tooltip) = &opt.tooltip {
692 link_data.tooltip = Some(tooltip.clone());
693 }
694 }
695
696 if let Some(idx) = existing_idx {
697 links.hyperlink[idx] = link_data;
698 } else {
699 links.hyperlink.push(link_data);
700 }
701 update_dimension(&mut ws)?;
702 self.sheet.insert(path, ws);
703 Ok(())
704 }
705
706 pub fn set_sheet_row(&self, sheet: &str, cell: &str, values: &[CellValue]) -> Result<()> {
708 let (col, row) =
709 cell_name_to_coordinates(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
710 for (i, value) in values.iter().enumerate() {
711 let cell_name = coordinates_to_cell_name(col + i as i32, row, false)
712 .map_err(|_| Box::new(ErrParameterInvalid))?;
713 self.set_cell_value(sheet, &cell_name, value.clone())?;
714 }
715 Ok(())
716 }
717
718 pub fn set_sheet_col(&self, sheet: &str, cell: &str, values: &[CellValue]) -> Result<()> {
720 let (col, row) =
721 cell_name_to_coordinates(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
722 for (i, value) in values.iter().enumerate() {
723 let cell_name = coordinates_to_cell_name(col, row + i as i32, false)
724 .map_err(|_| Box::new(ErrParameterInvalid))?;
725 self.set_cell_value(sheet, &cell_name, value.clone())?;
726 }
727 Ok(())
728 }
729}
730
731impl File {
736 fn set_cell_value_internal(&self, sheet: &str, cell: &str, value: CellValue) -> Result<()> {
737 let path =
738 self.get_sheet_xml_path(sheet)
739 .ok_or_else(|| crate::errors::ErrSheetNotExist {
740 sheet_name: sheet.to_string(),
741 })?;
742 let mut ws = self.work_sheet_reader(sheet)?;
743
744 match value {
745 CellValue::String(s) => self.set_string_cell_value(&mut ws, cell, &s),
746 CellValue::Int(n) => {
747 let c = get_or_make_cell(&mut ws, cell);
748 c.t = None;
749 c.v = Some(n.to_string());
750 c.f = None;
751 c.is = None;
752 }
753 CellValue::Float(n) => {
754 let c = get_or_make_cell(&mut ws, cell);
755 c.t = None;
756 c.v = Some(n.to_string());
757 c.f = None;
758 c.is = None;
759 }
760 CellValue::Bool(b) => {
761 let c = get_or_make_cell(&mut ws, cell);
762 c.t = Some("b".to_string());
763 c.v = Some(if b { "1".to_string() } else { "0".to_string() });
764 c.f = None;
765 c.is = None;
766 }
767 CellValue::Formula(f) => {
768 if f.starts_with('=') {
769 return Err(Box::new(ErrInvalidFormula));
770 }
771 let c = get_or_make_cell(&mut ws, cell);
772 c.t = None;
773 c.v = None;
774 c.f = Some(XlsxF {
775 content: f,
776 ..Default::default()
777 });
778 c.is = None;
779 }
780 CellValue::DateTime(dt) => {
781 self.set_default_date_time_style(&mut ws, cell, dt.date(), true)?;
782 let c = get_or_make_cell(&mut ws, cell);
783 c.v = Some(date::datetime_to_excel_serial(dt, self.date_1904()?).to_string());
784 c.t = None;
785 c.f = None;
786 c.is = None;
787 }
788 CellValue::Date(d) => {
789 self.set_default_date_time_style(&mut ws, cell, d, false)?;
790 let c = get_or_make_cell(&mut ws, cell);
791 c.v = Some(date::date_to_excel_serial(d, self.date_1904()?).to_string());
792 c.t = None;
793 c.f = None;
794 c.is = None;
795 }
796 CellValue::Time(t) => {
797 self.set_default_time_style(&mut ws, cell)?;
798 let c = get_or_make_cell(&mut ws, cell);
799 c.v = Some(date::time_to_excel_serial(t).to_string());
800 c.t = None;
801 c.f = None;
802 c.is = None;
803 }
804 CellValue::Duration(d) => {
805 self.set_default_time_style(&mut ws, cell)?;
806 let c = get_or_make_cell(&mut ws, cell);
807 let days = d.as_secs_f64() / 86400.0;
808 c.v = Some(days.to_string());
809 c.t = None;
810 c.f = None;
811 c.is = None;
812 }
813 CellValue::RichText(runs) => {
814 let c = get_or_make_cell(&mut ws, cell);
815 c.t = Some("inlineStr".to_string());
816 c.v = None;
817 c.f = None;
818 c.is = Some(runs_to_xlsx_si(&runs));
819 }
820 }
821
822 update_dimension(&mut ws)?;
823 self.sheet.insert(path, ws);
824 Ok(())
825 }
826
827 pub(crate) fn remove_formula(
832 &self,
833 c: &mut XlsxC,
834 ws: &mut XlsxWorksheet,
835 sheet: &str,
836 ) -> Result<()> {
837 self.clear_calc_cache();
838 if c.f.is_some() && c.vm.is_none() {
839 let sheet_id = self.get_sheet_id(sheet);
840 if let Some(ref cell_ref) = c.r {
841 self.delete_calc_chain(sheet_id, cell_ref)?;
842 }
843 let f = c.f.as_ref().unwrap();
844 if f.t.as_deref() == Some("shared")
845 && f.r#ref.as_deref().map_or(false, |s| !s.is_empty())
846 {
847 if let Some(si_val) = f.si {
848 for row in &mut ws.sheet_data.row {
849 for cell in &mut row.c {
850 if cell.f.as_ref().and_then(|ff| ff.si) == Some(si_val) {
851 if let Some(ref r) = cell.r {
852 let _ = self.delete_calc_chain(sheet_id, r);
853 }
854 cell.f = None;
855 }
856 }
857 }
858 }
859 }
860 c.f = None;
861 }
862 Ok(())
863 }
864
865 fn set_string_cell_value(&self, ws: &mut XlsxWorksheet, cell: &str, value: &str) {
866 if value.is_empty() {
867 return;
868 }
869 if value.len() > crate::constants::TOTAL_CELL_CHARS {
870 let _ = Err::<(), _>(Box::new(ErrCellCharsLength));
871 return;
872 }
873 let idx = self.shared_string_index(value);
874 let c = get_or_make_cell(ws, cell);
875 c.t = Some("s".to_string());
876 c.v = Some(idx.to_string());
877 c.f = None;
878 c.is = None;
879 }
880
881 fn set_default_time_style(&self, ws: &mut XlsxWorksheet, cell: &str) -> Result<()> {
882 self.set_default_date_time_style(ws, cell, NaiveDate::default(), true)
884 }
885
886 fn set_default_date_time_style(
887 &self,
888 ws: &mut XlsxWorksheet,
889 cell: &str,
890 date: NaiveDate,
891 has_time: bool,
892 ) -> Result<()> {
893 let c = get_or_make_cell(ws, cell);
894 if c.s.is_some() {
895 return Ok(());
896 }
897 let num_fmt_id = if has_time && date != NaiveDate::default() {
898 22 } else if has_time {
900 21 } else {
902 14 };
904 let style_id = self.find_or_create_style(num_fmt_id, None)?;
905 c.s = Some(style_id);
906 Ok(())
907 }
908
909 fn find_or_create_style(&self, num_fmt_id: i32, format_code: Option<&str>) -> Result<i64> {
911 let mut styles = self.styles_reader()?;
912 let cell_xfs = styles.cell_xfs.get_or_insert_with(Default::default);
913
914 for (idx, xf) in cell_xfs.xf.iter().enumerate() {
915 if xf.num_fmt_id == Some(num_fmt_id as i64) {
916 return Ok(idx as i64);
917 }
918 }
919
920 let new_idx = cell_xfs.xf.len() as i64;
921 cell_xfs.xf.push(XlsxXf {
922 num_fmt_id: Some(num_fmt_id as i64),
923 apply_number_format: Some(true),
924 ..Default::default()
925 });
926 cell_xfs.count = cell_xfs.xf.len() as i64;
927
928 if num_fmt_id >= 164 {
930 if let Some(code) = format_code {
931 let num_fmts = styles.num_fmts.get_or_insert_with(Default::default);
932 let custom_id = num_fmt_id;
933 if !num_fmts
934 .num_fmt
935 .iter()
936 .any(|n| n.num_fmt_id == custom_id as i64)
937 {
938 num_fmts.num_fmt.push(XlsxNumFmt {
939 num_fmt_id: custom_id as i64,
940 format_code: code.to_string(),
941 format_code_16: None,
942 });
943 num_fmts.count = num_fmts.num_fmt.len() as i64;
944 }
945 }
946 }
947
948 *self.styles.lock().unwrap() = Some(styles);
949 Ok(new_idx)
950 }
951
952 fn date_1904(&self) -> Result<bool> {
953 let wb = self.workbook_reader()?;
954 Ok(wb
955 .workbook_pr
956 .as_ref()
957 .and_then(|p| p.date1904)
958 .unwrap_or(false))
959 }
960
961 fn get_cell_num_fmt_id(&self, c: &XlsxC) -> Option<i32> {
963 let style_id = c.s.unwrap_or(0) as usize;
964 let styles = self.styles_reader().ok()?;
965 let cell_xfs = styles.cell_xfs?;
966 let xf = cell_xfs.xf.get(style_id)?;
967 xf.num_fmt_id.map(|id| id as i32)
968 }
969
970 fn get_num_fmt_code(&self, num_fmt_id: i32) -> Option<String> {
972 let styles = self.styles_reader().ok()?;
973 styles
974 .num_fmts?
975 .num_fmt
976 .into_iter()
977 .find(|n| n.num_fmt_id == num_fmt_id as i64)
978 .map(|n| n.format_code)
979 }
980
981 fn shared_string_index(&self, text: &str) -> i32 {
983 {
984 let map = self.shared_strings_map.lock().unwrap();
985 if let Some(&idx) = map.get(text) {
986 return idx;
987 }
988 }
989 let mut sst = self.shared_strings_reader().unwrap_or_default();
990 let mut map = self.shared_strings_map.lock().unwrap();
991 if let Some(&idx) = map.get(text) {
992 return idx;
993 }
994 let idx = map.len() as i32;
995 map.insert(text.to_string(), idx);
996 sst.si.push(XlsxSi {
997 t: Some(XlsxT {
998 space: None,
999 val: text.to_string(),
1000 }),
1001 ..Default::default()
1002 });
1003 sst.unique_count += 1;
1004 sst.count += 1;
1005 *self.shared_strings.lock().unwrap() = Some(sst);
1006 idx
1007 }
1008}
1009
1010pub(crate) fn find_cell<'a>(ws: &'a XlsxWorksheet, cell: &str) -> Option<&'a XlsxC> {
1015 for row in &ws.sheet_data.row {
1016 for c in &row.c {
1017 if c.r.as_deref() == Some(cell) {
1018 return Some(c);
1019 }
1020 }
1021 }
1022 None
1023}
1024
1025pub(crate) fn find_cell_mut<'a>(ws: &'a mut XlsxWorksheet, cell: &str) -> Option<&'a mut XlsxC> {
1026 for row in &mut ws.sheet_data.row {
1027 for c in &mut row.c {
1028 if c.r.as_deref() == Some(cell) {
1029 return Some(c);
1030 }
1031 }
1032 }
1033 None
1034}
1035
1036fn get_or_make_cell<'a>(ws: &'a mut XlsxWorksheet, cell: &str) -> &'a mut XlsxC {
1037 let (_col, row_num) = cell_name_to_coordinates(cell).unwrap_or((1, 1));
1038 let row_idx = ws
1039 .sheet_data
1040 .row
1041 .iter()
1042 .position(|r| r.r == Some(row_num as i64));
1043 if let Some(idx) = row_idx {
1044 if let Some(pos) = ws.sheet_data.row[idx]
1045 .c
1046 .iter()
1047 .position(|c| c.r.as_deref() == Some(cell))
1048 {
1049 return &mut ws.sheet_data.row[idx].c[pos];
1050 }
1051 let c = XlsxC {
1052 r: Some(cell.to_string()),
1053 ..Default::default()
1054 };
1055 ws.sheet_data.row[idx].c.push(c);
1056 let last = ws.sheet_data.row[idx].c.len() - 1;
1057 return &mut ws.sheet_data.row[idx].c[last];
1058 }
1059 let mut row = crate::xml::worksheet::XlsxRow {
1060 r: Some(row_num as i64),
1061 ..Default::default()
1062 };
1063 row.c.push(XlsxC {
1064 r: Some(cell.to_string()),
1065 ..Default::default()
1066 });
1067 ws.sheet_data.row.push(row);
1068 let last_row = ws.sheet_data.row.len() - 1;
1069 let last_cell = ws.sheet_data.row[last_row].c.len() - 1;
1070 &mut ws.sheet_data.row[last_row].c[last_cell]
1071}
1072
1073fn update_dimension(ws: &mut XlsxWorksheet) -> Result<()> {
1074 if ws.sheet_data.row.is_empty() {
1075 ws.dimension = Some(crate::xml::worksheet::XlsxDimension {
1076 r#ref: "A1".to_string(),
1077 });
1078 return Ok(());
1079 }
1080 let mut min_col = i32::MAX;
1081 let mut min_row = i32::MAX;
1082 let mut max_col = 1;
1083 let mut max_row = 1;
1084 for row in &ws.sheet_data.row {
1085 let row_num = row.r.unwrap_or(1) as i32;
1086 min_row = min_row.min(row_num);
1087 max_row = max_row.max(row_num);
1088 for c in &row.c {
1089 if let Some(name) = &c.r {
1090 if let Ok((col, _)) = cell_name_to_coordinates(name) {
1091 min_col = min_col.min(col);
1092 max_col = max_col.max(col);
1093 }
1094 }
1095 }
1096 }
1097 if min_col == i32::MAX {
1098 min_col = 1;
1099 }
1100 let first = coordinates_to_cell_name(min_col, min_row, false)?;
1101 let last = coordinates_to_cell_name(max_col, max_row, false)?;
1102 ws.dimension = Some(crate::xml::worksheet::XlsxDimension {
1103 r#ref: format!("{first}:{last}"),
1104 });
1105 Ok(())
1106}
1107
1108fn set_cell_default_value(c: &mut XlsxC, value: &str) {
1109 c.f = None;
1110 c.is = None;
1111 if value.parse::<f64>().is_ok() {
1112 c.t = None;
1113 c.v = Some(value.to_string());
1114 return;
1115 }
1116 if !value.is_empty() {
1117 c.t = Some("inlineStr".to_string());
1118 c.v = None;
1119 c.is = Some(XlsxSi {
1120 t: Some(XlsxT {
1121 space: None,
1122 val: value.to_string(),
1123 }),
1124 ..Default::default()
1125 });
1126 return;
1127 }
1128 c.t = Some(value.to_string());
1129 c.v = Some(value.to_string());
1130}
1131
1132fn prepare_cell_style(ws: &XlsxWorksheet, col: i64, row: i64, style: i64) -> i64 {
1133 if style != 0 {
1134 return style;
1135 }
1136 if row > 0 && row as usize <= ws.sheet_data.row.len() {
1137 if let Some(style_id) = ws.sheet_data.row[row as usize - 1].s {
1138 if style_id != 0 {
1139 return style_id;
1140 }
1141 }
1142 }
1143 if let Some(cols) = &ws.cols {
1144 for c in &cols.col {
1145 if c.min <= col && col <= c.max {
1146 if let Some(style_id) = c.style {
1147 if style_id != 0 {
1148 return style_id;
1149 }
1150 }
1151 }
1152 }
1153 }
1154 style
1155}
1156
1157fn merge_cells_parser(ws: &XlsxWorksheet, cell: &str) -> String {
1162 let upper = cell.to_uppercase();
1163 let Ok((col, row)) = cell_name_to_coordinates(&upper) else {
1164 return upper;
1165 };
1166 if let Some(merges) = &ws.merge_cells {
1167 for mc in &merges.cells {
1168 let mut r#ref = mc.r#ref.clone().unwrap_or_default();
1169 if r#ref.is_empty() {
1170 continue;
1171 }
1172 if !r#ref.contains(':') {
1173 r#ref = format!("{ref}:{ref}", ref = r#ref);
1174 }
1175 let Ok(mut coords) = range_ref_to_coordinates(&r#ref) else {
1176 continue;
1177 };
1178 let _ = sort_coordinates(&mut coords);
1179 if cell_in_range(&[col, row], &coords) {
1180 return r#ref.split(':').next().unwrap_or(&upper).to_string();
1181 }
1182 }
1183 }
1184 upper
1185}
1186
1187fn check_cell_in_range_ref(cell: &str, range_ref: &str) -> Result<bool> {
1188 let (col, row) = cell_name_to_coordinates(cell).map_err(|_| Box::new(ErrParameterInvalid))?;
1189 if !range_ref.contains(':') {
1190 return Ok(false);
1191 }
1192 let mut coords =
1193 range_ref_to_coordinates(range_ref).map_err(|_| Box::new(ErrParameterInvalid))?;
1194 sort_coordinates(&mut coords).map_err(|_| Box::new(ErrCoordinates))?;
1195 Ok(cell_in_range(&[col, row], &coords))
1196}
1197
1198fn cell_in_range(cell: &[i32], rect: &[i32]) -> bool {
1199 cell.len() >= 2
1200 && rect.len() >= 4
1201 && cell[0] >= rect[0]
1202 && cell[0] <= rect[2]
1203 && cell[1] >= rect[1]
1204 && cell[1] <= rect[3]
1205}
1206
1207fn find_shared_formula_master<'a>(ws: &'a XlsxWorksheet, si: i64) -> Option<&'a XlsxF> {
1212 for row in &ws.sheet_data.row {
1213 for c in &row.c {
1214 if let Some(f) = &c.f {
1215 if f.t.as_deref() == Some("shared")
1216 && f.si == Some(si)
1217 && !f.r#ref.as_deref().unwrap_or("").is_empty()
1218 {
1219 return Some(f);
1220 }
1221 }
1222 }
1223 }
1224 None
1225}
1226
1227fn remove_hyperlink(file: &File, ws: &mut XlsxWorksheet, sheet: &str, cell: &str) -> Result<()> {
1232 if ws.hyperlinks.is_none() {
1233 return Ok(());
1234 }
1235 let links = ws.hyperlinks.as_mut().unwrap();
1236 let mut i = 0;
1237 while i < links.hyperlink.len() {
1238 let link = &links.hyperlink[i];
1239 let remove =
1240 link.r#ref == cell || check_cell_in_range_ref(cell, &link.r#ref).unwrap_or(false);
1241 if remove {
1242 if let Some(rid) = &link.rid {
1243 file.delete_sheet_relationships(sheet, rid);
1244 }
1245 links.hyperlink.remove(i);
1246 } else {
1247 i += 1;
1248 }
1249 }
1250 if links.hyperlink.is_empty() {
1251 ws.hyperlinks = None;
1252 }
1253 Ok(())
1254}
1255
1256pub(crate) fn read_cell_value(file: &File, c: &XlsxC, raw: bool) -> String {
1261 let raw_value = match c.t.as_deref() {
1262 Some("s") => {
1263 if let Some(v) = &c.v {
1264 if let Ok(idx) = v.parse::<i32>() {
1265 return read_shared_string(file, idx);
1266 }
1267 }
1268 return String::new();
1269 }
1270 Some("inlineStr") => {
1271 return c
1272 .is
1273 .as_ref()
1274 .map(|is| inline_string_text(is))
1275 .unwrap_or_default();
1276 }
1277 Some("b") => {
1278 return c
1279 .v
1280 .as_deref()
1281 .map(|v| {
1282 if v == "1" {
1283 "TRUE".to_string()
1284 } else {
1285 "FALSE".to_string()
1286 }
1287 })
1288 .unwrap_or_default();
1289 }
1290 Some("str") => return c.v.clone().unwrap_or_default(),
1291 _ => c.v.clone().unwrap_or_default(),
1292 };
1293
1294 if c.f.is_some() && c.v.is_none() {
1296 return format!("={}", c.f.as_ref().unwrap().content);
1297 }
1298
1299 if raw {
1301 return raw_value;
1302 }
1303
1304 if let Some(num_fmt_id) = file.get_cell_num_fmt_id(c) {
1305 if let Ok(value) = raw_value.parse::<f64>() {
1306 let format_code = file.get_num_fmt_code(num_fmt_id);
1307 let date1904 = file.date_1904().unwrap_or(false);
1308 return numfmt::apply_number_format(
1309 value,
1310 num_fmt_id,
1311 format_code.as_deref(),
1312 date1904,
1313 );
1314 }
1315 }
1316
1317 raw_value
1318}
1319
1320fn read_shared_string(file: &File, idx: i32) -> String {
1321 let sst = file.shared_strings_reader().unwrap_or_default();
1322 sst.si
1323 .get(idx as usize)
1324 .map(|si| {
1325 if let Some(t) = &si.t {
1326 t.val.clone()
1327 } else {
1328 si.r.iter()
1329 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
1330 .collect()
1331 }
1332 })
1333 .unwrap_or_default()
1334}
1335
1336fn inline_string_text(si: &XlsxSi) -> String {
1337 if let Some(t) = &si.t {
1338 return t.val.clone();
1339 }
1340 si.r.iter()
1341 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
1342 .collect()
1343}
1344
1345pub(crate) fn runs_to_xlsx_si(runs: &[RichTextRun]) -> XlsxSi {
1350 let mut r = Vec::new();
1351 for run in runs {
1352 let mut xrun = XlsxR::default();
1353 if !run.text.is_empty() {
1354 let (text, space) = trim_cell_value(&run.text);
1355 xrun.t = Some(XlsxT { space, val: text });
1356 }
1357 if let Some(font) = &run.font {
1358 let mut rpr = crate::xml::common::XlsxRPr::default();
1359 if let Some(name) = &font.name {
1360 rpr.r_font = Some(crate::xml::common::AttrValString {
1361 val: Some(name.clone()),
1362 });
1363 }
1364 if let Some(size) = font.size {
1365 rpr.sz = Some(crate::xml::common::AttrValFloat { val: Some(size) });
1366 }
1367 if let Some(family) = font.family {
1368 rpr.family = Some(crate::xml::common::AttrValInt { val: Some(family) });
1369 }
1370 if let Some(charset) = font.charset {
1371 rpr.charset = Some(crate::xml::common::AttrValInt { val: Some(charset) });
1372 }
1373 if let Some(bold) = font.bold {
1374 rpr.b = Some(crate::xml::common::AttrValBool { val: Some(bold) });
1375 }
1376 if let Some(italic) = font.italic {
1377 rpr.i = Some(crate::xml::common::AttrValBool { val: Some(italic) });
1378 }
1379 if let Some(strike) = font.strike {
1380 rpr.strike = Some(crate::xml::common::AttrValBool { val: Some(strike) });
1381 }
1382 if let Some(underline) = &font.underline {
1383 rpr.u = Some(crate::xml::common::AttrValString {
1384 val: Some(underline.clone()),
1385 });
1386 }
1387 if let Some(color) = &font.color {
1388 rpr.color = Some(crate::xml::common::XlsxColor {
1389 rgb: Some(color.clone()),
1390 ..Default::default()
1391 });
1392 }
1393 if let Some(vert_align) = &font.vert_align {
1394 rpr.vert_align = Some(crate::xml::common::AttrValString {
1395 val: Some(vert_align.clone()),
1396 });
1397 }
1398 xrun.r_pr = Some(rpr);
1399 }
1400 r.push(xrun);
1401 }
1402 XlsxSi {
1403 t: None,
1404 r,
1405 ..Default::default()
1406 }
1407}
1408
1409fn runs_from_xlsx_si(si: &XlsxSi) -> Vec<RichTextRun> {
1410 let mut runs = Vec::new();
1411 if let Some(t) = &si.t {
1412 runs.push(RichTextRun {
1413 text: t.val.clone(),
1414 ..Default::default()
1415 });
1416 }
1417 for xrun in &si.r {
1418 let mut run = RichTextRun::default();
1419 if let Some(t) = &xrun.t {
1420 run.text = t.val.clone();
1421 }
1422 if let Some(rpr) = &xrun.r_pr {
1423 let mut font = crate::styles::Font::default();
1424 font.name = rpr.r_font.as_ref().and_then(|f| f.val.clone());
1425 font.size = rpr.sz.as_ref().and_then(|s| s.val);
1426 font.family = rpr.family.as_ref().and_then(|f| f.val);
1427 font.charset = rpr.charset.as_ref().and_then(|f| f.val);
1428 font.bold = rpr.b.as_ref().and_then(|b| b.val);
1429 font.italic = rpr.i.as_ref().and_then(|i| i.val);
1430 font.strike = rpr.strike.as_ref().and_then(|s| s.val);
1431 font.underline = rpr.u.as_ref().and_then(|u| u.val.clone());
1432 font.color = rpr.color.as_ref().and_then(|c| c.rgb.clone());
1433 font.vert_align = rpr.vert_align.as_ref().and_then(|v| v.val.clone());
1434 run.font = Some(font);
1435 }
1436 runs.push(run);
1437 }
1438 runs
1439}
1440
1441fn trim_cell_value(value: &str) -> (String, Option<String>) {
1442 let mut space = None;
1443 if !value.is_empty() {
1444 let prefix = value.as_bytes()[0];
1445 let suffix = value.as_bytes()[value.len() - 1];
1446 for &ascii in &[9u8, 10, 13, 32] {
1447 if prefix == ascii || suffix == ascii {
1448 space = Some("preserve".to_string());
1449 break;
1450 }
1451 }
1452 }
1453 (value.to_string(), space)
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458 use super::*;
1459 use chrono::{NaiveDate, NaiveTime};
1460
1461 #[test]
1462 fn set_and_get_string() {
1463 let f = File::new_with_options(crate::options::Options::default());
1464 f.set_cell_str("Sheet1", "A1", "hello").unwrap();
1465 assert_eq!(f.get_cell_value("Sheet1", "A1").unwrap(), "hello");
1466 }
1467
1468 #[test]
1469 fn set_cell_style_range() {
1470 let f = File::new_with_options(crate::options::Options::default());
1471 let style = f
1472 .new_style(&crate::styles::Style {
1473 fill: crate::styles::Fill {
1474 r#type: "pattern".to_string(),
1475 pattern: 1,
1476 color: vec!["63BE7B".to_string()],
1477 ..Default::default()
1478 },
1479 ..Default::default()
1480 })
1481 .unwrap();
1482
1483 f.set_cell_style("Sheet1", "B2", "C3", style).unwrap();
1484 assert_eq!(f.get_cell_style("Sheet1", "B2").unwrap(), style);
1485 assert_eq!(f.get_cell_style("Sheet1", "C3").unwrap(), style);
1486 assert_eq!(f.get_cell_style("Sheet1", "A1").unwrap(), 0);
1487 assert_eq!(f.get_cell_style("Sheet1", "D4").unwrap(), 0);
1488
1489 f.set_cell_style("Sheet1", "E5", "D4", style).unwrap();
1491 assert_eq!(f.get_cell_style("Sheet1", "D4").unwrap(), style);
1492 assert_eq!(f.get_cell_style("Sheet1", "E5").unwrap(), style);
1493
1494 f.set_cell_style("Sheet1", "A1", "A1", style).unwrap();
1496 assert_eq!(f.get_cell_style("Sheet1", "A1").unwrap(), style);
1497 }
1498
1499 #[test]
1500 fn set_and_get_int() {
1501 let f = File::new_with_options(crate::options::Options::default());
1502 f.set_cell_int("Sheet1", "B2", 42).unwrap();
1503 assert_eq!(f.get_cell_value("Sheet1", "B2").unwrap(), "42");
1504 }
1505
1506 #[test]
1507 fn set_and_get_bool() {
1508 let f = File::new_with_options(crate::options::Options::default());
1509 f.set_cell_bool("Sheet1", "C3", true).unwrap();
1510 assert_eq!(f.get_cell_value("Sheet1", "C3").unwrap(), "TRUE");
1511 }
1512
1513 #[test]
1514 fn set_and_get_formula() {
1515 let f = File::new_with_options(crate::options::Options::default());
1516 f.set_cell_formula("Sheet1", "D4", "SUM(A1:A3)").unwrap();
1517 assert_eq!(f.get_cell_value("Sheet1", "D4").unwrap(), "=SUM(A1:A3)");
1518 assert_eq!(f.get_cell_formula("Sheet1", "D4").unwrap(), "SUM(A1:A3)");
1519 }
1520
1521 #[test]
1522 fn set_and_get_date() {
1523 let f = File::new_with_options(crate::options::Options::default());
1524 let d = NaiveDate::from_ymd_opt(2024, 7, 13).unwrap();
1525 f.set_cell_value("Sheet1", "E5", CellValue::Date(d))
1526 .unwrap();
1527 assert_eq!(f.get_cell_value("Sheet1", "E5").unwrap(), "07-13-24");
1528 }
1529
1530 #[test]
1531 fn set_and_get_datetime() {
1532 let f = File::new_with_options(crate::options::Options::default());
1533 let dt = NaiveDate::from_ymd_opt(2024, 7, 13)
1534 .unwrap()
1535 .and_hms_opt(12, 30, 0)
1536 .unwrap();
1537 f.set_cell_value("Sheet1", "F6", CellValue::DateTime(dt))
1538 .unwrap();
1539 assert_eq!(f.get_cell_value("Sheet1", "F6").unwrap(), "7/13/24 12:30");
1540 }
1541
1542 #[test]
1543 fn set_and_get_time() {
1544 let f = File::new_with_options(crate::options::Options::default());
1545 let t = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
1546 f.set_cell_value("Sheet1", "G7", CellValue::Time(t))
1547 .unwrap();
1548 assert_eq!(f.get_cell_value("Sheet1", "G7").unwrap(), "14:30:00");
1549 }
1550
1551 #[test]
1552 fn raw_cell_value_respects_option() {
1553 let mut opts = crate::options::Options::default();
1554 opts.raw_cell_value = true;
1555 let f = File::new_with_options(opts);
1556 let d = NaiveDate::from_ymd_opt(2024, 7, 13).unwrap();
1557 f.set_cell_value("Sheet1", "H8", CellValue::Date(d))
1558 .unwrap();
1559 assert_eq!(f.get_cell_value("Sheet1", "H8").unwrap(), "45486");
1560 }
1561
1562 #[test]
1563 fn set_and_get_default() {
1564 let f = File::new_with_options(crate::options::Options::default());
1565 f.set_cell_default("Sheet1", "A1", "123").unwrap();
1566 assert_eq!(f.get_cell_value("Sheet1", "A1").unwrap(), "123");
1567 f.set_cell_default("Sheet1", "A2", "text").unwrap();
1568 assert_eq!(f.get_cell_value("Sheet1", "A2").unwrap(), "text");
1569 }
1570
1571 #[test]
1572 fn set_and_get_uint() {
1573 let f = File::new_with_options(crate::options::Options::default());
1574 f.set_cell_uint("Sheet1", "A1", 42).unwrap();
1575 assert_eq!(f.get_cell_value("Sheet1", "A1").unwrap(), "42");
1576 }
1577
1578 #[test]
1579 fn set_and_get_float_precision() {
1580 let f = File::new_with_options(crate::options::Options::default());
1581 f.set_cell_float_with_precision("Sheet1", "A1", 1.325, 2, 32)
1582 .unwrap();
1583 assert_eq!(f.get_cell_value("Sheet1", "A1").unwrap(), "1.33");
1584 }
1585
1586 #[test]
1587 fn set_and_get_sheet_row_and_col() {
1588 let f = File::new_with_options(crate::options::Options::default());
1589 f.set_sheet_row("Sheet1", "A1", &["a".into(), "b".into(), 1.into()])
1590 .unwrap();
1591 f.set_sheet_col("Sheet1", "B5", &["x".into(), "y".into()])
1592 .unwrap();
1593 assert_eq!(f.get_cell_value("Sheet1", "A1").unwrap(), "a");
1594 assert_eq!(f.get_cell_value("Sheet1", "C1").unwrap(), "1");
1595 assert_eq!(f.get_cell_value("Sheet1", "B6").unwrap(), "y");
1596 }
1597
1598 #[test]
1599 fn hyperlink_round_trip() {
1600 let f = File::new_with_options(crate::options::Options::default());
1601 f.set_cell_hyperlink("Sheet1", "A1", "https://example.com", "External", &[])
1602 .unwrap();
1603 let (has, link) = f.get_cell_hyperlink("Sheet1", "A1").unwrap();
1604 assert!(has);
1605 assert_eq!(link, "https://example.com");
1606
1607 let cells = f.get_hyperlink_cells("Sheet1", "").unwrap();
1608 assert_eq!(cells, vec!["A1"]);
1609
1610 f.set_cell_hyperlink("Sheet1", "A1", "", "None", &[])
1611 .unwrap();
1612 let (has, _) = f.get_cell_hyperlink("Sheet1", "A1").unwrap();
1613 assert!(!has);
1614 }
1615
1616 #[test]
1617 fn rich_text_round_trip_shared_string() {
1618 let f = File::new_with_options(crate::options::Options::default());
1619 let runs = vec![
1620 RichTextRun {
1621 text: "bold".to_string(),
1622 font: Some(crate::styles::Font {
1623 bold: Some(true),
1624 ..Default::default()
1625 }),
1626 },
1627 RichTextRun {
1628 text: " plain".to_string(),
1629 ..Default::default()
1630 },
1631 ];
1632 f.set_cell_rich_text("Sheet1", "A1", runs.clone()).unwrap();
1633 let got = f.get_cell_rich_text("Sheet1", "A1").unwrap();
1634 assert_eq!(got, runs);
1635 }
1636}