karo 0.1.2

Spreadsheet export
Documentation
#[cfg(test)]
mod tests;

use crate::{
    Borders, Fills, Fonts, Format, HorizontalAlignment, NumFormats,
    Result, VerticalAlignment, XmlWritable, XmlWriter,
};
use indexmap::{indexmap, IndexSet};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Hash, Eq, PartialEq, Debug)]
struct FormatData {
    num_format: Option<usize>,
    font: Option<usize>,
    fill: Option<usize>,
    border: Option<usize>,
    horizontal_alignment: Option<HorizontalAlignment>,
    vertical_alignment: Option<VerticalAlignment>,
}

impl XmlWritable for FormatData {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let mut attrs = indexmap! {
            "fontId" =>
                format!("{}", self.font.unwrap_or_default()),
            "numFmtId" =>
                format!("{}", self.num_format.unwrap_or_default()),
            "fillId" =>
                format!("{}", self.fill.unwrap_or_default()),
            "borderId" =>
                format!("{}", self.border.unwrap_or_default()),
            "xfId" => "0".to_string(),
        };

        if self.font.is_some() {
            attrs.insert("applyFont", "1".to_string());
        }
        if self.fill.is_some() {
            attrs.insert("applyFill", "1".to_string());
        }
        if self.border.is_some() {
            attrs.insert("applyBorder", "1".to_string());
        }
        if self.horizontal_alignment.is_some()
            || self.vertical_alignment.is_some()
        {
            // TODO: there are more conditions that enable the
            // applyAlignment flag
            // See libxlsxwriter _apply_alignment().
            attrs.insert("applyAlignment", "1".to_string());
        }
        // TODO: insert applyProtection
        let tag = "xf";
        match (self.horizontal_alignment, self.vertical_alignment) {
            (None, None) => {
                w.empty_tag_with_attrs(tag, attrs)?;
            }
            _ => {
                w.start_tag_with_attrs(tag, attrs)?;
                self.write_alignment(w)?;
                w.end_tag(tag)?;
            }
        }
        Ok(())
    }
}

impl FormatData {
    fn write_alignment<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let mut attrs = indexmap! {};
        if let Some(h) = self.horizontal_alignment {
            attrs.insert("horizontal", h.xml_identifier());
        }
        if let Some(v) = self.vertical_alignment {
            if let Some(s) = v.xml_identifier() {
                attrs.insert("vertical", s);
            }
        }
        w.empty_tag_with_attrs("alignment", attrs)
    }
}

#[derive(Default, Debug)]
struct Inner {
    formats: IndexSet<FormatData>,
    num_formats: NumFormats,
    fonts: Fonts,
    fills: Fills,
    borders: Borders,
}

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

impl Default for Formats {
    fn default() -> Self {
        let mut inner = Inner::default();
        inner.create_or_get_index_format(&Format::default());
        Formats {
            inner: Rc::new(RefCell::new(inner)),
        }
    }
}

impl Formats {
    pub fn create_or_get_index(
        &mut self,
        format: Option<&Format>,
    ) -> Option<usize> {
        self.inner.borrow_mut().create_or_get_index(format)
    }
}

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

impl XmlWritable for Inner {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "styleSheet";
        let attrs = indexmap! {
            "xmlns" =>
                "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
        };
        w.start_tag_with_attrs(tag, attrs)?;
        self.write_num_fmts(w)?;
        self.write_fonts(w)?;
        self.write_fills(w)?;
        self.write_borders(w)?;
        self.write_cell_style_xfs(w)?;
        self.write_cell_xfs(w)?;
        self.write_cell_styles(w)?;
        self.write_dxfs(w)?;
        self.write_table_styles(w)?;
        w.end_tag(tag)?;
        Ok(())
    }
}

impl Inner {
    fn create_or_get_index(
        &mut self,
        format: Option<&Format>,
    ) -> Option<usize> {
        format.map(|f| self.create_or_get_index_format(f))
    }

    fn create_or_get_index_format(&mut self, f: &Format) -> usize {
        let f = self.build_format_data(f);
        self.formats.insert_full(f).0
    }

    fn build_format_data(&mut self, f: &Format) -> FormatData {
        let font = if f.font == Default::default() {
            None
        } else {
            Some(&f.font)
        };
        let num_format = if f.num_format == Default::default() {
            None
        } else {
            Some(&f.num_format)
        };
        let fill = if f.fill == Default::default() {
            None
        } else {
            Some(&f.fill)
        };
        let borders = if f.borders == Default::default() {
            None
        } else {
            Some(&f.borders)
        };
        FormatData {
            font: self.fonts.create_or_get_index(font),
            num_format: self.num_formats.create_or_get_index(num_format),
            fill: self.fills.create_or_get_index(fill),
            border: self.borders.create_or_get_index(borders),
            horizontal_alignment: f.horizontal_alignment,
            vertical_alignment: f.vertical_alignment,
        }
    }

    fn write_num_fmts<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.num_formats.write_xml(w)
    }

    fn write_fonts<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.fonts.write_xml(w)
    }

    fn write_fills<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.fills.write_xml(w)
    }

    fn write_borders<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.borders.write_xml(w)
    }

    fn write_cell_style_xfs<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "cellStyleXfs";
        let attrs = indexmap! { "count" => "1" };
        w.start_tag_with_attrs(tag, attrs)?;

        {
            let tag = "xf";
            let attrs = indexmap! {
                "numFmtId" => "0",
                "fontId" => "0",
                "fillId" => "0",
                "borderId" => "0"
            };
            w.empty_tag_with_attrs(tag, attrs)?;
        }

        w.end_tag(tag)?;
        Ok(())
    }

    fn write_cell_xfs<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "cellXfs";
        let attrs = indexmap! {
            "count" => format!("{}", self.formats.len()),
        };
        w.start_tag_with_attrs(tag, attrs)?;

        {
            for format in self.formats.iter() {
                format.write_xml(w)?;
            }
        }

        w.end_tag(tag)?;
        Ok(())
    }

    fn write_cell_styles<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "cellStyles";
        let attrs = indexmap! { "count" => "1" };
        w.start_tag_with_attrs(tag, attrs)?;

        {
            let tag = "cellStyle";
            let attrs = indexmap! {
                "name" => "Normal",
                "xfId" => "0",
                "builtinId" => "0",
            };
            w.empty_tag_with_attrs(tag, attrs)?;
        }

        w.end_tag(tag)?;
        Ok(())
    }

    fn write_dxfs<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        w.empty_tag_with_attrs("dxfs", indexmap! { "count" => "0" })
    }

    fn write_table_styles<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        w.empty_tag_with_attrs(
            "tableStyles",
            indexmap! {
                "count" => "0",
                "defaultTableStyle" => "TableStyleMedium9",
                "defaultPivotStyle" => "PivotStyleLight16",
            },
        )
    }
}