karo 0.1.2

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

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

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

impl Borders {
    pub fn create_or_get_index(
        &mut self,
        border: Option<&CellBorder>,
    ) -> Option<usize> {
        let mut inner = self.inner.borrow_mut();
        border.map(|f| inner.borders.insert_full(f.clone()).0)
    }
}

impl Default for Borders {
    fn default() -> Self {
        let mut inner = Inner::default();
        inner.borders.insert(CellBorder::default());
        Borders {
            inner: Rc::new(RefCell::new(inner)),
        }
    }
}

impl XmlWritable for Borders {
    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<()> {
        if !self.borders.is_empty() {
            let tag = "borders";
            let attrs = indexmap! {
                "count" => format!("{}", self.borders.len()),
            };
            w.start_tag_with_attrs(tag, attrs)?;
            for border in self.borders.iter() {
                border.write_xml(w)?;
            }
            w.end_tag(tag)?;
        }
        Ok(())
    }
}

#[derive(Default, Clone, Hash, PartialEq, Eq, Debug)]
pub struct CellBorder {
    pub left: Option<Border>,
    pub right: Option<Border>,
    pub top: Option<Border>,
    pub bottom: Option<Border>,
    pub diagonal: Option<DiagonalBorder>,
}

impl CellBorder {
    pub fn set_around(&mut self, border: Option<Border>) {
        self.left = border;
        self.right = border;
        self.top = border;
        self.bottom = border;
    }
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct DiagonalBorder {
    pub diagonal_type: DiagonalBorderType,
    pub style: BorderStyle,
    pub color: Option<RGB8>,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct Border {
    pub style: BorderStyle,
    /// If this is None, the color will be set to 'auto'.
    pub color: Option<RGB8>,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub enum DiagonalBorderType {
    Up,
    Down,
    UpDown,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub enum BorderStyle {
    /// Can be used to override a cell border if e.g. a row or column
    /// border was set.
    None,
    Thin,
    Medium,
    Dashed,
    Dotted,
    Thick,
    Double,
    Hair,
    MediumDashed,
    DashDot,
    MediumDashDot,
    DashDotDot,
    MediumDashDotDot,
    SlantDashDot,
}

impl AsRef<str> for BorderStyle {
    fn as_ref(&self) -> &str {
        match self {
            BorderStyle::None => "none",
            BorderStyle::Thin => "thin",
            BorderStyle::Medium => "medium",
            BorderStyle::Dashed => "dashed",
            BorderStyle::Dotted => "dotted",
            BorderStyle::Thick => "thick",
            BorderStyle::Double => "dobule",
            BorderStyle::Hair => "hair",
            BorderStyle::MediumDashed => "mediumDashed",
            BorderStyle::DashDot => "dashDot",
            BorderStyle::MediumDashDot => "mediumDashDot",
            BorderStyle::DashDotDot => "dashDotDot",
            BorderStyle::MediumDashDotDot => "mediumDashDotDot",
            BorderStyle::SlantDashDot => "slantDashDot",
        }
    }
}

impl XmlWritable for CellBorder {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "border";

        let attrs = if let Some(ref border) = self.diagonal {
            match border.diagonal_type {
                DiagonalBorderType::Up => {
                    indexmap! {"diagonalUp" => "1"}
                }
                DiagonalBorderType::Down => {
                    indexmap! {"diagonalDown" => "1"}
                }
                DiagonalBorderType::UpDown => {
                    indexmap! {
                        "diagonalUp" => "1",
                        "diagonalDown" => "1",
                    }
                }
            }
        } else {
            indexmap! {}
        };

        w.start_tag_with_attrs(tag, attrs)?;
        self.write_left(w)?;
        self.write_right(w)?;
        self.write_top(w)?;
        self.write_bottom(w)?;
        self.write_diagonal(w)?;
        w.end_tag(tag)?;
        Ok(())
    }
}

impl CellBorder {
    fn write_left<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        Self::write_optional_border(w, "left", self.left.as_ref())
    }

    fn write_right<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        Self::write_optional_border(w, "right", self.right.as_ref())
    }

    fn write_top<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        Self::write_optional_border(w, "top", self.top.as_ref())
    }

    fn write_bottom<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        Self::write_optional_border(w, "bottom", self.bottom.as_ref())
    }

    fn write_diagonal<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        Self::write_optional_border(
            w,
            "diagonal",
            self.diagonal
                .map(|d| Border {
                    style: d.style,
                    color: d.color,
                })
                .as_ref(),
        )
    }

    fn write_optional_border<W: XmlWriter>(
        w: &mut W,
        name: &str,
        border: Option<&Border>,
    ) -> Result<()> {
        if let Some(b) = border {
            b.write_xml(w, name)
        } else {
            w.empty_tag(name)
        }
    }
}

impl Border {
    fn write_xml<W: XmlWriter>(
        &self,
        w: &mut W,
        name: &str,
    ) -> Result<()> {
        let attrs = indexmap! {
            "style" => self.style.as_ref()
        };
        w.start_tag_with_attrs(name, attrs)?;
        {
            let mut attrs = IndexMap::new();
            if let Some(color) = self.color {
                attrs.insert("rgb", color.as_palette_color());
            } else {
                attrs.insert("auto", "1".to_string());
            }
            w.empty_tag_with_attrs("color", attrs)?;
        }
        w.end_tag(name)?;
        Ok(())
    }
}