use crate::{
Error, XlRowCell, XL_CELL_BOOL, XL_CELL_DATE, XL_CELL_EMPTY, XL_CELL_ERROR, XL_CELL_FORMULA,
XL_CELL_NUMBER, XL_CELL_STRING, XL_ERROR,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CellType {
Empty = XL_CELL_EMPTY,
String = XL_CELL_STRING,
Number = XL_CELL_NUMBER,
Date = XL_CELL_DATE,
Bool = XL_CELL_BOOL,
Formula = XL_CELL_FORMULA,
Error = XL_CELL_ERROR,
}
impl CellType {
#[must_use]
pub fn from_raw(value: i32) -> Option<CellType> {
match value {
XL_CELL_EMPTY => Some(CellType::Empty),
XL_CELL_STRING => Some(CellType::String),
XL_CELL_NUMBER => Some(CellType::Number),
XL_CELL_DATE => Some(CellType::Date),
XL_CELL_BOOL => Some(CellType::Bool),
XL_CELL_FORMULA => Some(CellType::Formula),
XL_CELL_ERROR => Some(CellType::Error),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CellRef<'a> {
pub column: i32,
pub cell_type: CellType,
value: &'a [u8],
}
impl<'a> CellRef<'a> {
#[must_use]
pub fn as_bytes(&self) -> &'a [u8] {
self.value
}
pub fn as_str(&self) -> Result<&'a str, Error> {
std::str::from_utf8(self.value)
.map_err(|err| Error::from_status(XL_ERROR, format!("cell value is not valid UTF-8: {err}")))
}
}
#[derive(Debug, Clone, Copy)]
enum RowBacking<'a> {
Blob(&'a [u8]),
Decoded(&'a [XlRowCell]),
}
#[derive(Debug, Clone, Copy)]
pub struct RowRef<'a> {
backing: RowBacking<'a>,
len: usize,
}
impl<'a> RowRef<'a> {
pub(crate) fn from_blob(blob: &'a [u8]) -> Option<RowRef<'a>> {
let count = read_i32(blob, 0)?;
if count < 0 {
return None;
}
Some(RowRef {
backing: RowBacking::Blob(&blob[4..]),
len: count as usize,
})
}
pub(crate) unsafe fn from_decoded(cells: *const XlRowCell, count: i32) -> RowRef<'a> {
let len = if count > 0 { count as usize } else { 0 };
let slice = if len == 0 || cells.is_null() {
&[][..]
} else {
unsafe { std::slice::from_raw_parts(cells, len) }
};
RowRef { backing: RowBacking::Decoded(slice), len }
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn get(&self, index: usize) -> Option<CellRef<'a>> {
if index >= self.len {
return None;
}
match self.backing {
RowBacking::Decoded(cells) => cell_from_decoded(&cells[index]),
RowBacking::Blob(_) => self.iter().nth(index),
}
}
#[must_use]
pub fn iter(&self) -> CellIter<'a> {
CellIter { backing: self.backing, len: self.len, index: 0, offset: 0 }
}
}
impl<'a> IntoIterator for RowRef<'a> {
type Item = CellRef<'a>;
type IntoIter = CellIter<'a>;
fn into_iter(self) -> CellIter<'a> {
self.iter()
}
}
#[derive(Debug, Clone)]
pub struct CellIter<'a> {
backing: RowBacking<'a>,
len: usize,
index: usize,
offset: usize,
}
impl<'a> Iterator for CellIter<'a> {
type Item = CellRef<'a>;
fn next(&mut self) -> Option<CellRef<'a>> {
if self.index >= self.len {
return None;
}
match self.backing {
RowBacking::Decoded(cells) => {
let cell = cell_from_decoded(&cells[self.index])?;
self.index += 1;
Some(cell)
}
RowBacking::Blob(blob) => {
let (cell, next) = cell_from_blob(blob, self.offset)?;
self.offset = next;
self.index += 1;
Some(cell)
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len - self.index;
(0, Some(remaining))
}
}
fn read_i32(bytes: &[u8], offset: usize) -> Option<i32> {
let end = offset.checked_add(4)?;
let slice = bytes.get(offset..end)?;
Some(i32::from_le_bytes(slice.try_into().ok()?))
}
fn cell_from_blob(blob: &[u8], offset: usize) -> Option<(CellRef<'_>, usize)> {
let column = read_i32(blob, offset)?;
let raw_type = read_i32(blob, offset + 4)?;
let value_len = read_i32(blob, offset + 8)?;
if value_len < 0 {
return None;
}
let start = offset.checked_add(12)?;
let end = start.checked_add(value_len as usize)?;
let value = blob.get(start..end)?;
let cell_type = CellType::from_raw(raw_type)?;
Some((CellRef { column, cell_type, value }, end))
}
fn cell_from_decoded(raw: &XlRowCell) -> Option<CellRef<'_>> {
let cell_type = CellType::from_raw(raw.cell_type)?;
let value = if raw.value.is_null() || raw.value_len <= 0 {
&[][..]
} else {
unsafe { std::slice::from_raw_parts(raw.value, raw.value_len as usize) }
};
Some(CellRef { column: raw.column, cell_type, value })
}
const INITIAL_ROW_BUFFER: usize = 64 * 1024;
pub struct RowCursor<'w> {
handle: *mut crate::XlWorkbook,
buffer: Vec<u8>,
written: usize,
workbook: std::marker::PhantomData<&'w mut crate::workbook::Workbook>,
}
impl<'w> RowCursor<'w> {
pub(crate) fn new(handle: *mut crate::XlWorkbook) -> RowCursor<'w> {
RowCursor {
handle,
buffer: vec![0; INITIAL_ROW_BUFFER],
written: 0,
workbook: std::marker::PhantomData,
}
}
pub fn next_row(&mut self) -> Option<Result<RowRef<'_>, Error>> {
loop {
let mut written: i32 = 0;
let capacity = i32::try_from(self.buffer.len()).unwrap_or(i32::MAX);
let status = unsafe {
crate::xl_next_row(self.handle, self.buffer.as_mut_ptr(), capacity, &mut written)
};
match status {
crate::XL_OK => {
self.written = if written > 0 { written as usize } else { 0 };
let blob = &self.buffer[..self.written];
return Some(
RowRef::from_blob(blob).ok_or_else(|| {
Error::from_status(XL_ERROR, "native returned a malformed row blob".to_string())
}),
);
}
crate::XL_EOF => return None,
crate::XL_BUFFER_TOO_SMALL => {
let needed = if written > 0 { written as usize } else { self.buffer.len() * 2 };
if needed <= self.buffer.len() {
return Some(Err(Error::from_status(
XL_ERROR,
"native asked for a buffer no larger than the current one".to_string(),
)));
}
self.buffer.resize(needed, 0);
}
other => return Some(Err(crate::workbook::last_error(other))),
}
}
}
}
pub struct DecodedRows {
raw: crate::XlRows,
}
impl DecodedRows {
pub(crate) fn new(raw: crate::XlRows) -> DecodedRows {
DecodedRows { raw }
}
#[must_use]
pub fn len(&self) -> usize {
if self.raw.row_count > 0 { self.raw.row_count as usize } else { 0 }
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn get(&self, index: usize) -> Option<RowRef<'_>> {
if index >= self.len() || self.raw.rows.is_null() {
return None;
}
let row = unsafe { &*self.raw.rows.add(index) };
Some(unsafe { RowRef::from_decoded(row.cells, row.cell_count) })
}
pub fn iter(&self) -> impl Iterator<Item = RowRef<'_>> + '_ {
(0..self.len()).filter_map(move |index| self.get(index))
}
}
impl Drop for DecodedRows {
fn drop(&mut self) {
unsafe { crate::xl_free_rows(&mut self.raw) };
}
}
impl std::fmt::Debug for DecodedRows {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecodedRows").field("len", &self.len()).finish()
}
}
const INITIAL_ALL_ROWS_BUFFER: usize = 1024 * 1024;
pub struct AllRows {
buffer: Vec<u8>,
row_ranges: Vec<(usize, usize)>,
}
impl AllRows {
pub(crate) fn read(handle: *mut crate::XlWorkbook) -> Result<AllRows, Error> {
let mut buffer = vec![0u8; INITIAL_ALL_ROWS_BUFFER];
loop {
let mut written: i32 = 0;
let capacity = i32::try_from(buffer.len()).unwrap_or(i32::MAX);
let status = unsafe {
crate::xl_read_all_blob(handle, buffer.as_mut_ptr(), capacity, &mut written)
};
match status {
crate::XL_OK => {
buffer.truncate(if written > 0 { written as usize } else { 0 });
let row_ranges = parse_row_ranges(&buffer)?;
return Ok(AllRows { buffer, row_ranges });
}
crate::XL_BUFFER_TOO_SMALL => {
let needed = if written > 0 { written as usize } else { buffer.len() * 2 };
if needed <= buffer.len() {
return Err(Error::from_status(
XL_ERROR,
"native asked for a buffer no larger than the current one".to_string(),
));
}
buffer.resize(needed, 0);
}
other => return Err(crate::workbook::last_error(other)),
}
}
}
#[must_use]
pub fn len(&self) -> usize {
self.row_ranges.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.row_ranges.is_empty()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<RowRef<'_>> {
let &(start, end) = self.row_ranges.get(index)?;
RowRef::from_blob(&self.buffer[start..end])
}
pub fn iter(&self) -> impl Iterator<Item = RowRef<'_>> + '_ {
(0..self.len()).filter_map(move |index| self.get(index))
}
}
impl std::fmt::Debug for AllRows {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AllRows").field("len", &self.len()).finish()
}
}
fn parse_row_ranges(buffer: &[u8]) -> Result<Vec<(usize, usize)>, Error> {
let row_count = read_i32(buffer, 0).ok_or_else(malformed_all_rows_blob)?;
if row_count < 0 {
return Err(malformed_all_rows_blob());
}
let mut ranges = Vec::with_capacity(row_count as usize);
let mut offset = 4usize;
for _ in 0..row_count {
let row_length = read_i32(buffer, offset).ok_or_else(malformed_all_rows_blob)?;
if row_length < 0 {
return Err(malformed_all_rows_blob());
}
let start = offset.checked_add(4).ok_or_else(malformed_all_rows_blob)?;
let end = start.checked_add(row_length as usize).ok_or_else(malformed_all_rows_blob)?;
if end > buffer.len() {
return Err(malformed_all_rows_blob());
}
ranges.push((start, end));
offset = end;
}
if offset != buffer.len() {
return Err(malformed_all_rows_blob());
}
Ok(ranges)
}
fn malformed_all_rows_blob() -> Error {
Error::from_status(XL_ERROR, "native returned a malformed all-rows blob".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
fn blob(cells: &[(i32, i32, &str)]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(cells.len() as i32).to_le_bytes());
for (column, cell_type, value) in cells {
out.extend_from_slice(&column.to_le_bytes());
out.extend_from_slice(&cell_type.to_le_bytes());
out.extend_from_slice(&(value.len() as i32).to_le_bytes());
out.extend_from_slice(value.as_bytes());
}
out
}
#[test]
fn decodes_a_blob_row() {
let bytes = blob(&[(0, XL_CELL_STRING, "hello"), (2, XL_CELL_NUMBER, "42")]);
let row = RowRef::from_blob(&bytes).expect("well-formed blob");
assert_eq!(row.len(), 2);
assert!(!row.is_empty());
let first = row.get(0).expect("cell 0");
assert_eq!(first.column, 0);
assert_eq!(first.cell_type, CellType::String);
assert_eq!(first.as_str().unwrap(), "hello");
let second = row.get(1).expect("cell 1");
assert_eq!(second.column, 2);
assert_eq!(second.cell_type, CellType::Number);
assert_eq!(second.as_str().unwrap(), "42");
assert!(row.get(2).is_none());
}
#[test]
fn iterates_in_order() {
let bytes = blob(&[(0, XL_CELL_STRING, "a"), (1, XL_CELL_STRING, "b"), (2, XL_CELL_STRING, "c")]);
let row = RowRef::from_blob(&bytes).expect("well-formed blob");
let values: Vec<&str> = row.iter().map(|cell| cell.as_str().unwrap()).collect();
assert_eq!(values, ["a", "b", "c"]);
}
#[test]
fn empty_row_decodes() {
let bytes = blob(&[]);
let row = RowRef::from_blob(&bytes).expect("well-formed blob");
assert_eq!(row.len(), 0);
assert!(row.is_empty());
assert_eq!(row.iter().count(), 0);
}
#[test]
fn truncated_blob_is_rejected_not_panicked() {
let mut bytes = blob(&[(0, XL_CELL_STRING, "hello")]);
bytes.truncate(bytes.len() - 3);
let row = RowRef::from_blob(&bytes).expect("header is intact");
assert!(row.get(0).is_none());
}
#[test]
fn unknown_cell_type_is_none() {
assert_eq!(CellType::from_raw(99), None);
assert_eq!(CellType::from_raw(XL_CELL_ERROR), Some(CellType::Error));
}
fn all_rows_blob(rows: &[&[(i32, i32, &str)]]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(rows.len() as i32).to_le_bytes());
for cells in rows {
let row = blob(cells);
out.extend_from_slice(&(row.len() as i32).to_le_bytes());
out.extend_from_slice(&row);
}
out
}
#[test]
fn parses_row_ranges_for_multiple_rows() {
let bytes = all_rows_blob(&[
&[(0, XL_CELL_STRING, "a")],
&[],
&[(0, XL_CELL_STRING, "b"), (1, XL_CELL_NUMBER, "2")],
]);
let ranges = parse_row_ranges(&bytes).expect("well-formed buffer");
assert_eq!(ranges.len(), 3);
let rows: Vec<RowRef<'_>> =
ranges.iter().map(|&(start, end)| RowRef::from_blob(&bytes[start..end]).unwrap()).collect();
assert_eq!(rows[0].len(), 1);
assert_eq!(rows[0].get(0).unwrap().as_str().unwrap(), "a");
assert!(rows[1].is_empty());
assert_eq!(rows[2].len(), 2);
assert_eq!(rows[2].get(1).unwrap().as_str().unwrap(), "2");
}
#[test]
fn empty_all_rows_blob_parses_to_no_rows() {
let bytes = all_rows_blob(&[]);
let ranges = parse_row_ranges(&bytes).expect("well-formed buffer");
assert!(ranges.is_empty());
}
#[test]
fn truncated_all_rows_blob_is_rejected_not_panicked() {
let mut bytes = all_rows_blob(&[&[(0, XL_CELL_STRING, "hello")]]);
bytes.truncate(bytes.len() - 3);
assert!(parse_row_ranges(&bytes).is_err());
}
#[test]
fn trailing_garbage_after_declared_rows_is_rejected() {
let mut bytes = all_rows_blob(&[&[(0, XL_CELL_STRING, "a")]]);
bytes.push(0xFF);
assert!(parse_row_ranges(&bytes).is_err());
}
}