use std::{collections::HashMap, io::Read}; use ironcalc_base::types::{ Alignment, Border, BorderItem, BorderStyle, CellStyleXfs, CellStyles, CellXfs, Fill, Font, FontScheme, HorizontalAlignment, NumFmt, Styles, VerticalAlignment, }; use roxmltree::Node; use crate::error::XlsxError; use super::util::{get_attribute, get_bool, get_bool_false, get_color, get_number}; fn get_border(node: Node, name: &str) -> Result, XlsxError> { let style; let color; let border_nodes = node .children() .filter(|n| n.has_tag_name(name)) .collect::>(); if border_nodes.len() == 1 { let border = border_nodes[0]; style = match border.attribute("style") { Some("thin") => BorderStyle::Thin, Some("medium") => BorderStyle::Medium, Some("thick") => BorderStyle::Thick, Some("double") => BorderStyle::Double, Some("slantdashdot") => BorderStyle::SlantDashDot, Some("mediumdashed") => BorderStyle::MediumDashed, Some("mediumdashdot") => BorderStyle::MediumDashDot, Some("mediumdashdotdot") => BorderStyle::MediumDashDotDot, // TODO: Should we fail in this case or set the border to None? Some(_) => BorderStyle::Thin, None => { return Ok(None); } }; let color_node = border .children() .filter(|n| n.has_tag_name("color")) .collect::>(); if color_node.len() == 1 { color = get_color(color_node[0])?; } else { color = None; } } else { return Ok(None); } Ok(Some(BorderItem { style, color })) } pub(super) fn load_styles( archive: &mut zip::read::ZipArchive, ) -> Result { let mut file = archive.by_name("xl/styles.xml")?; let mut text = String::new(); file.read_to_string(&mut text)?; let doc = roxmltree::Document::parse(&text)?; let style_sheet = doc .root() .first_child() .ok_or_else(|| XlsxError::Xml("Corrupt XML structure".to_string()))?; let mut num_fmts = Vec::new(); let num_fmts_nodes = style_sheet .children() .filter(|n| n.has_tag_name("numFmts")) .collect::>(); if num_fmts_nodes.len() == 1 { for num_fmt in num_fmts_nodes[0].children() { let num_fmt_id = get_number(num_fmt, "numFmtId"); let format_code = num_fmt.attribute("formatCode").unwrap_or("").to_string(); num_fmts.push(NumFmt { num_fmt_id, format_code, }); } } let mut fonts = Vec::new(); let font_nodes = style_sheet .children() .filter(|n| n.has_tag_name("fonts")) .collect::>()[0]; for font in font_nodes.children() { let mut sz = 11; let mut name = "Calibri".to_string(); // NOTE: In Excel you can have simple underline or double underline // In IronCalc convert double underline to simple // This in excel is u with a value of "double" let mut u = false; let mut b = false; let mut i = false; let mut strike = false; // Default color is black let mut color = Some("#000000".to_string()); let mut family = 2; let mut scheme = FontScheme::default(); for feature in font.children() { match feature.tag_name().name() { "sz" => { sz = feature .attribute("val") .unwrap_or("11") .parse::() .unwrap_or(11); } "color" => { color = get_color(feature)?; } "u" => { u = true; } "b" => { b = true; } "i" => { i = true; } "strike" => { strike = true; } "name" => name = feature.attribute("val").unwrap_or("Calibri").to_string(), // If there is a theme the font scheme and family overrides other properties like the name "family" => { family = feature .attribute("val") .unwrap_or("2") .parse::() .unwrap_or(2); } "scheme" => { scheme = match feature.attribute("val") { None => FontScheme::default(), Some("minor") => FontScheme::Minor, Some("major") => FontScheme::Major, Some("none") => FontScheme::None, // TODO: Should we fail? Some(_) => FontScheme::default(), } } "charset" => {} _ => { println!("Unexpected feature {feature:?}"); } } } fonts.push(Font { strike, u, b, i, sz, color, name, family, scheme, }); } let mut fills = Vec::new(); let fill_nodes = style_sheet .children() .filter(|n| n.has_tag_name("fills")) .collect::>()[0]; for fill in fill_nodes.children() { let pattern_fill = fill .children() .filter(|n| n.has_tag_name("patternFill")) .collect::>(); if pattern_fill.len() != 1 { // safety belt // Some fills do not have a patternFill, but they have gradientFill fills.push(Fill { pattern_type: "solid".to_string(), fg_color: None, bg_color: None, }); continue; } let pattern_fill = pattern_fill[0]; let pattern_type = pattern_fill .attribute("patternType") .unwrap_or("none") .to_string(); let mut fg_color = None; let mut bg_color = None; for feature in pattern_fill.children() { match feature.tag_name().name() { "fgColor" => { fg_color = get_color(feature)?; } "bgColor" => { bg_color = get_color(feature)?; } _ => { println!("Unexpected pattern"); dbg!(feature); } } } fills.push(Fill { pattern_type, fg_color, bg_color, }) } let mut borders = Vec::new(); let border_nodes = style_sheet .children() .filter(|n| n.has_tag_name("borders")) .collect::>()[0]; for border in border_nodes.children() { let diagonal_up = get_bool_false(border, "diagonal_up"); let diagonal_down = get_bool_false(border, "diagonal_down"); let left = get_border(border, "left")?; let right = get_border(border, "right")?; let top = get_border(border, "top")?; let bottom = get_border(border, "bottom")?; let diagonal = get_border(border, "diagonal")?; borders.push(Border { diagonal_up, diagonal_down, left, right, top, bottom, diagonal, }); } let mut cell_style_xfs = Vec::new(); let cell_style_xfs_nodes = style_sheet .children() .filter(|n| n.has_tag_name("cellStyleXfs")) .collect::>()[0]; for xfs in cell_style_xfs_nodes.children() { let num_fmt_id = get_number(xfs, "numFmtId"); let font_id = get_number(xfs, "fontId"); let fill_id = get_number(xfs, "fillId"); let border_id = get_number(xfs, "borderId"); let apply_number_format = get_bool(xfs, "applyNumberFormat"); let apply_border = get_bool(xfs, "applyBorder"); let apply_alignment = get_bool(xfs, "applyAlignment"); let apply_protection = get_bool(xfs, "applyProtection"); let apply_font = get_bool(xfs, "applyFont"); let apply_fill = get_bool(xfs, "applyFill"); cell_style_xfs.push(CellStyleXfs { num_fmt_id, font_id, fill_id, border_id, apply_number_format, apply_border, apply_alignment, apply_protection, apply_font, apply_fill, }); } let mut cell_styles = Vec::new(); let mut style_names = HashMap::new(); let cell_style_nodes = style_sheet .children() .filter(|n| n.has_tag_name("cellStyles")) .collect::>()[0]; for cell_style in cell_style_nodes.children() { let name = get_attribute(&cell_style, "name")?.to_string(); let xf_id = get_number(cell_style, "xfId"); let builtin_id = get_number(cell_style, "builtinId"); style_names.insert(xf_id, name.clone()); cell_styles.push(CellStyles { name, xf_id, builtin_id, }) } let mut cell_xfs = Vec::new(); let cell_xfs_nodes = style_sheet .children() .filter(|n| n.has_tag_name("cellXfs")) .collect::>()[0]; for xfs in cell_xfs_nodes.children() { let xf_id = get_attribute(&xfs, "xfId")?.parse::()?; let num_fmt_id = get_number(xfs, "numFmtId"); let font_id = get_number(xfs, "fontId"); let fill_id = get_number(xfs, "fillId"); let border_id = get_number(xfs, "borderId"); let apply_number_format = get_bool_false(xfs, "applyNumberFormat"); let apply_border = get_bool_false(xfs, "applyBorder"); let apply_alignment = get_bool_false(xfs, "applyAlignment"); let apply_protection = get_bool_false(xfs, "applyProtection"); let apply_font = get_bool_false(xfs, "applyFont"); let apply_fill = get_bool_false(xfs, "applyFill"); let quote_prefix = get_bool_false(xfs, "quotePrefix"); // TODO: Pivot Tables // let pivotButton = get_bool(xfs, "pivotButton"); let alignment_nodes = xfs .children() .filter(|n| n.has_tag_name("alignment")) .collect::>(); let alignment = if alignment_nodes.len() == 1 { let alignment_node = alignment_nodes[0]; let wrap_text = get_bool_false(alignment_node, "wrapText"); let horizontal = match alignment_node.attribute("horizontal") { Some("center") => HorizontalAlignment::Center, Some("centerContinuous") => HorizontalAlignment::CenterContinuous, Some("distributed") => HorizontalAlignment::Distributed, Some("fill") => HorizontalAlignment::Fill, Some("general") => HorizontalAlignment::General, Some("justify") => HorizontalAlignment::Justify, Some("left") => HorizontalAlignment::Left, Some("right") => HorizontalAlignment::Right, // TODO: Should we fail in this case or set the alignment to default? Some(_) => HorizontalAlignment::default(), None => HorizontalAlignment::default(), }; let vertical = match alignment_node.attribute("vertical") { Some("bottom") => VerticalAlignment::Bottom, Some("center") => VerticalAlignment::Center, Some("distributed") => VerticalAlignment::Distributed, Some("justify") => VerticalAlignment::Justify, Some("top") => VerticalAlignment::Top, // TODO: Should we fail in this case or set the alignment to default? Some(_) => VerticalAlignment::default(), None => VerticalAlignment::default(), }; Some(Alignment { horizontal, vertical, wrap_text, }) } else { None }; cell_xfs.push(CellXfs { xf_id, num_fmt_id, font_id, fill_id, border_id, apply_number_format, apply_border, apply_alignment, apply_protection, apply_font, apply_fill, quote_prefix, alignment, }); } // TODO // let mut dxfs = Vec::new(); // let mut tableStyles = Vec::new(); // let mut colors = Vec::new(); // // // // // // // // Ok(Styles { num_fmts, fonts, fills, borders, cell_style_xfs, cell_xfs, cell_styles, }) }