karo/worksheet/
mod.rs

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        // TODO: fill all the required parameters
88        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 the range is only one cell, we don't need to merge it.
475        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        // TODO: this probably fits better into the Workbook.
525        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    /// Store the vertical and horizontal number of pages that will define
635    /// the maximum area printed.
636    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    /// Should be true by default.
771    ignore_blank: bool,
772    /// Should be true by default.
773    show_input: bool,
774    /// Should be true by default.
775    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    /// Restrict cell input to whole/integer numbers only.
888    Integer(Criterion<i64>),
889    /// Restrict cell input to whole/integer numbers only, using a cell
890    /// reference.
891    IntegerFormula(Criterion<String>),
892    /// Restrict cell input to decimal numbers only.
893    Decimal(Criterion<f64>),
894    /// Restrict cell input to decimal numbers only, using a cell
895    /// reference.
896    DecimalFormula(Criterion<String>),
897    /// Restrict cell input to a list of strings in a dropdown.
898    List {
899        /// The list of allowed values.
900        values: IndexSet<String>,
901        /// Show the drop-down.
902        // TODO: this is inverse logic in libxlsxwriter - need to check
903        // which we need.
904        show_dropdown: bool,
905    },
906    /// Restrict cell input to a list of strings in a dropdown, using a
907    /// cell range.
908    ListFormula {
909        /// A formula that yields the list of allowed values.
910        formula: String,
911        /// Show the drop-down.
912        // TODO: this is inverse logic in libxlsxwriter - need to check
913        // which we need.
914        show_dropdown: bool,
915    },
916    /// Restrict cell input to date values only.
917    Date(Criterion<DateTime<Utc>>),
918    /// Restrict cell input to date values only, using a cell reference.
919    DateFormula(Criterion<String>),
920    /// Restrict cell input to date values only, as a serial number.
921    /// Undocumented.
922    DateNumber(Criterion<i64>),
923    /// Restrict cell input to time values only.
924    Time(Criterion<NaiveTime>),
925    /// Restrict cell input to time values only, using a cell reference.
926    TimeFormula(Criterion<String>),
927    /// Restrict cell input to time values only, as a serial number.
928    /// Undocumented.
929    TimeNumber(Criterion<f64>),
930    /// Restrict cell input to strings of defined length.
931    Length(Criterion<usize>),
932    /// Restrict cell input to strings of defined length, using a cell
933    /// reference.
934    LengthFormula(Criterion<String>),
935    /// Restrict cell to input controlled by a custom formula that
936    /// returns a boolean value.
937    CustomFormula(String),
938    /// Allow any type of input. Mainly only useful for pop-up messages.
939    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                // Between is the default for 2 formulas and isn't added.
1114                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        // TODO: write sheetProtection
1301        // TODO: write autoFilter
1302        self.build_merge_cells().write_xml(w)?;
1303        self.data_validations.write_xml(w)?;
1304        // TODO: write hyperlinks
1305        // TODO: write printOptions
1306        self.page_margins.write_xml(w)?;
1307        // TODO: write pageSetup
1308        // TODO: write headerFooter
1309        // TODO: write rowBreaks
1310        // TODO: write colBreaks
1311        // TODO: write drawings
1312
1313        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            // TODO: check vba_codename
1407
1408            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        // TODO: push all the other attributes as in
1494        // libxlsxwriter/worksheet.c _worksheet_write_sheet_view().
1495        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        // TODO: push all the other attributes as in
1509        // libxlsxwriter/worksheet.c _worksheet_write_sheet_format_pr().
1510        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                // TODO: fetch the format index if required.
1530                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        // TODO: get the format index
1578        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        // TODO: write the style
1589        // TODO: write the customWidth
1590        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            // Spans are the same for blocks of 16 rows.
1618            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}