Use EncodedDatum all over
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Jul 2025 01:34:06 +0000 (18:34 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Jul 2025 01:34:06 +0000 (18:34 -0700)
rust/pspp/src/data.rs
rust/pspp/src/dictionary.rs
rust/pspp/src/format/display/mod.rs
rust/pspp/src/format/display/test.rs
rust/pspp/src/format/parse.rs
rust/pspp/src/main.rs
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/sys/cooked.rs
rust/pspp/src/sys/raw.rs
rust/pspp/src/sys/test.rs
rust/pspp/src/sys/write.rs

index c296c8a6afe08c48a245c9a367db4aa12b0b9196..b85033c4d72357f57fdf0d5ceb6ab78205762444 100644 (file)
@@ -44,6 +44,7 @@ use serde::{ser::SerializeTupleVariant, Serialize};
 use crate::{
     dictionary::{VarType, VarWidth},
     format::DisplayPlain,
+    sys::raw::RawDatum,
 };
 
 /// An owned string in an unspecified character encoding.
@@ -132,6 +133,12 @@ impl From<&[u8]> for RawString {
     }
 }
 
+impl From<EncodedString> for RawString {
+    fn from(value: EncodedString) -> Self {
+        Self(value.bytes)
+    }
+}
+
 impl Debug for RawString {
     fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
         <RawStr as Debug>::fmt(&*self, f)
@@ -264,7 +271,23 @@ pub enum EncodedDatum {
 }
 
 impl EncodedDatum {
-    /// Constructs a new numerical [Datum] for the system-missing value.
+    pub fn into_raw(self) -> Datum {
+        match self {
+            EncodedDatum::Number(number) => Datum::Number(number),
+            EncodedDatum::String(encoded_string) => Datum::String(encoded_string.into()),
+        }
+    }
+
+    pub fn as_raw(&self) -> Dat<'_> {
+        match self {
+            EncodedDatum::Number(number) => Dat::Number(*number),
+            EncodedDatum::String(encoded_string) => {
+                Dat::String(RawStr::from_bytes(encoded_string.as_bytes()))
+            }
+        }
+    }
+
+    /// Constructs a new numerical [EncodedDatum] for the system-missing value.
     pub const fn sysmis() -> Self {
         Self::Number(None)
     }
@@ -369,6 +392,24 @@ impl Serialize for EncodedDatum {
     }
 }
 
+impl From<f64> for EncodedDatum {
+    fn from(number: f64) -> Self {
+        Some(number).into()
+    }
+}
+
+impl From<Option<f64>> for EncodedDatum {
+    fn from(value: Option<f64>) -> Self {
+        Self::Number(value)
+    }
+}
+
+impl From<&str> for EncodedDatum {
+    fn from(value: &str) -> Self {
+        Self::String(EncodedString::from(value))
+    }
+}
+
 /// A borrowed [Datum] with a string encoding.
 #[derive(Copy, Clone)]
 pub enum EncodedDat<'a> {
