use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use encoding_rs::{DecoderResult, Encoding};
use num::integer::div_ceil;
+use ordered_float::OrderedFloat;
use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
TBD,
}
+#[derive(Clone, Debug)]
+pub enum Record {
+ Header(HeaderRecord),
+ Variable(VariableRecord),
+ ValueLabel(ValueLabelRecord),
+ Document(DocumentRecord),
+ IntegerInfo(IntegerInfoRecord),
+ FloatInfo(FloatInfoRecord),
+ VariableSets(VariableSetRecord),
+ VarDisplay(VarDisplayRecord),
+ //MultipleResponse(MultipleResponseRecord),
+ //LongStringValueLabels(LongStringValueLabelRecord),
+ Encoding(EncodingRecord),
+ NumberOfCases(NumberOfCasesRecord),
+ ProductInfo(ProductInfoRecord),
+ //LongNames(UnencodedString),
+ //LongStrings(UnencodedString),
+ //FileAttributes(UnencodedString),
+ //VariableAttributes(UnencodedString),
+ //OtherExtension(Extension),
+ //EndOfHeaders(u32),
+ //ZHeader(ZHeader),
+ //ZTrailer(ZTrailer),
+ //Case(Vec<Value>),
+}
+
+pub use crate::raw::IntegerInfoRecord;
+pub use crate::raw::FloatInfoRecord;
+pub use crate::raw::EncodingRecord;
+pub use crate::raw::NumberOfCasesRecord;
+
type DictIndex = usize;
pub struct Variable {
fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error>;
}
-#[derive(Clone)]
-pub struct Header {
+#[derive(Clone, Debug)]
+pub struct HeaderRecord {
pub eye_catcher: String,
pub weight_index: Option<usize>,
pub n_cases: Option<u64>,
pub file_label: String,
}
-impl Decode for Header {
+impl Decode for HeaderRecord {
type Input = crate::raw::HeaderRecord;
fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error> {
});
Default::default()
});
- Ok(Header {
+ Ok(HeaderRecord {
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),
}
}
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VarWidth {
Numeric,
String(u16),
}
}
+#[derive(Clone, Debug)]
pub struct VariableRecord {
pub width: VarWidth,
pub name: Identifier,
}
}
-#[derive(Clone)]
-pub struct Document(Vec<String>);
+#[derive(Clone, Debug)]
+pub struct DocumentRecord(Vec<String>);
-impl Decode for Document {
+impl Decode for DocumentRecord {
type Input = crate::raw::DocumentRecord;
fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result<Self, Error> {
- Ok(Document(
+ Ok(DocumentRecord(
input
.lines
.iter()
fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error>;
}
+#[derive(Clone, Debug)]
pub struct VariableSet {
pub name: String,
pub vars: Vec<String>,
}
}
-#[derive(Clone)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Value {
- Number(Option<f64>),
+ Number(Option<OrderedFloat<f64>>),
String(String),
}
impl Value {
pub fn decode(raw: raw::Value, decoder: &Decoder) -> Self {
match raw {
- raw::Value::Number(x) => Value::Number(x),
+ raw::Value::Number(x) => Value::Number(x.map(|x| x.into())),
raw::Value::String(s) => Value::String(decoder.decode_exact_length(&s.0).into()),
}
}
}
+#[derive(Clone, Debug)]
pub struct ValueLabelRecord {
pub var_type: VarType,
pub labels: Vec<(Value, String)>,
}
}
+#[derive(Clone, Debug)]
pub struct VariableSetRecord(Vec<VariableSet>);
impl TextRecord for VariableSetRecord {
}
}
-pub struct ProductInfo(pub String);
+#[derive(Clone, Debug)]
+pub struct ProductInfoRecord(pub String);
-impl TextRecord for ProductInfo {
+impl TextRecord for ProductInfoRecord {
const NAME: &'static str = "extra product info";
fn parse(input: &str, _warn: impl Fn(Error)) -> Result<Self, Error> {
- Ok(ProductInfo(input.into()))
+ Ok(ProductInfoRecord(input.into()))
}
}
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Measure {
Nominal,
Ordinal,
Scale,
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Alignment {
Left,
Right,
Center,
}
+#[derive(Clone, Debug)]
pub struct VarDisplay {
pub measure: Option<Measure>,
pub width: u32,
pub align: Option<Alignment>,
}
+#[derive(Clone, Debug)]
pub struct VarDisplayRecord(pub Vec<VarDisplay>);
#[cfg(test)]
Document(DocumentRecord),
IntegerInfo(IntegerInfoRecord),
FloatInfo(FloatInfoRecord),
- VariableSets(UnencodedString),
+ VariableSets(TextRecord),
VarDisplay(VarDisplayRecord),
MultipleResponse(MultipleResponseRecord),
LongStringValueLabels(LongStringValueLabelRecord),
Encoding(EncodingRecord),
NumberOfCases(NumberOfCasesRecord),
- ProductInfo(UnencodedString),
- LongNames(UnencodedString),
- LongStrings(UnencodedString),
- FileAttributes(UnencodedString),
- VariableAttributes(UnencodedString),
+ ProductInfo(TextRecord),
+ LongNames(TextRecord),
+ LongStrings(TextRecord),
+ FileAttributes(TextRecord),
+ VariableAttributes(TextRecord),
OtherExtension(Extension),
EndOfHeaders(u32),
ZHeader(ZHeader),
}
}
-#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VarType {
Numeric,
String,
}
}
+#[derive(Clone, Debug)]
+pub struct TextRecord {
+ /// Offset from the start of the file to the start of the record.
+ pub offset: u64,
+
+ /// The text content of the record.
+ pub text: UnencodedString,
+}
+
+impl From<Extension> for TextRecord {
+ fn from(source: Extension) -> Self {
+ TextRecord { offset: source.offset, text: source.data.into() }
+ }
+}
+
#[derive(Clone, Debug)]
pub struct Extension {
/// Offset from the start of the file to the start of the record.
endian,
|_| (),
)?)),
- 5 => Ok(Record::VariableSets(UnencodedString(extension.data))),
- 10 => Ok(Record::ProductInfo(UnencodedString(extension.data))),
- 13 => Ok(Record::LongNames(UnencodedString(extension.data))),
- 14 => Ok(Record::LongStrings(UnencodedString(extension.data))),
- 17 => Ok(Record::FileAttributes(UnencodedString(extension.data))),
- 18 => Ok(Record::VariableAttributes(UnencodedString(extension.data))),
+ 5 => Ok(Record::VariableSets(extension.into())),
+ 10 => Ok(Record::ProductInfo(extension.into())),
+ 13 => Ok(Record::LongNames(extension.into())),
+ 14 => Ok(Record::LongStrings(extension.into())),
+ 17 => Ok(Record::FileAttributes(extension.into())),
+ 18 => Ok(Record::VariableAttributes(extension.into())),
_ => Ok(Record::OtherExtension(extension)),
}
}