-#[cfg(test)]
-mod hex_float_tests {
- use crate::HexFloat;
- use num::Float;
-
- #[test]
- fn test() {
- assert_eq!(format!("{}", HexFloat(1.0)), "0x1.0p0");
- assert_eq!(format!("{}", HexFloat(123.0)), "0x1.ecp6");
- assert_eq!(format!("{}", HexFloat(1.0 / 16.0)), "0x1.0p-4");
- assert_eq!(format!("{}", HexFloat(f64::infinity())), "Infinity");
- assert_eq!(format!("{}", HexFloat(f64::neg_infinity())), "-Infinity");
- assert_eq!(format!("{}", HexFloat(f64::nan())), "NaN");
- assert_eq!(format!("{}", HexFloat(0.0)), "0.0");
- assert_eq!(format!("{}", HexFloat(f64::neg_zero())), "-0.0");
- }
-}
-
-impl Dissector {
- fn new<P: AsRef<Path>>(filename: P) -> Result<Dissector> {
- let mut r = BufReader::new(File::open(&filename)?);
- let filename = filename.as_ref().to_string_lossy().into_owned();
- let rec_type: [u8; 4] = read_bytes(&mut r)?;
- let zmagic = match &rec_type {
- b"$FL2" => false,
- b"$FL3" => true,
- _ => Err(anyhow!("This is not an SPSS system file."))?
- };
-
- let eye_catcher: [u8; 60] = read_bytes(&mut r)?;
- let layout_code: [u8; 4] = read_bytes(&mut r)?;
- let endianness = detect_endianness(layout_code)
- .ok_or_else(|| anyhow!("This is not an SPSS system file."))?;
- let layout_code: u32 = endianness.parse(layout_code);
- let _nominal_case_size: [u8; 4] = read_bytes(&mut r)?;
- let compressed: u32 = endianness.parse(read_bytes(&mut r)?);
- let compression = match (zmagic, compressed) {
- (false, 0) => None,
- (false, 1) => Some(Compression::Simple),
- (true, 2) => Some(Compression::ZLib),
- _ => Err(anyhow!("{} file header has invalid compression value {compressed}.",
- if zmagic { "ZSAV" } else { "SAV" }))?,
- };
-
- let weight_index: u32 = endianness.parse(read_bytes(&mut r)?);
- let n_cases: u32 = endianness.parse(read_bytes(&mut r)?);
-
- let bias: [u8; 8] = read_bytes(&mut r)?;
- let fp_format = detect_fp_format(bias)
- .unwrap_or_else(|| { eprintln!("Compression bias is not the usual value of 100, or system file uses unrecognized floating-point format."); endianness });
- let bias: f64 = fp_format.parse(bias);
-
- let mut d = Dissector {
- filename,
- r,
- compression,
- endianness,
- fp_format,
- bias,
- n_variable_records: 0,
- n_variables: 0,
- var_widths: Vec::new(),
- };
-
- let creation_date: [u8; 9] = read_bytes(&mut d.r)?;
- let creation_time: [u8; 8] = read_bytes(&mut d.r)?;
- let file_label: [u8; 64] = read_bytes(&mut d.r)?;
- let file_label = trim_end(Vec::from(file_label), b' ');
- d.skip_bytes(3)?;
-
- println!("File header record:");
- println!("{:>17}: {}", "Product name", String::from_utf8_lossy(&eye_catcher));
- println!("{:>17}: {}", "Layout code", layout_code);
- println!("{:>17}: {} ({})", "Compressed", compressed, match compression {
- None => "no compression",
- Some(Compression::Simple) => "simple compression",
- Some(Compression::ZLib) => "ZLIB compression",
- });
- println!("{:>17}: {}", "Weight index", weight_index);
- println!("{:>17}: {}", "Number of cases", n_cases);
- println!("{:>17}: {}", "Compression bias", bias);
- println!("{:>17}: {}", "Creation date", String::from_utf8_lossy(&creation_date));
- println!("{:>17}: {}", "Creation time", String::from_utf8_lossy(&creation_time));
- println!("{:>17}: \"{}\"", "File label", String::from_utf8_lossy(&file_label));
-
- loop {
- let rec_type: u32 = d.read_swap()?;
- match rec_type {
- 2 => d.read_variable_record()?,
- 3 => d.read_value_label_record()?,
- 4 => Err(anyhow!("Misplaced type 4 record."))?,
- 6 => d.read_document_record()?,
- 7 => d.read_extension_record()?,
- 999 => break,
- _ => Err(anyhow!("Unrecognized record type {rec_type}."))?
- }
- }
-
- let pos = d.r.stream_position()?;
- println!("{:08x}: end-of-dictionary record (first byte of data at {:0x})", pos, pos + 4);
-
- Ok(d)
- }
-
- fn read_extension_record(&mut self) -> Result<()> {
- let offset = self.r.stream_position()?;
- let subtype: u32 = self.read_swap()?;
- let size: u32 = self.read_swap()?;
- let count: u32 = self.read_swap()?;
- println!("{offset:08x}: Record 7, subtype {subtype}, size={size}, count={count}");
- match subtype {
- 3 => self.read_machine_integer_info(size, count),
- 4 => self.read_machine_float_info(size, count),
- _ => self.read_unknown_extension(subtype, size, count),
- }
- }
-
- fn warn(&mut self, s: String) -> Result<()> {
- println!("\"{}\" near offset 0x{:08x}: {s}", self.filename, self.r.stream_position()?);
- Ok(())
- }
-
- fn skip_bytes(&mut self, mut n: u64) -> Result<()> {
- let mut buf = [0; 1024];
- while n > 0 {
- let chunk = u64::min(n, buf.len() as u64);
- self.r.read_exact(&mut buf[0..chunk as usize])?;
- n -= chunk;
- }
- Ok(())
- }
-
- fn read_unknown_extension(&mut self, subtype: u32, size: u32, count: u32) -> Result<()> {
- self.warn(format!("Unrecognized record type 7, subtype {subtype}."))?;
- if size == 0 || count > 65536 / size {
- self.skip_bytes(size as u64 * count as u64)?;
- } else if size != 1 {
- let mut offset = 0;
- for _ in 0..count {
- let vec = read_vec(&mut self.r, size as usize)?;
- println!("{}", HexViewBuilder::new(&vec).address_offset(offset).finish());
- offset += size as usize;
- }
- }
- Ok(())
- }
-
- fn read_variable_record(&mut self) -> Result<()> {
- self.n_variable_records += 1;
- println!("{:08x}: variable record {}", self.r.stream_position()?, self.n_variable_records);
- let width: i32 = self.read_swap()?;
- let has_variable_label: u32 = self.read_swap()?;
- let missing_value_code: i32 = self.read_swap()?;
- let print_format: u32 = self.read_swap()?;
- let write_format: u32 = self.read_swap()?;
- let name: [u8; 8] = read_bytes(&mut self.r)?;
- let name: Vec<u8> = trim_end(Vec::from(name), b'\0');
-
- if width >= 0 {
- self.n_variables += 1;
- }
- self.var_widths.push(width);
-
- println!("\tWidth: {width} ({})", match width {
- _ if width > 0 => "string",
- _ if width == 0 => "numeric",
- _ => "long string continuation record"
- });
-
- println!("\tVariable label: {has_variable_label}");
- println!("\tMissing values code: {missing_value_code} ({})",
- match missing_value_code {
- 0 => "no missing values",
- 1 => "one missing value",
- 2 => "two missing values",
- 3 => "three missing values",
- -2 => "one missing value range",
- -3 => "one missing value, one range",
- _ => "bad value"
- });
- for (which, format) in [("Print", print_format),
- ("Worite", write_format)] {
- let type_ = format_name(format >> 16);
- let w = (format >> 8) & 0xff;
- let d = format & 0xff;
- println!("\t{which} format: {format:06x} ({type_}{w}.{d})");
- }
- println!("\tName: {}", String::from_utf8_lossy(&name));
-
- // Read variable label.
- match has_variable_label {
- 0 => (),
- 1 => {
- let offset = self.r.stream_position()?;
- let len: u32 = self.read_swap()?;
- let read_len = len.min(65535) as usize;
- let label = read_vec(&mut self.r, read_len)?;
- println!("\t{offset:08x} Variable label: \"{}\"", String::from_utf8_lossy(&label));
-
- self.skip_bytes((round_up(len, 4) - len).into())?;
- },
- _ => Err(anyhow!("Variable label indicator field is not 0 or 1."))?,