use xlsbye_core::error::{Result, XlsByeError};
use crate::record::header::RecordIter;
use crate::record::ids::{BRT_BEGIN_PIVOT_CACHE_DEF, BRT_END_PIVOT_CACHE_DEF};
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedPivotTable {
pub has_pivot_definition: bool,
pub record_count: usize,
pub raw_payload: Vec<u8>,
}
pub fn parse_pivot_table(data: &[u8]) -> Result<ParsedPivotTable> {
let mut has_begin = false;
let mut has_end = false;
let mut record_count = 0usize;
for record in RecordIter::new(data) {
let (record_type, _) = record?;
record_count = record_count.saturating_add(1);
if record_type == BRT_BEGIN_PIVOT_CACHE_DEF.as_u16() {
has_begin = true;
}
if record_type == BRT_END_PIVOT_CACHE_DEF.as_u16() {
has_end = true;
}
}
if has_begin && !has_end {
return Err(XlsByeError::Biff12(
"pivot table stream missing BrtEndPivotCacheDef".to_string(),
));
}
Ok(ParsedPivotTable {
has_pivot_definition: has_begin,
record_count,
raw_payload: data.to_vec(),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_varint(mut value: u32) -> Vec<u8> {
let mut out = Vec::new();
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
out.push(byte);
if value == 0 {
break;
}
}
out
}
fn encode_record(record_type: u16, payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&encode_varint(u32::from(record_type)));
out.extend_from_slice(&encode_varint(payload.len() as u32));
out.extend_from_slice(payload);
out
}
#[test]
fn detects_pivot_table_stream_and_preserves_bytes() {
let mut data = Vec::new();
data.extend_from_slice(&encode_record(BRT_BEGIN_PIVOT_CACHE_DEF.as_u16(), &[1, 2]));
data.extend_from_slice(&encode_record(0x0802, &[]));
data.extend_from_slice(&encode_record(BRT_END_PIVOT_CACHE_DEF.as_u16(), &[]));
let parsed = parse_pivot_table(&data).expect("pivot parse should succeed");
assert!(parsed.has_pivot_definition);
assert_eq!(parsed.record_count, 3);
assert_eq!(parsed.raw_payload, data);
}
#[test]
fn rejects_unterminated_pivot_definition() {
let data = encode_record(BRT_BEGIN_PIVOT_CACHE_DEF.as_u16(), &[]);
let err = parse_pivot_table(&data).expect_err("missing end should fail");
assert!(format!("{err}").contains("BrtEndPivotCacheDef"));
}
}