use crate::writer::{Result, XmlWriter};
use std::io::Write;
use xlsbye_core::types::{
Alignment, Border, BorderSide, BorderStyle, Color, ColorType, DxfStyle, Fill, Font,
FontScheme, GradientFill, GradientType, HorizontalAlign, NumFmt, PatternFill, PatternType,
Stylesheet, SuperSub, UnderlineStyle, VerticalAlign, Xf,
};
use xlsbye_core::xml_names::SPREADSHEET_NS;
pub fn write_styles(writer: impl Write, stylesheet: &Stylesheet) -> Result<()> {
let mut writer = XmlWriter::new(writer);
writer.write_xml_declaration()?;
writer.write_start_element_with_ns(
"styleSheet",
[("", SPREADSHEET_NS)],
std::iter::empty::<(&str, &str)>(),
)?;
let custom_num_fmts: Vec<&NumFmt> = stylesheet
.num_fmts
.iter()
.filter(|fmt| fmt.id > 163)
.collect();
writer.write_start_element(
"numFmts",
[("count", custom_num_fmts.len().to_string())],
)?;
for num_fmt in custom_num_fmts {
writer.write_empty_element(
"numFmt",
[
("numFmtId".to_string(), num_fmt.id.to_string()),
("formatCode".to_string(), num_fmt.format_code.clone()),
],
)?;
}
writer.write_end_element("numFmts")?;
writer.write_start_element("fonts", [("count", stylesheet.fonts.len().to_string())])?;
for font in &stylesheet.fonts {
write_font(&mut writer, font)?;
}
writer.write_end_element("fonts")?;
writer.write_start_element("fills", [("count", stylesheet.fills.len().to_string())])?;
for fill in &stylesheet.fills {
write_fill(&mut writer, fill)?;
}
writer.write_end_element("fills")?;
writer.write_start_element(
"borders",
[("count", stylesheet.borders.len().to_string())],
)?;
for border in &stylesheet.borders {
write_border(&mut writer, border)?;
}
writer.write_end_element("borders")?;
writer.write_start_element(
"cellStyleXfs",
[("count", stylesheet.cell_style_xfs.len().to_string())],
)?;
for xf in &stylesheet.cell_style_xfs {
write_xf(&mut writer, xf)?;
}
writer.write_end_element("cellStyleXfs")?;
writer.write_start_element(
"cellXfs",
[("count", stylesheet.cell_xfs.len().to_string())],
)?;
for xf in &stylesheet.cell_xfs {
write_xf(&mut writer, xf)?;
}
writer.write_end_element("cellXfs")?;
write_cell_styles(&mut writer, stylesheet.cell_style_xfs.len())?;
writer.write_start_element("dxfs", [("count", stylesheet.dxfs.len().to_string())])?;
for dxf in &stylesheet.dxfs {
write_dxf(&mut writer, dxf)?;
}
writer.write_end_element("dxfs")?;
writer.write_empty_element(
"tableStyles",
[
("count", "0"),
("defaultTableStyle", "TableStyleMedium9"),
("defaultPivotStyle", "PivotStyleLight16"),
],
)?;
writer.write_end_element("styleSheet")?;
Ok(())
}
fn write_cell_styles<W: Write>(writer: &mut XmlWriter<W>, cell_style_xf_count: usize) -> Result<()> {
let count = cell_style_xf_count.max(1);
writer.write_start_element("cellStyles", [("count", count.to_string())])?;
for index in 0..count {
let mut attrs = vec![
(
"name".to_string(),
if index == 0 {
"Normal".to_string()
} else {
format!("Style {}", index)
},
),
("xfId".to_string(), index.to_string()),
];
if index == 0 {
attrs.push(("builtinId".to_string(), "0".to_string()));
}
writer.write_empty_element("cellStyle", attrs)?;
}
writer.write_end_element("cellStyles")
}
fn write_font<W: Write>(writer: &mut XmlWriter<W>, font: &Font) -> Result<()> {
writer.write_start_element("font", std::iter::empty::<(&str, &str)>())?;
if font.bold {
writer.write_empty_element("b", std::iter::empty::<(&str, &str)>())?;
}
if font.italic {
writer.write_empty_element("i", std::iter::empty::<(&str, &str)>())?;
}
if font.strikethrough {
writer.write_empty_element("strike", std::iter::empty::<(&str, &str)>())?;
}
match font.underline {
UnderlineStyle::None => {}
UnderlineStyle::Single => {
writer.write_empty_element("u", std::iter::empty::<(&str, &str)>())?
}
UnderlineStyle::Double => writer.write_empty_element("u", [("val", "double")])?,
UnderlineStyle::SingleAccounting => {
writer.write_empty_element("u", [("val", "singleAccounting")])?
}
UnderlineStyle::DoubleAccounting => {
writer.write_empty_element("u", [("val", "doubleAccounting")])?
}
}
match font.superscript {
SuperSub::None => {}
SuperSub::Superscript => {
writer.write_empty_element("vertAlign", [("val", "superscript")])?
}
SuperSub::Subscript => writer.write_empty_element("vertAlign", [("val", "subscript")])?,
}
writer.write_empty_element("sz", [("val", (font.size_twips as f64 / 20.0).to_string())])?;
write_color_element(writer, "color", &font.color)?;
writer.write_empty_element("name", [("val", font.name.as_str())])?;
writer.write_empty_element("family", [("val", font.family.to_string())])?;
if font.charset != 0 {
writer.write_empty_element("charset", [("val", font.charset.to_string())])?;
}
match font.scheme {
FontScheme::None => {}
FontScheme::Major => writer.write_empty_element("scheme", [("val", "major")])?,
FontScheme::Minor => writer.write_empty_element("scheme", [("val", "minor")])?,
}
writer.write_end_element("font")
}
fn write_fill<W: Write>(writer: &mut XmlWriter<W>, fill: &Fill) -> Result<()> {
writer.write_start_element("fill", std::iter::empty::<(&str, &str)>())?;
match &fill.pattern {
PatternFill::None => {
writer.write_empty_element("patternFill", [("patternType", "none")])?;
}
PatternFill::Solid { fg_color, bg_color } => {
writer.write_start_element("patternFill", [("patternType", "solid")])?;
write_color_element(writer, "fgColor", fg_color)?;
write_color_element(writer, "bgColor", bg_color)?;
writer.write_end_element("patternFill")?;
}
PatternFill::Pattern {
pattern_type,
fg_color,
bg_color,
} => {
let has_meaningful_colors = !is_default_color(fg_color) || !is_default_color(bg_color);
if has_meaningful_colors {
writer.write_start_element(
"patternFill",
[("patternType", pattern_type_to_str(*pattern_type))],
)?;
if !is_default_color(fg_color) {
write_color_element(writer, "fgColor", fg_color)?;
}
if !is_default_color(bg_color) {
write_color_element(writer, "bgColor", bg_color)?;
}
writer.write_end_element("patternFill")?;
} else {
writer.write_empty_element("patternFill", [("patternType", pattern_type_to_str(*pattern_type))])?;
}
}
PatternFill::Gradient(gradient) => {
write_gradient_fill(writer, gradient)?;
}
}
writer.write_end_element("fill")
}
fn write_gradient_fill<W: Write>(writer: &mut XmlWriter<W>, gradient: &GradientFill) -> Result<()> {
let mut attrs = vec![(
"type".to_string(),
match gradient.gradient_type {
GradientType::Linear => "linear".to_string(),
GradientType::Path => "path".to_string(),
},
)];
if gradient.degree != 0.0 {
attrs.push(("degree".to_string(), gradient.degree.to_string()));
}
if gradient.left != 0.0 {
attrs.push(("left".to_string(), gradient.left.to_string()));
}
if gradient.right != 0.0 {
attrs.push(("right".to_string(), gradient.right.to_string()));
}
if gradient.top != 0.0 {
attrs.push(("top".to_string(), gradient.top.to_string()));
}
if gradient.bottom != 0.0 {
attrs.push(("bottom".to_string(), gradient.bottom.to_string()));
}
writer.write_start_element("gradientFill", attrs)?;
for stop in &gradient.stops {
writer.write_start_element("stop", [("position", stop.position.to_string())])?;
write_color_element(writer, "color", &stop.color)?;
writer.write_end_element("stop")?;
}
writer.write_end_element("gradientFill")
}
fn write_border<W: Write>(writer: &mut XmlWriter<W>, border: &Border) -> Result<()> {
let mut attrs = Vec::new();
if border.diagonal_down {
attrs.push(("diagonalDown".to_string(), "1".to_string()));
}
if border.diagonal_up {
attrs.push(("diagonalUp".to_string(), "1".to_string()));
}
writer.write_start_element("border", attrs)?;
write_border_side(writer, "left", &border.left)?;
write_border_side(writer, "right", &border.right)?;
write_border_side(writer, "top", &border.top)?;
write_border_side(writer, "bottom", &border.bottom)?;
write_border_side(writer, "diagonal", &border.diagonal)?;
writer.write_end_element("border")
}
fn write_border_side<W: Write>(writer: &mut XmlWriter<W>, name: &str, side: &BorderSide) -> Result<()> {
if side.style == BorderStyle::None {
writer.write_empty_element(name, std::iter::empty::<(&str, &str)>())?;
return Ok(());
}
writer.write_start_element(name, [("style", border_style_to_str(side.style))])?;
write_color_element(writer, "color", &side.color)?;
writer.write_end_element(name)
}
fn write_xf<W: Write>(writer: &mut XmlWriter<W>, xf: &Xf) -> Result<()> {
let mut attrs = vec![
("numFmtId".to_string(), xf.num_fmt_id.to_string()),
("fontId".to_string(), xf.font_id.to_string()),
("fillId".to_string(), xf.fill_id.to_string()),
("borderId".to_string(), xf.border_id.to_string()),
];
if let Some(xf_id) = xf.xf_id {
attrs.push(("xfId".to_string(), xf_id.to_string()));
}
if xf.apply_number_format {
attrs.push(("applyNumberFormat".to_string(), "1".to_string()));
}
if xf.apply_font {
attrs.push(("applyFont".to_string(), "1".to_string()));
}
if xf.apply_fill {
attrs.push(("applyFill".to_string(), "1".to_string()));
}
if xf.apply_border {
attrs.push(("applyBorder".to_string(), "1".to_string()));
}
if xf.apply_alignment {
attrs.push(("applyAlignment".to_string(), "1".to_string()));
}
if xf.apply_protection {
attrs.push(("applyProtection".to_string(), "1".to_string()));
}
let has_alignment = has_alignment(&xf.alignment);
let has_protection = xf.protection.locked || xf.protection.hidden;
if !has_alignment && !has_protection {
writer.write_empty_element("xf", attrs)?;
return Ok(());
}
writer.write_start_element("xf", attrs)?;
if has_alignment {
write_alignment(writer, &xf.alignment)?;
}
if has_protection {
let mut protection_attrs = Vec::new();
if xf.protection.locked {
protection_attrs.push(("locked".to_string(), "1".to_string()));
}
if xf.protection.hidden {
protection_attrs.push(("hidden".to_string(), "1".to_string()));
}
writer.write_empty_element("protection", protection_attrs)?;
}
writer.write_end_element("xf")
}
fn write_alignment<W: Write>(writer: &mut XmlWriter<W>, alignment: &Alignment) -> Result<()> {
let mut attrs = Vec::new();
if alignment.horizontal != HorizontalAlign::General {
attrs.push((
"horizontal".to_string(),
horizontal_to_str(alignment.horizontal).to_string(),
));
}
if alignment.vertical != VerticalAlign::Bottom {
attrs.push((
"vertical".to_string(),
vertical_to_str(alignment.vertical).to_string(),
));
}
if alignment.wrap_text {
attrs.push(("wrapText".to_string(), "1".to_string()));
}
if alignment.shrink_to_fit {
attrs.push(("shrinkToFit".to_string(), "1".to_string()));
}
if alignment.text_rotation != 0 {
attrs.push((
"textRotation".to_string(),
alignment.text_rotation.to_string(),
));
}
if alignment.indent != 0 {
attrs.push(("indent".to_string(), alignment.indent.to_string()));
}
if alignment.reading_order != 0 {
attrs.push((
"readingOrder".to_string(),
alignment.reading_order.to_string(),
));
}
writer.write_empty_element("alignment", attrs)
}
fn write_dxf<W: Write>(writer: &mut XmlWriter<W>, dxf: &DxfStyle) -> Result<()> {
writer.write_start_element("dxf", std::iter::empty::<(&str, &str)>())?;
if let Some(font) = &dxf.font {
write_font(writer, font)?;
}
if let Some(fill) = &dxf.fill {
write_fill(writer, fill)?;
}
if let Some(border) = &dxf.border {
write_border(writer, border)?;
}
if let Some(num_fmt) = &dxf.num_fmt {
writer.write_empty_element(
"numFmt",
[
("numFmtId".to_string(), num_fmt.id.to_string()),
("formatCode".to_string(), num_fmt.format_code.clone()),
],
)?;
}
if let Some(alignment) = &dxf.alignment {
write_alignment(writer, alignment)?;
}
writer.write_end_element("dxf")
}
fn is_default_color(color: &Color) -> bool {
matches!(color.color_type, ColorType::Auto | ColorType::Indexed(64) | ColorType::Indexed(65))
}
fn write_color_element<W: Write>(writer: &mut XmlWriter<W>, name: &str, color: &Color) -> Result<()> {
match &color.color_type {
ColorType::Auto => writer.write_empty_element(name, [("auto", "1")]),
ColorType::Indexed(indexed) => {
writer.write_empty_element(name, [("indexed", indexed.to_string())])
}
ColorType::Rgb(a, r, g, b) => {
writer.write_empty_element(name, [("rgb", format!("{:02X}{:02X}{:02X}{:02X}", a, r, g, b))])
}
ColorType::Theme { theme, tint } => {
if *tint == 0.0 {
writer.write_empty_element(name, [("theme", theme.to_string())])
} else {
writer.write_empty_element(
name,
[
("theme".to_string(), theme.to_string()),
("tint".to_string(), tint.to_string()),
],
)
}
}
}
}
fn has_alignment(alignment: &Alignment) -> bool {
alignment.horizontal != HorizontalAlign::General
|| alignment.vertical != VerticalAlign::Bottom
|| alignment.wrap_text
|| alignment.shrink_to_fit
|| alignment.text_rotation != 0
|| alignment.indent != 0
|| alignment.reading_order != 0
}
fn pattern_type_to_str(pattern_type: PatternType) -> &'static str {
match pattern_type {
PatternType::DarkDown => "darkDown",
PatternType::DarkGray => "darkGray",
PatternType::DarkGrid => "darkGrid",
PatternType::DarkHorizontal => "darkHorizontal",
PatternType::DarkTrellis => "darkTrellis",
PatternType::DarkUp => "darkUp",
PatternType::DarkVertical => "darkVertical",
PatternType::Gray0625 => "gray0625",
PatternType::Gray125 => "gray125",
PatternType::LightDown => "lightDown",
PatternType::LightGray => "lightGray",
PatternType::LightGrid => "lightGrid",
PatternType::LightHorizontal => "lightHorizontal",
PatternType::LightTrellis => "lightTrellis",
PatternType::LightUp => "lightUp",
PatternType::LightVertical => "lightVertical",
PatternType::MediumGray => "mediumGray",
}
}
fn border_style_to_str(style: BorderStyle) -> &'static str {
match style {
BorderStyle::None => "none",
BorderStyle::Thin => "thin",
BorderStyle::Medium => "medium",
BorderStyle::Dashed => "dashed",
BorderStyle::Dotted => "dotted",
BorderStyle::Thick => "thick",
BorderStyle::Double => "double",
BorderStyle::Hair => "hair",
BorderStyle::MediumDashed => "mediumDashed",
BorderStyle::DashDot => "dashDot",
BorderStyle::MediumDashDot => "mediumDashDot",
BorderStyle::DashDotDot => "dashDotDot",
BorderStyle::MediumDashDotDot => "mediumDashDotDot",
BorderStyle::SlantDashDot => "slantDashDot",
}
}
fn horizontal_to_str(horizontal: HorizontalAlign) -> &'static str {
match horizontal {
HorizontalAlign::General => "general",
HorizontalAlign::Left => "left",
HorizontalAlign::Center => "center",
HorizontalAlign::Right => "right",
HorizontalAlign::Fill => "fill",
HorizontalAlign::Justify => "justify",
HorizontalAlign::CenterContinuous => "centerContinuous",
HorizontalAlign::Distributed => "distributed",
}
}
fn vertical_to_str(vertical: VerticalAlign) -> &'static str {
match vertical {
VerticalAlign::Top => "top",
VerticalAlign::Center => "center",
VerticalAlign::Bottom => "bottom",
VerticalAlign::Justify => "justify",
VerticalAlign::Distributed => "distributed",
}
}