work
[pspp] / rust / src / raw.rs
index f0e8c540c2ac170f6fe18682bd8358ba817bc85d..ca0596f5414aecdb378b38d1e5e8cd3824fe6a18 100644 (file)
@@ -3,6 +3,7 @@ use crate::Error;
 
 use flate2::read::ZlibDecoder;
 use num::Integer;
+use std::fmt::{Debug, Formatter, Result as FmtResult};
 use std::str::from_utf8;
 use std::{
     collections::VecDeque,
@@ -18,6 +19,7 @@ pub enum Compression {
     ZLib,
 }
 
+#[derive(Clone, Debug)]
 pub enum Record {
     Header(Header),
     Document(Document),
@@ -49,6 +51,27 @@ impl Record {
     }
 }
 
+pub struct FallbackEncoding<'a>(&'a [u8]);
+
+impl<'a> Debug for FallbackEncoding<'a> {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        if let Ok(s) = from_utf8(self.0) {
+            let s = s.trim_end();
+            write!(f, "\"{s}\"")
+        } else {
+            let s: String = self
+                .0
+                .iter()
+                .map(|c| char::from(*c).escape_default())
+                .flatten()
+                .collect();
+            let s = s.trim_end();
+            write!(f, "\"{s}\"")
+        }
+    }
+}
+
+#[derive(Clone)]
 pub struct Header {
     /// Magic number.
     pub magic: Magic,
@@ -90,6 +113,30 @@ pub struct Header {
     pub endian: Endian,
 }
 
+impl Header {
+    fn debug_field<T: Debug>(&self, f: &mut Formatter, name: &str, value: T) -> FmtResult {
+        writeln!(f, "{name:>17}: {:?}", value)
+    }
+}
+
+impl Debug for Header {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        writeln!(f, "File header record:")?;
+        self.debug_field(f, "Magic", self.magic)?;
+        self.debug_field(f, "Product name", FallbackEncoding(&self.eye_catcher))?;
+        self.debug_field(f, "Layout code", self.layout_code)?;
+        self.debug_field(f, "Nominal case size", self.nominal_case_size)?;
+        self.debug_field(f, "Compression", self.compression)?;
+        self.debug_field(f, "Weight index", self.weight_index)?;
+        self.debug_field(f, "Number of cases", self.n_cases)?;
+        self.debug_field(f, "Compression bias", self.bias)?;
+        self.debug_field(f, "Creation date", FallbackEncoding(&self.creation_date))?;
+        self.debug_field(f, "Creation time", FallbackEncoding(&self.creation_time))?;
+        self.debug_field(f, "File label", FallbackEncoding(&self.file_label))?;
+        self.debug_field(f, "Endianness", self.endian)
+    }
+}
+
 impl Header {
     fn read<R: Read>(r: &mut R) -> Result<Header, Error> {
         let magic: [u8; 4] = read_bytes(r)?;
@@ -116,7 +163,7 @@ impl Header {
         };
 
         let weight_index: u32 = endian.parse(read_bytes(r)?);
-        let weight_index = (weight_index > 0).then_some(weight_index - 1);
+        let weight_index = (weight_index > 0).then(|| weight_index - 1);
 
         let n_cases: u32 = endian.parse(read_bytes(r)?);
         let n_cases = (n_cases < i32::MAX as u32 / 2).then_some(n_cases);
@@ -160,6 +207,18 @@ impl Magic {
     pub const EBCDIC: Magic = Magic([0x5b, 0xc6, 0xd3, 0xf2]);
 }
 
+impl Debug for Magic {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        let s = match self {
+            &Magic::SAV => "$FL2",
+            &Magic::ZSAV => "$FL3",
+            &Magic::EBCDIC => "($FL2 in EBCDIC)",
+            _ => return write!(f, "{:?}", self.0),
+        };
+        write!(f, "{s}")
+    }
+}
+
 impl TryFrom<[u8; 4]> for Magic {
     type Error = Error;
 
@@ -336,7 +395,21 @@ pub enum Value {
     String([u8; 8]),
 }
 
+impl Debug for Value {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        match self {
+            Value::Number(Some(number)) => write!(f, "{number:?}"),
+            Value::Number(None) => write!(f, "SYSMIS"),
+            Value::String(bytes) => write!(f, "{:?}", FallbackEncoding(bytes)),
+        }
+    }
+}
+
 impl Value {
+    fn read<R: Read>(r: &mut R, var_type: VarType, endian: Endian) -> Result<Value, IoError> {
+        Ok(Self::from_raw(var_type, read_bytes(r)?, endian))
+    }
+
     pub fn from_raw(var_type: VarType, raw: [u8; 8], endian: Endian) -> Value {
         match var_type {
             VarType::String => Value::String(raw),
@@ -517,6 +590,132 @@ impl Iterator for Reader {
 
 impl FusedIterator for Reader {}
 
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+pub struct Format(pub u32);
+
+impl Debug for Format {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        let type_ = format_name(self.0 >> 16);
+        let w = (self.0 >> 8) & 0xff;
+        let d = self.0 & 0xff;
+        write!(f, "{:06x} ({type_}{w}.{d})", self.0)
+    }
+}
+
+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",
+        _ => "(unknown)",
+    }
+}
+
+#[derive(Clone)]
+pub struct MissingValues {
+    /// Individual missing values, up to 3 of them.
+    pub values: Vec<Value>,
+
+    /// Optional range of missing values.
+    pub range: Option<(Value, Value)>,
+}
+
+impl Debug for MissingValues {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        for (i, value) in self.values.iter().enumerate() {
+            if i > 0 {
+                write!(f, ", ")?;
+            }
+            write!(f, "{value:?}")?;
+        }
+
+        if let Some((low, high)) = self.range {
+            if !self.values.is_empty() {
+                write!(f, ", ")?;
+            }
+            write!(f, "{low:?} THRU {high:?}")?;
+        }
+
+        if self.is_empty() {
+            write!(f, "none")?;
+        }
+
+        Ok(())
+    }
+}
+
+impl MissingValues {
+    fn is_empty(&self) -> bool {
+        self.values.is_empty() && self.range.is_none()
+    }
+
+    fn read<R: Read + Seek>(
+        r: &mut R,
+        offset: u64,
+        width: i32,
+        code: i32,
+        endian: Endian,
+    ) -> Result<MissingValues, Error> {
+        let (n_values, has_range) = match (width, code) {
+            (_, 0..=3) => (code, false),
+            (0, -2) => (0, true),
+            (0, -3) => (1, true),
+            (0, _) => return Err(Error::BadNumericMissingValueCode { offset, code }),
+            (_, _) => return Err(Error::BadStringMissingValueCode { offset, code }),
+        };
+
+        let var_type = VarType::from_width(width);
+
+        let mut values = Vec::new();
+        for _ in 0..n_values {
+            values.push(Value::read(r, var_type, endian)?);
+        }
+        let range = if has_range {
+            let low = Value::read(r, var_type, endian)?;
+            let high = Value::read(r, var_type, endian)?;
+            Some((low, high))
+        } else {
+            None
+        };
+        Ok(MissingValues { values, range })
+    }
+}
+
+#[derive(Clone)]
 pub struct Variable {
     /// Offset from the start of the file to the start of the record.
     pub offset: u64,
@@ -533,16 +732,41 @@ pub struct Variable {
     /// Write format.
     pub write_format: u32,
 
-    /// Missing value code, one of -3, -2, 0, 1, 2, or 3.
-    pub missing_value_code: i32,
-
-    /// Raw missing values, up to 3 of them.
-    pub missing: Vec<[u8; 8]>,
+    /// Missing values.
+    pub missing_values: MissingValues,
 
     /// Optional variable label.
     pub label: Option<Vec<u8>>,
 }
 
+impl Debug for Variable {
+    fn fmt(&self, f: &mut Formatter) -> FmtResult {
+        writeln!(
+            f,
+            "Width: {} ({})",
+            self.width,
+            if self.width > 0 {
+                "string"
+            } else if self.width == 0 {
+                "numeric"
+            } else {
+                "long string continuation record"
+            }
+        )?;
+        writeln!(f, "Print format: {:?}", Format(self.print_format))?;
+        writeln!(f, "Write format: {:?}", Format(self.write_format))?;
+        writeln!(f, "Name: {:?}", FallbackEncoding(&self.name))?;
+        writeln!(
+            f,
+            "Variable label: {:?}",
+            self.label
+                .as_ref()
+                .map(|label| FallbackEncoding(&label[..]))
+        )?;
+        writeln!(f, "Missing values: {:?}", self.missing_values)
+    }
+}
+
 impl Variable {
     fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Variable, Error> {
         let offset = r.stream_position()?;
@@ -573,29 +797,7 @@ impl Variable {
             }
         };
 
-        let mut missing = Vec::new();
-        if missing_value_code != 0 {
-            match (width, missing_value_code) {
-                (0, -3 | -2 | 1 | 2 | 3) => (),
-                (0, _) => {
-                    return Err(Error::BadNumericMissingValueCode {
-                        offset,
-                        code: missing_value_code,
-                    })
-                }
-                (_, 0..=3) => (),
-                (_, _) => {
-                    return Err(Error::BadStringMissingValueCode {
-                        offset,
-                        code: missing_value_code,
-                    })
-                }
-            }
-
-            for _ in 0..missing_value_code.abs() {
-                missing.push(read_bytes(r)?);
-            }
-        }
+        let missing_values = MissingValues::read(r, offset, width, missing_value_code, endian)?;
 
         Ok(Variable {
             offset,
@@ -603,13 +805,13 @@ impl Variable {
             name,
             print_format,
             write_format,
-            missing_value_code,
-            missing,
+            missing_values,
             label,
         })
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct ValueLabel {
     /// Offset from the start of the file to the start of the record.
     pub offset: u64,
@@ -648,6 +850,7 @@ impl ValueLabel {
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct VarIndexes {
     /// Offset from the start of the file to the start of the record.
     pub offset: u64,
@@ -682,6 +885,7 @@ impl VarIndexes {
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct Document {
     /// Offset from the start of the file to the start of the record.
     pub pos: u64,
@@ -1348,6 +1552,7 @@ impl ExtensionRecord for NumberOfCasesRecord {
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct Extension {
     /// Offset from the start of the file to the start of the record.
     pub offset: u64,
@@ -1443,6 +1648,7 @@ impl Extension {
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct ZHeader {
     /// File offset to the start of the record.
     pub offset: u64,
@@ -1473,6 +1679,7 @@ impl ZHeader {
     }
 }
 
+#[derive(Clone, Debug)]
 pub struct ZTrailer {
     /// File offset to the start of the record.
     pub offset: u64,
@@ -1491,6 +1698,7 @@ pub struct ZTrailer {
     pub blocks: Vec<ZBlock>,
 }
 
+#[derive(Clone, Debug)]
 pub struct ZBlock {
     /// Offset of block of data if simple compression were used.
     pub uncompressed_ofs: u64,