1#[cfg(test)]
2mod tests;
3
4use crate::cell::AsRangeString;
5use crate::{
6 error, AsExcelDate, AsPaletteColor, Cell, Chart, Col, ColRange,
7 DateMode, Format, Formats, Index, IndexRange, Result, Row, RowRange,
8 SharedStrings, WorkbookSheetProperties, XmlWritable, XmlWriter,
9};
10use chrono::{DateTime, NaiveTime, Utc};
11use indexmap::{indexmap, IndexMap, IndexSet};
12use rgb::RGB8;
13use std::cell::RefCell;
14use std::cmp::max;
15use std::collections::BTreeMap;
16use std::convert::TryFrom;
17use std::path::Path;
18use std::rc::Rc;
19
20const STR_MAX: usize = 0x7fff;
21const DEFAULT_COL_WIDTH: f64 = 8.43f64;
22const DEFAULT_ROW_HEIGHT: f64 = 15.0f64;
23const F1: &str = "formula1";
24const F2: &str = "formula2";
25
26struct Inner {
27 sheet_index: usize,
28 shared_strings: SharedStrings,
29 formats: Formats,
30 workbook_sheet_properties: WorkbookSheetProperties,
31 cells: BTreeMap<Row, BTreeMap<Col, Cell>>,
32 row_dimension: Option<RowRange>,
33 col_dimension: Option<ColRange>,
34 col_options: BTreeMap<
35 Col,
36 (ColRange, Option<RowColOptions>, f64, Option<usize>),
37 >,
38 outline_col_level: OutlineLevel,
39 row_options: BTreeMap<
40 Row,
41 (RowRange, Option<RowColOptions>, f64, Option<usize>),
42 >,
43 page_margins: PageMargins,
44 data_validations: Vec<Validation>,
45 tab_color: Option<RGB8>,
46 fit_to_pages: Option<PageFit>,
47 outline: Outline,
48 merged_ranges: Vec<IndexRange>,
49}
50
51struct PageFit {
52 width: u16,
53 height: u16,
54}
55
56impl Inner {
57 fn insert_cell(&mut self, index: Index, cell: Cell) -> Result<()> {
58 let Index { row, col } = index;
59 self.extend_row_dimension_to(row);
60 self.extend_col_dimension_to(col);
61 self.cells
62 .entry(row)
63 .or_insert_with(Default::default)
64 .insert(col, cell);
65 Ok(())
66 }
67
68 fn build_sheet_pr(&self) -> SheetPr {
69 SheetPr {
70 tab_color: self.tab_color,
71 fit_page: self.fit_to_pages.is_some(),
72 filter_on: false,
73 outline: self.outline.clone(),
74 }
75 }
76
77 fn build_dimension(&self) -> Dimension {
78 Dimension {
79 range: IndexRange::new_from_ranges(
80 self.row_dimension.unwrap_or_default(),
81 self.col_dimension.unwrap_or_default(),
82 ),
83 }
84 }
85
86 fn build_sheet_format_pr(&self) -> SheetFormatPr {
87 SheetFormatPr {
89 default_row_height: 15f64,
90 }
91 }
92
93 fn build_sheet_views(&self) -> SheetViews {
94 SheetViews {
95 view: SheetView {
96 tab_selected: self
97 .workbook_sheet_properties
98 .selected(self.sheet_index),
99 },
100 }
101 }
102
103 fn build_sheet_data(&self) -> SheetData {
104 SheetData { cells: &self.cells }
105 }
106
107 fn build_columns(&self) -> Columns {
108 Columns {
109 col_options: &self.col_options,
110 }
111 }
112
113 fn build_merge_cells(&self) -> MergeCells {
114 MergeCells {
115 merged_ranges: &self.merged_ranges,
116 }
117 }
118
119 fn extend_row_dimension_to<R>(&mut self, r: R) -> &mut Self
120 where
121 RowRange: From<R>,
122 {
123 match self.row_dimension {
124 Some(ref mut dimension) => {
125 dimension.extend_to(r);
126 }
127 None => self.row_dimension = Some(RowRange::from(r)),
128 }
129 self
130 }
131
132 fn extend_col_dimension_to<C>(&mut self, c: C) -> &mut Self
133 where
134 ColRange: From<C>,
135 {
136 match self.col_dimension {
137 Some(ref mut dimension) => {
138 dimension.extend_to(c);
139 }
140 None => self.col_dimension = Some(ColRange::from(c)),
141 }
142 self
143 }
144
145 fn set_column_options(
146 &mut self,
147 range: ColRange,
148 width: f64,
149 format: Option<&Format>,
150 options: Option<RowColOptions>,
151 ) -> Result<()> {
152 let RowColOptions {
153 hidden,
154 level,
155 collapsed: _,
156 } = options.clone().unwrap_or_default();
157
158 if format.is_some() || (width != DEFAULT_COL_WIDTH && hidden) {
159 self.extend_col_dimension_to(range);
160 }
161
162 self.outline_col_level = max(self.outline_col_level, level);
163 let format_index = self.formats.create_or_get_index(format);
164 self.col_options
165 .insert(range.first(), (range, options, width, format_index));
166
167 Ok(())
168 }
169
170 fn set_row_options(
171 &mut self,
172 range: RowRange,
173 height: f64,
174 format: Option<&Format>,
175 options: Option<RowColOptions>,
176 ) -> Result<()> {
177 let RowColOptions {
178 hidden,
179 level,
180 collapsed: _,
181 } = options.clone().unwrap_or_default();
182
183 if format.is_some() || (height != DEFAULT_ROW_HEIGHT && hidden) {
184 self.extend_row_dimension_to(range);
185 }
186
187 self.outline_col_level = max(self.outline_col_level, level);
188 let format_index = self.formats.create_or_get_index(format);
189 self.row_options
190 .insert(range.first(), (range, options, height, format_index));
191
192 Ok(())
193 }
194}
195
196#[derive(Clone)]
197pub struct Worksheet {
198 inner: Rc<RefCell<Inner>>,
199}
200
201impl Worksheet {
202 pub(crate) fn new(
203 sheet_index: usize,
204 shared_strings: SharedStrings,
205 formats: Formats,
206 workbook_sheet_properties: WorkbookSheetProperties,
207 ) -> Self {
208 Worksheet {
209 inner: Rc::new(RefCell::new(Inner {
210 sheet_index,
211 shared_strings,
212 formats,
213 workbook_sheet_properties,
214 cells: Default::default(),
215 row_dimension: None,
216 col_dimension: None,
217 col_options: Default::default(),
218 outline_col_level: Default::default(),
219 row_options: Default::default(),
220 page_margins: Default::default(),
221 data_validations: Default::default(),
222 tab_color: Default::default(),
223 fit_to_pages: Default::default(),
224 outline: Default::default(),
225 merged_ranges: Default::default(),
226 })),
227 }
228 }
229
230 pub fn write_number(
231 &mut self,
232 index: Index,
233 number: f64,
234 format: Option<&Format>,
235 ) -> Result<()> {
236 let mut inner = self.inner.borrow_mut();
237 let format = inner.formats.create_or_get_index(format);
238 inner.insert_cell(index, Cell::new_number(number, format))
239 }
240
241 pub fn write_string<S: AsRef<str>>(
242 &mut self,
243 index: Index,
244 string: S,
245 format: Option<&Format>,
246 ) -> Result<()> {
247 if string.as_ref() == "" {
248 self.write_blank(index, format)
249 } else {
250 Self::check_str_max_length(string.as_ref())?;
251 let mut inner = self.inner.borrow_mut();
252 let string_index =
253 inner.shared_strings.create_or_get_index(string, false);
254 let format = inner.formats.create_or_get_index(format);
255 inner
256 .insert_cell(index, Cell::new_string(string_index, format))
257 }
258 }
259
260 pub fn write_formula<S: Into<String>>(
261 &mut self,
262 index: Index,
263 formula: S,
264 format: Option<&Format>,
265 ) -> Result<()> {
266 self.write_formula_num(index, formula, format, 0f64)
267 }
268
269 pub fn write_array_formula<S: AsRef<str>>(
270 &mut self,
271 start: Index,
272 end: Index,
273 formula: S,
274 format: Option<&Format>,
275 ) -> Result<()> {
276 unimplemented!();
277 }
278
279 pub fn write_array_formula_num<S: AsRef<str>>(
280 &mut self,
281 start: Index,
282 end: Index,
283 formula: S,
284 format: Option<&Format>,
285 number: f64,
286 ) -> Result<()> {
287 unimplemented!();
288 }
289
290 pub fn write_datetime(
291 &mut self,
292 index: Index,
293 datetime: DateTime<Utc>,
294 format: Option<&Format>,
295 ) -> Result<()> {
296 let excel_date = datetime.as_excel_date(DateMode::BasedOn1900);
297 self.write_number(index, excel_date, format)
298 }
299
300 pub fn write_url<S: AsRef<str>>(
301 &mut self,
302 index: Index,
303 url: S,
304 format: Option<&Format>,
305 ) -> Result<()> {
306 unimplemented!();
307 }
308
309 pub fn write_boolean(
310 &mut self,
311 index: Index,
312 value: bool,
313 format: Option<&Format>,
314 ) -> Result<()> {
315 unimplemented!();
316 }
317
318 pub fn write_blank(
319 &mut self,
320 index: Index,
321 format: Option<&Format>,
322 ) -> Result<()> {
323 let mut inner = self.inner.borrow_mut();
324 if format.is_some() {
325 let format = inner.formats.create_or_get_index(format);
326 inner.insert_cell(index, Cell::new_blank(format))
327 } else {
328 Ok(())
329 }
330 }
331
332 pub fn write_formula_num<S: Into<String>>(
333 &mut self,
334 index: Index,
335 formula: S,
336 format: Option<&Format>,
337 result: f64,
338 ) -> Result<()> {
339 let mut inner = self.inner.borrow_mut();
340 let format = inner.formats.create_or_get_index(format);
341 inner.insert_cell(
342 index,
343 Cell::new_formula(formula.into(), result, format),
344 )
345 }
346
347 pub fn write_rich_string(
348 &mut self,
349 index: Index,
350 rich_string: &[RichStringTuple],
351 format: Option<&Format>,
352 ) -> Result<()> {
353 unimplemented!();
354 }
355
356 pub fn set_row(
357 &mut self,
358 range: RowRange,
359 height: f64,
360 format: Option<&Format>,
361 ) -> Result<()> {
362 self.inner
363 .borrow_mut()
364 .set_row_options(range, height, format, None)
365 }
366
367 pub fn set_row_options(
368 &mut self,
369 range: RowRange,
370 height: f64,
371 format: Option<&Format>,
372 options: RowColOptions,
373 ) -> Result<()> {
374 self.inner.borrow_mut().set_row_options(
375 range,
376 height,
377 format,
378 Some(options),
379 )
380 }
381
382 pub fn set_column(
383 &mut self,
384 range: ColRange,
385 width: f64,
386 format: Option<&Format>,
387 ) -> Result<()> {
388 self.inner
389 .borrow_mut()
390 .set_column_options(range, width, format, None)
391 }
392
393 pub fn set_column_options(
394 &mut self,
395 range: ColRange,
396 width: f64,
397 format: Option<&Format>,
398 options: RowColOptions,
399 ) -> Result<()> {
400 self.inner.borrow_mut().set_column_options(
401 range,
402 width,
403 format,
404 Some(options),
405 )
406 }
407
408 pub fn insert_image<P: AsRef<Path>>(
409 &mut self,
410 index: Index,
411 filename: P,
412 ) -> Result<()> {
413 unimplemented!();
414 }
415
416 pub fn insert_image_options<P: AsRef<Path>>(
417 &mut self,
418 index: Index,
419 filename: P,
420 options: ImageOptions,
421 ) -> Result<()> {
422 unimplemented!();
423 }
424
425 pub fn insert_image_buffer(
426 &mut self,
427 index: Index,
428 image_buffer: &[u8],
429 ) -> Result<()> {
430 unimplemented!();
431 }
432
433 pub fn insert_image_buffer_options(
434 &mut self,
435 index: Index,
436 image_buffer: &[u8],
437 options: ImageOptions,
438 ) -> Result<()> {
439 unimplemented!();
440 }
441
442 pub fn insert_chart(
443 &mut self,
444 index: Index,
445 chart: Chart,
446 ) -> Result<()> {
447 unimplemented!();
448 }
449
450 pub fn insert_chart_options(
451 &mut self,
452 index: Index,
453 chart: Chart,
454 options: ImageOptions,
455 ) -> Result<()> {
456 unimplemented!();
457 }
458
459 pub fn merge_range(
460 &mut self,
461 range: IndexRange,
462 format: Option<&Format>,
463 ) -> Result<()> {
464 let data_index = range.top_left();
465 for row in range.row_range.iter() {
466 for col in range.col_range.iter() {
467 let index = Index { row, col };
468 if index != data_index {
469 self.write_blank(index, format)?;
470 }
471 }
472 }
473
474 if range.top_left() != range.bottom_right() {
476 self.inner.borrow_mut().merged_ranges.push(range);
477 }
478
479 Ok(())
480 }
481
482 pub fn merge_range_str(
483 &mut self,
484 range: IndexRange,
485 string: &str,
486 format: Option<&Format>,
487 ) -> Result<()> {
488 self.write_string(range.top_left(), string, format)?;
489 self.merge_range(range, format)
490 }
491
492 pub fn autofilter(&mut self, range: IndexRange) -> Result<()> {
493 unimplemented!();
494 }
495
496 pub fn data_validation(
497 &mut self,
498 validation: Validation,
499 ) -> Result<()> {
500 self.inner.borrow_mut().data_validations.push(validation);
501 Ok(())
502 }
503
504 pub fn activate(&mut self) {
505 let inner = self.inner.borrow_mut();
506 inner
507 .workbook_sheet_properties
508 .set_active(inner.sheet_index);
509 }
510
511 pub fn select(&mut self) {
512 let mut inner = self.inner.borrow_mut();
513 let index = inner.sheet_index;
514 inner.workbook_sheet_properties.set_selected(index, true);
515 }
516
517 pub fn hide(&mut self) {
518 let mut inner = self.inner.borrow_mut();
519 let index = inner.sheet_index;
520 inner.workbook_sheet_properties.set_hidden(index, true);
521 }
522
523 pub fn set_first_sheet(&mut self) {
524 unimplemented!();
526 }
527
528 pub fn freeze_panes(&mut self, index: Index) {
529 unimplemented!();
530 }
531
532 pub fn split_panes(&mut self, vertical: f64, horizontal: f64) {
533 unimplemented!();
534 }
535
536 pub fn set_selection(&mut self, start: Index, end: Index) {
537 unimplemented!();
538 }
539
540 pub fn set_landscape(&mut self) {
541 unimplemented!();
542 }
543
544 pub fn set_portrait(&mut self) {
545 unimplemented!();
546 }
547
548 pub fn set_page_view(&mut self) {
549 unimplemented!();
550 }
551
552 pub fn set_paper(&mut self, paper_type: Paper) {
553 unimplemented!();
554 }
555
556 pub fn set_margins(
557 &mut self,
558 left: f64,
559 right: f64,
560 top: f64,
561 bottom: f64,
562 ) {
563 unimplemented!();
564 }
565
566 pub fn set_header<S: AsRef<str>>(&mut self, string: S) -> Result<()> {
567 unimplemented!();
568 }
569
570 pub fn set_footer<S: AsRef<str>>(&mut self, string: S) -> Result<()> {
571 unimplemented!();
572 }
573
574 pub fn set_header_options<S: AsRef<str>>(
575 &mut self,
576 string: S,
577 options: HeaderFooterOptions,
578 ) -> Result<()> {
579 unimplemented!();
580 }
581
582 pub fn set_footer_options<S: AsRef<str>>(
583 &mut self,
584 string: S,
585 options: HeaderFooterOptions,
586 ) -> Result<()> {
587 unimplemented!();
588 }
589
590 pub fn set_h_pagebreaks(&mut self, breaks: &[Row]) -> Result<()> {
591 unimplemented!();
592 }
593
594 pub fn set_v_pagebreaks(&mut self, breaks: &[Col]) -> Result<()> {
595 unimplemented!();
596 }
597
598 pub fn print_across(&mut self) {
599 unimplemented!();
600 }
601
602 pub fn set_zoom(&mut self, scale: u16) {
603 unimplemented!();
604 }
605
606 pub fn set_gridlines(&mut self, option: Gridlines) {
607 unimplemented!();
608 }
609
610 pub fn center_horizontally(&mut self) {
611 unimplemented!();
612 }
613
614 pub fn center_vertically(&mut self) {
615 unimplemented!();
616 }
617
618 pub fn print_row_col_headers(&mut self) {
619 unimplemented!();
620 }
621
622 pub fn repeat_rows(&mut self, range: RowRange) -> Result<()> {
623 unimplemented!();
624 }
625
626 pub fn repeat_columns(&mut self, range: ColRange) -> Result<()> {
627 unimplemented!();
628 }
629
630 pub fn print_area(&mut self, range: IndexRange) -> Result<()> {
631 unimplemented!();
632 }
633
634 pub fn fit_to_pages(&mut self, width: u16, height: u16) {
637 self.inner.borrow_mut().fit_to_pages =
638 Some(PageFit { width, height })
639 }
640
641 pub fn set_start_page(&mut self, start_page: u16) {
642 unimplemented!();
643 }
644
645 pub fn set_print_scale(&mut self, scale: u16) {
646 unimplemented!();
647 }
648
649 pub fn right_to_left(&mut self) {
650 unimplemented!();
651 }
652
653 pub fn hide_zero(&mut self) {
654 unimplemented!();
655 }
656
657 pub fn set_tab_color(&mut self, color: RGB8) {
658 self.inner.borrow_mut().tab_color = Some(color);
659 }
660
661 pub fn protect(
662 &mut self,
663 password: Option<&str>,
664 options: Protection,
665 ) {
666 unimplemented!();
667 }
668
669 pub fn outline_settings(
670 &mut self,
671 visible: bool,
672 symbols_below: bool,
673 symbols_right: bool,
674 auto_style: bool,
675 ) {
676 unimplemented!();
677 }
678
679 pub fn set_default_row(
680 &mut self,
681 height: f64,
682 hide_unused_rows: bool,
683 ) {
684 unimplemented!();
685 }
686
687 pub fn set_vba_name<N: AsRef<str>>(&mut self, name: N) -> Result<()> {
688 unimplemented!();
689 }
690
691 fn check_str_max_length<S: AsRef<str>>(string: S) -> Result<()> {
692 if string.as_ref().chars().count() > STR_MAX {
693 error::MaxStringLengthExceeded { maximum: STR_MAX }.fail()
694 } else {
695 Ok(())
696 }
697 }
698}
699
700pub struct RichStringTuple {
701 pub format: Option<Format>,
702 pub string: String,
703}
704
705#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
706#[repr(u8)]
707pub enum OutlineLevel {
708 Level0,
709 Level1,
710 Level2,
711 Level3,
712 Level4,
713 Level5,
714 Level6,
715 Level7,
716}
717
718impl Default for OutlineLevel {
719 fn default() -> OutlineLevel {
720 OutlineLevel::Level0
721 }
722}
723
724impl Into<u8> for OutlineLevel {
725 fn into(self) -> u8 {
726 self as u8
727 }
728}
729
730impl TryFrom<u8> for OutlineLevel {
731 type Error = error::Error;
732
733 fn try_from(level: u8) -> Result<Self> {
734 match level {
735 0 => Ok(OutlineLevel::Level0),
736 1 => Ok(OutlineLevel::Level1),
737 2 => Ok(OutlineLevel::Level2),
738 3 => Ok(OutlineLevel::Level3),
739 4 => Ok(OutlineLevel::Level4),
740 5 => Ok(OutlineLevel::Level5),
741 6 => Ok(OutlineLevel::Level6),
742 7 => Ok(OutlineLevel::Level7),
743 _ => error::OutlineLevelOutOfRange {
744 min: 0,
745 max: 7,
746 level,
747 }
748 .fail(),
749 }
750 }
751}
752
753#[derive(Clone, Debug, Default)]
754pub struct RowColOptions {
755 pub level: OutlineLevel,
756 pub hidden: bool,
757 pub collapsed: bool,
758}
759
760pub struct ImageOptions {
761 pub x_offset: i32,
762 pub y_offset: i32,
763 pub x_scale: f64,
764 pub y_scale: f64,
765}
766
767pub struct Validation {
768 validation_type: ValidationType,
769 range: IndexRange,
770 ignore_blank: bool,
772 show_input: bool,
774 show_error: bool,
776 pub input_title: Option<String>,
777 pub input_message: Option<String>,
778 pub error_title: Option<String>,
779 pub error_message: Option<String>,
780 pub error_type: ValidationErrorType,
781}
782
783impl Validation {
784 pub fn new(
785 validation_type: ValidationType,
786 range: impl Into<IndexRange>,
787 ) -> Self {
788 Validation {
789 validation_type,
790 range: range.into(),
791 ignore_blank: true,
792 show_input: true,
793 show_error: true,
794 input_title: None,
795 input_message: None,
796 error_title: None,
797 error_message: None,
798 error_type: ValidationErrorType::Stop,
799 }
800 }
801}
802
803impl XmlWritable for Validation {
804 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
805 let mut attrs = IndexMap::new();
806
807 let t = &self.validation_type;
808 if let Some(t) = t.get_type() {
809 attrs.insert("type", t);
810 }
811 if let Some(o) = t.get_operator() {
812 attrs.insert("operator", o);
813 }
814 match self.error_type {
815 ValidationErrorType::Stop => {}
816 ValidationErrorType::Warning => {
817 attrs.insert("errorStyle", "warning");
818 }
819 ValidationErrorType::Information => {
820 attrs.insert("errorStyle", "information");
821 }
822 }
823 if self.ignore_blank {
824 attrs.insert("allowBlank", "1");
825 }
826 if let Some(d) = t.get_show_dropdown() {
827 attrs.insert("showDropDown", d);
828 }
829 if self.show_input {
830 attrs.insert("showInputMessage", "1");
831 }
832 if self.show_error {
833 attrs.insert("showErrorMessage", "1");
834 }
835 if let Some(ref t) = self.error_title {
836 attrs.insert("errorTitle", t);
837 }
838 if let Some(ref t) = self.error_message {
839 attrs.insert("error", t);
840 }
841 if let Some(ref t) = self.input_title {
842 attrs.insert("promptTitle", t);
843 }
844 if let Some(ref t) = self.input_message {
845 attrs.insert("prompt", t);
846 }
847 let sqref = self.range.as_range_string();
848 attrs.insert("sqref", &sqref);
849
850 let tag = "dataValidation";
851
852 let formulas = t.get_formulas();
853 if formulas.is_empty() {
854 w.empty_tag_with_attrs(tag, attrs)?;
855 } else {
856 w.start_tag_with_attrs(tag, attrs)?;
857 for (k, v) in formulas.iter() {
858 w.tag_with_text(k, v)?;
859 }
860 w.end_tag(tag)?;
861 }
862
863 Ok(())
864 }
865}
866
867impl XmlWritable for Vec<Validation> {
868 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
869 if self.is_empty() {
870 Ok(())
871 } else {
872 let tag = "dataValidations";
873 let attrs = indexmap! {
874 "count" => format!("{}", self.len()),
875 };
876 w.start_tag_with_attrs(tag, attrs)?;
877 for validation in self.iter() {
878 validation.write_xml(w)?;
879 }
880 w.end_tag(tag)?;
881 Ok(())
882 }
883 }
884}
885
886pub enum ValidationType {
887 Integer(Criterion<i64>),
889 IntegerFormula(Criterion<String>),
892 Decimal(Criterion<f64>),
894 DecimalFormula(Criterion<String>),
897 List {
899 values: IndexSet<String>,
901 show_dropdown: bool,
905 },
906 ListFormula {
909 formula: String,
911 show_dropdown: bool,
915 },
916 Date(Criterion<DateTime<Utc>>),
918 DateFormula(Criterion<String>),
920 DateNumber(Criterion<i64>),
923 Time(Criterion<NaiveTime>),
925 TimeFormula(Criterion<String>),
927 TimeNumber(Criterion<f64>),
930 Length(Criterion<usize>),
932 LengthFormula(Criterion<String>),
935 CustomFormula(String),
938 Any,
940}
941
942impl ValidationType {
943 fn get_formulas(&self) -> IndexMap<&'static str, String> {
944 use ValidationType as D;
945 match self {
946 D::Integer(c) => c.get_formulas(),
947 D::Decimal(c) => c.get_formulas(),
948 D::List { values, .. } => {
949 let s = values
950 .iter()
951 .cloned()
952 .collect::<Vec<_>>()
953 .join(",")
954 .to_string();
955 indexmap! {
956 F1 => format!("\"{}\"", s),
957 }
958 }
959 D::Date(c) => c.get_formulas(),
960 D::DateNumber(c) => c.get_formulas(),
961 D::Time(c) => c.get_formulas(),
962 D::TimeNumber(c) => c.get_formulas(),
963 D::Length(c) => c.get_formulas(),
964 D::IntegerFormula(c) => c.get_formulas(),
965 D::DecimalFormula(c) => c.get_formulas(),
966 D::ListFormula { formula, .. } => indexmap! {
967 F1 => formula.formula_string(),
968 },
969 D::DateFormula(c) => c.get_formulas(),
970 D::TimeFormula(c) => c.get_formulas(),
971 D::LengthFormula(c) => c.get_formulas(),
972 D::CustomFormula(f) => indexmap! { F1 => f.to_string() },
973 D::Any => indexmap! {},
974 }
975 }
976
977 fn get_type(&self) -> Option<&'static str> {
978 use ValidationType as D;
979 match self {
980 D::Integer(_) | D::IntegerFormula(_) => Some("whole"),
981 D::Decimal(_) | D::DecimalFormula(_) => Some("decimal"),
982 D::List { .. } | D::ListFormula { .. } => Some("list"),
983 D::Date(_) | D::DateNumber(_) | D::DateFormula(_) => {
984 Some("date")
985 }
986 D::Time(_) | D::TimeNumber(_) | D::TimeFormula(_) => {
987 Some("time")
988 }
989 D::Length(_) | D::LengthFormula(_) => Some("textLength"),
990 D::CustomFormula(_) => Some("custom"),
991 D::Any => None,
992 }
993 }
994
995 fn get_operator(&self) -> Option<&'static str> {
996 use ValidationType as D;
997 match self {
998 D::Integer(c) => c.get_operator(),
999 D::IntegerFormula(c) => c.get_operator(),
1000 D::Decimal(c) => c.get_operator(),
1001 D::DecimalFormula(c) => c.get_operator(),
1002 D::Date(c) => c.get_operator(),
1003 D::DateNumber(c) => c.get_operator(),
1004 D::DateFormula(c) => c.get_operator(),
1005 D::Time(c) => c.get_operator(),
1006 D::TimeNumber(c) => c.get_operator(),
1007 D::TimeFormula(c) => c.get_operator(),
1008 D::Length(c) => c.get_operator(),
1009 D::LengthFormula(c) => c.get_operator(),
1010
1011 D::List { .. }
1012 | D::ListFormula { .. }
1013 | D::CustomFormula(_)
1014 | D::Any => None,
1015 }
1016 }
1017
1018 fn get_show_dropdown(&self) -> Option<&'static str> {
1019 use ValidationType as D;
1020 match self {
1021 D::List { show_dropdown, .. }
1022 | D::ListFormula { show_dropdown, .. }
1023 if !show_dropdown =>
1024 {
1025 Some("1")
1026 }
1027 D::List { .. }
1028 | D::ListFormula { .. }
1029 | D::Integer(_)
1030 | D::IntegerFormula(_)
1031 | D::Decimal(_)
1032 | D::DecimalFormula(_)
1033 | D::Date(_)
1034 | D::DateNumber(_)
1035 | D::DateFormula(_)
1036 | D::Time(_)
1037 | D::TimeNumber(_)
1038 | D::TimeFormula(_)
1039 | D::Length(_)
1040 | D::LengthFormula(_)
1041 | D::CustomFormula(_)
1042 | D::Any => None,
1043 }
1044 }
1045}
1046
1047pub enum ValidationErrorType {
1048 Stop,
1049 Warning,
1050 Information,
1051}
1052
1053pub trait FormulaString {
1054 fn formula_string(&self) -> String;
1055}
1056
1057impl FormulaString for f64 {
1058 fn formula_string(&self) -> String {
1059 format!("{}", *self)
1060 }
1061}
1062
1063impl FormulaString for i64 {
1064 fn formula_string(&self) -> String {
1065 format!("{}", *self)
1066 }
1067}
1068
1069impl FormulaString for usize {
1070 fn formula_string(&self) -> String {
1071 format!("{}", *self)
1072 }
1073}
1074
1075impl FormulaString for String {
1076 fn formula_string(&self) -> String {
1077 self.trim_start_matches('=').to_string()
1078 }
1079}
1080
1081impl FormulaString for DateTime<Utc> {
1082 fn formula_string(&self) -> String {
1083 format!("{}", self.as_excel_date(DateMode::BasedOn1900))
1084 }
1085}
1086
1087impl FormulaString for NaiveTime {
1088 fn formula_string(&self) -> String {
1089 use chrono::Timelike;
1090 let seconds_since_midnight =
1091 self.num_seconds_from_midnight() as f64;
1092 const SECONDS_PER_DAY: f64 = 24f64 * 60f64 * 60f64;
1093 format!("{}", seconds_since_midnight / SECONDS_PER_DAY)
1094 }
1095}
1096
1097pub enum Criterion<T: FormulaString> {
1098 Between(T, T),
1099 NotBetween(T, T),
1100 EqualTo(T),
1101 NotEqualTo(T),
1102 GreaterThan(T),
1103 LessThan(T),
1104 GreaterThanOrEqualTo(T),
1105 LessThanOrEqualTo(T),
1106}
1107
1108impl<T: FormulaString> Criterion<T> {
1109 pub fn get_operator(&self) -> Option<&'static str> {
1110 use Criterion as C;
1111 match self {
1112 C::Between(_, _) => {
1113 None
1115 }
1116 C::NotBetween(_, _) => Some("notBetween"),
1117 C::EqualTo(_) => Some("equal"),
1118 C::NotEqualTo(_) => Some("notEqual"),
1119 C::GreaterThan(_) => Some("greaterThan"),
1120 C::LessThan(_) => Some("lessThan"),
1121 C::GreaterThanOrEqualTo(_) => Some("greaterThanOrEqual"),
1122 C::LessThanOrEqualTo(_) => Some("lessThanOrEqual"),
1123 }
1124 }
1125
1126 pub fn get_formulas(&self) -> IndexMap<&'static str, String> {
1127 use Criterion as C;
1128 match self {
1129 C::Between(a, b) => indexmap! {
1130 F1 => a.formula_string(),
1131 F2 => b.formula_string(),
1132 },
1133 C::NotBetween(a, b) => indexmap! {
1134 F1 => a.formula_string(),
1135 F2 => b.formula_string(),
1136 },
1137 C::EqualTo(a) => indexmap! { F1 => a.formula_string() },
1138 C::NotEqualTo(a) => indexmap! { F1 => a.formula_string() },
1139 C::GreaterThan(a) => indexmap! { F1 => a.formula_string() },
1140 C::LessThan(a) => indexmap! { F1 => a.formula_string() },
1141 C::GreaterThanOrEqualTo(a) => indexmap! {
1142 F1 => a.formula_string(),
1143 },
1144 C::LessThanOrEqualTo(a) => indexmap! {
1145 F1 => a.formula_string(),
1146 },
1147 }
1148 }
1149}
1150
1151#[derive(Clone, Copy, Debug)]
1152pub enum Paper {
1153 PrinterDefault,
1154 Letter,
1155 LetterSmall,
1156 Tabloid,
1157 Ledger,
1158 Legal,
1159 Statement,
1160 Executive,
1161 A3,
1162 A4,
1163 A4Small,
1164 A5,
1165 B4,
1166 B5,
1167 Folio,
1168 Quarto,
1169 Paper10x14Inch,
1170 Paper11x17Inch,
1171 Note,
1172 Envelope9,
1173 Envelope10,
1174 Envelope11,
1175 Envelope12,
1176 Envelope14,
1177 CSizeSheet,
1178 DSizeSheet,
1179 ESizeSheet,
1180 EnvelopeDL,
1181 EnvelopeC3,
1182 EnvelopeC4,
1183 EnvelopeC5,
1184 EnvelopeC6,
1185 EnvelopeC65,
1186 EnvelopeB4,
1187 EnvelopeB5,
1188 EnvelopeB6,
1189 Envelope110x230mm,
1190 Monarch,
1191 Envelope3_5_8x6_1_2Inch,
1192 Fanfold,
1193 GermanStdFanfold,
1194 GermanLegalFanfold,
1195}
1196
1197#[derive(Clone, Copy, Debug)]
1198pub struct HeaderFooterOptions {
1199 pub margin: f64,
1200}
1201
1202#[derive(Clone, Copy, Debug)]
1203pub enum Gridlines {
1204 HideAllGridlines,
1205 ShowScreenGridlens,
1206 ShowPrintGridlines,
1207 ShowAllGridlines,
1208}
1209
1210#[derive(Clone, Copy, Debug)]
1211pub struct Protection {
1212 pub no_select_locked_cells: bool,
1213 pub no_select_unlocked_cells: bool,
1214 pub format_cells: bool,
1215 pub format_columns: bool,
1216 pub format_rows: bool,
1217 pub insert_columns: bool,
1218 pub insert_rows: bool,
1219 pub insert_hyperlinks: bool,
1220 pub delete_columns: bool,
1221 pub delete_rows: bool,
1222 pub sort: bool,
1223 pub autofilter: bool,
1224 pub pivot_tables: bool,
1225 pub scenarios: bool,
1226 pub objects: bool,
1227 pub no_content: bool,
1228 pub no_objects: bool,
1229}
1230
1231impl Protection {
1232 pub fn all_protected() -> Self {
1233 Protection {
1234 no_select_locked_cells: true,
1235 no_select_unlocked_cells: true,
1236 format_cells: true,
1237 format_columns: true,
1238 format_rows: true,
1239 insert_columns: true,
1240 insert_rows: true,
1241 insert_hyperlinks: true,
1242 delete_columns: true,
1243 delete_rows: true,
1244 sort: true,
1245 autofilter: true,
1246 pivot_tables: true,
1247 scenarios: true,
1248 objects: true,
1249 no_content: true,
1250 no_objects: true,
1251 }
1252 }
1253
1254 pub fn all_unprotected() -> Self {
1255 Protection {
1256 no_select_locked_cells: false,
1257 no_select_unlocked_cells: false,
1258 format_cells: false,
1259 format_columns: false,
1260 format_rows: false,
1261 insert_columns: false,
1262 insert_rows: false,
1263 insert_hyperlinks: false,
1264 delete_columns: false,
1265 delete_rows: false,
1266 sort: false,
1267 autofilter: false,
1268 pivot_tables: false,
1269 scenarios: false,
1270 objects: false,
1271 no_content: false,
1272 no_objects: false,
1273 }
1274 }
1275}
1276
1277impl XmlWritable for Worksheet {
1278 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1279 self.inner.borrow().write_xml(w)
1280 }
1281}
1282
1283impl XmlWritable for Inner {
1284 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1285 let tag = "worksheet";
1286 let attrs = indexmap! {
1287 "xmlns" =>
1288 "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
1289 "xmlns:r" =>
1290 "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
1291 };
1292 w.start_tag_with_attrs(tag, attrs)?;
1293
1294 self.build_sheet_pr().write_xml(w)?;
1295 self.build_dimension().write_xml(w)?;
1296 self.build_sheet_views().write_xml(w)?;
1297 self.build_sheet_format_pr().write_xml(w)?;
1298 self.build_columns().write_xml(w)?;
1299 self.build_sheet_data().write_xml(w)?;
1300 self.build_merge_cells().write_xml(w)?;
1303 self.data_validations.write_xml(w)?;
1304 self.page_margins.write_xml(w)?;
1307 w.end_tag(tag)?;
1314 Ok(())
1315 }
1316}
1317
1318struct PageMargins {
1319 left: f64,
1320 right: f64,
1321 top: f64,
1322 bottom: f64,
1323 header: f64,
1324 footer: f64,
1325}
1326
1327impl Default for PageMargins {
1328 fn default() -> Self {
1329 PageMargins {
1330 left: 0.7,
1331 right: 0.7,
1332 top: 0.75,
1333 bottom: 0.75,
1334 header: 0.3,
1335 footer: 0.3,
1336 }
1337 }
1338}
1339
1340impl XmlWritable for PageMargins {
1341 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1342 let attrs = indexmap! {
1343 "left" => format!("{}", self.left),
1344 "right" => format!("{}", self.right),
1345 "top" => format!("{}", self.top),
1346 "bottom" => format!("{}", self.bottom),
1347 "header" => format!("{}", self.header),
1348 "footer" => format!("{}", self.footer),
1349 };
1350 w.empty_tag_with_attrs("pageMargins", attrs)
1351 }
1352}
1353
1354struct Dimension {
1355 range: IndexRange,
1356}
1357
1358impl XmlWritable for Dimension {
1359 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1360 let attrs = indexmap! {
1361 "ref" => self.range.as_range_string(),
1362 };
1363 w.empty_tag_with_attrs("dimension", attrs)
1364 }
1365}
1366
1367struct SheetPr {
1368 tab_color: Option<RGB8>,
1369 fit_page: bool,
1370 filter_on: bool,
1371 outline: Outline,
1372}
1373
1374#[derive(Debug, Eq, PartialEq, Clone)]
1375struct Outline {
1376 style: bool,
1377 below: bool,
1378 right: bool,
1379 on: bool,
1380}
1381
1382impl Default for Outline {
1383 fn default() -> Self {
1384 Outline {
1385 style: false,
1386 below: true,
1387 right: true,
1388 on: true,
1389 }
1390 }
1391}
1392
1393impl XmlWritable for SheetPr {
1394 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1395 let outline_changed = self.outline != Outline::default();
1396 if !self.fit_page
1397 && !self.filter_on
1398 && self.tab_color.is_none()
1399 && outline_changed
1400 {
1401 Ok(())
1402 } else {
1403 let tag = "sheetPr";
1404 let mut attrs = indexmap! {};
1405
1406 if self.filter_on {
1409 attrs.insert("filterMode", "1");
1410 }
1411
1412 if self.fit_page || self.tab_color.is_some() || outline_changed
1413 {
1414 w.start_tag_with_attrs(tag, attrs)?;
1415 self.write_tab_color(w)?;
1416 self.outline.write_xml(w)?;
1417 self.write_page_set_up_pr(w)?;
1418 w.end_tag(tag)?;
1419 } else {
1420 w.empty_tag_with_attrs(tag, attrs)?;
1421 }
1422 Ok(())
1423 }
1424 }
1425}
1426
1427impl SheetPr {
1428 fn write_tab_color<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1429 if let Some(ref color) = self.tab_color {
1430 let attrs = indexmap! {
1431 "rgb" => color.as_palette_color(),
1432 };
1433 w.empty_tag_with_attrs("tabColor", attrs)?;
1434 }
1435 Ok(())
1436 }
1437
1438 fn write_page_set_up_pr<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1439 if self.fit_page {
1440 let attrs = indexmap! {
1441 "fitToPage" => "1"
1442 };
1443 w.empty_tag_with_attrs("pageSetUpPr", attrs)?;
1444 }
1445 Ok(())
1446 }
1447}
1448
1449impl XmlWritable for Outline {
1450 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1451 if self == &Outline::default() {
1452 Ok(())
1453 } else {
1454 let mut attrs = indexmap! {};
1455 if self.style {
1456 attrs.insert("applyStyles", "1");
1457 }
1458 if !self.below {
1459 attrs.insert("summaryBelow", "0");
1460 }
1461 if !self.right {
1462 attrs.insert("summaryRight", "0");
1463 }
1464 if !self.on {
1465 attrs.insert("showOutlineSymbols", "0");
1466 }
1467 w.empty_tag_with_attrs("outlinePr", attrs)
1468 }
1469 }
1470}
1471
1472struct SheetViews {
1473 view: SheetView,
1474}
1475
1476impl XmlWritable for SheetViews {
1477 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1478 w.tag_with_xml_writable("sheetViews", &self.view)
1479 }
1480}
1481
1482struct SheetView {
1483 tab_selected: bool,
1484}
1485
1486impl XmlWritable for SheetView {
1487 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1488 let mut attrs = indexmap! {};
1489 if self.tab_selected {
1490 attrs.insert("tabSelected", "1");
1491 }
1492 attrs.insert("workbookViewId", "0");
1493 w.empty_tag_with_attrs("sheetView", attrs)
1496 }
1497}
1498
1499struct SheetFormatPr {
1500 default_row_height: f64,
1501}
1502
1503impl XmlWritable for SheetFormatPr {
1504 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1505 let attrs = indexmap! {
1506 "defaultRowHeight" => format!("{}", self.default_row_height),
1507 };
1508 w.empty_tag_with_attrs("sheetFormatPr", attrs)
1511 }
1512}
1513
1514struct Columns<'a> {
1515 col_options: &'a BTreeMap<
1516 Col,
1517 (ColRange, Option<RowColOptions>, f64, Option<usize>),
1518 >,
1519}
1520
1521impl<'a> XmlWritable for Columns<'a> {
1522 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1523 if !self.col_options.is_empty() {
1524 let tag = "cols";
1525 w.start_tag(tag)?;
1526 for (range, options, width, format_index) in
1527 self.col_options.values().cloned()
1528 {
1529 Column {
1531 range,
1532 options,
1533 width,
1534 format_index,
1535 }
1536 .write_xml(w)?;
1537 }
1538 w.end_tag(tag)?;
1539 }
1540
1541 Ok(())
1542 }
1543}
1544
1545struct MergeCells<'a> {
1546 merged_ranges: &'a Vec<IndexRange>,
1547}
1548
1549impl<'a> XmlWritable for MergeCells<'a> {
1550 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1551 if !self.merged_ranges.is_empty() {
1552 let tag = "mergeCells";
1553 let attrs = indexmap! {
1554 "count" => format!("{}", self.merged_ranges.len())
1555 };
1556 w.start_tag_with_attrs(tag, attrs)?;
1557 for range in self.merged_ranges.iter() {
1558 let attrs = indexmap! {"ref" => range.as_range_string() };
1559 w.empty_tag_with_attrs("mergeCell", attrs)?;
1560 }
1561 w.end_tag(tag)?;
1562 }
1563
1564 Ok(())
1565 }
1566}
1567
1568struct Column {
1569 range: ColRange,
1570 options: Option<RowColOptions>,
1571 width: f64,
1572 format_index: Option<usize>,
1573}
1574
1575impl XmlWritable for Column {
1576 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1577 let first: u16 = self.range.first().into();
1579 let last: u16 = self.range.last().into();
1580 let mut attrs = indexmap! {
1581 "min" => format!("{}", first + 1),
1582 "max" => format!("{}", last + 1),
1583 };
1584 if self.width != DEFAULT_COL_WIDTH {
1585 attrs.insert("customWidth", "1".to_string());
1586 attrs.insert("width", format!("{}", self.width));
1587 }
1588 if let Some(ref options) = self.options {
1591 if options.level > OutlineLevel::Level0 {
1592 let level: u8 = options.level.into();
1593 attrs.insert("outlineLevel", format!("{}", level));
1594 }
1595 if options.collapsed {
1596 attrs.insert("collapsed", "1".to_string());
1597 }
1598 }
1599
1600 w.empty_tag_with_attrs("col", attrs)
1601 }
1602}
1603
1604struct SheetData<'a> {
1605 cells: &'a BTreeMap<Row, BTreeMap<Col, Cell>>,
1606}
1607
1608impl XmlWritable for SheetData<'_> {
1609 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1610 let tag = "sheetData";
1611
1612 if self.cells.is_empty() {
1613 w.empty_tag(tag)?;
1614 } else {
1615 w.start_tag(tag)?;
1616
1617 fn calculate_spans<T>(
1619 rows: &BTreeMap<Row, BTreeMap<Col, T>>,
1620 span_index: u32,
1621 ) -> Result<(u32, ColRange)> {
1622 let start = Row::try_from(span_index * 16)?;
1623 let end = Row::try_from(std::cmp::min(
1624 crate::cell::ROW_MAX - 1,
1625 (span_index + 1) * 16 - 1,
1626 ))?;
1627
1628 let mut range: Option<ColRange> = None;
1629
1630 for (_, cells) in rows.iter().filter(|(index, _)| {
1631 **index >= start && **index <= end
1632 }) {
1633 let first: Option<Col> = cells.keys().next().cloned();
1634 let last: Option<Col> =
1635 cells.keys().rev().next().cloned();
1636 match (first, last, range.take()) {
1637 (Some(first), Some(last), Some(mut r)) => {
1638 r.extend_to(first);
1639 r.extend_to(last);
1640 range = Some(r);
1641 }
1642 (Some(first), Some(last), None) => {
1643 let mut r = ColRange::from(first);
1644 r.extend_to(last);
1645 range = Some(r);
1646 }
1647 (Some(_), None, _) | (None, Some(_), _) => {
1648 unreachable!()
1649 }
1650 (None, None, _) => {}
1651 }
1652 }
1653 Ok((span_index, range.unwrap_or_default()))
1654 }
1655
1656 let mut spans = calculate_spans(self.cells, 0)?;
1657
1658 for (row, cells) in self.cells {
1659 let tag = "row";
1660
1661 let row_index: u32 = (*row).into();
1662 let span_block_index = row_index / 16;
1663 if spans.0 != span_block_index {
1664 spans = calculate_spans(self.cells, span_block_index)?;
1665 }
1666
1667 let first: u16 = spans.1.first().into();
1668 let last: u16 = spans.1.last().into();
1669
1670 let attrs = indexmap! {
1671 "r" => row.as_range_string(),
1672 "spans" => format!("{}:{}", first + 1, last + 1),
1673 };
1674
1675 w.start_tag_with_attrs(tag, attrs)?;
1676
1677 for (col, cell) in cells {
1678 CellWriter {
1679 inner: cell,
1680 index: Index::new(*row, *col),
1681 }
1682 .write_xml(w)?;
1683 }
1684
1685 w.end_tag(tag)?;
1686 }
1687
1688 w.end_tag(tag)?;
1689 }
1690
1691 Ok(())
1692 }
1693}
1694
1695struct CellWriter<'a> {
1696 inner: &'a Cell,
1697 index: Index,
1698}
1699
1700impl XmlWritable for CellWriter<'_> {
1701 fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1702 use crate::cell::Data as D;
1703
1704 match self.inner.data {
1705 D::Number(ref number) => self.write_number_cell(w, *number),
1706 D::String(ref index) => self.write_string_cell(w, *index),
1707 D::InlineString(_) => unimplemented!(),
1708 D::InlineRichString(_) => unimplemented!(),
1709 D::Formula(ref formula, ref value) => {
1710 self.write_formula_cell(w, formula, *value)
1711 }
1712 D::ArrayFormula(_) => unimplemented!(),
1713 D::Blank => self.write_blank_cell(w),
1714 D::Boolean(_) => unimplemented!(),
1715 D::HyperlinkUrl(_) => unimplemented!(),
1716 D::HyperlinkInternal(_) => unimplemented!(),
1717 D::HyperlinkExternal(_) => unimplemented!(),
1718 }
1719 }
1720}
1721
1722impl CellWriter<'_> {
1723 fn write_number_cell<W: XmlWriter>(
1724 &self,
1725 w: &mut W,
1726 value: f64,
1727 ) -> Result<()> {
1728 let tag = "c";
1729
1730 let mut attrs = indexmap! {
1731 "r" => self.index.as_range_string(),
1732 "t" => "n".to_string(),
1733 };
1734 if let Some(ref style) = self.inner.format {
1735 attrs.insert("s", format!("{}", style));
1736 }
1737
1738 w.start_tag_with_attrs(tag, attrs)?;
1739 w.tag_with_text("v", &format!("{}", value))?;
1740 w.end_tag(tag)?;
1741 Ok(())
1742 }
1743
1744 fn write_string_cell<W: XmlWriter>(
1745 &self,
1746 w: &mut W,
1747 string_index: usize,
1748 ) -> Result<()> {
1749 let tag = "c";
1750
1751 let mut attrs = indexmap! {};
1752 attrs.insert("r", self.index.as_range_string());
1753 if let Some(ref style) = self.inner.format {
1754 attrs.insert("s", format!("{}", style));
1755 }
1756 attrs.insert("t", "s".to_string());
1757
1758 w.start_tag_with_attrs(tag, attrs)?;
1759 w.tag_with_text("v", &format!("{}", string_index))?;
1760 w.end_tag(tag)?;
1761 Ok(())
1762 }
1763
1764 fn write_formula_cell<W: XmlWriter>(
1765 &self,
1766 w: &mut W,
1767 formula: &str,
1768 value: f64,
1769 ) -> Result<()> {
1770 let tag = "c";
1771
1772 let mut attrs = indexmap! {};
1773 attrs.insert("r", self.index.as_range_string());
1774 if let Some(ref style) = self.inner.format {
1775 attrs.insert("s", format!("{}", style));
1776 }
1777 w.start_tag_with_attrs(tag, attrs)?;
1778 w.tag_with_text("f", formula)?;
1779 w.tag_with_text("v", &format!("{}", value))?;
1780 w.end_tag(tag)?;
1781 Ok(())
1782 }
1783
1784 fn write_blank_cell<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
1785 let tag = "c";
1786
1787 let mut attrs = indexmap! {};
1788 attrs.insert("r", self.index.as_range_string());
1789 if let Some(ref style) = self.inner.format {
1790 attrs.insert("s", format!("{}", style));
1791 }
1792 w.empty_tag_with_attrs(tag, attrs)?;
1793 Ok(())
1794 }
1795}