karo 0.1.2

Spreadsheet export
Documentation
use crate::{Result, XmlWritable, XmlWriter};
use indexmap::{indexmap, IndexSet};
use std::cell::RefCell;
use std::rc::Rc;

const CUSTOM_FORMAT_OFFSET: usize = 164usize;

#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum NumFormat {
    Builtin(Builtin),
    Custom(String),
}

impl Default for NumFormat {
    fn default() -> Self {
        NumFormat::Builtin(Default::default())
    }
}

impl NumFormat {
    pub fn to_builtin_if_possible(self) -> Self {
        match self {
            NumFormat::Builtin(_) => self,
            NumFormat::Custom(s) => Self::from_format_string(&s),
        }
    }

    pub fn from_format_string(s: &str) -> Self {
        use NumFormat::{Builtin as B, Custom as C};
        match s {
            "" => B(Builtin::Index0),
            "0" => B(Builtin::Index1),
            "0.00" => B(Builtin::Index2),
            "#,##0" => B(Builtin::Index3),
            "#,##0.00" => B(Builtin::Index4),
            "($#,##0_);($#,##0)" => B(Builtin::Index5),
            "($#,##0_);[Red]($#,##0)" => B(Builtin::Index6),
            "($#,##0.00_);($#,##0.00)" => B(Builtin::Index7),
            "($#,##0.00_);[Red]($#,##0.00)" => B(Builtin::Index8),
            "0%" => B(Builtin::Index9),
            "0.00%" => B(Builtin::Index10),
            "0.00E+00" => B(Builtin::Index11),
            "# ?/?" => B(Builtin::Index12),
            "# ??/??" => B(Builtin::Index13),
            "m/d/yy" => B(Builtin::Index14),
            "d-mmm-yy" => B(Builtin::Index15),
            "d-mmm" => B(Builtin::Index16),
            "mmm-yy" => B(Builtin::Index17),
            "h:mm AM/PM" => B(Builtin::Index18),
            "h:mm:ss AM/PM" => B(Builtin::Index19),
            "h:mm" => B(Builtin::Index20),
            "h:mm:ss" => B(Builtin::Index21),
            "m/d/yy h:mm" => B(Builtin::Index22),
            "(#,##0_);(#,##0)" => B(Builtin::Index37),
            "(#,##0_);[Red](#,##0)" => B(Builtin::Index38),
            "(#,##0.00_);(#,##0.00)" => B(Builtin::Index39),
            "(#,##0.00_);[Red](#,##0.00)" => B(Builtin::Index40),
            "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)" => {
                B(Builtin::Index41)
            }
            "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)" => {
                B(Builtin::Index42)
            }
            "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)" => {
                B(Builtin::Index43)
            }
            "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)" => {
                B(Builtin::Index44)
            }
            "mm:ss" => B(Builtin::Index45),
            "[h]:mm:ss" => B(Builtin::Index46),
            "mm:ss.0" => B(Builtin::Index47),
            "##0.0E+0" => B(Builtin::Index48),
            "@" => B(Builtin::Index49),
            s => C(s.to_string()),
        }
    }
}

/// Excel built-in number formats.
///
/// * Numeric formats 23 to 36 are not documented by Microsoft and my
///   differ in international versions. The listed date and currency
///   formats may also vary depending on system settings.
/// * The dollar sign in the above format appears as the defined local
///   currency symbol.
/// * These formats can also be set via `Format::set_num_format()`.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[repr(u8)]
pub enum Builtin {
    /// General
    Index0 = 0u8,
    /// `0`
    Index1,
    /// `0.00`
    Index2,
    /// `#,##0`
    Index3,
    /// `#,##0.00`
    Index4,
    /// `($#,##0_);($#,##0)`
    Index5,
    /// `($#,##0_);[Red]($#,##0)`
    Index6,
    /// `($#,##0.00_);($#,##0.00)`
    Index7,
    /// `($#,##0.00_);[Red]($#,##0.00)`
    Index8,
    /// `0%`
    Index9,
    /// `0.00%`
    Index10,
    /// `0.00E+00`
    Index11,
    /// `# ?/?`
    Index12,
    /// `# ??/??`
    Index13,
    /// `m/d/yy`
    Index14,
    /// `d-mmm-yy`
    Index15,
    /// `d-mmm`
    Index16,
    /// `mmm-yy`
    Index17,
    /// `h:mm AM/PM`
    Index18,
    /// `h:mm:ss AM/PM`
    Index19,
    /// `h:mm`
    Index20,
    /// `h:mm:ss`
    Index21,
    /// `m/d/yy h:mm`
    Index22,
    /// `(#,##0_);(#,##0)`
    Index37 = 37u8,
    /// `(#,##0_);[Red](#,##0)`
    Index38,
    /// `(#,##0.00_);(#,##0.00)`
    Index39,
    /// `(#,##0.00_);[Red](#,##0.00)`
    Index40,
    /// `_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)`
    Index41,
    /// `_($* #,##0_);_($* (#,##0);_($* "-"_);_(@_)`
    Index42,
    /// `_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)`
    Index43,
    /// `_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)`
    Index44,
    /// `mm:ss`
    Index45,
    /// `[h]:mm:ss`
    Index46,
    /// `mm:ss.0`
    Index47,
    /// `##0.0E+0`
    Index48,
    /// `@`
    Index49,
}

impl Default for Builtin {
    fn default() -> Self {
        Builtin::Index0
    }
}

impl Builtin {
    pub fn get_builtin_index(&self) -> usize {
        *self as usize
    }
}

#[derive(Default, Debug)]
struct Inner {
    num_formats: IndexSet<String>,
}

#[derive(Clone, Default, Debug)]
pub(crate) struct NumFormats {
    inner: Rc<RefCell<Inner>>,
}

impl NumFormats {
    pub fn create_or_get_index(
        &mut self,
        num_format: Option<&NumFormat>,
    ) -> Option<usize> {
        let mut inner = self.inner.borrow_mut();
        num_format.map(|f| match f {
            NumFormat::Builtin(f) => f.get_builtin_index(),
            NumFormat::Custom(s) => {
                inner.num_formats.insert_full(s.clone()).0
                    + CUSTOM_FORMAT_OFFSET
            }
        })
    }
}

impl XmlWritable for Inner {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if !self.num_formats.is_empty() {
            let tag = "numFmts";
            let attrs = indexmap! {
                "count" => format!("{}", self.num_formats.len())
            };
            w.start_tag_with_attrs(tag, attrs)?;

            for (index, format) in self.num_formats.iter().enumerate() {
                let attrs = indexmap! {
                    "numFmtId" =>
                        format!("{}", index + CUSTOM_FORMAT_OFFSET),
                    "formatCode" => format.replace("\"", "&quot;")
                };
                w.empty_tag_with_attrs("numFmt", attrs)?;
            }

            w.end_tag(tag)?;
        }

        Ok(())
    }
}

impl XmlWritable for NumFormats {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.inner.borrow().write_xml(w)
    }
}