e0a7ef1fea7da5d22ad473ca3500734ef6abf322
[pspp] / rust / src / cooked.rs
1 use std::{borrow::Cow, collections::HashSet};
2
3 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
4 use encoding_rs::Encoding;
5
6 use crate::{
7     format::{Spec, UncheckedSpec, Width},
8     identifier::{Error as IdError, Identifier},
9     raw::{self, MissingValues},
10     {endian::Endian, CategoryLabels, Compression},
11 };
12 use thiserror::Error as ThisError;
13
14 #[derive(ThisError, Debug)]
15 pub enum Error {
16     #[error("Variable record at offset {offset:#x} specifies width {width} not in valid range [-1,255).")]
17     BadVariableWidth { offset: u64, width: i32 },
18
19     #[error("This file has corrupted metadata written by a buggy version of PSPP.  To ensure that other software can read it correctly, save a new copy of the file.")]
20     BadLongMissingValueFormat,
21
22     #[error("File creation date {creation_date} is not in the expected format \"DD MMM YY\" format.  Using 01 Jan 1970.")]
23     InvalidCreationDate { creation_date: String },
24
25     #[error("File creation time {creation_time} is not in the expected format \"HH:MM:SS\" format.  Using midnight.")]
26     InvalidCreationTime { creation_time: String },
27
28     #[error("Invalid variable name: {0}")]
29     BadIdentifier(#[from] IdError),
30
31     #[error("Details TBD")]
32     TBD,
33 }
34
35 pub struct Decoder {
36     pub compression: Option<Compression>,
37     pub endian: Endian,
38     pub encoding: &'static Encoding,
39     pub var_names: HashSet<Identifier>,
40     n_generated_names: usize,
41 }
42
43 impl Decoder {
44     fn take_name(&mut self, id: &Identifier) -> bool {
45         self.var_names.insert(id.clone())
46     }
47     fn generate_name(&mut self) -> Identifier {
48         loop {
49             self.n_generated_names += 1;
50             let name = Identifier::new(&format!("VAR{:03}", self.n_generated_names), self.encoding)
51                 .unwrap();
52             if self.take_name(&name) {
53                 return name;
54             }
55             assert!(self.n_generated_names < usize::MAX);
56         }
57     }
58     fn decode_string<'a>(&self, input: &'a [u8], warn: &impl Fn(Error)) -> Cow<'a, str> {
59         let (output, malformed) = self.encoding.decode_without_bom_handling(input);
60         if malformed {
61             warn(Error::TBD);
62         }
63         output
64     }
65 }
66
67 pub trait Decode: Sized {
68     type Input;
69     fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error>;
70 }
71
72 #[derive(Clone)]
73 pub struct Header {
74     pub eye_catcher: String,
75     pub weight_index: Option<usize>,
76     pub n_cases: Option<u64>,
77     pub creation: NaiveDateTime,
78     pub file_label: String,
79 }
80
81 impl Decode for Header {
82     type Input = crate::raw::Header;
83
84     fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error> {
85         let eye_catcher = decoder.decode_string(&input.eye_catcher.0, &warn);
86         let file_label = decoder.decode_string(&input.file_label.0, &warn);
87         let creation_date = decoder.decode_string(&input.creation_date.0, &warn);
88         let creation_date = NaiveDate::parse_from_str(&creation_date, "%v").unwrap_or_else(|_| {
89             warn(Error::InvalidCreationDate {
90                 creation_date: creation_date.into(),
91             });
92             Default::default()
93         });
94         let creation_time = decoder.decode_string(&input.creation_time.0, &warn);
95         let creation_time =
96             NaiveTime::parse_from_str(&creation_time, "%H:%M:%S").unwrap_or_else(|_| {
97                 warn(Error::InvalidCreationTime {
98                     creation_time: creation_time.into(),
99                 });
100                 Default::default()
101             });
102         Ok(Header {
103             eye_catcher: eye_catcher.into(),
104             weight_index: input.weight_index.map(|n| n as usize),
105             n_cases: input.n_cases.map(|n| n as u64),
106             creation: NaiveDateTime::new(creation_date, creation_time),
107             file_label: file_label.into(),
108         })
109     }
110 }
111
112 pub struct Variable {
113     pub width: Width,
114     pub name: Identifier,
115     pub print_format: Spec,
116     pub write_format: Spec,
117     pub missing_values: MissingValues,
118     pub label: Option<String>,
119 }
120
121 fn decode_format(raw: raw::Spec, name: &str, width: Width) -> Spec {
122     UncheckedSpec::try_from(raw)
123         .and_then(Spec::try_from)
124         .and_then(|x| x.check_width_compatibility(Some(name), width))
125         .unwrap_or_else(|_warning| {
126             /*warn(warning);*/
127             Spec::default_for_width(width)
128         })
129 }
130
131 impl Variable {
132     pub fn decode(
133         decoder: &mut Decoder,
134         input: &crate::raw::Variable,
135         warn: impl Fn(Error),
136     ) -> Result<Option<Variable>, Error> {
137         match input.width {
138             0..=255 => (),
139             -1 => return Ok(None),
140             _ => {
141                 return Err(Error::BadVariableWidth {
142                     offset: input.offset,
143                     width: input.width,
144                 })
145             }
146         };
147         let width = input.width as Width;
148         let name = decoder.decode_string(&input.name.0, &warn);
149         let name = match Identifier::new(&name, decoder.encoding) {
150             Ok(name) => {
151                 if !decoder.take_name(&name) {
152                     decoder.generate_name()
153                 } else {
154                     name
155                 }
156             }
157             Err(error) => {
158                 warn(error.into());
159                 decoder.generate_name()
160             }
161         };
162         let print_format = decode_format(input.print_format, &name.0, width);
163         let write_format = decode_format(input.write_format, &name.0, width);
164         let label = input
165             .label
166             .as_ref()
167             .map(|label| decoder.decode_string(&label.0, &warn).into());
168         Ok(Some(Variable {
169             width,
170             name,
171             print_format,
172             write_format,
173             missing_values: input.missing_values.clone(),
174             label,
175         }))
176     }
177 }
178
179 #[derive(Clone)]
180 pub struct Document(Vec<String>);
181
182 impl Decode for Document {
183     type Input = crate::raw::Document;
184
185     fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error> {
186         Ok(Document(
187             input
188                 .lines
189                 .iter()
190                 .map(|s| decoder.decode_string(&s.0, &warn).into())
191                 .collect(),
192         ))
193     }
194 }
195
196 pub use crate::raw::FloatInfo;
197 pub use crate::raw::IntegerInfo;
198
199 trait TextRecord
200 where
201     Self: Sized,
202 {
203     const NAME: &'static str;
204     fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error>;
205 }
206
207 pub struct VariableSet {
208     pub name: String,
209     pub vars: Vec<String>,
210 }
211
212 impl VariableSet {
213     fn parse(input: &str) -> Result<Self, Error> {
214         let (name, input) = input.split_once('=').ok_or(Error::TBD)?;
215         let vars = input.split_ascii_whitespace().map(String::from).collect();
216         Ok(VariableSet {
217             name: name.into(),
218             vars,
219         })
220     }
221 }
222
223 pub struct VariableSetRecord(Vec<VariableSet>);
224
225 impl TextRecord for VariableSetRecord {
226     const NAME: &'static str = "variable set";
227     fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
228         let mut sets = Vec::new();
229         for line in input.lines() {
230             match VariableSet::parse(line) {
231                 Ok(set) => sets.push(set),
232                 Err(error) => warn(error),
233             }
234         }
235         Ok(VariableSetRecord(sets))
236     }
237 }
238
239 pub struct ProductInfo(pub String);
240
241 impl TextRecord for ProductInfo {
242     const NAME: &'static str = "extra product info";
243     fn parse(input: &str, _warn: impl Fn(Error)) -> Result<Self, Error> {
244         Ok(ProductInfo(input.into()))
245     }
246 }
247
248 pub struct LongVariableName {
249     pub short_name: String,
250     pub long_name: String,
251 }
252
253 pub struct LongVariableNameRecord(Vec<LongVariableName>);
254
255 impl TextRecord for LongVariableNameRecord {
256     const NAME: &'static str = "long variable names";
257     fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
258         let mut names = Vec::new();
259         for pair in input.split('\t').filter(|s| !s.is_empty()) {
260             if let Some((short_name, long_name)) = pair.split_once('=') {
261                 let name = LongVariableName {
262                     short_name: short_name.into(),
263                     long_name: long_name.into(),
264                 };
265                 names.push(name);
266             } else {
267                 warn(Error::TBD)
268             }
269         }
270         Ok(LongVariableNameRecord(names))
271     }
272 }
273
274 pub struct VeryLongString {
275     pub short_name: String,
276     pub length: usize,
277 }
278
279 impl VeryLongString {
280     fn parse(input: &str) -> Result<VeryLongString, Error> {
281         let Some((short_name, length)) = input.split_once('=') else {
282             return Err(Error::TBD);
283         };
284         let length: usize = length.parse().map_err(|_| Error::TBD)?;
285         Ok(VeryLongString {
286             short_name: short_name.into(),
287             length,
288         })
289     }
290 }
291
292 pub struct VeryLongStringRecord(Vec<VeryLongString>);
293
294 impl TextRecord for VeryLongStringRecord {
295     const NAME: &'static str = "very long strings";
296     fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
297         let mut very_long_strings = Vec::new();
298         for tuple in input
299             .split('\0')
300             .map(|s| s.trim_end_matches('\t'))
301             .filter(|s| !s.is_empty())
302         {
303             match VeryLongString::parse(tuple) {
304                 Ok(vls) => very_long_strings.push(vls),
305                 Err(error) => warn(error),
306             }
307         }
308         Ok(VeryLongStringRecord(very_long_strings))
309     }
310 }
311
312 pub struct Attribute {
313     pub name: String,
314     pub values: Vec<String>,
315 }
316
317 impl Attribute {
318     fn parse<'a>(input: &'a str, warn: &impl Fn(Error)) -> Result<(Attribute, &'a str), Error> {
319         let Some((name, mut input)) = input.split_once('(') else {
320             return Err(Error::TBD);
321         };
322         let mut values = Vec::new();
323         loop {
324             let Some((value, rest)) = input.split_once('\n') else {
325                 return Err(Error::TBD);
326             };
327             if let Some(stripped) = value
328                 .strip_prefix('\'')
329                 .and_then(|value| value.strip_suffix('\''))
330             {
331                 values.push(stripped.into());
332             } else {
333                 warn(Error::TBD);
334                 values.push(value.into());
335             }
336             if let Some(rest) = rest.strip_prefix(')') {
337                 return Ok((
338                     Attribute {
339                         name: name.into(),
340                         values,
341                     },
342                     rest,
343                 ));
344             }
345             input = rest;
346         }
347     }
348 }
349
350 pub struct AttributeSet(pub Vec<Attribute>);
351
352 impl AttributeSet {
353     fn parse<'a>(
354         mut input: &'a str,
355         sentinel: Option<char>,
356         warn: &impl Fn(Error),
357     ) -> Result<(AttributeSet, &'a str), Error> {
358         let mut attributes = Vec::new();
359         let rest = loop {
360             match input.chars().next() {
361                 None => break input,
362                 c if c == sentinel => break &input[1..],
363                 _ => {
364                     let (attribute, rest) = Attribute::parse(input, &warn)?;
365                     attributes.push(attribute);
366                     input = rest;
367                 }
368             }
369         };
370         Ok((AttributeSet(attributes), rest))
371     }
372 }
373
374 pub struct FileAttributeRecord(AttributeSet);
375
376 impl TextRecord for FileAttributeRecord {
377     const NAME: &'static str = "data file attributes";
378     fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
379         let (set, rest) = AttributeSet::parse(input, None, &warn)?;
380         if !rest.is_empty() {
381             warn(Error::TBD);
382         }
383         Ok(FileAttributeRecord(set))
384     }
385 }
386
387 pub struct VarAttributeSet {
388     pub long_var_name: String,
389     pub attributes: AttributeSet,
390 }
391
392 impl VarAttributeSet {
393     fn parse<'a>(
394         input: &'a str,
395         warn: &impl Fn(Error),
396     ) -> Result<(VarAttributeSet, &'a str), Error> {
397         let Some((long_var_name, rest)) = input.split_once(':') else {
398             return Err(Error::TBD);
399         };
400         let (attributes, rest) = AttributeSet::parse(rest, Some('/'), warn)?;
401         Ok((
402             VarAttributeSet {
403                 long_var_name: long_var_name.into(),
404                 attributes,
405             },
406             rest,
407         ))
408     }
409 }
410
411 pub struct VariableAttributeRecord(Vec<VarAttributeSet>);
412
413 impl TextRecord for VariableAttributeRecord {
414     const NAME: &'static str = "variable attributes";
415     fn parse(mut input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
416         let mut var_attribute_sets = Vec::new();
417         while !input.is_empty() {
418             match VarAttributeSet::parse(input, &warn) {
419                 Ok((var_attribute, rest)) => {
420                     var_attribute_sets.push(var_attribute);
421                     input = rest;
422                 }
423                 Err(error) => {
424                     warn(error);
425                     break;
426                 }
427             }
428         }
429         Ok(VariableAttributeRecord(var_attribute_sets))
430     }
431 }
432
433 #[derive(Clone, Debug)]
434 pub enum MultipleResponseType {
435     MultipleDichotomy {
436         value: String,
437         labels: CategoryLabels,
438     },
439     MultipleCategory,
440 }
441 #[derive(Clone, Debug)]
442 pub struct MultipleResponseSet {
443     pub name: String,
444     pub label: String,
445     pub mr_type: MultipleResponseType,
446     pub vars: Vec<String>,
447 }
448
449 #[derive(Clone, Debug)]
450 pub struct MultipleResponseRecord(Vec<MultipleResponseSet>);
451
452 pub enum Measure {
453     Nominal,
454     Ordinal,
455     Scale,
456 }
457
458 pub enum Alignment {
459     Left,
460     Right,
461     Center,
462 }
463
464 pub struct VarDisplay {
465     pub measure: Option<Measure>,
466     pub width: u32,
467     pub align: Option<Alignment>,
468 }
469
470 pub struct VarDisplayRecord(pub Vec<VarDisplay>);