@@ -466,6 +507,69 @@ impl<'a> PartialEq for EncodedDat<'a> {
 
 impl<'a> Eq for EncodedDat<'a> {}
 
+/// A borrowed [Datum].
+#[derive(Clone)]
+pub enum Dat<'a> {
+    /// A numeric value.
+    Number(
+        /// A number, or `None` for the system-missing value.
+        Option<f64>,
+    ),
+    /// A string value.
+    String(
+        /// The value, in the variable's encoding.
+        &'a RawStr,
+    ),
+}
+
+impl Dat<'_> {
+    /// Constructs a new numerical [Datum] for the system-missing value.
+    pub const fn sysmis() -> Self {
+        Self::Number(None)
+    }
+
+    /// Returns the number inside this datum, or `None` if this is a string
+    /// datum.
+    pub fn as_number(&self) -> Option<Option<f64>> {
+        match self {
+            Self::Number(number) => Some(*number),
+            Self::String(_) => None,
+        }
+    }
+
+    /// Returns the string inside this datum, or `None` if this is a numeric
+    /// datum.
+    pub fn as_string(&self) -> Option<&RawStr> {
+        match self {
+            Self::Number(_) => None,
+            Self::String(s) => Some(s),
+        }
+    }
+
+    pub fn as_encoded<'a>(&'a self, encoding: &'static Encoding) -> EncodedDat<'a> {
+        match self {
+            Self::Number(number) => EncodedDat::Number(*number),
+            Self::String(raw_string) => EncodedDat::String(raw_string.as_encoded(encoding)),
+        }
+    }
+
+    /// Returns the [VarType] corresponding to this datum.
+    pub fn var_type(&self) -> VarType {
+        match self {
+            Self::Number(_) => VarType::Numeric,
+            Self::String(_) => VarType::String,
+        }
+    }
+
+    /// Returns the [VarWidth] corresponding to this datum.
+    pub fn width(&self) -> VarWidth {
+        match self {
+            Self::Number(_) => VarWidth::Numeric,
+            Self::String(s) => VarWidth::String(s.len().try_into().unwrap()),
+        }
+    }
+}
+
 /// The value of a [Variable](crate::dictionary::Variable).
 #[derive(Clone)]
 pub enum Datum {
@@ -687,7 +791,7 @@ impl From<&[u8]> for Datum {
 
 /// A case in a data set.
 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
-pub struct Case(
+pub struct RawCase(
     /// One [Datum] per variable in the corresponding [Dictionary], in the same
     /// order.
     ///
@@ -695,6 +799,66 @@ pub struct Case(
     pub Vec<Datum>,
 );
 
+impl RawCase {
+    pub fn as_encoding(&self, encoding: &'static Encoding) -> Case<&'_ [Datum]> {
+        Case {
+            encoding,
+            data: &self.0,
+        }
+    }
+    pub fn with_encoding(self, encoding: &'static Encoding) -> Case<Vec<Datum>> {
+        Case {
+            encoding,
+            data: self.0,
+        }
+    }
+}
+
+pub struct Case<B>
+where
+    B: Borrow<[Datum]>,
+{
+    encoding: &'static Encoding,
+    data: B,
+}
+
+impl<B> Case<B>
+where
+    B: Borrow<[Datum]>,
+{
+    fn len(&self) -> usize {
+        self.data.borrow().len()
+    }
+}
+
+impl IntoIterator for Case<Vec<Datum>> {
+    type Item = EncodedDatum;
+
+    type IntoIter = CaseVecIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CaseVecIter {
+            encoding: self.encoding,
+            iter: self.data.into_iter(),
+        }
+    }
+}
+
+pub struct CaseVecIter {
+    encoding: &'static Encoding,
+    iter: std::vec::IntoIter<Datum>,
+}
+
+impl Iterator for CaseVecIter {
+    type Item = EncodedDatum;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter
+            .next()
+            .map(|datum| datum.with_encoding(self.encoding))
+    }
+}
+
 /// An owned string and its [Encoding].
 ///
 /// The string is not guaranteed to be valid in the encoding.
@@ -714,6 +878,14 @@ impl EncodedString {
         self.bytes.len()
     }
 
+    /// Returns this string recoded in UTF-8.  Invalid characters will be
+    /// replaced by [REPLACEMENT_CHARACTER].
+    ///
+    /// [REPLACEMENT_CHARACTER]: std::char::REPLACEMENT_CHARACTER
+    pub fn as_str(&self) -> Cow<'_, str> {
+        self.encoding.decode_without_bom_handling(&self.bytes).0
+    }
+
     /// Returns the bytes in the string, in its encoding.
     pub fn as_bytes(&self) -> &[u8] {
         &self.bytes
@@ -756,6 +928,15 @@ impl EncodedString {
     }
 }
 
+impl From<&str> for EncodedString {
+    fn from(value: &str) -> Self {
+        Self {
+            bytes: value.into(),
+            encoding: UTF_8,
+        }
+    }
+}
+
 impl<'a> From<&'a EncodedString> for EncodedStr<'a> {
     fn from(value: &'a EncodedString) -> Self {
         value.borrowed()
index d6b2015909e7b4330e37310f22b5aea7834a500d..30a31f915e912af75a6186a3fe5843221592b6ac 100644 (file)
@@ -1020,7 +1020,10 @@ impl<'a> OutputMrsets<'a> {
 
             let mr_type_name = match &mrset.mr_type {
                 MultipleResponseType::MultipleDichotomy { datum, .. } => {
-                    pt.insert(&[row, 2], Value::new_datum(datum, self.dictionary.encoding));
+                    pt.insert(
+                        &[row, 2],
+                        Value::new_datum(datum.as_encoded(self.dictionary.encoding)),
+                    );
                     "Dichotomies"
                 }
                 MultipleResponseType::MultipleCategory => "Categories",
index 3a956ee84e3d5feeb9727076451480222f52a0a9..d4638cf34aee132308cc33e718675a5b5e67216c 100644 (file)
@@ -29,7 +29,7 @@ use smallvec::{Array, SmallVec};
 
 use crate::{
     calendar::{calendar_offset_to_gregorian, day_of_year, month_name, short_month_name},
-    data::{Datum, QuotedEncodedDat},
+    data::{Datum, EncodedDat, EncodedDatum, QuotedEncodedDat},
     endian::{endian_to_smallvec, ToBytes},
     format::{Category, DateTemplate, Decimal, Format, NumberStyle, Settings, TemplateItem, Type},
     settings::{EndianSettings, Settings as PsppSettings},
@@ -39,8 +39,7 @@ pub struct DisplayDatum<'a, 'b> {
     format: Format,
     settings: &'b Settings,
     endian: EndianSettings,
-    datum: &'a Datum,
-    encoding: &'static Encoding,
+    datum: EncodedDat<'a>,
 
     /// If true, the output will remove leading and trailing spaces from numeric
     /// values, and trailing spaces from string values.  (This might make the
@@ -83,33 +82,46 @@ impl Display for DisplayPlainF64 {
     }
 }
 
-impl Datum {
-    /// Returns an object that implements [Display] for printing this [Datum] as
-    /// `format`.  `encoding` specifies this `Datum`'s encoding (therefore, it
-    /// is used only if this is a `Datum::String`).
+impl EncodedDat<'_> {
+    /// Returns an object that implements [Display] for printing this
+    /// [EncodedDatum] as `format`.
     ///
     /// [Display]: std::fmt::Display
-    pub fn display(&self, format: Format, encoding: &'static Encoding) -> DisplayDatum {
-        DisplayDatum::new(format, self, encoding)
+    pub fn display(&self, format: Format) -> DisplayDatum {
+        DisplayDatum::new(format, *self)
     }
 
-    pub fn display_plain(&self, encoding: &'static Encoding) -> QuotedEncodedDat<'_> {
-        self.as_encoded(encoding).quoted()
+    pub fn display_plain(&self) -> QuotedEncodedDat<'_> {
+        self.quoted()
+    }
+}
+
+impl EncodedDatum {
+    /// Returns an object that implements [Display] for printing this
+    /// [EncodedDatum] as `format`.
+    ///
+    /// [Display]: std::fmt::Display
+    pub fn display(&self, format: Format) -> DisplayDatum {
+        DisplayDatum::new(format, self.borrowed())
+    }
+
+    pub fn display_plain(&self) -> QuotedEncodedDat<'_> {
+        self.quoted()
     }
 }
 
 impl Display for DisplayDatum<'_, '_> {
     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
         let number = match self.datum {
-            Datum::Number(number) => *number,
-            Datum::String(string) => {
+            EncodedDat::Number(number) => number,
+            EncodedDat::String(string) => {
                 if self.format.type_() == Type::AHex {
-                    for byte in &string.0 {
+                    for byte in string.as_bytes() {
                         write!(f, "{byte:02x}")?;
                     }
                 } else {
                     let quote = if self.quote_strings { "\"" } else { "" };
-                    let s = self.encoding.decode_without_bom_handling(&string.0).0;
+                    let s = string.as_str();
                     let s = if self.trim_spaces {
                         s.trim_end_matches(' ')
                     } else {
@@ -161,12 +173,11 @@ impl Display for DisplayDatum<'_, '_> {
 }
 
 impl<'a, 'b> DisplayDatum<'a, 'b> {
-    pub fn new(format: Format, value: &'a Datum, encoding: &'static Encoding) -> Self {
+    pub fn new(format: Format, datum: EncodedDat<'a>) -> Self {
         let settings = PsppSettings::global();
         Self {
             format,
-            datum: value,
-            encoding,
+            datum,
             settings: &settings.formats,
             endian: settings.endian,
             trim_spaces: false,
index d9b4dd5f64ec6b82f2fc9b90d5b05697d1c9e9b8..6def5489b028d04d04f9dbe169a45346bea4b39c 100644 (file)
@@ -23,7 +23,7 @@ use smallstr::SmallString;
 use smallvec::SmallVec;
 
 use crate::{
-    data::Datum,
+    data::{Datum, EncodedDatum},
     endian::Endian,
     format::{AbstractFormat, Epoch, Format, Settings, Type, UncheckedFormat, CC},
     lex::{scan::StringScanner, segment::Syntax, Punct, Token},
@@ -75,8 +75,8 @@ fn test(name: &str) {
                 let format: Format = format.try_into().unwrap();
                 assert_eq!(tokens.get(1), Some(&Token::Punct(Punct::Colon)));
                 let expected = tokens[2].as_string().unwrap();
-                let actual = Datum::Number(value)
-                    .display(format, UTF_8)
+                let actual = EncodedDatum::Number(value)
+                    .display(format)
                     .with_settings(&settings)
                     .with_endian(endian)
                     .to_string();
@@ -183,11 +183,11 @@ fn leading_zeros() {
         }
 
         fn test_with_settings(value: f64, expected: [&str; 2], settings: &Settings) {
-            let value = Datum::from(value);
+            let value = EncodedDatum::from(value);
             for (expected, d) in expected.into_iter().zip([2, 1].into_iter()) {
                 assert_eq!(
                     &value
-                        .display(Format::new(Type::F, 5, d).unwrap(), UTF_8)
+                        .display(Format::new(Type::F, 5, d).unwrap())
                         .with_settings(settings)
                         .to_string(),
                     expected
@@ -214,8 +214,8 @@ fn leading_zeros() {
 fn non_ascii_cc() {
     fn test(settings: &Settings, value: f64, expected: &str) {
         assert_eq!(
-            &Datum::from(value)
-                .display(Format::new(Type::CC(CC::A), 10, 2).unwrap(), UTF_8)
+            &EncodedDatum::from(value)
+                .display(Format::new(Type::CC(CC::A), 10, 2).unwrap())
                 .with_settings(settings)
                 .to_string(),
             expected
@@ -266,8 +266,8 @@ fn test_binhex(name: &str) {
                 assert_eq!(tokens.get(1), Some(&Token::Punct(Punct::Colon)));
                 let expected = tokens[2].as_string().unwrap();
                 let mut actual = SmallVec::<[u8; 16]>::new();
-                Datum::Number(value)
-                    .display(format, UTF_8)
+                EncodedDatum::Number(value)
+                    .display(format)
                     .with_endian(endian)
                     .write(&mut actual, UTF_8)
                     .unwrap();
@@ -339,11 +339,8 @@ fn test_dates(format: Format, expect: &[&str]) {
     ];
     assert_eq!(expect.len(), INPUTS.len());
     for (input, expect) in INPUTS.iter().copied().zip_eq(expect.iter().copied()) {
-        let value = parser.parse(input).unwrap();
-        let formatted = value
-            .display(format, UTF_8)
-            .with_settings(&settings)
-            .to_string();
+        let value = parser.parse(input).unwrap().with_encoding(UTF_8);
+        let formatted = value.display(format).with_settings(&settings).to_string();
         assert_eq!(&formatted, expect);
     }
 }
@@ -1295,8 +1292,12 @@ fn test_times(format: Format, name: &str) {
         .zip_eq(output.lines().map(|r| r.unwrap()))
         .zip(1..)
     {
-        let value = parser.parse(&input).unwrap();
-        let formatted = value.display(format, UTF_8).to_string();
+        let formatted = parser
+            .parse(&input)
+            .unwrap()
+            .with_encoding(UTF_8)
+            .display(format)
+            .to_string();
         assert!(
                 formatted == expect,
                 "formatting {}:{line_number} as {format}:\n  actual: {formatted:?}\nexpected: {expect:?}",
index d2f6b22bc74d72cc093df9acd3a44428b9f7bf54..495037938bb7218135a1706a79015ed16bf74a08 100644 (file)
@@ -943,7 +943,8 @@ mod test {
             let error = result.clone().err();
             let value = result
                 .unwrap_or(type_.default_value())
-                .display(Format::new(Type::F, 10, 4).unwrap(), UTF_8)
+                .with_encoding(UTF_8)
+                .display(Format::new(Type::F, 10, 4).unwrap())
                 .to_string();
             if value != expected {
                 panic!(
index 972775998fa114ecc68b952a53cf2b4dc431cb58..2104cbfa204b78ca22274388b59630f9279ef953 100644 (file)
@@ -25,6 +25,7 @@ use pspp::{
         ReadOptions, Records,
     },
 };
+use serde::Serialize;
 use std::{
     fs::File,
     io::{stdout, BufReader, Write},
@@ -158,15 +159,9 @@ impl Convert {
                 }
 
                 for case in cases {
-                    output.write_record(
-                        case?.0.into_iter().zip(dictionary.variables.iter()).map(
-                            |(datum, variable)| {
-                                datum
-                                    .display(variable.print_format, variable.encoding())
-                                    .to_string()
-                            },
-                        ),
-                    )?;
+                    output.write_record(case?.into_iter().zip(dictionary.variables.iter()).map(
+                        |(datum, variable)| datum.display(variable.print_format).to_string(),
+                    ))?;
                 }
             }
             OutputFormat::Sys => {
@@ -177,7 +172,7 @@ impl Convert {
                     .with_compression(self.sys_options.compression)
                     .write_file(&dictionary, output)?;
                 for case in cases {
-                    output.write_case(case?.0.iter())?;
+                    output.write_case(case?.into_iter().map(|datum| datum.into_raw()))?;
                 }
             }
         }
@@ -221,18 +216,18 @@ impl Decrypt {
     }
 }
 
-/// Dissects SPSS system files.
+/// Show SPSS system file dictionary and data.
 #[derive(Args, Clone, Debug)]
-struct Dissect {
+struct Show {
     /// Maximum number of cases to print.
     #[arg(long = "data", default_value_t = 0)]
     max_cases: u64,
 
-    /// Files to dissect.
+    /// Files to show.
     #[arg(required = true)]
     files: Vec<PathBuf>,
 
-    /// How to dissect the file.
+    /// What to show.
     #[arg(short, long, value_enum, default_value_t)]
     mode: Mode,
 
@@ -241,10 +236,10 @@ struct Dissect {
     encoding: Option<&'static Encoding>,
 }
 
-impl Dissect {
+impl Show {
     fn run(self) -> Result<()> {
         for file in self.files {
-            dissect(&file, self.max_cases, self.mode, self.encoding)?;
+            show(&file, self.max_cases, self.mode, self.encoding)?;
         }
         Ok(())
     }
@@ -254,7 +249,7 @@ impl Dissect {
 enum Command {
     Convert(Convert),
     Decrypt(Decrypt),
-    Dissect(Dissect),
+    Show(Show),
 }
 
 impl Command {
@@ -262,7 +257,7 @@ impl Command {
         match self {
             Command::Convert(convert) => convert.run(),
             Command::Decrypt(decrypt) => decrypt.run(),
-            Command::Dissect(dissect) => dissect.run(),
+            Command::Show(show) => show.run(),
         }
     }
 }
@@ -291,7 +286,16 @@ fn main() -> Result<()> {
     Cli::parse().command.run()
 }
 
-fn dissect(
+fn show_json<T>(value: &T) -> Result<()>
+where
+    T: Serialize,
+{
+    serde_json::to_writer_pretty(stdout(), value)?;
+    println!();
+    Ok(())
+}
+
+fn show(
     file_name: &Path,
     max_cases: u64,
     mode: Mode,
@@ -311,12 +315,12 @@ fn dissect(
             return Ok(());
         }
         Mode::Raw => {
-            serde_json::to_writer_pretty(stdout(), reader.header())?;
+            show_json(reader.header())?;
             for record in reader.records() {
-                serde_json::to_writer_pretty(stdout(), &record?)?;
+                show_json(&record?)?;
             }
             for (_index, case) in (0..max_cases).zip(reader.cases()) {
-                serde_json::to_writer_pretty(stdout(), &case?)?;
+                show_json(&case?)?;
             }
         }
         Mode::Decoded => {
@@ -327,7 +331,7 @@ fn dissect(
             };
             let mut decoder = Decoder::new(encoding, |e| eprintln!("{e}"));
             for record in records {
-                serde_json::to_writer_pretty(stdout(), &record.decode(&mut decoder))?;
+                show_json(&record.decode(&mut decoder))?;
             }
         }
         Mode::Parsed => {
@@ -346,8 +350,8 @@ fn dissect(
                     |e| eprintln!("{e}"),
                 )
                 .into_parts();
-            serde_json::to_writer_pretty(stdout(), &dictionary)?;
-            serde_json::to_writer_pretty(stdout(), &metadata)?;
+            show_json(&dictionary)?;
+            show_json(&metadata)?;
         }
     }
 
index 60620286638aaf69f49d3fe99f52965132e19ca6..8260b95488ace76bec45343ddeb25b4638b52814 100644 (file)
@@ -68,7 +68,7 @@ use thiserror::Error as ThisError;
 use tlo::parse_tlo;
 
 use crate::{
-    data::Datum,
+    data::{Datum, EncodedDat, EncodedDatum},
     dictionary::{VarType, Variable},
     format::{Decimal, Format, Settings as FormatSettings, Type, UncheckedFormat},
     settings::{Settings, Show},
@@ -1860,10 +1860,10 @@ impl Value {
             variable_label: variable.label.clone(),
         }))
     }
-    pub fn new_datum(value: &Datum, encoding: &'static Encoding) -> Self {
+    pub fn new_datum(value: EncodedDat) -> Self {
         match value {
-            Datum::Number(number) => Self::new_number(*number),
-            Datum::String(string) => Self::new_user_text(string.decode(encoding).into_owned()),
+            EncodedDat::Number(number) => Self::new_number(number),
+            EncodedDat::String(string) => Self::new_user_text(string.as_str()),
         }
     }
     pub fn new_variable_value(variable: &Variable, value: &Datum) -> Self {
@@ -2206,7 +2206,7 @@ impl Display for DisplayValue<'_> {
                         *format
                     };
                     let mut buf = SmallString::<[u8; 40]>::new();
-                    write!(&mut buf, "{}", Datum::Number(*value).display(format, UTF_8)).unwrap();
+                    write!(&mut buf, "{}", EncodedDat::Number(*value).display(format)).unwrap();
                     write!(f, "{}", buf.trim_start_matches(' '))?;
                 }
                 if let Some(label) = self.show_label {
index db67102f9d5fd62298a6355e6e49bc750b7908f4..22662678e559062bf78328eb0c273fd904a95969 100644 (file)
@@ -26,7 +26,7 @@ use std::{
 use crate::{
     calendar::date_time_to_pspp,
     crypto::EncryptedFile,
-    data::{Datum, EncodedDatum, RawString},
+    data::{Case, Datum, EncodedDatum, RawString},
     dictionary::{
         DictIndexMultipleResponseSet, DictIndexVariableSet, Dictionary, InvalidRole, MissingValues,
         MissingValuesError, MultipleResponseType, VarWidth, Variable,
@@ -47,7 +47,7 @@ use crate::{
                 VarDisplayRecord, VariableAttributesRecord, VariableRecord, VariableSetRecord,
                 VeryLongStringsRecord,
             },
-            Cases, DecodedRecord, RawDatum, RawWidth, Reader,
+            DecodedRecord, RawCases, RawDatum, RawWidth, Reader,
         },
         serialize_endian,
     },
@@ -775,7 +775,7 @@ impl Records {
     pub fn decode(
         mut self,
         header: FileHeader<String>,
-        mut cases: Cases,
+        mut cases: RawCases,
         encoding: &'static Encoding,
         mut warn: impl FnMut(Error),
     ) -> SystemFile {
@@ -1035,7 +1035,8 @@ impl Records {
                             .map(|value| {
                                 value
                                     .decode(variable.width)
-                                    .display(variable.print_format, variable.encoding())
+                                    .as_encoded(variable.encoding())
+                                    .display(variable.print_format)
                                     .with_trimming()
                                     .with_quoted_string()
                                     .to_string()
@@ -1311,7 +1312,7 @@ impl Records {
         SystemFile {
             dictionary,
             metadata,
-            cases,
+            cases: Cases::new(encoding, cases),
         }
     }
 }
@@ -1676,32 +1677,29 @@ impl MultipleResponseType {
     }
 }
 
-/*
-trait Quoted {
-    fn quoted(self) -> WithQuotes<Self>
-    where
-        Self: Display + Sized;
+pub struct Cases {
+    encoding: &'static Encoding,
+    inner: RawCases,
 }
 
-impl<T> Quoted for T
-where
-    T: Display,
-{
-    fn quoted(self) -> WithQuotes<Self> {
-        WithQuotes(self)
+impl Cases {
+    pub fn new(encoding: &'static Encoding, inner: RawCases) -> Self {
+        Self { encoding, inner }
     }
 }
 
-struct WithQuotes<T>(T)
-where
-    T: Display;
+impl Debug for Cases {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "Cases")
+    }
+}
 
-impl<T> Display for WithQuotes<T>
-where
-    T: Display,
-{
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "\"{}\"", &self.0)
+impl Iterator for Cases {
+    type Item = Result<Case<Vec<Datum>>, raw::Error>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.inner
+            .next()
+            .map(|result| result.map(|case| case.with_encoding(self.encoding)))
     }
 }
-*/
index 642ef61cb18614cca8decf8a5ae0f95014e4fca4..27ffb2f8109455d59eadda93f19d219ee46bed76 100644 (file)
@@ -20,7 +20,7 @@
 //! raw details.  Most readers will want to use higher-level interfaces.
 
 use crate::{
-    data::{Case, Datum, RawStr, RawString},
+    data::{Datum, RawCase, RawStr, RawString},
     dictionary::{VarType, VarWidth},
     endian::{Endian, Parse, ToBytes},
     identifier::{Error as IdError, Identifier},
@@ -946,12 +946,12 @@ impl Datum {
         reader: &mut R,
         case_vars: &[CaseVar],
         endian: Endian,
-    ) -> Result<Option<Case>, Error> {
+    ) -> Result<Option<RawCase>, Error> {
         fn eof<R: Seek>(
             reader: &mut R,
             case_vars: &[CaseVar],
             case_start: u64,
-        ) -> Result<Option<Case>, Error> {
+        ) -> Result<Option<RawCase>, Error> {
             let offset = reader.stream_position()?;
             if offset == case_start {
                 Ok(None)
@@ -993,7 +993,7 @@ impl Datum {
                 }
             }
         }
-        Ok(Some(Case(values)))
+        Ok(Some(RawCase(values)))
     }
 
     fn read_compressed_chunk<R: Read>(
@@ -1025,12 +1025,12 @@ impl Datum {
         codes: &mut VecDeque<u8>,
         endian: Endian,
         bias: f64,
-    ) -> Result<Option<Case>, Error> {
+    ) -> Result<Option<RawCase>, Error> {
         fn eof<R: Seek>(
             reader: &mut R,
             case_start: u64,
             n_chunks: usize,
-        ) -> Result<Option<Case>, Error> {
+        ) -> Result<Option<RawCase>, Error> {
             let offset = reader.stream_position()?;
             if n_chunks > 0 {
                 Err(Error::new(
@@ -1080,7 +1080,7 @@ impl Datum {
                 }
             }
         }
-        Ok(Some(Case(values)))
+        Ok(Some(RawCase(values)))
     }
 }
 
@@ -1154,7 +1154,7 @@ where
     var_types: VarTypes,
 
     state: ReaderState,
-    cases: Option<Cases>,
+    cases: Option<RawCases>,
 }
 
 impl<'a, R> Reader<'a, R>
@@ -1194,7 +1194,7 @@ where
     /// there is an error reading the headers, or if [cases](Self::cases) is
     /// called before all of the headers have been read, the returned [Cases]
     /// will be empty.
-    pub fn cases(self) -> Cases {
+    pub fn cases(self) -> RawCases {
         self.cases.unwrap_or_default()
     }
 }
@@ -1210,7 +1210,7 @@ where
 {
     fn cases(&mut self, ztrailer_offset: Option<u64>) {
         self.0.state = ReaderState::End;
-        self.0.cases = Some(Cases::new(
+        self.0.cases = Some(RawCases::new(
             self.0.reader.take().unwrap(),
             take(&mut self.0.var_types),
             &self.0.header,
@@ -1372,7 +1372,7 @@ impl CaseVar {
 ///
 /// [Dictionary]: crate::dictionary::Dictionary
 /// [SystemFile]: crate::sys::cooked::SystemFile
-pub struct Cases {
+pub struct RawCases {
     reader: Box<dyn ReadSeek>,
     case_vars: Vec<CaseVar>,
     compression: Option<Compression>,
@@ -1384,13 +1384,13 @@ pub struct Cases {
     read_cases: u64,
 }
 
-impl Debug for Cases {
+impl Debug for RawCases {
     fn fmt(&self, f: &mut Formatter) -> FmtResult {
         write!(f, "Cases")
     }
 }
 
-impl Default for Cases {
+impl Default for RawCases {
     fn default() -> Self {
         Self {
             reader: Box::new(empty()),
@@ -1406,7 +1406,7 @@ impl Default for Cases {
     }
 }
 
-impl Cases {
+impl RawCases {
     fn new<R>(
         reader: R,
         var_types: VarTypes,
@@ -1461,8 +1461,8 @@ impl Cases {
     }
 }
 
-impl Iterator for Cases {
-    type Item = Result<Case, Error>;
+impl Iterator for RawCases {
+    type Item = Result<RawCase, Error>;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.eof {
index eda4aebd675dea3c644c2e8407cbe3306b739c43..aa1714124a70b5384eeb164f11955fc61a8f2eb6 100644 (file)
@@ -665,9 +665,8 @@ where
                         case_numbers
                             .push(Value::new_integer(Some((case_numbers.len() + 1) as f64)));
                         data.push(
-                            case.0
-                                .into_iter()
-                                .map(|datum| Value::new_datum(&datum, dictionary.encoding()))
+                            case.into_iter()
+                                .map(|datum| Value::new_datum(datum.borrowed()))
                                 .collect::<Vec<_>>(),
                         );
                     }
index f7c38ed58d027e1ccc8369334574f610025bf57f..e42f1c5066f564063a617ee1b7649eb4983cc7ef 100644 (file)
@@ -17,7 +17,7 @@ use itertools::zip_eq;
 use smallvec::SmallVec;
 
 use crate::{
-    data::{Datum, EncodedDatum},
+    data::{Dat, Datum, EncodedDatum},
     dictionary::{
         Alignment, Attributes, CategoryLabels, Dictionary, Measure, MultipleResponseType,
         ValueLabels, VarWidth,
@@ -842,7 +842,7 @@ where
 
     fn write_case_uncompressed<'c>(
         &mut self,
-        case: impl Iterator<Item = &'c Datum>,
+        case: impl Iterator<Item = Datum>,
     ) -> Result<(), BinError> {
         for (var, datum) in zip_eq(self.case_vars, case) {
             match var {
@@ -865,7 +865,7 @@ where
     }
     fn write_case_compressed<'c>(
         &mut self,
-        case: impl Iterator<Item = &'c Datum>,
+        case: impl Iterator<Item = Datum>,
     ) -> Result<(), BinError> {
         for (var, datum) in zip_eq(self.case_vars, case) {
             match var {
@@ -991,7 +991,7 @@ where
     /// Panics if [try_finish](Self::try_finish) has been called.
     pub fn write_case<'a>(
         &mut self,
-        case: impl IntoIterator<Item = &'a Datum>,
+        case: impl IntoIterator<Item = Datum>,
     ) -> Result<(), BinError> {
         match self.inner.as_mut().unwrap() {
             Either::Left(inner) => {