work
[pspp] / rust / src / raw.rs
index ac2d1960acab3c9f03cfcc682666699acfbe748d..18e71d3623f5e4d98de552cc8653100f5b631f23 100644 (file)
@@ -152,6 +152,12 @@ pub enum Error {
     #[error("Text string contains invalid bytes for {encoding} encoding: {text}")]
     MalformedString { encoding: String, text: String },
 
+    #[error("Invalid variable measurement level value {0}")]
+    InvalidMeasurement(u32),
+
+    #[error("Invalid variable display alignment value {0}")]
+    InvalidAlignment(u32),
+
     #[error("Details TBD")]
     TBD,
 }
@@ -198,7 +204,7 @@ impl Record {
             2 => Ok(Some(VariableRecord::read(reader, endian)?)),
             3 => Ok(ValueLabelRecord::read(reader, endian, var_types, warn)?),
             6 => Ok(Some(DocumentRecord::read(reader, endian)?)),
-            7 => Extension::read(reader, endian, warn),
+            7 => Extension::read(reader, endian, var_types.len(), warn),
             999 => Ok(Some(Record::EndOfHeaders(
                 endian.parse(read_bytes(reader)?),
             ))),
@@ -403,7 +409,7 @@ impl Decoder {
         }
         output
     }
-        
+
     fn decode<'a>(&self, input: &'a RawString) -> Cow<'a, str> {
         self.decode_slice(input.0.as_slice())
     }
@@ -1464,7 +1470,11 @@ impl DocumentRecord<RawDocumentLine> {
     fn decode<'a>(&'a self, decoder: &Decoder) -> DocumentRecord<Cow<'a, str>> {
         DocumentRecord {
             offsets: self.offsets.clone(),
-            lines: self.lines.iter().map(|s| decoder.decode_slice(&s.0)).collect(),
+            lines: self
+                .lines
+                .iter()
+                .map(|s| decoder.decode_slice(&s.0))
+                .collect(),
         }
     }
 }
@@ -1692,23 +1702,97 @@ fn parse_counted_string(input: &[u8]) -> Result<(RawString, &[u8]), Error> {
     Ok((string.into(), rest))
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Measure {
+    Nominal,
+    Ordinal,
+    Scale,
+}
+
+impl Measure {
+    fn try_decode(source: u32) -> Result<Option<Measure>, Error> {
+        match source {
+            0 => Ok(None),
+            1 => Ok(Some(Measure::Nominal)),
+            2 => Ok(Some(Measure::Ordinal)),
+            3 => Ok(Some(Measure::Scale)),
+            _ => Err(Error::InvalidMeasurement(source)),
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Alignment {
+    Left,
+    Right,
+    Center,
+}
+
+impl Alignment {
+    fn try_decode(source: u32) -> Result<Option<Alignment>, Error> {
+        match source {
+            0 => Ok(None),
+            1 => Ok(Some(Alignment::Left)),
+            2 => Ok(Some(Alignment::Right)),
+            3 => Ok(Some(Alignment::Center)),
+            _ => Err(Error::InvalidAlignment(source)),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct VarDisplay {
+    pub measure: Option<Measure>,
+    pub width: Option<u32>,
+    pub alignment: Option<Alignment>,
+}
+
 #[derive(Clone, Debug)]
-pub struct VarDisplayRecord(pub Vec<u32>);
+pub struct VarDisplayRecord(pub Vec<VarDisplay>);
 
-impl ExtensionRecord for VarDisplayRecord {
+impl VarDisplayRecord {
     const SUBTYPE: u32 = 11;
-    const SIZE: Option<u32> = Some(4);
-    const COUNT: Option<u32> = None;
-    const NAME: &'static str = "variable display record";
 
-    fn parse(ext: &Extension, endian: Endian) -> Result<Record, Error> {
-        ext.check_size::<Self>()?;
+    fn parse(
+        ext: &Extension,
+        n_vars: usize,
+        endian: Endian,
+        warn: &Box<dyn Fn(Error)>,
+    ) -> Result<Record, Error> {
+        if ext.size != 4 {
+            return Err(Error::BadRecordSize {
+                offset: ext.offsets.start,
+                record: String::from("variable display record"),
+                size: ext.size,
+                expected_size: 4,
+            });
+        }
 
+        let has_width = if ext.count as usize == 3 * n_vars {
+            true
+        } else if ext.count as usize == 2 * n_vars {
+            false
+        } else {
+            return Err(Error::TBD);
+        };
+
+        let mut var_displays = Vec::new();
         let mut input = &ext.data[..];
-        let display = (0..ext.count)
-            .map(|_| endian.parse(read_bytes(&mut input).unwrap()))
-            .collect();
-        Ok(Record::VarDisplay(VarDisplayRecord(display)))
+        for _ in 0..n_vars {
+            let measure = Measure::try_decode(endian.parse(read_bytes(&mut input).unwrap()))
+                .warn_on_error(&warn)
+                .flatten();
+            let width = has_width.then(|| endian.parse(read_bytes(&mut input).unwrap()));
+            let alignment = Alignment::try_decode(endian.parse(read_bytes(&mut input).unwrap()))
+                .warn_on_error(&warn)
+                .flatten();
+            var_displays.push(VarDisplay {
+                measure,
+                width,
+                alignment,
+            });
+        }
+        Ok(Record::VarDisplay(VarDisplayRecord(var_displays)))
     }
 }
 
@@ -1859,7 +1943,7 @@ impl VariableSet {
 #[derive(Clone, Debug)]
 pub struct VariableSetRecord {
     pub offsets: Range<u64>,
-    pub sets: Vec<VariableSet>
+    pub sets: Vec<VariableSet>,
 }
 
 impl VariableSetRecord {
@@ -1871,7 +1955,10 @@ impl VariableSetRecord {
                 sets.push(set)
             }
         }
-        VariableSetRecord { offsets: source.offsets.clone(), sets }
+        VariableSetRecord {
+            offsets: source.offsets.clone(),
+            sets,
+        }
     }
 }
 
@@ -1935,6 +2022,7 @@ impl Extension {
     fn read<R: Read + Seek>(
         r: &mut R,
         endian: Endian,
+        n_vars: usize,
         warn: &Box<dyn Fn(Error)>,
     ) -> Result<Option<Record>, Error> {
         let subtype = endian.parse(read_bytes(r)?);
@@ -1962,7 +2050,7 @@ impl Extension {
         let result = match subtype {
             IntegerInfoRecord::SUBTYPE => IntegerInfoRecord::parse(&extension, endian),
             FloatInfoRecord::SUBTYPE => FloatInfoRecord::parse(&extension, endian),
-            VarDisplayRecord::SUBTYPE => VarDisplayRecord::parse(&extension, endian),
+            VarDisplayRecord::SUBTYPE => VarDisplayRecord::parse(&extension, n_vars, endian, warn),
             MultipleResponseRecord::SUBTYPE | 19 => {
                 MultipleResponseRecord::parse(&extension, endian)
             }