use crate::cell::{Cell, Value};
use crate::error::{Error, Result};
use crate::reference::{CellRef, RangeRef};
use crate::shared_strings::SharedStrings;
use crate::style::{
Alignment, Border, BorderStyle, DiagonalBorder, Fill, Font, HorizontalAlignment,
NumberFormat, PatternFill, PatternType, Side, Style, StyleId, VerticalAlignment,
};
use crate::workbook::Workbook;
use crate::worksheet::Worksheet;
use quick_xml::events::{BytesStart, Event};
use quick_xml::Reader;
use std::collections::HashMap;
use std::io::Read;
use std::path::Path;
pub fn read_workbook<P: AsRef<Path>>(path: P) -> Result<Workbook> {
let file = std::fs::File::open(path)?;
let mut archive = zip::ZipArchive::new(file)?;
let mut wb = Workbook::empty();
let shared_strings = if archive.file_names().any(|n| n == "xl/sharedStrings.xml") {
let mut file = archive.by_name("xl/sharedStrings.xml")?;
let mut xml = String::new();
file.read_to_string(&mut xml)?;
parse_shared_strings(&xml)?
} else {
SharedStrings::new()
};
let rels = {
let mut file = archive.by_name("xl/_rels/workbook.xml.rels")?;
let mut xml = String::new();
file.read_to_string(&mut xml)?;
parse_relationships(&xml)?
};
let sheet_infos = {
let mut file = archive.by_name("xl/workbook.xml")?;
let mut xml = String::new();
file.read_to_string(&mut xml)?;
parse_workbook_sheets(&xml)?
};
let styles = {
let mut file = archive.by_name("xl/styles.xml")?;
let mut xml = String::new();
file.read_to_string(&mut xml)?;
parse_styles(&xml)?
};
wb.style_manager = styles.manager;
if let Ok(mut file) = archive.by_name("docProps/core.xml") {
let mut xml = String::new();
file.read_to_string(&mut xml)?;
if let Some(creator) = parse_simple_text(&xml, "dc:creator") {
wb.set_creator(creator);
}
if let Some(title) = parse_simple_text(&xml, "dc:title") {
wb.set_title(title);
}
}
for (name, rid) in sheet_infos {
let target = rels.get(&rid).ok_or_else(|| {
Error::Message(format!("relationship {rid} for sheet {name} not found"))
})?;
let path = normalize_sheet_path(target);
let mut file = archive.by_name(&path)?;
let mut xml = String::new();
file.read_to_string(&mut xml)?;
let mut ws = Worksheet::new(&name, wb.next_sheet_id);
wb.next_sheet_id += 1;
parse_worksheet(&xml, &shared_strings, &styles.xfs, &mut ws)?;
wb.sheets.push(ws);
}
if wb.sheets.is_empty() {
wb.create_sheet("Sheet")?;
}
wb.active_sheet = 0;
Ok(wb)
}
fn normalize_sheet_path(target: &str) -> String {
if target.starts_with("worksheets/") {
format!("xl/{target}")
} else if target.starts_with("/xl/") {
target[1..].to_string()
} else if target.starts_with("xl/") {
target.to_string()
} else {
format!("xl/worksheets/{target}")
}
}
fn parse_relationships(xml: &str) -> Result<HashMap<String, String>> {
let mut rels = HashMap::new();
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(e)) | Ok(Event::Start(e)) if e.local_name().as_ref() == b"Relationship" => {
let mut id = String::new();
let mut target = String::new();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"Id" => id = value,
"Target" => target = value,
_ => {}
}
}
if !id.is_empty() {
rels.insert(id, target);
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(rels)
}
fn parse_workbook_sheets(xml: &str) -> Result<Vec<(String, String)>> {
let mut sheets = Vec::new();
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(e)) | Ok(Event::Start(e)) if e.local_name().as_ref() == b"sheet" => {
let mut name = String::new();
let mut rid = String::new();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"name" => name = value,
"id" | "r:id" => rid = value,
_ => {}
}
}
if !name.is_empty() && !rid.is_empty() {
sheets.push((name, rid));
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(sheets)
}
fn parse_shared_strings(xml: &str) -> Result<SharedStrings> {
let mut strings = SharedStrings::new();
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut in_si = false;
let mut in_t = false;
let mut text = String::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let name = e.local_name().as_ref().to_vec();
if name == b"si" {
in_si = true;
text.clear();
} else if name == b"t" && in_si {
in_t = true;
}
}
Ok(Event::Text(e)) => {
if in_t {
text.push_str(&e.unescape()?);
}
}
Ok(Event::End(e)) => {
let name = e.local_name().as_ref().to_vec();
if name == b"si" {
strings.add(text.clone());
in_si = false;
text.clear();
} else if name == b"t" {
in_t = false;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(strings)
}
#[derive(Debug, Default)]
struct ParsedStyles {
manager: crate::style::StyleManager,
xfs: Vec<StyleId>,
}
fn parse_styles(xml: &str) -> Result<ParsedStyles> {
let mut fonts: Vec<Font> = Vec::new();
let mut fills: Vec<Fill> = Vec::new();
let mut borders: Vec<Border> = Vec::new();
let mut num_fmts: HashMap<u32, String> = HashMap::new();
let mut xfs: Vec<(CellXfTemp, Option<Alignment>)> = Vec::new();
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut current_font: Option<Font> = None;
let mut current_fill: Option<Fill> = None;
let mut current_border: Option<Border> = None;
let mut current_xf: Option<CellXfTemp> = None;
let mut current_alignment: Option<Alignment> = None;
let mut stack: Vec<String> = Vec::new();
let mut seen_fills: usize = 0;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
stack.push(name.clone());
match name.as_str() {
"font" => current_font = Some(Font::default()),
"fill" => current_fill = Some(Fill::default()),
"border" => current_border = Some(Border::default()),
"xf" => {
let mut xf = CellXfTemp::default();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"fontId" => xf.font_id = value.parse().ok(),
"fillId" => xf.fill_id = value.parse().ok(),
"borderId" => xf.border_id = value.parse().ok(),
"numFmtId" => xf.num_fmt_id = value.parse().ok(),
_ => {}
}
}
current_xf = Some(xf);
current_alignment = None;
}
"alignment" => {
current_alignment = Some(parse_alignment(&e));
}
"patternFill" => {
if let Some(fill) = current_fill.as_mut() {
if let Some(pt) = parse_pattern_fill_type(&e) {
*fill = Fill::Pattern(PatternFill::new(pt));
}
}
}
"numFmt" => {
let mut id = None;
let mut code = String::new();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"numFmtId" => id = value.parse().ok(),
"formatCode" => code = value,
_ => {}
}
}
if let Some(id) = id {
num_fmts.insert(id, code);
}
}
_ => {}
}
}
Ok(Event::Empty(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
match name.as_str() {
"b" => {
if let Some(font) = current_font.as_mut() {
*font = font.clone().bold(true);
}
}
"i" => {
if let Some(font) = current_font.as_mut() {
*font = font.clone().italic(true);
}
}
"strike" => {
if let Some(font) = current_font.as_mut() {
*font = font.clone().strike(true);
}
}
"u" => {
if let Some(font) = current_font.as_mut() {
let style = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"val")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned())
.and_then(|s| parse_underline(&s));
if let Some(u) = style {
*font = font.clone().underline(u);
} else {
*font = font.clone().underline(crate::style::UnderlineStyle::Single);
}
}
}
"sz" => {
if let Some(font) = current_font.as_mut() {
if let Some(size) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"val")
.and_then(|a| a.unescape_value().ok())
.and_then(|s| s.parse().ok())
{
*font = font.clone().size(size);
}
}
}
"color" => {
let color = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"rgb")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned());
if let Some(color) = color {
if stack.last().map(|s| s.as_str()) == Some("font") {
if let Some(font) = current_font.as_mut() {
*font = font.clone().color(color);
}
}
}
}
"fgColor" => {
let color = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"rgb")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned());
if let Some(color) = color {
if let Some(fill) = current_fill.as_mut() {
*fill = match fill {
Fill::Pattern(p) => Fill::Pattern(p.clone().fg_color(color)),
Fill::None => Fill::Pattern(PatternFill::solid(color)),
};
}
}
}
"bgColor" => {
let color = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"rgb")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned());
if let Some(color) = color {
if let Some(fill) = current_fill.as_mut() {
*fill = match fill {
Fill::Pattern(p) => Fill::Pattern(p.clone().bg_color(color)),
Fill::None => Fill::Pattern(PatternFill::new(crate::style::PatternType::Solid).bg_color(color)),
};
}
}
}
"name" => {
if let Some(font) = current_font.as_mut() {
if let Some(name) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"val")
.and_then(|a| a.unescape_value().ok())
{
*font = font.clone().name(name.into_owned());
}
}
}
"family" => {
if let Some(font) = current_font.as_mut() {
if let Some(family) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"val")
.and_then(|a| a.unescape_value().ok())
.and_then(|s| s.parse().ok())
{
*font = font.clone().family(family);
}
}
}
"patternFill" => {
if let Some(fill) = current_fill.as_mut() {
if let Some(pt) = parse_pattern_fill_type(&e) {
*fill = Fill::Pattern(PatternFill::new(pt));
}
}
}
"alignment" => {
current_alignment = Some(parse_alignment(&e));
}
"left" | "right" | "top" | "bottom" => {
if let Some(border) = current_border.as_mut() {
let side = parse_side(&e)?;
match name.as_str() {
"left" => border.left = side,
"right" => border.right = side,
"top" => border.top = side,
"bottom" => border.bottom = side,
_ => {}
};
}
}
"diagonal" => {
if let Some(border) = current_border.as_mut() {
let side = parse_side(&e)?;
let up = e
.attributes()
.flatten()
.any(|a| a.key.local_name().as_ref() == b"diagonalUp");
let down = e
.attributes()
.flatten()
.any(|a| a.key.local_name().as_ref() == b"diagonalDown");
if let Some(side) = side {
*border = border.clone().diagonal(DiagonalBorder {
style: side.style,
color: side.color,
up,
down,
});
}
}
}
"xf" => {
let mut xf = CellXfTemp::default();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"fontId" => xf.font_id = value.parse().ok(),
"fillId" => xf.fill_id = value.parse().ok(),
"borderId" => xf.border_id = value.parse().ok(),
"numFmtId" => xf.num_fmt_id = value.parse().ok(),
_ => {}
}
}
if stack.last().map(|s| s.as_str()) == Some("cellXfs") {
xfs.push((xf, current_alignment.clone()));
}
}
_ => {}
}
}
Ok(Event::End(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
if name == "font" {
if let Some(font) = current_font.take() {
fonts.push(font);
}
} else if name == "fill" {
seen_fills += 1;
if let Some(fill) = current_fill.take() {
if seen_fills > 2 {
fills.push(fill);
}
}
} else if name == "border" {
if let Some(border) = current_border.take() {
borders.push(border);
}
} else if name == "xf" {
let in_cell_xfs =
stack.iter().rev().nth(1).map(|s| s.as_str()) == Some("cellXfs");
if in_cell_xfs {
if let Some(xf) = current_xf.take() {
xfs.push((xf, current_alignment.take()));
}
}
}
stack.pop();
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
let mut manager = crate::style::StyleManager::default();
let mut style_ids = Vec::with_capacity(xfs.len());
for (xf, alignment) in xfs {
let mut style = Style::default();
if let Some(fid) = xf.font_id {
if let Some(font) = fonts.get(fid) {
style = style.font(font.clone());
}
}
if let Some(fid) = xf.fill_id {
match fid {
0 => {}
1 => {}
_ => {
if let Some(fill) = fills.get(fid.saturating_sub(2)) {
style = style.fill(fill.clone());
}
}
}
}
if let Some(bid) = xf.border_id {
if let Some(border) = borders.get(bid) {
style = style.border(border.clone());
}
}
if let Some(nid) = xf.num_fmt_id {
if nid >= 164 {
if let Some(code) = num_fmts.get(&nid) {
style = style.number_format(NumberFormat::new(code));
}
} else {
let code = built_in_number_format(nid);
if !code.is_empty() {
style = style.number_format(NumberFormat::new(code));
}
}
}
if let Some(alignment) = alignment {
style = style.alignment(alignment);
}
style_ids.push(manager.register(style));
}
Ok(ParsedStyles { manager, xfs: style_ids })
}
#[derive(Debug, Default)]
struct CellXfTemp {
font_id: Option<usize>,
fill_id: Option<usize>,
border_id: Option<usize>,
num_fmt_id: Option<u32>,
}
fn built_in_number_format(id: u32) -> &'static str {
match id {
0 => "General",
1 => "0",
2 => "0.00",
3 => "#,##0",
4 => "#,##0.00",
9 => "0%",
10 => "0.00%",
11 => "0.00E+00",
12 => "# ?/?",
13 => "# ??/??",
14 => "mm-dd-yy",
15 => "d-mmm-yy",
16 => "d-mmm",
17 => "mmm-yy",
18 => "h:mm AM/PM",
19 => "h:mm:ss AM/PM",
20 => "h:mm",
21 => "h:mm:ss",
22 => "m/d/yy h:mm",
_ => "",
}
}
fn parse_side(e: &BytesStart<'_>) -> Result<Option<Side>> {
let style = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"style")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned())
.and_then(|s| parse_border_style(&s));
let color = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"rgb")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned());
Ok(style.map(|style| Side { style, color }))
}
fn parse_alignment(e: &BytesStart<'_>) -> Alignment {
let mut alignment = Alignment::default();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value().unwrap_or_default().into_owned();
match key.as_ref() {
"horizontal" => alignment.horizontal = parse_horizontal(&value),
"vertical" => alignment.vertical = parse_vertical(&value),
"wrapText" => alignment.wrap_text = value == "1" || value == "true",
"shrinkToFit" => alignment.shrink_to_fit = value == "1" || value == "true",
"textRotation" => alignment.text_rotation = value.parse().ok(),
_ => {}
}
}
alignment
}
fn parse_pattern_fill_type(e: &BytesStart<'_>) -> Option<PatternType> {
e.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"patternType")
.and_then(|a| a.unescape_value().ok())
.map(|s| s.into_owned())
.and_then(|s| parse_pattern_type(&s))
}
fn parse_horizontal(s: &str) -> Option<HorizontalAlignment> {
match s {
"left" => Some(HorizontalAlignment::Left),
"center" => Some(HorizontalAlignment::Center),
"right" => Some(HorizontalAlignment::Right),
"fill" => Some(HorizontalAlignment::Fill),
"justify" => Some(HorizontalAlignment::Justify),
"centerContinuous" => Some(HorizontalAlignment::CenterContinuous),
"distributed" => Some(HorizontalAlignment::Distributed),
_ => None,
}
}
fn parse_vertical(s: &str) -> Option<VerticalAlignment> {
match s {
"top" => Some(VerticalAlignment::Top),
"center" => Some(VerticalAlignment::Center),
"bottom" => Some(VerticalAlignment::Bottom),
"justify" => Some(VerticalAlignment::Justify),
"distributed" => Some(VerticalAlignment::Distributed),
_ => None,
}
}
fn parse_underline(s: &str) -> Option<crate::style::UnderlineStyle> {
use crate::style::UnderlineStyle;
match s {
"single" => Some(UnderlineStyle::Single),
"double" => Some(UnderlineStyle::Double),
"singleAccounting" => Some(UnderlineStyle::SingleAccounting),
"doubleAccounting" => Some(UnderlineStyle::DoubleAccounting),
_ => None,
}
}
fn parse_pattern_type(s: &str) -> Option<PatternType> {
match s {
"none" => Some(PatternType::None),
"solid" => Some(PatternType::Solid),
"darkGray" => Some(PatternType::DarkGray),
"mediumGray" => Some(PatternType::MediumGray),
"lightGray" => Some(PatternType::LightGray),
"gray125" => Some(PatternType::Gray125),
"gray0625" => Some(PatternType::Gray0625),
_ => None,
}
}
fn parse_border_style(s: &str) -> Option<BorderStyle> {
match s {
"thin" => Some(BorderStyle::Thin),
"medium" => Some(BorderStyle::Medium),
"thick" => Some(BorderStyle::Thick),
"dashed" => Some(BorderStyle::Dashed),
"dotted" => Some(BorderStyle::Dotted),
"double" => Some(BorderStyle::Double),
"none" => Some(BorderStyle::None),
_ => None,
}
}
fn parse_simple_text(xml: &str, tag: &str) -> Option<String> {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut in_target = false;
let mut text = String::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
if String::from_utf8_lossy(e.local_name().as_ref()) == tag {
in_target = true;
text.clear();
}
}
Ok(Event::Text(e)) => {
if in_target {
text.push_str(&e.unescape().ok()?);
}
}
Ok(Event::End(e)) => {
if String::from_utf8_lossy(e.local_name().as_ref()) == tag {
return Some(text.clone());
}
}
Ok(Event::Eof) => break,
_ => {}
}
buf.clear();
}
None
}
fn parse_worksheet(
xml: &str,
shared_strings: &SharedStrings,
_style_xfs: &[StyleId],
ws: &mut Worksheet,
) -> Result<()> {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut current_cell_ref: Option<CellRef> = None;
let mut current_cell_type: String = String::new();
let mut current_cell_style: Option<usize> = None;
let mut in_f = false;
let mut in_v = false;
let mut current_formula = String::new();
let mut current_value = String::new();
let mut stack: Vec<String> = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
stack.push(name.clone());
match name.as_str() {
"row" => {
let mut ht = None;
let mut hidden = false;
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"ht" => ht = value.parse().ok(),
"hidden" => hidden = value == "1" || value == "true",
_ => {}
}
}
if let Some(ht) = ht {
if let Some(row_idx) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"r")
.and_then(|a| a.unescape_value().ok())
.and_then(|s| s.parse().ok())
{
ws.row_heights.insert(row_idx, ht);
}
}
if hidden {
if let Some(row_idx) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"r")
.and_then(|a| a.unescape_value().ok())
.and_then(|s| s.parse().ok())
{
ws.hidden_rows.insert(row_idx);
}
}
}
"c" => {
current_cell_type.clear();
current_cell_style = None;
current_formula.clear();
current_value.clear();
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"r" => {
current_cell_ref = CellRef::parse(&value).ok();
}
"t" => current_cell_type = value,
"s" => current_cell_style = value.parse().ok(),
_ => {}
}
}
}
"f" => in_f = true,
"v" => in_v = true,
"mergeCell" => parse_merge_cell(&e, ws)?,
"col" => parse_col(&e, ws)?,
_ => {}
}
}
Ok(Event::Text(e)) => {
if in_f {
current_formula.push_str(&e.unescape()?);
} else if in_v {
current_value.push_str(&e.unescape()?);
} else if stack.last().map(|s| s.as_str()) == Some("t") {
current_value.push_str(&e.unescape()?);
}
}
Ok(Event::End(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
if name == "f" {
in_f = false;
} else if name == "v" {
in_v = false;
} else if name == "c" {
if let Some(cell_ref) = current_cell_ref {
let value = build_value(
¤t_cell_type,
¤t_value,
¤t_formula,
shared_strings,
);
let mut cell = Cell::new(value);
if let Some(s) = current_cell_style {
cell.set_style_id(StyleId(s));
}
ws.cells.insert((cell_ref.row, cell_ref.col), cell);
}
current_cell_ref = None;
}
stack.pop();
}
Ok(Event::Empty(e)) => {
let name = String::from_utf8_lossy(e.local_name().as_ref()).to_string();
match name.as_str() {
"mergeCell" => parse_merge_cell(&e, ws)?,
"col" => parse_col(&e, ws)?,
_ => {}
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(())
}
fn parse_merge_cell(e: &BytesStart<'_>, ws: &mut Worksheet) -> Result<()> {
if let Some(attr) = e
.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == b"ref")
{
let value = attr.unescape_value()?.into_owned();
if let Ok(range) = RangeRef::parse(&value) {
ws.merged_cells.push(range);
}
}
Ok(())
}
fn parse_col(e: &BytesStart<'_>, ws: &mut Worksheet) -> Result<()> {
let mut min = None;
let mut max = None;
let mut width = None;
let mut hidden = false;
for attr in e.attributes().flatten() {
let key = String::from_utf8_lossy(attr.key.local_name().as_ref()).into_owned();
let value = attr.unescape_value()?.into_owned();
match key.as_ref() {
"min" => min = value.parse().ok(),
"max" => max = value.parse().ok(),
"width" => width = value.parse().ok(),
"hidden" => hidden = value == "1" || value == "true",
_ => {}
}
}
if let (Some(min), Some(max), Some(width)) = (min, max, width) {
for col in min..=max {
ws.col_widths.insert(col, width);
}
}
if hidden {
if let (Some(min), Some(max)) = (min, max) {
for col in min..=max {
ws.hidden_cols.insert(col);
}
}
}
Ok(())
}
fn build_value(
cell_type: &str,
value: &str,
formula: &str,
shared_strings: &SharedStrings,
) -> Value {
if !formula.is_empty() {
return Value::Formula(format!("={formula}"));
}
match cell_type {
"s" => {
if let Ok(idx) = value.parse::<usize>() {
if let Some(s) = shared_strings.get(idx) {
return Value::String(s.to_string());
}
}
Value::String(value.to_string())
}
"b" => Value::Bool(value == "1" || value.eq_ignore_ascii_case("true")),
"e" => Value::Error(value.to_string()),
"str" | "inlineStr" => Value::String(value.to_string()),
_ => {
if let Ok(n) = value.parse::<f64>() {
Value::Number(n)
} else {
Value::String(value.to_string())
}
}
}
}