use quick_xml::events::{BytesStart, Event};
use std::io::{Read, Seek};
use super::{
expand_shared_formula, get_dimension, get_row, get_row_column, read_string_with_bufs,
Dimensions, XlReader,
};
use crate::attrs::RawAttributes;
use crate::{
datatype::DataRef,
formats::{format_excel_f64_ref, CellFormat},
utils::unescape_entity_to_buffer,
Cell, XlsxError,
};
#[derive(Clone, Debug)]
struct SharedFormula {
formula: String,
range: Dimensions,
}
struct WorkbookContext<'a> {
strings: &'a [String],
formats: &'a [CellFormat],
is_1904: bool,
}
struct ValueBufs {
xml: Vec<u8>,
value: String,
str_inner: Vec<u8>,
}
impl ValueBufs {
fn new() -> Self {
Self {
xml: Vec::with_capacity(1024),
value: String::with_capacity(64),
str_inner: Vec::with_capacity(1024),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum XlsxFormulaMetadata {
Normal {
formula: String,
},
Shared {
shared_index: usize,
range: Option<Dimensions>,
formula: String,
},
SharedDerived {
shared_index: usize,
},
}
impl XlsxFormulaMetadata {
pub fn shared_index(&self) -> Option<usize> {
match self {
Self::Normal { .. } => None,
Self::Shared { shared_index, .. } | Self::SharedDerived { shared_index } => {
Some(*shared_index)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum FormulaMetadata {
Normal {
formula: String,
},
Shared {
shared_index: usize,
range: Option<Dimensions>,
formula: String,
},
SharedDerived {
shared_index: usize,
translated_formula: Option<String>,
},
}
impl FormulaMetadata {
fn formula_text(&self) -> Option<&str> {
match self {
Self::Normal { formula } | Self::Shared { formula, .. } => Some(formula),
Self::SharedDerived {
translated_formula, ..
} => translated_formula.as_deref(),
}
}
fn into_metadata(self) -> XlsxFormulaMetadata {
match self {
Self::Normal { formula } => XlsxFormulaMetadata::Normal { formula },
Self::Shared {
shared_index,
range,
formula,
} => XlsxFormulaMetadata::Shared {
shared_index,
range,
formula,
},
Self::SharedDerived { shared_index, .. } => {
XlsxFormulaMetadata::SharedDerived { shared_index }
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct XlsxCellFormula<'a> {
pub pos: (u32, u32),
pub value: DataRef<'a>,
pub formula: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct XlsxCellFormulaMetadataRecord<'a> {
pub pos: (u32, u32),
pub value: DataRef<'a>,
pub formula: Option<XlsxFormulaMetadata>,
}
struct XlsxCellFormulaMetadataRecordInternal<'a> {
pos: (u32, u32),
value: DataRef<'a>,
formula: Option<FormulaMetadata>,
}
pub struct XlsxCellReader<'a, RS>
where
RS: Read + Seek,
{
xml: XlReader<'a, RS>,
strings: &'a [String],
formats: &'a [CellFormat],
is_1904: bool,
dimensions: Dimensions,
row_index: u32,
col_index: u32,
buf: Vec<u8>,
cell_buf: Vec<u8>,
value_bufs: ValueBufs,
formulas: Vec<Option<SharedFormula>>,
}
impl<'a, RS> XlsxCellReader<'a, RS>
where
RS: Read + Seek,
{
pub fn new(
mut xml: XlReader<'a, RS>,
strings: &'a [String],
formats: &'a [CellFormat],
is_1904: bool,
) -> Result<Self, XlsxError> {
let mut buf = Vec::with_capacity(1024);
let mut dimensions = Dimensions::default();
let mut sh_type = None;
'xml: loop {
buf.clear();
match xml.read_event_into(&mut buf).map_err(XlsxError::Xml)? {
Event::Start(e) => match e.local_name().as_ref() {
b"dimension" => {
if let Some(rdim) = e.raw_attr(b"ref")? {
dimensions = get_dimension(rdim)?;
continue 'xml;
}
return Err(XlsxError::UnexpectedNode("dimension"));
}
b"sheetData" => break,
typ => {
if sh_type.is_none() {
sh_type = Some(xml.decoder().decode(typ)?.to_string());
}
}
},
Event::Eof => {
if let Some(typ) = sh_type {
return Err(XlsxError::NotAWorksheet(typ));
} else {
return Err(XlsxError::XmlEof("worksheet"));
}
}
_ => (),
}
}
Ok(Self {
xml,
strings,
formats,
is_1904,
dimensions,
row_index: 0,
col_index: 0,
buf: Vec::with_capacity(1024),
cell_buf: Vec::with_capacity(1024),
value_bufs: ValueBufs::new(),
formulas: Vec::with_capacity(1024),
})
}
pub fn dimensions(&self) -> Dimensions {
self.dimensions
}
pub fn next_cell(&mut self) -> Result<Option<Cell<DataRef<'a>>>, XlsxError> {
loop {
self.buf.clear();
match self.xml.read_event_into(&mut self.buf) {
Ok(Event::Start(row_element)) if row_element.local_name().as_ref() == b"row" => {
if let Some(r) = row_element.raw_attr(b"r")? {
self.row_index = get_row(r)?;
}
}
Ok(Event::End(row_element)) if row_element.local_name().as_ref() == b"row" => {
self.row_index += 1;
self.col_index = 0;
}
Ok(Event::Start(c_element)) if c_element.local_name().as_ref() == b"c" => {
let (pos_attr, style_attr, type_attr) =
get_attrs!(c_element, b"r" => r, b"s" => s, b"t" => t)?;
let pos = if let Some(range) = pos_attr {
let (row, col) = get_row_column(range)?;
self.col_index = col;
(row, col)
} else {
(self.row_index, self.col_index)
};
let mut value = DataRef::Empty;
loop {
self.cell_buf.clear();
match self.xml.read_event_into(&mut self.cell_buf) {
Ok(Event::Start(e)) => {
let ctx = WorkbookContext {
strings: self.strings,
formats: self.formats,
is_1904: self.is_1904,
};
value = read_value(
&ctx,
&mut self.xml,
&e,
style_attr,
type_attr,
&mut self.value_bufs,
)?;
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"c" => break,
Ok(Event::Eof) => return Err(XlsxError::XmlEof("c")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
self.col_index += 1;
return Ok(Some(Cell::new(pos, value)));
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"sheetData" => {
return Ok(None);
}
Ok(Event::Eof) => return Err(XlsxError::XmlEof("sheetData")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
}
fn read_formula_record(
xml: &mut XlReader<'_, RS>,
formulas: &mut Vec<Option<SharedFormula>>,
e: &BytesStart<'_>,
pos: (u32, u32),
expand_shared_derived: bool,
) -> Result<Option<FormulaMetadata>, XlsxError> {
let formula = read_formula(xml, e)?;
let (t_attr, si_attr, ref_attr) = get_attrs!(e, b"t" => t, b"si" => si, b"ref" => ref_)?;
if t_attr == Some(b"shared".as_slice()) {
let shared_index = match si_attr {
Some(res) => match atoi_simd::parse::<usize, true, false>(res) {
Ok(res) => res,
Err(_) => return Err(XlsxError::Unexpected("si attribute must be a number")),
},
None => {
return Err(XlsxError::Unexpected(
"si attribute is mandatory if it is shared",
));
}
};
return match ref_attr {
Some(res) => {
let range = get_dimension(res)?;
let formula = formula.unwrap_or_default();
if expand_shared_derived {
if formulas.len() <= shared_index {
formulas.resize(shared_index + 1, None);
}
formulas[shared_index] = Some(SharedFormula {
formula: formula.clone(),
range,
});
}
Ok(Some(FormulaMetadata::Shared {
shared_index,
range: Some(range),
formula,
}))
}
None => {
let translated_formula = if expand_shared_derived {
formulas
.get(shared_index)
.and_then(|template| template.as_ref())
.map(|template| {
expand_shared_formula(&template.formula, template.range.start, pos)
})
.transpose()?
} else {
None
};
Ok(Some(FormulaMetadata::SharedDerived {
shared_index,
translated_formula,
}))
}
};
}
Ok(formula.map(|formula| FormulaMetadata::Normal { formula }))
}
pub fn next_cell_with_formula(&mut self) -> Result<Option<XlsxCellFormula<'a>>, XlsxError> {
Ok(self
.next_cell_formula_record_impl(true)?
.map(|record| XlsxCellFormula {
pos: record.pos,
value: record.value,
formula: record
.formula
.and_then(|formula| formula.formula_text().map(str::to_string)),
}))
}
pub fn next_cell_with_formula_metadata(
&mut self,
) -> Result<Option<XlsxCellFormulaMetadataRecord<'a>>, XlsxError> {
Ok(self
.next_cell_formula_record_impl(false)?
.map(|record| XlsxCellFormulaMetadataRecord {
pos: record.pos,
value: record.value,
formula: record.formula.map(FormulaMetadata::into_metadata),
}))
}
fn next_cell_formula_record_impl(
&mut self,
expand_shared_derived: bool,
) -> Result<Option<XlsxCellFormulaMetadataRecordInternal<'a>>, XlsxError> {
loop {
self.buf.clear();
match self.xml.read_event_into(&mut self.buf) {
Ok(Event::Start(row_element)) if row_element.local_name().as_ref() == b"row" => {
if let Some(r) = row_element.raw_attr(b"r")? {
self.row_index = get_row(r)?;
}
}
Ok(Event::End(row_element)) if row_element.local_name().as_ref() == b"row" => {
self.row_index += 1;
self.col_index = 0;
}
Ok(Event::Start(c_element)) if c_element.local_name().as_ref() == b"c" => {
let (pos_attr, style_attr, type_attr) =
get_attrs!(c_element, b"r" => r, b"s" => s, b"t" => t)?;
let pos = if let Some(range) = pos_attr {
let (row, col) = get_row_column(range)?;
self.col_index = col;
(row, col)
} else {
(self.row_index, self.col_index)
};
let mut value = DataRef::Empty;
let mut formula = None;
loop {
self.cell_buf.clear();
match self.xml.read_event_into(&mut self.cell_buf) {
Ok(Event::Start(e)) if e.local_name().as_ref() == b"f" => {
formula = Self::read_formula_record(
&mut self.xml,
&mut self.formulas,
&e,
pos,
expand_shared_derived,
)?;
}
Ok(Event::Start(e)) => {
let ctx = WorkbookContext {
strings: self.strings,
formats: self.formats,
is_1904: self.is_1904,
};
value = read_value(
&ctx,
&mut self.xml,
&e,
style_attr,
type_attr,
&mut self.value_bufs,
)?;
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"c" => break,
Ok(Event::Eof) => return Err(XlsxError::XmlEof("c")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
self.col_index += 1;
return Ok(Some(XlsxCellFormulaMetadataRecordInternal {
pos,
value,
formula,
}));
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"sheetData" => {
return Ok(None);
}
Ok(Event::Eof) => return Err(XlsxError::XmlEof("sheetData")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
}
pub fn next_formula(&mut self) -> Result<Option<Cell<String>>, XlsxError> {
loop {
self.buf.clear();
match self.xml.read_event_into(&mut self.buf) {
Ok(Event::Start(row_element)) if row_element.local_name().as_ref() == b"row" => {
if let Some(r) = row_element.raw_attr(b"r")? {
self.row_index = get_row(r)?;
}
}
Ok(Event::End(row_element)) if row_element.local_name().as_ref() == b"row" => {
self.row_index += 1;
self.col_index = 0;
}
Ok(Event::Start(c_element)) if c_element.local_name().as_ref() == b"c" => {
let pos = if let Some(r) = c_element.raw_attr(b"r")? {
let (row, col) = get_row_column(r)?;
self.col_index = col;
(row, col)
} else {
(self.row_index, self.col_index)
};
let mut value = None;
loop {
self.cell_buf.clear();
match self.xml.read_event_into(&mut self.cell_buf) {
Ok(Event::Start(e)) => {
let formula_record = Self::read_formula_record(
&mut self.xml,
&mut self.formulas,
&e,
pos,
true,
)?;
if let Some(formula_text) = formula_record
.and_then(|record| record.formula_text().map(str::to_string))
{
value = Some(formula_text);
}
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"c" => break,
Ok(Event::Eof) => return Err(XlsxError::XmlEof("c")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
self.col_index += 1;
return Ok(Some(Cell::new(pos, value.unwrap_or_default())));
}
Ok(Event::End(e)) if e.local_name().as_ref() == b"sheetData" => {
return Ok(None);
}
Ok(Event::Eof) => return Err(XlsxError::XmlEof("sheetData")),
Err(e) => return Err(XlsxError::Xml(e)),
_ => (),
}
}
}
}
fn read_value<'s, RS>(
ctx: &WorkbookContext<'s>,
xml: &mut XlReader<'_, RS>,
e: &BytesStart<'_>,
style_attr: Option<&[u8]>,
type_attr: Option<&[u8]>,
bufs: &mut ValueBufs,
) -> Result<DataRef<'s>, XlsxError>
where
RS: Read + Seek,
{
Ok(match e.local_name().as_ref() {
b"is" => {
read_string_with_bufs(xml, e.name(), &mut bufs.xml, &mut bufs.str_inner)?
.map_or(DataRef::Empty, DataRef::String)
}
b"v" if matches!(type_attr, Some(b"inlineStr") | Some(b"is")) => {
bufs.xml.clear();
xml.read_to_end_into(e.name(), &mut bufs.xml)?;
DataRef::Empty
}
b"v" => match type_attr {
Some(b"n") | Some(b"s") | Some(b"b") | Some(b"e") | None => {
bufs.xml.clear();
let val = match xml.read_event_into(&mut bufs.xml)? {
Event::Text(t) => read_v(ctx, &t, style_attr, type_attr)?,
Event::End(end) if end.name() == e.name() => return Ok(DataRef::Empty),
Event::Eof => return Err(XlsxError::XmlEof("v")),
_ => DataRef::Empty,
};
bufs.xml.clear();
xml.read_to_end_into(e.name(), &mut bufs.xml)?;
val
}
_ => {
bufs.value.clear();
loop {
bufs.xml.clear();
match xml.read_event_into(&mut bufs.xml)? {
Event::Text(t) => bufs.value.push_str(&t.xml10_content()?),
Event::GeneralRef(e) => unescape_entity_to_buffer(&e, &mut bufs.value)?,
Event::End(end) if end.name() == e.name() => break,
Event::Eof => return Err(XlsxError::XmlEof("v")),
_ => (),
}
}
read_v(ctx, bufs.value.as_bytes(), style_attr, type_attr)?
}
},
b"f" => {
bufs.xml.clear();
xml.read_to_end_into(e.name(), &mut bufs.xml)?;
DataRef::Empty
}
_n => return Err(XlsxError::UnexpectedNode("v, f, or is")),
})
}
fn v_as_str(v: &[u8]) -> Result<&str, XlsxError> {
std::str::from_utf8(v).map_err(|_| XlsxError::Unexpected("invalid UTF-8 in cell value"))
}
fn read_v<'s>(
ctx: &WorkbookContext<'s>,
v: &[u8],
style_attr: Option<&[u8]>,
type_attr: Option<&[u8]>,
) -> Result<DataRef<'s>, XlsxError> {
let cell_format = match style_attr {
Some(style) => {
let id = atoi_simd::parse::<usize, true, false>(style).unwrap_or(0);
ctx.formats.get(id)
}
None => Some(&CellFormat::Other),
};
match type_attr {
Some(b"s") => {
if v.is_empty() {
return Ok(DataRef::Empty);
}
let idx = atoi_simd::parse::<usize, true, false>(v).unwrap_or(0);
ctx.strings
.get(idx)
.map(|s| DataRef::SharedString(s))
.ok_or(XlsxError::Unexpected(
"Cell string index not found in shared strings table",
))
}
Some(b"b") => Ok(DataRef::Bool(v != b"0")),
Some(b"d") => Ok(DataRef::DateTimeIso(v_as_str(v)?.to_string())),
Some(b"e") => Ok(DataRef::Error(v_as_str(v)?.parse()?)),
Some(b"str") => Ok(DataRef::String(v_as_str(v)?.to_string())),
Some(b"n") | None => {
if v.is_empty() {
return Ok(DataRef::Empty);
}
fast_float2::parse::<f64, _>(v)
.map(|n| format_excel_f64_ref(n, cell_format, ctx.is_1904))
.or_else(|_| {
if type_attr.is_none() {
Ok(DataRef::String(v_as_str(v)?.to_string()))
} else {
Err(XlsxError::ParseFloat(
v_as_str(v)?.parse::<f64>().unwrap_err(),
))
}
})
}
Some(t) => {
let t = std::str::from_utf8(t).unwrap_or("<utf8 error>").to_string();
Err(XlsxError::CellTAttribute(t))
}
}
}
fn read_formula<RS>(xml: &mut XlReader<RS>, e: &BytesStart) -> Result<Option<String>, XlsxError>
where
RS: Read + Seek,
{
match e.local_name().as_ref() {
b"is" | b"v" => {
xml.read_to_end_into(e.name(), &mut Vec::new())?;
Ok(None)
}
b"f" => {
let mut f_buf = Vec::with_capacity(512);
let mut f = String::new();
loop {
match xml.read_event_into(&mut f_buf)? {
Event::Text(t) => f.push_str(&t.xml10_content()?),
Event::GeneralRef(e) => unescape_entity_to_buffer(&e, &mut f)?,
Event::End(end) if end.name() == e.name() => break,
Event::Eof => return Err(XlsxError::XmlEof("f")),
_ => (),
}
f_buf.clear();
}
Ok(Some(f))
}
_ => Err(XlsxError::UnexpectedNode("v, f, or is")),
}
}