1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-23 21:17:45 +00:00
physis/src/exd.rs

242 lines
7.4 KiB
Rust
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
use std::io::{Cursor, Seek, SeekFrom};
use binrw::binrw;
2024-04-20 13:18:03 -04:00
use binrw::{BinRead, Endian};
2022-07-21 19:58:58 -04:00
use crate::common::Language;
use crate::exh::{ColumnDataType, ExcelColumnDefinition, ExcelDataPagination, EXH};
use crate::ByteSpan;
2022-07-21 19:58:58 -04:00
#[binrw]
#[brw(magic = b"EXDF")]
#[brw(big)]
2022-09-15 16:26:31 -04:00
#[allow(dead_code)]
2022-07-21 19:58:58 -04:00
struct EXDHeader {
2022-08-16 11:52:07 -04:00
version: u16,
2022-07-21 19:58:58 -04:00
#[br(pad_before = 2)]
#[br(pad_after = 20)]
2022-08-16 11:52:07 -04:00
index_size: u32,
2022-07-21 19:58:58 -04:00
}
#[binrw]
#[brw(big)]
2022-07-21 19:58:58 -04:00
struct ExcelDataOffset {
2022-08-16 11:52:07 -04:00
row_id: u32,
pub offset: u32,
2022-07-21 19:58:58 -04:00
}
#[binrw]
#[brw(big)]
2022-09-15 16:26:31 -04:00
#[allow(dead_code)]
2022-07-21 19:58:58 -04:00
struct ExcelDataRowHeader {
2022-08-16 11:52:07 -04:00
data_size: u32,
row_count: u16,
2022-07-21 19:58:58 -04:00
}
#[binrw]
#[brw(big)]
2022-09-15 16:26:31 -04:00
#[allow(dead_code)]
2022-07-21 19:58:58 -04:00
pub struct EXD {
2022-08-16 11:52:07 -04:00
header: EXDHeader,
2022-07-21 19:58:58 -04:00
#[br(count = header.index_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
2022-08-16 11:52:07 -04:00
data_offsets: Vec<ExcelDataOffset>,
2022-07-21 19:58:58 -04:00
#[brw(ignore)]
2022-08-16 11:52:07 -04:00
pub rows: Vec<ExcelRow>,
2022-07-21 19:58:58 -04:00
}
#[derive(Debug)]
pub enum ColumnData {
String(String),
Bool(bool),
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Float32(f32),
Int64(i64),
2022-08-16 11:52:07 -04:00
UInt64(u64),
2022-07-21 19:58:58 -04:00
}
pub struct ExcelRow {
2022-08-16 11:52:07 -04:00
pub data: Vec<ColumnData>,
2022-07-21 19:58:58 -04:00
}
impl EXD {
2024-04-14 13:28:43 -04:00
pub fn from_existing(exh: &EXH, buffer: ByteSpan) -> Option<EXD> {
let mut cursor = Cursor::new(buffer);
let mut exd = EXD::read(&mut cursor).ok()?;
for i in 0..exh.header.row_count {
for offset in &exd.data_offsets {
if offset.row_id == i {
cursor.seek(SeekFrom::Start(offset.offset.into())).ok()?;
let row_header = ExcelDataRowHeader::read(&mut cursor).ok()?;
2024-04-20 13:18:03 -04:00
let header_offset = offset.offset + 6; // std::mem::size_of::<ExcelDataRowHeader>() as u32;
2024-04-14 13:28:43 -04:00
let mut read_row = |row_offset: u32| -> Option<ExcelRow> {
let mut subrow = ExcelRow {
data: Vec::with_capacity(exh.column_definitions.len()),
};
for column in &exh.column_definitions {
cursor
.seek(SeekFrom::Start((row_offset + column.offset as u32).into()))
.ok()?;
2024-04-20 13:18:03 -04:00
subrow.data.push(
Self::read_column(&mut cursor, exh, row_offset, column).unwrap(),
);
2024-04-14 13:28:43 -04:00
}
Some(subrow)
};
if row_header.row_count > 1 {
for i in 0..row_header.row_count {
let subrow_offset =
header_offset + (i * exh.header.data_offset + 2 * (i + 1)) as u32;
exd.rows.push(read_row(subrow_offset).unwrap());
}
} else {
exd.rows.push(read_row(header_offset).unwrap());
}
}
}
}
Some(exd)
}
2024-04-20 13:18:03 -04:00
fn read_data_raw<Z: BinRead<Args<'static> = ()>>(cursor: &mut Cursor<ByteSpan>) -> Option<Z> {
Z::read_options(cursor, Endian::Big, ()).ok()
2022-07-21 19:58:58 -04:00
}
2022-08-16 11:52:07 -04:00
fn read_column(
cursor: &mut Cursor<ByteSpan>,
2022-08-16 11:52:07 -04:00
exh: &EXH,
2024-04-14 13:28:43 -04:00
row_offset: u32,
2022-08-16 11:52:07 -04:00
column: &ExcelColumnDefinition,
) -> Option<ColumnData> {
let mut read_packed_bool = |shift: i32| -> bool {
2022-07-21 19:58:58 -04:00
let bit = 1 << shift;
2024-04-14 13:28:43 -04:00
let bool_data: i32 = Self::read_data_raw(cursor).unwrap_or(0);
2022-07-21 19:58:58 -04:00
(bool_data & bit) == bit
};
2022-08-16 11:50:18 -04:00
match column.data_type {
2022-07-21 19:58:58 -04:00
ColumnDataType::String => {
2022-08-16 11:52:07 -04:00
let string_offset: u32 = Self::read_data_raw(cursor).unwrap();
2022-07-21 19:58:58 -04:00
2022-08-16 11:52:07 -04:00
cursor
.seek(SeekFrom::Start(
2024-04-14 13:28:43 -04:00
(row_offset + exh.header.data_offset as u32 + string_offset).into(),
2022-08-16 11:52:07 -04:00
))
.ok()?;
2022-07-21 19:58:58 -04:00
let mut string = String::new();
2022-08-16 11:52:07 -04:00
let mut byte: u8 = Self::read_data_raw(cursor).unwrap();
2022-07-21 19:58:58 -04:00
while byte != 0 {
string.push(byte as char);
byte = Self::read_data_raw(cursor).unwrap();
}
Some(ColumnData::String(string))
}
ColumnDataType::Bool => {
// FIXME: i believe Bool is int8?
2022-08-16 11:52:07 -04:00
let bool_data: i32 = Self::read_data_raw(cursor).unwrap();
2022-07-21 19:58:58 -04:00
Some(ColumnData::Bool(bool_data == 1))
}
2022-08-16 11:52:07 -04:00
ColumnDataType::Int8 => Some(ColumnData::Int8(Self::read_data_raw(cursor).unwrap())),
ColumnDataType::UInt8 => Some(ColumnData::UInt8(Self::read_data_raw(cursor).unwrap())),
ColumnDataType::Int16 => Some(ColumnData::Int16(Self::read_data_raw(cursor).unwrap())),
2022-07-21 19:58:58 -04:00
ColumnDataType::UInt16 => {
Some(ColumnData::UInt16(Self::read_data_raw(cursor).unwrap()))
}
2022-08-16 11:52:07 -04:00
ColumnDataType::Int32 => Some(ColumnData::Int32(Self::read_data_raw(cursor).unwrap())),
2022-07-21 19:58:58 -04:00
ColumnDataType::UInt32 => {
Some(ColumnData::UInt32(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::Float32 => {
Some(ColumnData::Float32(Self::read_data_raw(cursor).unwrap()))
}
2022-08-16 11:52:07 -04:00
ColumnDataType::Int64 => Some(ColumnData::Int64(Self::read_data_raw(cursor).unwrap())),
2022-07-21 19:58:58 -04:00
ColumnDataType::UInt64 => {
Some(ColumnData::UInt64(Self::read_data_raw(cursor).unwrap()))
}
2022-08-16 11:52:07 -04:00
ColumnDataType::PackedBool0 => Some(ColumnData::Bool(read_packed_bool(0))),
ColumnDataType::PackedBool1 => Some(ColumnData::Bool(read_packed_bool(1))),
ColumnDataType::PackedBool2 => Some(ColumnData::Bool(read_packed_bool(2))),
ColumnDataType::PackedBool3 => Some(ColumnData::Bool(read_packed_bool(3))),
ColumnDataType::PackedBool4 => Some(ColumnData::Bool(read_packed_bool(4))),
ColumnDataType::PackedBool5 => Some(ColumnData::Bool(read_packed_bool(5))),
ColumnDataType::PackedBool6 => Some(ColumnData::Bool(read_packed_bool(6))),
ColumnDataType::PackedBool7 => Some(ColumnData::Bool(read_packed_bool(7))),
2022-07-21 19:58:58 -04:00
}
}
2022-08-16 11:52:07 -04:00
pub fn calculate_filename(
name: &str,
language: Language,
page: &ExcelDataPagination,
) -> String {
2022-07-21 19:58:58 -04:00
use crate::common::get_language_code;
2022-09-15 16:26:31 -04:00
match language {
2022-07-21 19:58:58 -04:00
Language::None => {
format!("{name}_{}.exd", page.start_id)
}
lang => {
format!("{name}_{}_{}.exd", page.start_id, get_language_code(&lang))
}
2022-09-15 16:26:31 -04:00
}
2022-07-21 19:58:58 -04:00
}
2022-08-16 11:52:07 -04:00
}
2024-04-16 21:52:14 -04:00
#[cfg(test)]
mod tests {
2024-04-20 13:18:03 -04:00
use crate::exh::EXHHeader;
2024-04-16 21:52:14 -04:00
use std::fs::read;
use std::path::PathBuf;
use super::*;
#[test]
fn test_invalid() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("random");
let exh = EXH {
header: EXHHeader {
version: 0,
data_offset: 0,
column_count: 0,
page_count: 0,
language_count: 0,
row_count: 0,
},
column_definitions: vec![],
pages: vec![],
languages: vec![],
};
// Feeding it invalid data should not panic
EXD::from_existing(&exh, &read(d).unwrap());
}
}