work
[pspp] / rust / src / main.rs
index fbe11c5dfb230a569ad9a55ec2bc53899507249c..8085957daad73d8b57df657ce3779855438d8b0a 100644 (file)
@@ -1,28 +1,26 @@
 /* PSPP - a program for statistical analysis.
  Copyright (C) 2023 Free Software Foundation, Inc.
-
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
-
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
-
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-use anyhow::{anyhow, Result};
* Copyright (C) 2023 Free Software Foundation, Inc.
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
+ *
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+use anyhow::Result;
 use clap::Parser;
-use hexplay::HexViewBuilder;
-use num::{Float, Num};
-use std::{fmt, num::FpCategory};
+use pspp::{raw::{Reader, Record}, locale_charset::locale_charset};
 use std::fs::File;
-use std::io::prelude::*;
 use std::io::BufReader;
 use std::path::{Path, PathBuf};
+use std::str;
 
 /// A utility to dissect SPSS system files.
 #[derive(Parser, Debug)]
@@ -30,703 +28,41 @@ use std::path::{Path, PathBuf};
 struct Args {
     /// Maximum number of cases to print.
     #[arg(long = "data", default_value_t = 0)]
-    max_cases: usize,
+    max_cases: u64,
 
     /// Files to dissect.
     #[arg(required = true)]
-    files: Vec<PathBuf>
+    files: Vec<PathBuf>,
 }
 
 fn main() -> Result<()> {
+    println!("locale_charset={}", locale_charset());
     let Args { max_cases, files } = Args::parse();
 
     for file in files {
-        Dissector::new(file)?;
+        dissect(&file, max_cases)?;
     }
     Ok(())
 }
 
-#[derive(Copy, Clone, Debug)]
-enum Compression {
-    Simple,
-    ZLib
-}
-
-#[derive(Copy, Clone, Debug)]
-enum Endianness {
-    BigEndian,
-    LittleEndian
-}
-use Endianness::*;
-
-trait Parse<T, const N: usize> {
-    fn parse(self, bytes: [u8; N]) -> T;
-}
-impl Parse<u64, 8> for Endianness {
-    fn parse(self, bytes: [u8; 8]) -> u64 {
-        match self {
-            BigEndian => u64::from_be_bytes(bytes),
-            LittleEndian => u64::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<u32, 4> for Endianness {
-    fn parse(self, bytes: [u8; 4]) -> u32 {
-        match self {
-            BigEndian => u32::from_be_bytes(bytes),
-            LittleEndian => u32::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<u16, 2> for Endianness {
-    fn parse(self, bytes: [u8; 2]) -> u16 {
-        match self {
-            BigEndian => u16::from_be_bytes(bytes),
-            LittleEndian => u16::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<u8, 1> for Endianness {
-    fn parse(self, bytes: [u8; 1]) -> u8 {
-        match self {
-            BigEndian => u8::from_be_bytes(bytes),
-            LittleEndian => u8::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<i64, 8> for Endianness {
-    fn parse(self, bytes: [u8; 8]) -> i64 {
-        match self {
-            BigEndian => i64::from_be_bytes(bytes),
-            LittleEndian => i64::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<i32, 4> for Endianness {
-    fn parse(self, bytes: [u8; 4]) -> i32 {
-        match self {
-            BigEndian => i32::from_be_bytes(bytes),
-            LittleEndian => i32::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<i16, 2> for Endianness {
-    fn parse(self, bytes: [u8; 2]) -> i16 {
-        match self {
-            BigEndian => i16::from_be_bytes(bytes),
-            LittleEndian => i16::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<i8, 1> for Endianness {
-    fn parse(self, bytes: [u8; 1]) -> i8 {
-        match self {
-            BigEndian => i8::from_be_bytes(bytes),
-            LittleEndian => i8::from_le_bytes(bytes)
-        }
-    }
-}
-impl Parse<f64, 8> for Endianness {
-    fn parse(self, bytes: [u8; 8]) -> f64 {
-        match self {
-            BigEndian => f64::from_be_bytes(bytes),
-            LittleEndian => f64::from_le_bytes(bytes)
-        }
-    }
-}
-
-fn read_bytes<const N: usize>(r: &mut BufReader<File>) -> Result<[u8; N]> {
-    let mut buf = [0; N];
-    r.read_exact(&mut buf)?;
-    Ok(buf)
-}
-
-fn read_vec(r: &mut BufReader<File>, n: usize) -> Result<Vec<u8>> {
-    let mut vec = Vec::with_capacity(n);
-    vec.resize(n, 0);
-    r.read_exact(&mut vec)?;
-    Ok(vec)
-}    
-
-trait ReadSwap<T> {
-    fn read_swap(&mut self) -> Result<T>;
-}
-
-impl ReadSwap<u32> for Dissector {
-    fn read_swap(&mut self) -> Result<u32> {
-        Ok(self.endianness.parse(read_bytes(&mut self.r)?))
-    }
-}
-impl ReadSwap<u8> for Dissector {
-    fn read_swap(&mut self) -> Result<u8> {
-        Ok(self.endianness.parse(read_bytes(&mut self.r)?))
-    }
-}
-
-impl ReadSwap<i32> for Dissector {
-    fn read_swap(&mut self) -> Result<i32> {
-        Ok(self.endianness.parse(read_bytes(&mut self.r)?))
-    }
-}
-
-impl ReadSwap<f64> for Dissector {
-    fn read_swap(&mut self) -> Result<f64> {
-        Ok(self.endianness.parse(read_bytes(&mut self.r)?))
-    }
-}
-
-struct Dissector {
-    filename: String,
-    r: BufReader<File>,
-    compression: Option<Compression>,
-    endianness: Endianness,
-    fp_format: Endianness,
-    bias: f64,
-    n_variable_records: usize,
-    n_variables: usize,
-    var_widths: Vec<i32>,
-}
-
-fn detect_endianness(layout_code: [u8; 4]) -> Option<Endianness> {
-    for endianness in [BigEndian, LittleEndian] {
-        match endianness.parse(layout_code) {
-            2 | 3 => return Some(endianness),
-            _ => ()
-        }
-    }
-    None
-}
-
-fn detect_fp_format(bias: [u8; 8]) -> Option<Endianness> {
-    for endianness in [BigEndian, LittleEndian] {
-        let value: f64 = endianness.parse(bias);
-        if value == 100.0 {
-            return Some(endianness)
-        }
-    }
-    None
-}
-
-fn trim_end(mut s: Vec<u8>, c: u8) -> Vec<u8> {
-    while s.last() == Some(&c) {
-        s.pop();
-    }
-    s
-}
-
-fn slice_trim_end(mut s: &[u8], c: u8) -> &[u8] {
-    while s.last() == Some(&c) {
-        s = s.split_last().unwrap().1;
-    }
-    s
-}
-
-fn format_name(type_: u32) -> &'static str {
-    match type_ {
-        1 => "A",
-        2 => "AHEX",
-        3 => "COMMA",
-        4 => "DOLLAR",
-        5 => "F",
-        6 => "IB",
-        7 => "PIBHEX",
-        8 => "P",
-        9 => "PIB",
-        10 => "PK",
-        11 => "RB",
-        12 => "RBHEX",
-        15 => "Z",
-        16 => "N",
-        17 => "E",
-        20 => "DATE",
-        21 => "TIME",
-        22 => "DATETIME",
-        23 => "ADATE",
-        24 => "JDATE",
-        25 => "DTIME",
-        26 => "WKDAY",
-        27 => "MONTH",
-        28 => "MOYR",
-        29 => "QYR",
-        30 => "WKYR",
-        31 => "PCT",
-        32 => "DOT",
-        33 => "CCA",
-        34 => "CCB",
-        35 => "CCC",
-        36 => "CCD",
-        37 => "CCE",
-        38 => "EDATE",
-        39 => "SDATE",
-        40 => "MTIME",
-        41 => "YMDHMS",
-        _ => "invalid"
-    }
-}
-
-fn round_up<T: Num + Copy>(x: T, y: T) -> T
-{
-    (x + (y - T::one())) / y * y
-}
-
-struct UntypedValue {
-    raw: [u8; 8],
-    endianness: Endianness
-}
+fn dissect(file_name: &Path, max_cases: u64) -> Result<()> {
+    let reader = File::open(file_name)?;
+    let reader = BufReader::new(reader);
+    let mut reader = Reader::new(reader)?;
+    let records: Vec<Record> = reader.collect_headers()?;
 
-impl UntypedValue {
-    fn new(raw: [u8; 8], endianness: Endianness) -> UntypedValue {
-        UntypedValue { raw, endianness }
-    }
-}
-
-impl fmt::Display for UntypedValue {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let numeric: f64 = self.endianness.parse(self.raw);
-        let n_printable = self.raw.iter().take_while(|&&x| x == b' ' || x.is_ascii_graphic()).count();
-        let printable_prefix = std::str::from_utf8(&self.raw[0..n_printable]).unwrap();
-        write!(f, "{numeric}/\"{printable_prefix}\"")
-    }
-}
-
-struct HexFloat<T: Float>(T);
-
-impl<T: Float> fmt::Display for HexFloat<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let sign = if self.0.is_sign_negative() { "-" } else { "" };
-        match self.0.classify() {
-            FpCategory::Nan => return write!(f, "NaN"),
-            FpCategory::Infinite => return write!(f, "{sign}Infinity"),
-            FpCategory::Zero => return write!(f, "{sign}0.0"),
-            _ => (),
+    for record in records {
+        println!("{record:?}");
+        if let Record::EndOfHeaders(_) = record {
+            break;
         };
-        let (significand, mut exponent, _) = self.0.integer_decode();
-        let mut hex_sig = format!("{:x}", significand);
-        while hex_sig.ends_with('0') {
-            hex_sig.pop();
-            exponent += 4;
-        }
-        match hex_sig.len() {
-            0 => write!(f, "{sign}0.0"),
-            1 => write!(f, "{sign}0x{hex_sig}.0p{exponent}"),
-            len => write!(f, "{sign}0x{}.{}p{}",
-                          hex_sig.chars().nth(0).unwrap(),
-                          &hex_sig[1..],
-                          exponent + 4 * (len as i16 - 1))
-        }
     }
-}
 
-#[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."))?,
+    for _ in 0..max_cases {
+        let Some(Ok(Record::Case(data))) = reader.next() else {
+            break;
         };
-
-        // Read missing values.
-        if missing_value_code != 0 {
-            print!("\t{:08x} Missing values:", self.r.stream_position()?);
-            if width == 0 {
-                let (has_range, n_individual) = match missing_value_code {
-                    -3 => (true, 1),
-                    -2 => (true, 0),
-                    1 | 2 | 3 => (false, missing_value_code),
-                    _ => Err(anyhow!("Numeric missing value indicator field is not -3, -2, 0, 1, 2, or 3."))?,
-                };
-                if has_range {
-                    let low: f64 = self.read_swap()?;
-                    let high: f64 = self.read_swap()?;
-                    print!(" {low}...{high}");
-                }
-                for _ in 0..n_individual {
-                    let value: f64 = self.read_swap()?;
-                    print!(" {value}");
-                }
-            } else if width > 0 {
-                if missing_value_code < 1 || missing_value_code > 3 {
-                    Err(anyhow!("String missing value indicator field is not 0, 1, 2, or 3."))?;
-                }
-                for _ in 0..missing_value_code {
-                    let string: [u8; 8] = read_bytes(&mut self.r)?;
-                    let string: Vec<u8> = trim_end(Vec::from(string), b'\0');
-                    println!(" {}", String::from_utf8_lossy(&string));
-                }
-            }
-            println!();
-        }
-
-        Ok(())
-    }
-
-    fn read_value_label_record(&mut self) -> Result<()> {
-        println!("{:08x}: value labels record", self.r.stream_position()?);
-
-        // Read the labels.
-        let n_labels: u32 = self.read_swap()?;
-        for _ in 0..n_labels {
-            let raw: [u8; 8] = read_bytes(&mut self.r)?;
-            let value = UntypedValue::new(raw, self.fp_format);
-            let label_len: u8 = self.read_swap()?;
-            let padded_len = round_up(label_len as usize + 1, 8);
-
-            let mut label = read_vec(&mut self.r, padded_len)?;
-            label.truncate(label_len as usize);
-            let label = String::from_utf8_lossy(&label);
-
-            println!("\t{value}: {label}");
-        }
-
-        // Read the type-4 record with the corresponding variable indexes.
-        let rec_type: u32 = self.read_swap()?;
-        if rec_type != 4 {
-            Err(anyhow!("Variable index record (type 4) does not immediately \
-                         follow value label record (type 3) as it should."))?;
-        }
-
-        println!("\t{:08x}: apply to variables", self.r.stream_position()?);
-        let n_vars: u32 = self.read_swap()?;
-        for _ in 0..n_vars {
-            let index: u32 = self.read_swap()?;
-            print!(" {index}");
-        }
-        println!();
-
-        Ok(())
-    }
-
-    fn read_document_record(&mut self) -> Result<()> {
-        println!("{:08x}: document record", self.r.stream_position()?);
-        let n_lines: u32 = self.read_swap()?;
-        println!("\t{n_lines} lines of documents");
-
-        for i in 0..n_lines {
-            print!("\t{:08x}: ", self.r.stream_position()?);
-            let line: [u8; 64] = read_bytes(&mut self.r)?;
-            let line = trim_end(Vec::from(line), b' ');
-            println!("line {i}: \"{}\"", String::from_utf8_lossy(&line));
-        }
-        Ok(())
-    }
-
-    fn read_machine_integer_info(&mut self, size: u32, count: u32) -> Result<()> {
-        let offset = self.r.stream_position()?;
-        let version_major: u32 = self.read_swap()?;
-        let version_minor: u32 = self.read_swap()?;
-        let version_revision: u32 = self.read_swap()?;
-        let machine_code: u32 = self.read_swap()?;
-        let float_representation: u32 = self.read_swap()?;
-        let compression_code: u32 = self.read_swap()?;
-        let integer_representation: u32 = self.read_swap()?;
-        let character_code: u32 = self.read_swap()?;
-
-        println!("{offset:08x}: machine integer info");
-        if size != 4 || count != 8 {
-            Err(anyhow!("Bad size ({size}) or count ({count}) field on record type 7, subtype 3"))?;
-        }
-        println!("\tVersion: {version_major}.{version_minor}.{version_revision}");
-        println!("\tMachine code: {machine_code}");
-        println!("\tFloating point representation: {float_representation} ({})",
-                 match float_representation {
-                     1 => "IEEE 754",
-                     2 => "IBM 370",
-                     3 => "DEC VAX",
-                     _ => "unknown"
-                 });
-        println!("\tCompression code: {compression_code}");
-        println!("\tEndianness: {integer_representation} ({})",
-                 match integer_representation {
-                     1 => "big",
-                     2 => "little",
-                     _ => "unknown"
-                 });
-        println!("\tCharacter code: {character_code}");
-        Ok(())
-    }
-
-    fn read_machine_float_info(&mut self, size: u32, count: u32) -> Result<()> {
-        let offset = self.r.stream_position()?;
-        let sysmis: f64 = self.read_swap()?;
-        let highest: f64 = self.read_swap()?;
-        let lowest: f64 = self.read_swap()?;
-
-        println!("{offset:08x}: machine float info");
-        if size != 4 || count != 8 {
-            Err(anyhow!("Bad size ({size}) or count ({count}) field on extension 4."))?;
-        }
-
-        println!("\tsysmis: {sysmis} ({})", HexFloat(sysmis));
-        println!("\thighest: {highest} ({})", HexFloat(highest));
-        println!("\tlowest: {lowest} ({})", HexFloat(lowest));
-        Ok(())
-    }
-
-    fn read_variable_sets(&mut self, size: u32, count: u32) -> Result<()> {
-        println!("{:08x}: variable sets", self.r.stream_position()?);
-        let mut text = self.open_text_record(size, count)?;
-        loop {
-            while text.match_byte(b'\n') {
-                continue;
-            }
-            let set = match text.tokenize(b'=') {
-                Some(set) => String::from_utf8_lossy(&set).into_owned(),
-                None => break,
-            };
-
-            // Always present even for an empty set.
-            text.match_byte(b' ');
-
-            match text.tokenize(b'\n') {
-                None => println!("\tset \"{set}\" is empty"),
-                Some(variables) => {
-                    println!("\tset \"{set}\" contains \"{}\"", String::from_utf8_lossy(variables).trim_end_matches('\r'));
-                },
-            };
-            
-        }
-        Ok(())
-    }
-
-    fn read_extra_product_info(&mut self, size: u32, count: u32) -> Result<()> {
-        print!("{:08x}: extra product info", self.r.stream_position()?);
-        let mut text = self.open_text_record(size, count)?;
-
-    }
-
-    fn open_text_record(&mut self, size: u32, count: u32) -> Result<TextRecord> {
-        let n_bytes = match u32::checked_mul(size, count) {
-            Some(n) => n,
-            None => Err(anyhow!("Extension record too large."))?
-        };
-        Ok(TextRecord::new(read_vec(&mut self.r, n_bytes as usize)?))
-    }
-}
-
-struct TextRecord {
-    buffer: Vec<u8>,
-    pos: usize
-}
-
-impl TextRecord {
-    fn new(buffer: Vec<u8>) -> TextRecord {
-        TextRecord { buffer, pos: 0 }
-    }
-
-    fn tokenize<'a>(&'a mut self, delimiter: u8) -> Option<&'a [u8]> {
-        let mut start = self.pos;
-        while self.pos < self.buffer.len() && self.buffer[self.pos] != delimiter && self.buffer[self.pos] != 0 {
-            self.pos += 1
-        }
-        if start == self.pos {
-            None
-        } else {
-            Some(&self.buffer[start..self.pos])
-        }
-    }
-
-    fn match_byte(&mut self, c: u8) -> bool {
-        if self.pos < self.buffer.len() && self.buffer[self.pos] == c {
-            self.pos += 1;
-            true
-        } else {
-            false
-        }
+        println!("{:?}", data);
     }
+    Ok(())
 }