use std::collections::HashMap;
use crate::sheet::Result;
use crate::ooxml::xlsx::styles::{Styles, NumberFormat, Font, Fill, Border, CellStyle};
pub fn parse_styles_xml(content: &str) -> Result<Styles> {
let mut styles = Styles::default();
if let Some(num_fmts_start) = content.find("<numFmts")
&& let Some(num_fmts_end) = content[num_fmts_start..].find("</numFmts>") {
let num_fmts_content = &content[num_fmts_start..num_fmts_start + num_fmts_end];
parse_number_formats(num_fmts_content, &mut styles.number_formats)?;
}
if let Some(fonts_start) = content.find("<fonts")
&& let Some(fonts_end) = content[fonts_start..].find("</fonts>") {
let fonts_content = &content[fonts_start..fonts_start + fonts_end];
styles.fonts = parse_fonts(fonts_content)?;
}
if let Some(fills_start) = content.find("<fills")
&& let Some(fills_end) = content[fills_start..].find("</fills>") {
let fills_content = &content[fills_start..fills_start + fills_end];
styles.fills = parse_fills(fills_content)?;
}
if let Some(borders_start) = content.find("<borders")
&& let Some(borders_end) = content[borders_start..].find("</borders>") {
let borders_content = &content[borders_start..borders_start + borders_end];
styles.borders = parse_borders(borders_content)?;
}
if let Some(cell_style_xfs_start) = content.find("<cellStyleXfs")
&& let Some(cell_style_xfs_end) = content[cell_style_xfs_start..].find("</cellStyleXfs>") {
let cell_style_xfs_content = &content[cell_style_xfs_start..cell_style_xfs_start + cell_style_xfs_end];
styles.cell_styles = parse_cell_styles(cell_style_xfs_content)?;
}
if let Some(cell_xfs_start) = content.find("<cellXfs")
&& let Some(cell_xfs_end) = content[cell_xfs_start..].find("</cellXfs>") {
let cell_xfs_content = &content[cell_xfs_start..cell_xfs_start + cell_xfs_end];
styles.cell_xfs = parse_cell_xfs(cell_xfs_content)?;
}
Ok(styles)
}
fn parse_number_formats(content: &str, number_formats: &mut HashMap<u32, NumberFormat>) -> Result<()> {
let mut pos = 0;
while let Some(num_fmt_start) = content[pos..].find("<numFmt ") {
let num_fmt_start_pos = pos + num_fmt_start;
if let Some(num_fmt_end) = content[num_fmt_start_pos..].find("/>") {
let num_fmt_xml = &content[num_fmt_start_pos..num_fmt_start_pos + num_fmt_end + 2];
if let Some(fmt) = parse_number_format(num_fmt_xml)? {
number_formats.insert(fmt.id, fmt);
}
pos = num_fmt_start_pos + num_fmt_end + 2;
} else {
break;
}
}
Ok(())
}
fn parse_number_format(num_fmt_xml: &str) -> Result<Option<NumberFormat>> {
let id = if let Some(id_start) = num_fmt_xml.find("numFmtId=\"") {
let id_content = &num_fmt_xml[id_start + 10..];
if let Some(quote_pos) = id_content.find('"') {
id_content[..quote_pos].parse::<u32>().ok()
} else {
None
}
} else {
None
};
let code = if let Some(code_start) = num_fmt_xml.find("formatCode=\"") {
let code_content = &num_fmt_xml[code_start + 12..];
code_content.find('"').map(|quote_pos| code_content[..quote_pos].to_string())
} else {
None
};
match (id, code) {
(Some(id), Some(code)) => Ok(Some(NumberFormat { id, code })),
_ => Ok(None),
}
}
fn parse_fonts(content: &str) -> Result<Vec<Font>> {
let mut fonts = Vec::new();
let mut pos = 0;
while let Some(font_start) = content[pos..].find("<font>") {
let font_start_pos = pos + font_start;
if let Some(font_end) = content[font_start_pos..].find("</font>") {
let font_xml = &content[font_start_pos..font_start_pos + font_end + 7];
fonts.push(parse_font(font_xml));
pos = font_start_pos + font_end + 7;
} else {
break;
}
}
Ok(fonts)
}
fn parse_font(font_xml: &str) -> Font {
let mut font = Font::default();
if let Some(name_start) = font_xml.find("<name val=\"") {
let name_content = &font_xml[name_start + 11..];
if let Some(quote_pos) = name_content.find('"') {
font.name = Some(name_content[..quote_pos].to_string());
}
}
if let Some(sz_start) = font_xml.find("<sz val=\"") {
let sz_content = &font_xml[sz_start + 9..];
if let Some(quote_pos) = sz_content.find('"')
&& let Ok(size) = sz_content[..quote_pos].parse::<f64>() {
font.size = Some(size);
}
}
font.bold = font_xml.contains("<b/>") || font_xml.contains("<b>");
font.italic = font_xml.contains("<i/>") || font_xml.contains("<i>");
font
}
fn parse_fills(content: &str) -> Result<Vec<Fill>> {
let mut fills = Vec::new();
let mut pos = 0;
while let Some(fill_start) = content[pos..].find("<fill>") {
let fill_start_pos = pos + fill_start;
if let Some(fill_end) = content[fill_start_pos..].find("</fill>") {
let fill_xml = &content[fill_start_pos..fill_start_pos + fill_end + 7];
fills.push(parse_fill(fill_xml));
pos = fill_start_pos + fill_end + 7;
} else {
break;
}
}
Ok(fills)
}
fn parse_fill(fill_xml: &str) -> Fill {
if let Some(pattern_start) = fill_xml.find("<patternFill ") {
let pattern_content = &fill_xml[pattern_start..];
let pattern_type = if let Some(pt_start) = pattern_content.find("patternType=\"") {
let pt_content = &pattern_content[pt_start + 13..];
if let Some(quote_pos) = pt_content.find('"') {
pt_content[..quote_pos].to_string()
} else {
"solid".to_string()
}
} else {
"solid".to_string()
};
let fg_color = parse_color(pattern_content, "fgColor");
let bg_color = parse_color(pattern_content, "bgColor");
Fill::Pattern {
pattern_type,
fg_color,
bg_color,
}
} else {
Fill::Pattern {
pattern_type: "solid".to_string(),
fg_color: None,
bg_color: None,
}
}
}
fn parse_borders(content: &str) -> Result<Vec<Border>> {
let mut borders = Vec::new();
let mut pos = 0;
while let Some(border_start) = content[pos..].find("<border>") {
let border_start_pos = pos + border_start;
if let Some(border_end) = content[border_start_pos..].find("</border>") {
let border_xml = &content[border_start_pos..border_start_pos + border_end + 9];
borders.push(parse_border(border_xml));
pos = border_start_pos + border_end + 9;
} else {
break;
}
}
Ok(borders)
}
fn parse_border(border_xml: &str) -> Border {
Border {
left: parse_border_style(border_xml, "left"),
right: parse_border_style(border_xml, "right"),
top: parse_border_style(border_xml, "top"),
bottom: parse_border_style(border_xml, "bottom"),
}
}
fn parse_cell_styles(content: &str) -> Result<Vec<CellStyle>> {
let mut styles = Vec::new();
let mut pos = 0;
while let Some(xf_start) = content[pos..].find("<xf ") {
let xf_start_pos = pos + xf_start;
if let Some(xf_end) = content[xf_start_pos..].find("/>") {
let xf_xml = &content[xf_start_pos..xf_start_pos + xf_end + 2];
if let Some(style) = parse_cell_style(xf_xml)? {
styles.push(style);
}
pos = xf_start_pos + xf_end + 2;
} else {
break;
}
}
Ok(styles)
}
fn parse_cell_xfs(content: &str) -> Result<Vec<CellStyle>> {
let mut xfs = Vec::new();
let mut pos = 0;
while let Some(xf_start) = content[pos..].find("<xf ") {
let xf_start_pos = pos + xf_start;
if let Some(xf_end) = content[xf_start_pos..].find("/>") {
let xf_xml = &content[xf_start_pos..xf_start_pos + xf_end + 2];
if let Some(xf) = parse_cell_style(xf_xml)? {
xfs.push(xf);
}
pos = xf_start_pos + xf_end + 2;
} else {
break;
}
}
Ok(xfs)
}
fn parse_cell_style(xf_xml: &str) -> Result<Option<CellStyle>> {
let num_fmt_id = if let Some(nf_start) = xf_xml.find("numFmtId=\"") {
let nf_content = &xf_xml[nf_start + 10..];
if let Some(quote_pos) = nf_content.find('"') {
nf_content[..quote_pos].parse::<u32>().ok()
} else {
None
}
} else {
None
};
let font_id = if let Some(f_start) = xf_xml.find("fontId=\"") {
let f_content = &xf_xml[f_start + 8..];
if let Some(quote_pos) = f_content.find('"') {
f_content[..quote_pos].parse::<u32>().ok()
} else {
None
}
} else {
None
};
let fill_id = if let Some(fill_start) = xf_xml.find("fillId=\"") {
let fill_content = &xf_xml[fill_start + 8..];
if let Some(quote_pos) = fill_content.find('"') {
fill_content[..quote_pos].parse::<u32>().ok()
} else {
None
}
} else {
None
};
let border_id = if let Some(b_start) = xf_xml.find("borderId=\"") {
let b_content = &xf_xml[b_start + 10..];
if let Some(quote_pos) = b_content.find('"') {
b_content[..quote_pos].parse::<u32>().ok()
} else {
None
}
} else {
None
};
Ok(Some(CellStyle {
num_fmt_id,
font_id,
fill_id,
border_id,
alignment: None, }))
}
fn parse_border_style(border_xml: &str, side: &str) -> Option<crate::ooxml::xlsx::styles::BorderStyle> {
let side_tag = format!("<{}>", side);
if let Some(side_start) = border_xml.find(&side_tag) {
let side_content = &border_xml[side_start..];
let style = if let Some(style_start) = side_content.find("style=\"") {
let style_content = &side_content[style_start + 7..];
style_content.find('"').map(|quote_pos| style_content[..quote_pos].to_string())
} else {
None
};
style.map(|s| crate::ooxml::xlsx::styles::BorderStyle {
style: s,
color: parse_color(side_content, "color"),
})
} else {
None
}
}
fn parse_color(content: &str, color_type: &str) -> Option<String> {
let color_tag = format!("<{} ", color_type);
if let Some(color_start) = content.find(&color_tag) {
let color_content = &content[color_start..];
if let Some(rgb_start) = color_content.find("rgb=\"") {
let rgb_content = &color_content[rgb_start + 5..];
if let Some(quote_pos) = rgb_content.find('"') {
return Some(rgb_content[..quote_pos].to_string());
}
}
if let Some(theme_start) = color_content.find("theme=\"") {
let theme_content = &color_content[theme_start + 7..];
if let Some(quote_pos) = theme_content.find('"') {
return Some(format!("theme:{}", &theme_content[..quote_pos]));
}
}
}
None
}