use std::{
borrow::Cow,
io::{Cursor, Read, Seek},
};
use binrw::{binread, until_eof, BinRead, BinResult, ReadOptions};
use crate::error::{Error, ErrorValue, Result};
use super::file::File;
#[binread]
#[derive(Debug)]
#[br(big, magic = b"EXDF")]
pub struct ExcelData {
_version: u16,
#[br(pad_before = 2, temp)]
index_size: u32,
#[br(
pad_before = 20,
count = index_size / RowDefinition::SIZE,
)]
rows: Vec<RowDefinition>,
#[br(parse_with = current_position)]
data_offset: u64,
#[br(parse_with = until_eof)]
data: Vec<u8>,
}
impl ExcelData {
pub fn row_data(&self, row_id: u32) -> Result<&[u8]> {
let (row_header, offset) = self.row_meta(row_id)?;
let length: usize = row_header.data_size.try_into().unwrap();
Ok(&self.data[offset..offset + length])
}
pub fn subrow_data(&self, row_id: u32, subrow_id: u16) -> Result<&[u8]> {
let (row_header, offset) = self.row_meta(row_id)?;
let error_value = || ErrorValue::Row {
row: row_id,
subrow: subrow_id,
sheet: None,
};
if subrow_id >= row_header.row_count {
return Err(Error::NotFound(error_value()));
}
let subrow_size =
usize::try_from(row_header.data_size / u32::from(row_header.row_count)).unwrap();
let offset = offset + subrow_size * usize::try_from(subrow_id).unwrap();
let mut cursor = Cursor::new(&self.data);
cursor.set_position(offset.try_into().unwrap());
let subrow_header = SubrowHeader::read(&mut cursor)?;
if subrow_header.id != subrow_id {
return Err(Error::Invalid(
error_value(),
format!("Subrow data reports as unexpected ID {}.", subrow_header.id),
));
}
Ok(&self.data[offset + SubrowHeader::SIZE..offset + subrow_size])
}
fn row_meta(&self, row_id: u32) -> Result<(RowHeader, usize)> {
let row_definition = self.rows.iter().find(|row| row.id == row_id).ok_or({
Error::NotFound(ErrorValue::Row {
row: row_id,
subrow: 0,
sheet: None,
})
})?;
let mut cursor = Cursor::new(&self.data);
cursor.set_position(u64::from(row_definition.offset) - self.data_offset);
let row_header = RowHeader::read(&mut cursor)?;
Ok((row_header, cursor.position().try_into().unwrap()))
}
}
impl File for ExcelData {
fn read<'a>(data: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
Ok(<Self as BinRead>::read(&mut Cursor::new(data.into()))?)
}
}
#[binread]
#[derive(Debug)]
#[br(big)]
struct RowDefinition {
id: u32,
offset: u32,
}
impl RowDefinition {
const SIZE: u32 = 8;
}
fn current_position<R: Read + Seek>(reader: &mut R, _: &ReadOptions, _: ()) -> BinResult<u64> {
Ok(reader.stream_position()?)
}
#[binread]
#[derive(Debug)]
#[br(big)]
struct RowHeader {
data_size: u32,
row_count: u16,
}
#[binread]
#[derive(Debug)]
#[br(big)]
struct SubrowHeader {
id: u16,
}
impl SubrowHeader {
const SIZE: usize = 2;
}