use crate::error::Result;
use crate::style::{
Alignment, Border, BorderStyle, CellXf, Fill, Font, NumberFormat, Protection, Side,
StyleManager,
};
use crate::xml;
use quick_xml::events::{BytesEnd, BytesStart, Event};
pub fn write<W: std::io::Write>(sink: W, styles: &StyleManager) -> Result<()> {
let mut writer = xml::writer(sink);
let mut root = BytesStart::new("styleSheet");
root.push_attribute(("xmlns", xml::NS_MAIN));
writer.write_event(Event::Start(root))?;
write_num_fmts(&mut writer, styles.number_formats())?;
write_fonts(&mut writer, styles.fonts())?;
write_fills(&mut writer, styles.fills())?;
write_borders(&mut writer, styles.borders())?;
write_cell_style_xfs(&mut writer)?;
write_cell_styles(&mut writer)?;
write_cell_xfs(&mut writer, styles.cell_xfs())?;
writer.write_event(Event::End(BytesEnd::new("styleSheet")))?;
Ok(())
}
fn write_num_fmts<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
formats: &indexmap::IndexMap<NumberFormat, usize>,
) -> Result<()> {
if formats.is_empty() {
return Ok(());
}
let mut start = BytesStart::new("numFmts");
start.push_attribute(("count", formats.len().to_string().as_str()));
writer.write_event(Event::Start(start))?;
for (fmt, id) in formats {
let mut elem = BytesStart::new("numFmt");
elem.push_attribute(("numFmtId", id.to_string().as_str()));
elem.push_attribute(("formatCode", fmt.code.as_str()));
writer.write_event(Event::Empty(elem))?;
}
writer.write_event(Event::End(BytesEnd::new("numFmts")))?;
Ok(())
}
fn write_fonts<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
fonts: &indexmap::IndexMap<Font, usize>,
) -> Result<()> {
let count = fonts.len().max(1);
let mut start = BytesStart::new("fonts");
start.push_attribute(("count", count.to_string().as_str()));
writer.write_event(Event::Start(start))?;
if fonts.is_empty() {
write_font_elem(writer, &Font::default())?;
} else {
let mut items: Vec<_> = fonts.iter().collect();
items.sort_by_key(|(_, idx)| *idx);
for (font, _) in items {
write_font_elem(writer, font)?;
}
}
writer.write_event(Event::End(BytesEnd::new("fonts")))?;
Ok(())
}
fn write_font_elem<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, font: &Font) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("font")))?;
if font.bold {
writer.write_event(Event::Empty(BytesStart::new("b")))?;
}
if font.italic {
writer.write_event(Event::Empty(BytesStart::new("i")))?;
}
if let Some(u) = font.underline {
let mut elem = BytesStart::new("u");
elem.push_attribute(("val", u.to_string().as_str()));
writer.write_event(Event::Empty(elem))?;
}
if font.strike {
writer.write_event(Event::Empty(BytesStart::new("strike")))?;
}
if let Some(size) = font.size {
let mut elem = BytesStart::new("sz");
elem.push_attribute(("val", size.to_string().as_str()));
writer.write_event(Event::Empty(elem))?;
}
if let Some(color) = &font.color {
writer.write_event(Event::Empty(color_elem("color", color)))?;
}
if let Some(name) = &font.name {
let mut elem = BytesStart::new("name");
elem.push_attribute(("val", name.as_str()));
writer.write_event(Event::Empty(elem))?;
}
if let Some(family) = font.family {
let mut elem = BytesStart::new("family");
elem.push_attribute(("val", family.to_string().as_str()));
writer.write_event(Event::Empty(elem))?;
}
if let Some(scheme) = &font.scheme {
let mut elem = BytesStart::new("scheme");
elem.push_attribute(("val", scheme.as_str()));
writer.write_event(Event::Empty(elem))?;
}
writer.write_event(Event::End(BytesEnd::new("font")))?;
Ok(())
}
fn write_fills<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
fills: &indexmap::IndexMap<Fill, usize>,
) -> Result<()> {
let count = fills.len() + 2;
let mut start = BytesStart::new("fills");
start.push_attribute(("count", count.to_string().as_str()));
writer.write_event(Event::Start(start))?;
writer.write_event(Event::Start(BytesStart::new("fill")))?;
let mut pt = BytesStart::new("patternFill");
pt.push_attribute(("patternType", "none"));
writer.write_event(Event::Empty(pt))?;
writer.write_event(Event::End(BytesEnd::new("fill")))?;
writer.write_event(Event::Start(BytesStart::new("fill")))?;
let mut pt = BytesStart::new("patternFill");
pt.push_attribute(("patternType", "gray125"));
writer.write_event(Event::Empty(pt))?;
writer.write_event(Event::End(BytesEnd::new("fill")))?;
let mut items: Vec<_> = fills.iter().collect();
items.sort_by_key(|(_, idx)| *idx);
for (fill, _) in items {
write_fill_elem(writer, fill)?;
}
writer.write_event(Event::End(BytesEnd::new("fills")))?;
Ok(())
}
fn write_fill_elem<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, fill: &Fill) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("fill")))?;
match fill {
Fill::None => {
let mut pt = BytesStart::new("patternFill");
pt.push_attribute(("patternType", "none"));
writer.write_event(Event::Empty(pt))?;
}
Fill::Pattern(p) => {
let mut pt = BytesStart::new("patternFill");
pt.push_attribute(("patternType", p.pattern_type.to_string().as_str()));
writer.write_event(Event::Start(pt))?;
if let Some(fg) = &p.fg_color {
writer.write_event(Event::Empty(color_elem("fgColor", fg)))?;
}
if let Some(bg) = &p.bg_color {
writer.write_event(Event::Empty(color_elem("bgColor", bg)))?;
}
writer.write_event(Event::End(BytesEnd::new("patternFill")))?;
}
}
writer.write_event(Event::End(BytesEnd::new("fill")))?;
Ok(())
}
fn write_borders<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
borders: &indexmap::IndexMap<Border, usize>,
) -> Result<()> {
let count = borders.len().max(1);
let mut start = BytesStart::new("borders");
start.push_attribute(("count", count.to_string().as_str()));
writer.write_event(Event::Start(start))?;
if borders.is_empty() {
write_border_elem(writer, &Border::default())?;
} else {
let mut items: Vec<_> = borders.iter().collect();
items.sort_by_key(|(_, idx)| *idx);
for (border, _) in items {
write_border_elem(writer, border)?;
}
}
writer.write_event(Event::End(BytesEnd::new("borders")))?;
Ok(())
}
fn write_border_elem<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, border: &Border) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("border")))?;
write_side(writer, "left", border.left.clone())?;
write_side(writer, "right", border.right.clone())?;
write_side(writer, "top", border.top.clone())?;
write_side(writer, "bottom", border.bottom.clone())?;
write_side(
writer,
"diagonal",
border.diagonal.as_ref().map(|d| Side {
style: d.style,
color: d.color.clone(),
}),
)?;
writer.write_event(Event::End(BytesEnd::new("border")))?;
Ok(())
}
fn write_side<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
name: &str,
side: Option<Side>,
) -> Result<()> {
let mut elem = BytesStart::new(name);
match side {
Some(s) if s.style != BorderStyle::None => {
elem.push_attribute(("style", s.style.to_string().as_str()));
writer.write_event(Event::Start(elem))?;
if let Some(color) = &s.color {
writer.write_event(Event::Empty(color_elem("color", color)))?;
}
writer.write_event(Event::End(BytesEnd::new(name)))?;
}
_ => {
writer.write_event(Event::Empty(elem))?;
}
}
Ok(())
}
fn write_cell_style_xfs<W: std::io::Write>(writer: &mut quick_xml::Writer<W>) -> Result<()> {
let mut start = BytesStart::new("cellStyleXfs");
start.push_attribute(("count", "1"));
writer.write_event(Event::Start(start))?;
let mut xf = BytesStart::new("xf");
xf.push_attribute(("numFmtId", "0"));
xf.push_attribute(("fontId", "0"));
xf.push_attribute(("fillId", "0"));
xf.push_attribute(("borderId", "0"));
writer.write_event(Event::Empty(xf))?;
writer.write_event(Event::End(BytesEnd::new("cellStyleXfs")))?;
Ok(())
}
fn write_cell_styles<W: std::io::Write>(writer: &mut quick_xml::Writer<W>) -> Result<()> {
let mut start = BytesStart::new("cellStyles");
start.push_attribute(("count", "1"));
writer.write_event(Event::Start(start))?;
let mut style = BytesStart::new("cellStyle");
style.push_attribute(("name", "Normal"));
style.push_attribute(("xfId", "0"));
style.push_attribute(("builtinId", "0"));
writer.write_event(Event::Empty(style))?;
writer.write_event(Event::End(BytesEnd::new("cellStyles")))?;
Ok(())
}
fn write_cell_xfs<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, xfs: &[CellXf]) -> Result<()> {
let count = xfs.len().max(1);
let mut start = BytesStart::new("cellXfs");
start.push_attribute(("count", count.to_string().as_str()));
writer.write_event(Event::Start(start))?;
if xfs.is_empty() {
let mut xf = BytesStart::new("xf");
xf.push_attribute(("numFmtId", "0"));
xf.push_attribute(("fontId", "0"));
xf.push_attribute(("fillId", "0"));
xf.push_attribute(("borderId", "0"));
xf.push_attribute(("xfId", "0"));
writer.write_event(Event::Empty(xf))?;
} else {
for xf in xfs {
write_cell_xf(writer, xf)?;
}
}
writer.write_event(Event::End(BytesEnd::new("cellXfs")))?;
Ok(())
}
fn write_cell_xf<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, xf: &CellXf) -> Result<()> {
let mut start = BytesStart::new("xf");
start.push_attribute(("numFmtId", xf.num_fmt_id.unwrap_or(0).to_string().as_str()));
start.push_attribute(("fontId", xf.font_id.unwrap_or(0).to_string().as_str()));
start.push_attribute(
(
"fillId",
xf.fill_id.map(|i| i + 2).unwrap_or(0).to_string().as_str(),
),
);
start.push_attribute(("borderId", xf.border_id.unwrap_or(0).to_string().as_str()));
start.push_attribute(("xfId", "0"));
let has_alignment = xf.alignment.is_some();
let has_protection = xf.protection.is_some();
if has_alignment || has_protection {
writer.write_event(Event::Start(start))?;
if let Some(a) = &xf.alignment {
write_alignment(writer, a)?;
}
if let Some(p) = &xf.protection {
write_protection(writer, p)?;
}
writer.write_event(Event::End(BytesEnd::new("xf")))?;
} else {
writer.write_event(Event::Empty(start))?;
}
Ok(())
}
fn write_alignment<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, a: &Alignment) -> Result<()> {
let mut elem = BytesStart::new("alignment");
if let Some(h) = a.horizontal {
elem.push_attribute(("horizontal", h.to_string().as_str()));
}
if let Some(v) = a.vertical {
elem.push_attribute(("vertical", v.to_string().as_str()));
}
if a.wrap_text {
elem.push_attribute(("wrapText", "1"));
}
if a.shrink_to_fit {
elem.push_attribute(("shrinkToFit", "1"));
}
if let Some(rot) = a.text_rotation {
elem.push_attribute(("textRotation", rot.to_string().as_str()));
}
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_protection<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, p: &Protection) -> Result<()> {
let mut elem = BytesStart::new("protection");
elem.push_attribute(("locked", if p.locked { "1" } else { "0" }));
elem.push_attribute(("hidden", if p.hidden { "1" } else { "0" }));
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn color_elem<'a>(name: &'a str, rgb: &'a str) -> BytesStart<'a> {
let mut elem = BytesStart::new(name);
elem.push_attribute(("rgb", rgb));
elem
}