use std::io::{Read, Write};
use crate::error::{Result, SpssError};
pub struct SavReader<R: Read> {
inner: R,
bswap: bool,
}
impl<R: Read> SavReader<R> {
pub fn new(inner: R) -> Self {
SavReader {
inner,
bswap: false,
}
}
pub fn set_bswap(&mut self, bswap: bool) {
self.bswap = bswap;
}
#[allow(dead_code)]
pub fn bswap(&self) -> bool {
self.bswap
}
pub fn inner_mut(&mut self) -> &mut R {
&mut self.inner
}
pub fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; n];
self.inner.read_exact(&mut buf)?;
Ok(buf)
}
pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
self.inner.read_exact(buf)?;
Ok(())
}
pub fn read_i32(&mut self) -> Result<i32> {
let mut buf = [0u8; 4];
self.inner.read_exact(&mut buf)?;
let val = if self.bswap {
i32::from_be_bytes(buf)
} else {
i32::from_le_bytes(buf)
};
Ok(val)
}
#[allow(dead_code)]
pub fn read_u32(&mut self) -> Result<u32> {
let mut buf = [0u8; 4];
self.inner.read_exact(&mut buf)?;
let val = if self.bswap {
u32::from_be_bytes(buf)
} else {
u32::from_le_bytes(buf)
};
Ok(val)
}
pub fn read_i64(&mut self) -> Result<i64> {
let mut buf = [0u8; 8];
self.inner.read_exact(&mut buf)?;
let val = if self.bswap {
i64::from_be_bytes(buf)
} else {
i64::from_le_bytes(buf)
};
Ok(val)
}
pub fn read_f64(&mut self) -> Result<f64> {
let mut buf = [0u8; 8];
self.inner.read_exact(&mut buf)?;
let val = if self.bswap {
f64::from_be_bytes(buf)
} else {
f64::from_le_bytes(buf)
};
Ok(val)
}
pub fn read_8_bytes(&mut self) -> Result<[u8; 8]> {
let mut buf = [0u8; 8];
self.inner.read_exact(&mut buf)?;
Ok(buf)
}
#[allow(dead_code)]
pub fn read_fixed_string(&mut self, len: usize) -> Result<Vec<u8>> {
let buf = self.read_bytes(len)?;
Ok(trim_trailing_padding(&buf).to_vec())
}
pub fn skip(&mut self, n: usize) -> Result<()> {
let mut remaining = n;
let mut discard = [0u8; 4096];
while remaining > 0 {
let to_read = remaining.min(discard.len());
self.inner.read_exact(&mut discard[..to_read])?;
remaining -= to_read;
}
Ok(())
}
}
pub fn trim_trailing_padding(buf: &[u8]) -> &[u8] {
let mut end = buf.len();
while end > 0 && (buf[end - 1] == b' ' || buf[end - 1] == 0) {
end -= 1;
}
&buf[..end]
}
pub fn round_up(len: usize, alignment: usize) -> usize {
if alignment == 0 {
return len;
}
let remainder = len % alignment;
if remainder == 0 {
len
} else {
len + alignment - remainder
}
}
pub fn bytes_to_string_lossy(bytes: &[u8]) -> String {
String::from_utf8(bytes.to_vec())
.unwrap_or_else(|_| String::from_utf8_lossy(bytes).into_owned())
}
#[allow(dead_code)]
pub fn read_pascal_string<R: Read>(reader: &mut SavReader<R>) -> Result<Vec<u8>> {
let len = reader.read_i32()? as usize;
if len == 0 {
return Ok(Vec::new());
}
reader.read_bytes(len)
}
#[allow(dead_code)]
pub fn read_pascal_string_aligned<R: Read>(reader: &mut SavReader<R>) -> Result<Vec<u8>> {
let len = reader.read_i32()? as usize;
if len == 0 {
return Ok(Vec::new());
}
let padded_len = round_up(len, 4);
let data = reader.read_bytes(padded_len)?;
Ok(data[..len].to_vec())
}
pub fn detect_endianness(layout_code_bytes: [u8; 4]) -> Result<bool> {
let le_val = i32::from_le_bytes(layout_code_bytes);
let be_val = i32::from_be_bytes(layout_code_bytes);
if le_val == 2 || le_val == 3 {
Ok(false) } else if be_val == 2 || be_val == 3 {
Ok(true) } else {
Err(SpssError::InvalidVariable(format!(
"cannot determine endianness from layout_code bytes: {layout_code_bytes:?}"
)))
}
}
pub trait SavWriteExt: Write {
fn write_i32_le(&mut self, val: i32) -> Result<()> {
self.write_all(&val.to_le_bytes())?;
Ok(())
}
fn write_f64_le(&mut self, val: f64) -> Result<()> {
self.write_all(&val.to_le_bytes())?;
Ok(())
}
#[allow(dead_code)]
fn write_i64_le(&mut self, val: i64) -> Result<()> {
self.write_all(&val.to_le_bytes())?;
Ok(())
}
fn write_fixed_string(&mut self, s: &str, len: usize) -> Result<()> {
let bytes = s.as_bytes();
let to_write = bytes.len().min(len);
self.write_all(&bytes[..to_write])?;
if to_write < len {
let padding = vec![b' '; len - to_write];
self.write_all(&padding)?;
}
Ok(())
}
fn write_zero_padding(&mut self, count: usize) -> Result<()> {
if count > 0 {
let buf = vec![0u8; count];
self.write_all(&buf)?;
}
Ok(())
}
}
impl<W: Write> SavWriteExt for W {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trim_trailing_padding() {
assert_eq!(trim_trailing_padding(b"hello "), b"hello");
assert_eq!(trim_trailing_padding(b"hello\0\0\0"), b"hello");
assert_eq!(trim_trailing_padding(b"hello \0 "), b"hello");
assert_eq!(trim_trailing_padding(b" "), b"");
assert_eq!(trim_trailing_padding(b""), b"");
}
#[test]
fn test_round_up() {
assert_eq!(round_up(0, 4), 0);
assert_eq!(round_up(1, 4), 4);
assert_eq!(round_up(4, 4), 4);
assert_eq!(round_up(5, 4), 8);
assert_eq!(round_up(7, 8), 8);
assert_eq!(round_up(8, 8), 8);
}
#[test]
fn test_detect_endianness_le() {
let bytes = 2_i32.to_le_bytes();
assert!(!detect_endianness(bytes).unwrap());
}
#[test]
fn test_detect_endianness_be() {
let bytes = 2_i32.to_be_bytes();
assert!(detect_endianness(bytes).unwrap());
}
#[test]
fn test_sav_reader_i32() {
let data = 42_i32.to_le_bytes();
let mut reader = SavReader::new(&data[..]);
assert_eq!(reader.read_i32().unwrap(), 42);
}
#[test]
fn test_sav_reader_f64() {
let data = 3.14_f64.to_le_bytes();
let mut reader = SavReader::new(&data[..]);
let val = reader.read_f64().unwrap();
assert!((val - 3.14).abs() < 1e-10);
}
}