use std::{
collections::HashMap,
io::{Read, Seek, SeekFrom, Write},
};
use binrw::{BinRead, BinResult, BinWrite, Endian};
use crate::common_file_operations::read_null_terminated_utf8;
use crate::{
excel::{Field, Row},
exd::EXD,
exh::{ColumnDataType, EXH, ExcelColumnDefinition},
};
pub(crate) fn read_row<T: Read + Seek>(reader: &mut T, exh: &EXH, row_offset: u64) -> Option<Row> {
let mut subrow = Row {
columns: Vec::with_capacity(exh.column_definitions.len()),
};
for column in &exh.column_definitions {
reader
.seek(SeekFrom::Start(row_offset + column.offset as u64))
.ok()?;
if let Some(column) = EXD::read_column(reader, exh, row_offset, column) {
subrow.columns.push(column);
} else if cfg!(debug_assertions) {
println!(
"Failed to read column {column:?}, please report this as a bug if it's a problem?"
);
}
}
Some(subrow)
}
pub(crate) fn write_row<T: Write + Seek>(writer: &mut T, exh: &EXH, row: &Row) {
let mut column_definitions: Vec<(ExcelColumnDefinition, Field)> = exh
.column_definitions
.clone()
.into_iter()
.zip(row.columns.clone())
.collect::<Vec<_>>();
column_definitions.sort_by_key(|(a, _)| a.offset);
let mut packed_bools: HashMap<u16, u8> = HashMap::new();
let mut write_packed_bool = |definition: &ExcelColumnDefinition, shift: i32, boolean: &bool| {
packed_bools.entry(definition.offset).or_insert(0u8);
if *boolean {
let bit = 1 << shift;
*packed_bools.get_mut(&definition.offset).unwrap() |= bit;
}
};
for (definition, column) in &column_definitions {
if let Field::Bool(val) = &column {
match definition.data_type {
ColumnDataType::PackedBool0 => write_packed_bool(definition, 0, val),
ColumnDataType::PackedBool1 => write_packed_bool(definition, 1, val),
ColumnDataType::PackedBool2 => write_packed_bool(definition, 2, val),
ColumnDataType::PackedBool3 => write_packed_bool(definition, 3, val),
ColumnDataType::PackedBool4 => write_packed_bool(definition, 4, val),
ColumnDataType::PackedBool5 => write_packed_bool(definition, 5, val),
ColumnDataType::PackedBool6 => write_packed_bool(definition, 6, val),
ColumnDataType::PackedBool7 => write_packed_bool(definition, 7, val),
_ => {} }
};
}
let mut strings_len = 0;
for (definition, column) in &column_definitions {
EXD::write_column(
writer,
column,
definition,
&mut strings_len,
&mut packed_bools,
);
if definition.data_type == ColumnDataType::Int8 && column_definitions.len() == 1 {
0u32.write_le(writer).unwrap();
}
if definition.offset == 60
&& definition.data_type == ColumnDataType::PackedBool0
&& column_definitions.len() == 44
{
[0u8; 3].write_le(writer).unwrap();
}
}
}
impl EXD {
fn read_data_raw<T: Read + Seek, Z: BinRead<Args<'static> = ()>>(
cursor: &mut T,
) -> BinResult<Z> {
Z::read_options(cursor, Endian::Big, ())
}
pub(crate) fn read_column<T: Read + Seek>(
cursor: &mut T,
exh: &EXH,
row_offset: u64,
column: &ExcelColumnDefinition,
) -> Option<Field> {
let mut read_packed_bool = |shift: i32| -> bool {
let bit = 1 << shift;
let bool_data: u8 = Self::read_data_raw(cursor).unwrap_or(0);
(bool_data & bit) == bit
};
match column.data_type {
ColumnDataType::String => {
let string_offset: u32 = Self::read_data_raw(cursor).ok()?;
cursor
.seek(SeekFrom::Start(
row_offset + exh.header.row_size as u64 + string_offset as u64,
))
.ok()?;
Some(Field::String(read_null_terminated_utf8(cursor)))
}
ColumnDataType::Bool => {
let bool_data: i8 = Self::read_data_raw(cursor).ok()?;
Some(Field::Bool(bool_data == 1))
}
ColumnDataType::Int8 => Some(Field::Int8(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::UInt8 => Some(Field::UInt8(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::Int16 => Some(Field::Int16(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::UInt16 => Some(Field::UInt16(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::Int32 => Some(Field::Int32(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::UInt32 => Some(Field::UInt32(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::Float32 => Some(Field::Float32(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::Int64 => Some(Field::Int64(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::UInt64 => Some(Field::UInt64(Self::read_data_raw(cursor).ok()?)),
ColumnDataType::PackedBool0 => Some(Field::Bool(read_packed_bool(0))),
ColumnDataType::PackedBool1 => Some(Field::Bool(read_packed_bool(1))),
ColumnDataType::PackedBool2 => Some(Field::Bool(read_packed_bool(2))),
ColumnDataType::PackedBool3 => Some(Field::Bool(read_packed_bool(3))),
ColumnDataType::PackedBool4 => Some(Field::Bool(read_packed_bool(4))),
ColumnDataType::PackedBool5 => Some(Field::Bool(read_packed_bool(5))),
ColumnDataType::PackedBool6 => Some(Field::Bool(read_packed_bool(6))),
ColumnDataType::PackedBool7 => Some(Field::Bool(read_packed_bool(7))),
}
}
fn write_data_raw<T: Write + Seek, Z: BinWrite<Args<'static> = ()>>(cursor: &mut T, value: &Z) {
value.write_options(cursor, Endian::Big, ()).unwrap()
}
pub(crate) fn write_column<T: Write + Seek>(
cursor: &mut T,
column: &Field,
column_definition: &ExcelColumnDefinition,
strings_len: &mut u32,
packed_bools: &mut HashMap<u16, u8>,
) {
match column {
Field::String(val) => {
let string_offset = *strings_len;
Self::write_data_raw(cursor, &string_offset);
*strings_len += val.len() as u32 + 1;
}
Field::Bool(_) => match column_definition.data_type {
ColumnDataType::Bool => todo!(),
ColumnDataType::PackedBool0
| ColumnDataType::PackedBool1
| ColumnDataType::PackedBool2
| ColumnDataType::PackedBool3
| ColumnDataType::PackedBool4
| ColumnDataType::PackedBool5
| ColumnDataType::PackedBool6
| ColumnDataType::PackedBool7 => {
if let Some(byte) = packed_bools.get(&column_definition.offset) {
byte.write_le(cursor).unwrap();
packed_bools.remove(&column_definition.offset);
}
}
_ => panic!("This makes no sense!"),
},
Field::Int8(val) => Self::write_data_raw(cursor, val),
Field::UInt8(val) => Self::write_data_raw(cursor, val),
Field::Int16(val) => Self::write_data_raw(cursor, val),
Field::UInt16(val) => Self::write_data_raw(cursor, val),
Field::Int32(val) => Self::write_data_raw(cursor, val),
Field::UInt32(val) => Self::write_data_raw(cursor, val),
Field::Float32(val) => Self::write_data_raw(cursor, val),
Field::Int64(val) => Self::write_data_raw(cursor, val),
Field::UInt64(val) => Self::write_data_raw(cursor, val),
}
}
}