mod tests;
use std::io::Cursor;
use std::mem;
use crate::format::Format;
use crate::xmlwriter::{
xml_declaration, xml_empty_tag, xml_empty_tag_only, xml_end_tag, xml_start_tag,
xml_start_tag_only,
};
use crate::{
Alignment, Border, Color, Fill, Font, FontScheme, FormatAlign, FormatBorder,
FormatDiagonalBorder, FormatPattern, FormatScript, FormatUnderline,
};
pub struct Styles<'a> {
pub(crate) writer: Cursor<Vec<u8>>,
xf_formats: &'a Vec<Format>,
dxf_formats: &'a Vec<Format>,
font_count: u16,
fill_count: u16,
border_count: u16,
num_formats: Vec<String>,
has_hyperlink_style: bool,
has_comments: bool,
is_rich_string_style: bool,
hyperlink_font_id: u16,
}
impl<'a> Styles<'a> {
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
xf_formats: &'a Vec<Format>,
dxf_formats: &'a Vec<Format>,
font_count: u16,
fill_count: u16,
border_count: u16,
num_formats: Vec<String>,
has_hyperlink_style: bool,
has_comments: bool,
is_rich_string_style: bool,
) -> Styles<'a> {
let writer = Cursor::new(Vec::with_capacity(2048));
Styles {
writer,
xf_formats,
dxf_formats,
font_count,
fill_count,
border_count,
num_formats,
has_hyperlink_style,
has_comments,
is_rich_string_style,
hyperlink_font_id: 0,
}
}
pub(crate) fn assemble_xml_file(&mut self) {
xml_declaration(&mut self.writer);
self.write_style_sheet();
self.write_num_fmts();
self.write_fonts();
self.write_fills();
self.write_borders();
self.write_cell_style_xfs();
self.write_cell_xfs();
self.write_cell_styles();
self.write_dxfs();
self.write_table_styles();
xml_end_tag(&mut self.writer, "styleSheet");
}
fn write_style_sheet(&mut self) {
let attributes = [(
"xmlns",
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
)];
xml_start_tag(&mut self.writer, "styleSheet", &attributes);
}
fn write_fonts(&mut self) {
let mut count = self.font_count;
if self.has_comments {
count += 1;
}
let attributes = [("count", count.to_string())];
xml_start_tag(&mut self.writer, "fonts", &attributes);
let mut font_id = 0;
for xf_format in self.xf_formats {
if xf_format.has_font {
self.write_font(&xf_format.font, false);
if xf_format.font.is_hyperlink {
self.hyperlink_font_id = font_id;
}
font_id += 1;
}
}
if self.has_comments {
self.write_comment_font();
}
xml_end_tag(&mut self.writer, "fonts");
}
pub(crate) fn write_font(&mut self, font: &Font, dxf_format: bool) {
if self.is_rich_string_style {
xml_start_tag_only(&mut self.writer, "rPr");
} else {
xml_start_tag_only(&mut self.writer, "font");
}
if font.bold {
xml_empty_tag_only(&mut self.writer, "b");
}
if font.italic {
xml_empty_tag_only(&mut self.writer, "i");
}
if font.strikethrough {
xml_empty_tag_only(&mut self.writer, "strike");
}
if font.underline != FormatUnderline::None {
self.write_font_underline(font);
}
if font.script != FormatScript::None {
self.write_vert_align(font);
}
if !dxf_format {
self.write_font_size(font);
}
self.write_font_color(font, dxf_format);
if !dxf_format {
self.write_font_name(font);
if font.family > 0 {
self.write_font_family(font);
}
if font.charset > 0 {
self.write_font_charset(font);
}
self.write_font_scheme(font);
}
if self.is_rich_string_style {
xml_end_tag(&mut self.writer, "rPr");
} else {
xml_end_tag(&mut self.writer, "font");
}
}
fn write_font_size(&mut self, font: &Font) {
let attributes = [("val", font.size.as_str())];
xml_empty_tag(&mut self.writer, "sz", &attributes);
}
fn write_font_color(&mut self, font: &Font, dxf_format: bool) {
let mut attributes = vec![];
match font.color {
Color::Automatic => {
}
Color::Default => {
if !dxf_format {
attributes.push(("theme", "1".to_string()));
xml_empty_tag(&mut self.writer, "color", &attributes);
}
}
_ => {
attributes.append(&mut font.color.attributes());
xml_empty_tag(&mut self.writer, "color", &attributes);
}
}
}
fn write_font_name(&mut self, font: &Font) {
let attributes = [("val", font.name.as_ref())];
if self.is_rich_string_style {
xml_empty_tag(&mut self.writer, "rFont", &attributes);
} else {
xml_empty_tag(&mut self.writer, "name", &attributes);
}
}
fn write_font_family(&mut self, font: &Font) {
let attributes = [("val", font.family.to_string())];
xml_empty_tag(&mut self.writer, "family", &attributes);
}
fn write_font_charset(&mut self, font: &Font) {
let attributes = [("val", font.charset.to_string())];
xml_empty_tag(&mut self.writer, "charset", &attributes);
}
fn write_font_scheme(&mut self, font: &Font) {
let mut attributes = vec![];
match font.scheme {
FontScheme::Body => {
attributes.push(("val", "minor".to_string()));
xml_empty_tag(&mut self.writer, "scheme", &attributes);
}
FontScheme::Headings => {
attributes.push(("val", "major".to_string()));
xml_empty_tag(&mut self.writer, "scheme", &attributes);
}
FontScheme::None => {}
}
}
fn write_font_underline(&mut self, font: &Font) {
let mut attributes = vec![];
match font.underline {
FormatUnderline::Double => {
attributes.push(("val", "double".to_string()));
}
FormatUnderline::SingleAccounting => {
attributes.push(("val", "singleAccounting".to_string()));
}
FormatUnderline::DoubleAccounting => {
attributes.push(("val", "doubleAccounting".to_string()));
}
_ => {}
}
xml_empty_tag(&mut self.writer, "u", &attributes);
}
fn write_comment_font(&mut self) {
xml_start_tag_only(&mut self.writer, "font");
xml_empty_tag(&mut self.writer, "sz", &[("val", "8")]);
xml_empty_tag(&mut self.writer, "color", &[("indexed", "81")]);
xml_empty_tag(&mut self.writer, "name", &[("val", "Tahoma")]);
xml_empty_tag(&mut self.writer, "family", &[("val", "2")]);
xml_end_tag(&mut self.writer, "font");
}
fn write_vert_align(&mut self, font: &Font) {
let mut attributes = vec![];
match font.script {
FormatScript::Superscript => {
attributes.push(("val", "superscript".to_string()));
}
FormatScript::Subscript => {
attributes.push(("val", "subscript".to_string()));
}
FormatScript::None => {}
}
xml_empty_tag(&mut self.writer, "vertAlign", &attributes);
}
fn write_fills(&mut self) {
let attributes = [("count", self.fill_count.to_string())];
xml_start_tag(&mut self.writer, "fills", &attributes);
self.write_default_fill("none".to_string());
self.write_default_fill("gray125".to_string());
for xf_format in self.xf_formats {
if xf_format.has_fill {
self.write_fill(&xf_format.fill, false);
}
}
xml_end_tag(&mut self.writer, "fills");
}
fn write_default_fill(&mut self, pattern: String) {
let attributes = [("patternType", pattern)];
xml_start_tag_only(&mut self.writer, "fill");
xml_empty_tag(&mut self.writer, "patternFill", &attributes);
xml_end_tag(&mut self.writer, "fill");
}
fn write_fill(&mut self, fill: &Fill, dxf_format: bool) {
if fill.pattern != FormatPattern::None
&& (fill.background_color == Color::Default
|| fill.background_color == Color::Automatic)
&& (fill.foreground_color == Color::Default
|| fill.foreground_color == Color::Automatic)
{
self.write_default_fill(fill.pattern.to_string());
return;
}
let mut foreground_color_attributes = fill.foreground_color.attributes();
let mut background_color_attributes = fill.background_color.attributes();
if !dxf_format
&& fill.pattern == FormatPattern::Solid
&& fill.background_color != Color::Default
&& fill.foreground_color != Color::Default
{
mem::swap(
&mut foreground_color_attributes,
&mut background_color_attributes,
);
}
xml_start_tag_only(&mut self.writer, "fill");
let mut attributes = vec![("patternType", fill.pattern.to_string())];
if dxf_format
&& (fill.pattern == FormatPattern::None || fill.pattern == FormatPattern::Solid)
{
attributes = vec![];
}
xml_start_tag(&mut self.writer, "patternFill", &attributes);
if fill.foreground_color != Color::Default && fill.foreground_color != Color::Automatic {
xml_empty_tag(&mut self.writer, "fgColor", &foreground_color_attributes);
}
if fill.background_color == Color::Default || fill.background_color == Color::Automatic {
let attributes = [("indexed", "64")];
xml_empty_tag(&mut self.writer, "bgColor", &attributes);
} else {
xml_empty_tag(&mut self.writer, "bgColor", &background_color_attributes);
}
xml_end_tag(&mut self.writer, "patternFill");
xml_end_tag(&mut self.writer, "fill");
}
fn write_borders(&mut self) {
let attributes = [("count", self.border_count.to_string())];
xml_start_tag(&mut self.writer, "borders", &attributes);
for xf_format in self.xf_formats {
if xf_format.has_border {
self.write_border(&xf_format.borders, false);
}
}
xml_end_tag(&mut self.writer, "borders");
}
fn write_border(&mut self, borders: &Border, dxf_format: bool) {
match borders.diagonal_type {
FormatDiagonalBorder::None => {
xml_start_tag_only(&mut self.writer, "border");
}
FormatDiagonalBorder::BorderUp => {
let attributes = [("diagonalUp", "1")];
xml_start_tag(&mut self.writer, "border", &attributes);
}
FormatDiagonalBorder::BorderDown => {
let attributes = [("diagonalDown", "1")];
xml_start_tag(&mut self.writer, "border", &attributes);
}
FormatDiagonalBorder::BorderUpDown => {
let attributes = [("diagonalUp", "1"), ("diagonalDown", "1")];
xml_start_tag(&mut self.writer, "border", &attributes);
}
}
self.write_sub_border("left", borders.left_style, borders.left_color);
self.write_sub_border("right", borders.right_style, borders.right_color);
self.write_sub_border("top", borders.top_style, borders.top_color);
self.write_sub_border("bottom", borders.bottom_style, borders.bottom_color);
if !dxf_format {
self.write_sub_border("diagonal", borders.diagonal_style, borders.diagonal_color);
}
if dxf_format {
xml_empty_tag_only(&mut self.writer, "vertical");
xml_empty_tag_only(&mut self.writer, "horizontal");
}
xml_end_tag(&mut self.writer, "border");
}
fn write_sub_border(
&mut self,
border_type: &str,
border_style: FormatBorder,
border_color: Color,
) {
if border_style == FormatBorder::None {
xml_empty_tag_only(&mut self.writer, border_type);
return;
}
let mut attributes = vec![("style", border_style.to_string())];
xml_start_tag(&mut self.writer, border_type, &attributes);
if border_color != Color::Default && border_color != Color::Automatic {
attributes = border_color.attributes();
} else {
attributes = vec![("auto", "1".to_string())];
}
xml_empty_tag(&mut self.writer, "color", &attributes);
xml_end_tag(&mut self.writer, border_type);
}
fn write_cell_style_xfs(&mut self) {
let mut count = 1;
if self.has_hyperlink_style {
count = 2;
}
let attributes = [("count", count.to_string())];
xml_start_tag(&mut self.writer, "cellStyleXfs", &attributes);
let xf_format = self.xf_formats.first().unwrap();
self.write_cell_xf(xf_format, XFormatType::Style);
if self.has_hyperlink_style {
self.write_hyperlink_style_xf();
}
xml_end_tag(&mut self.writer, "cellStyleXfs");
}
fn write_hyperlink_style_xf(&mut self) {
let attributes = [
("numFmtId", "0".to_string()),
("fontId", self.hyperlink_font_id.to_string()),
("fillId", "0".to_string()),
("borderId", "0".to_string()),
("applyNumberFormat", "0".to_string()),
("applyFill", "0".to_string()),
("applyBorder", "0".to_string()),
("applyAlignment", "0".to_string()),
("applyProtection", "0".to_string()),
];
xml_start_tag(&mut self.writer, "xf", &attributes);
self.write_hyperlink_alignment();
self.write_hyperlink_protection();
xml_end_tag(&mut self.writer, "xf");
}
fn write_hyperlink_alignment(&mut self) {
let attributes = [("vertical", "top")];
xml_empty_tag(&mut self.writer, "alignment", &attributes);
}
fn write_hyperlink_protection(&mut self) {
let attributes = [("locked", "0")];
xml_empty_tag(&mut self.writer, "protection", &attributes);
}
fn write_cell_xfs(&mut self) {
let xf_count = format!("{}", self.xf_formats.len());
let attributes = [("count", xf_count)];
xml_start_tag(&mut self.writer, "cellXfs", &attributes);
let mut cell_type = XFormatType::Default;
for xf_format in self.xf_formats {
self.write_cell_xf(xf_format, cell_type);
cell_type = XFormatType::User;
}
xml_end_tag(&mut self.writer, "cellXfs");
}
fn write_cell_xf(&mut self, xf_format: &Format, xf_type: XFormatType) {
let has_checkbox = xf_format.has_checkbox();
let is_hyperlink = xf_format.font.is_hyperlink;
let has_alignment = xf_format.has_alignment();
let has_protection = xf_format.has_protection();
let xf_id = i32::from(is_hyperlink);
let mut attributes = vec![
("numFmtId", xf_format.num_format_index.to_string()),
("fontId", xf_format.font_index.to_string()),
("fillId", xf_format.fill_index.to_string()),
("borderId", xf_format.border_index.to_string()),
];
let apply_alignment =
(xf_format.apply_alignment() || is_hyperlink) && xf_type == XFormatType::User;
if xf_type != XFormatType::Style {
attributes.push(("xfId", xf_id.to_string()));
}
if xf_format.quote_prefix {
attributes.push(("quotePrefix", "1".to_string()));
}
if xf_format.num_format_index > 0 {
attributes.push(("applyNumberFormat", "1".to_string()));
}
if xf_format.font_index > 0 && !is_hyperlink {
attributes.push(("applyFont", "1".to_string()));
}
if xf_format.fill_index > 0 {
attributes.push(("applyFill", "1".to_string()));
}
if xf_format.border_index > 0 {
attributes.push(("applyBorder", "1".to_string()));
}
if apply_alignment {
attributes.push(("applyAlignment", "1".to_string()));
}
if has_protection || is_hyperlink {
attributes.push(("applyProtection", "1".to_string()));
}
if has_alignment || has_protection || has_checkbox {
xml_start_tag(&mut self.writer, "xf", &attributes);
if has_alignment {
self.write_alignment(xf_format.alignment);
}
if has_protection {
self.write_protection(xf_format);
}
if has_checkbox {
self.write_xf_format_extensions();
}
xml_end_tag(&mut self.writer, "xf");
} else {
xml_empty_tag(&mut self.writer, "xf", &attributes);
}
}
fn write_protection(&mut self, xf_format: &Format) {
let mut attributes = vec![];
if !xf_format.locked {
attributes.push(("locked", "0".to_string()));
}
if xf_format.hidden {
attributes.push(("hidden", "1".to_string()));
}
xml_empty_tag(&mut self.writer, "protection", &attributes);
}
fn write_alignment(&mut self, alignment: Alignment) {
let mut attributes = vec![];
let mut horizontal_align = alignment.horizontal;
let mut shrink = alignment.shrink;
if alignment.indent > 0
&& horizontal_align != FormatAlign::Left
&& horizontal_align != FormatAlign::Right
&& horizontal_align != FormatAlign::Distributed
&& alignment.vertical != FormatAlign::Top
&& alignment.vertical != FormatAlign::Bottom
&& alignment.vertical != FormatAlign::Distributed
{
horizontal_align = FormatAlign::Left;
}
if alignment.text_wrap
|| horizontal_align == FormatAlign::Fill
|| horizontal_align == FormatAlign::Justify
|| horizontal_align == FormatAlign::Distributed
{
shrink = false;
}
match horizontal_align {
FormatAlign::Center => {
attributes.push(("horizontal", "center".to_string()));
}
FormatAlign::CenterAcross => {
attributes.push(("horizontal", "centerContinuous".to_string()));
}
FormatAlign::Distributed => {
attributes.push(("horizontal", "distributed".to_string()));
}
FormatAlign::Fill => {
attributes.push(("horizontal", "fill".to_string()));
}
FormatAlign::Justify => {
attributes.push(("horizontal", "justify".to_string()));
}
FormatAlign::Left => {
attributes.push(("horizontal", "left".to_string()));
}
FormatAlign::Right => {
attributes.push(("horizontal", "right".to_string()));
}
_ => {}
}
match alignment.vertical {
FormatAlign::VerticalCenter => {
attributes.push(("vertical", "center".to_string()));
}
FormatAlign::VerticalDistributed => {
attributes.push(("vertical", "distributed".to_string()));
}
FormatAlign::VerticalJustify => {
attributes.push(("vertical", "justify".to_string()));
}
FormatAlign::Top => {
attributes.push(("vertical", "top".to_string()));
}
_ => {}
}
if alignment.rotation != 0 {
attributes.push(("textRotation", alignment.rotation.to_string()));
}
if alignment.indent != 0 {
attributes.push(("indent", alignment.indent.to_string()));
}
if alignment.text_wrap {
attributes.push(("wrapText", "1".to_string()));
}
if shrink {
attributes.push(("shrinkToFit", "1".to_string()));
}
if alignment.reading_direction > 0 && alignment.reading_direction <= 2 {
attributes.push(("readingOrder", alignment.reading_direction.to_string()));
}
xml_empty_tag(&mut self.writer, "alignment", &attributes);
}
fn write_cell_styles(&mut self) {
let mut count = 1;
if self.has_hyperlink_style {
count = 2;
}
let attributes = [("count", count.to_string())];
xml_start_tag(&mut self.writer, "cellStyles", &attributes);
if self.has_hyperlink_style {
self.write_hyperlink_cell_style();
}
self.write_normal_cell_style();
xml_end_tag(&mut self.writer, "cellStyles");
}
fn write_normal_cell_style(&mut self) {
let attributes = [("name", "Normal"), ("xfId", "0"), ("builtinId", "0")];
xml_empty_tag(&mut self.writer, "cellStyle", &attributes);
}
fn write_hyperlink_cell_style(&mut self) {
let attributes = [("name", "Hyperlink"), ("xfId", "1"), ("builtinId", "8")];
xml_empty_tag(&mut self.writer, "cellStyle", &attributes);
}
fn write_dxfs(&mut self) {
let attributes = [("count", self.dxf_formats.len().to_string())];
if self.dxf_formats.is_empty() {
xml_empty_tag(&mut self.writer, "dxfs", &attributes);
} else {
xml_start_tag(&mut self.writer, "dxfs", &attributes);
for dxf_format in self.dxf_formats {
xml_start_tag_only(&mut self.writer, "dxf");
if dxf_format.has_dxf_font() {
self.write_font(&dxf_format.font, true);
}
if dxf_format.num_format_index > 0 {
self.write_num_fmt(dxf_format.num_format_index, &dxf_format.num_format);
}
if dxf_format.has_dxf_fill() {
self.write_fill(&dxf_format.fill, true);
}
if dxf_format.has_border {
self.write_border(&dxf_format.borders, true);
}
if dxf_format.has_checkbox() {
self.write_dxf_format_extensions();
}
xml_end_tag(&mut self.writer, "dxf");
}
xml_end_tag(&mut self.writer, "dxfs");
}
}
fn write_table_styles(&mut self) {
let attributes = [
("count", "0"),
("defaultTableStyle", "TableStyleMedium9"),
("defaultPivotStyle", "PivotStyleLight16"),
];
xml_empty_tag(&mut self.writer, "tableStyles", &attributes);
}
fn write_num_fmts(&mut self) {
if self.num_formats.is_empty() {
return;
}
let attributes = [("count", self.num_formats.len().to_string())];
xml_start_tag(&mut self.writer, "numFmts", &attributes);
for (index, num_format) in self.num_formats.clone().iter().enumerate() {
self.write_num_fmt(index as u16 + 164, num_format);
}
xml_end_tag(&mut self.writer, "numFmts");
}
fn write_num_fmt(&mut self, num_format_index: u16, num_format: &str) {
let attributes = [
("numFmtId", num_format_index.to_string()),
("formatCode", num_format.to_string()),
];
xml_empty_tag(&mut self.writer, "numFmt", &attributes);
}
fn write_xf_format_extensions(&mut self) {
let attributes = [
("uri", "{C7286773-470A-42A8-94C5-96B5CB345126}"),
(
"xmlns:xfpb",
"http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
),
];
xml_start_tag_only(&mut self.writer, "extLst");
xml_start_tag(&mut self.writer, "ext", &attributes);
xml_empty_tag(&mut self.writer, "xfpb:xfComplement", &[("i", "0")]);
xml_end_tag(&mut self.writer, "ext");
xml_end_tag(&mut self.writer, "extLst");
}
fn write_dxf_format_extensions(&mut self) {
let attributes = [
("uri", "{0417FA29-78FA-4A13-93AC-8FF0FAFDF519}"),
(
"xmlns:xfpb",
"http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
),
];
xml_start_tag_only(&mut self.writer, "extLst");
xml_start_tag(&mut self.writer, "ext", &attributes);
xml_empty_tag(&mut self.writer, "xfpb:DXFComplement", &[("i", "0")]);
xml_end_tag(&mut self.writer, "ext");
xml_end_tag(&mut self.writer, "extLst");
}
}
#[derive(Clone, PartialEq)]
enum XFormatType {
Style,
Default,
User,
}