+use std::borrow::Cow;
+
+use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
+use encoding_rs::Encoding;
+
+use crate::{
+ Error,
+ {endian::Endian, CategoryLabels, Compression},
+ format::UncheckedFormat,
+};
+
+pub struct Decoder {
+ pub compression: Option<Compression>,
+ pub endian: Endian,
+ pub encoding: &'static Encoding,
+}
+
+impl Decoder {
+ fn decode_string<'a>(&self, input: &'a [u8], warn: &impl Fn(Error)) -> Cow<'a, str> {
+ let (output, malformed) = self.encoding.decode_without_bom_handling(input);
+ if malformed {
+ warn(Error::TBD);
+ }
+ output
+ }
+}
+
+pub trait Decode: Sized {
+ type Input;
+ fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Self;
+}
+
+#[derive(Clone)]
+pub struct Header {
+ pub eye_catcher: String,
+ pub weight_index: Option<usize>,
+ pub n_cases: Option<u64>,
+ pub creation: NaiveDateTime,
+ pub file_label: String,
+}
+
+impl Decode for Header {
+ type Input = crate::raw::Header;
+
+ fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Self {
+ let eye_catcher = decoder.decode_string(&input.eye_catcher, &warn);
+ let file_label = decoder.decode_string(&input.file_label, &warn);
+ let creation_date = decoder.decode_string(&input.creation_date, &warn);
+ let creation_date = NaiveDate::parse_from_str(&creation_date, "%v").unwrap_or_else(|_| {
+ warn(Error::InvalidCreationDate {
+ creation_date: creation_date.into(),
+ });
+ Default::default()
+ });
+ let creation_time = decoder.decode_string(&input.creation_time, &warn);
+ let creation_time =
+ NaiveTime::parse_from_str(&creation_time, "%H:%M:%S").unwrap_or_else(|_| {
+ warn(Error::InvalidCreationTime {
+ creation_time: creation_time.into(),
+ });
+ Default::default()
+ });
+ Header {
+ eye_catcher: eye_catcher.into(),
+ weight_index: input.weight_index.map(|n| n as usize),
+ n_cases: input.n_cases.map(|n| n as u64),
+ creation: NaiveDateTime::new(creation_date, creation_time),
+ file_label: file_label.into(),
+ }
+ }
+}
+
+pub struct Variable {
+ pub width: i32,
+ pub name: String,
+ pub print_format: UncheckedFormat,
+ pub write_format: UncheckedFormat,
+}
+
+#[derive(Clone)]
+pub struct Document(Vec<String>);
+
+impl Decode for Document {
+ type Input = crate::raw::Document;
+
+ fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Self {
+ Document(
+ input
+ .lines
+ .iter()
+ .map(|s| decoder.decode_string(s, &warn).into())
+ .collect(),
+ )
+ }
+}
+
+pub use crate::raw::FloatInfo;
+pub use crate::raw::IntegerInfo;
+
+#[derive(Clone, Debug)]
+pub enum MultipleResponseType {
+ MultipleDichotomy {
+ value: String,
+ labels: CategoryLabels,
+ },
+ MultipleCategory,
+}
+#[derive(Clone, Debug)]
+pub struct MultipleResponseSet {
+ pub name: String,
+ pub label: String,
+ pub mr_type: MultipleResponseType,
+ pub vars: Vec<String>,
+}
+
+#[derive(Clone, Debug)]
+pub struct MultipleResponseRecord(Vec<MultipleResponseSet>);
+
+#[derive(Clone, Debug)]
+pub struct ProductInfo(String);
+
+pub enum Measure {
+ Nominal,
+ Ordinal,
+ Scale,
+}
+
+pub enum Alignment {
+ Left,
+ Right,
+ Center,
+}
+
+pub struct VarDisplay {
+ pub measure: Option<Measure>,
+ pub width: u32,
+ pub align: Option<Alignment>,
+}
+
+pub struct VarDisplayRecord(pub Vec<VarDisplay>);