Variable(Variable),
ValueLabel(ValueLabel),
VarIndexes(VarIndexes),
+ IntegerInfo(IntegerInfo),
+ FloatInfo(FloatInfo),
+ VariableSets(UnencodedString),
+ VarDisplay(VarDisplayRecord),
+ MultipleResponse(MultipleResponseRecord),
+ LongStringValueLabels(LongStringValueLabelRecord),
+ Encoding(EncodingRecord),
+ NumberOfCases(NumberOfCasesRecord),
+ ProductInfo(UnencodedString),
+ LongNames(UnencodedString),
+ LongStrings(UnencodedString),
+ FileAttributes(UnencodedString),
+ VariableAttributes(UnencodedString),
+ TextExtension(TextExtension),
Extension(Extension),
EndOfHeaders(u32),
ZHeader(ZHeader),
3 => Ok(Record::ValueLabel(ValueLabel::read(reader, endian)?)),
4 => Ok(Record::VarIndexes(VarIndexes::read(reader, endian)?)),
6 => Ok(Record::Document(Document::read(reader, endian)?)),
- 7 => Ok(Record::Extension(Extension::read(reader, endian)?)),
+ 7 => Ok(Extension::read(reader, endian)?),
999 => Ok(Record::EndOfHeaders(endian.parse(read_bytes(reader)?))),
_ => Err(Error::BadRecordType {
offset: reader.stream_position()?,
state: Some(state::new(reader)),
})
}
+ pub fn collect_headers(&mut self) -> Result<Vec<Record>, Error> {
+ let mut headers = Vec::new();
+ for record in self {
+ match record? {
+ Record::EndOfHeaders(_) => break,
+ r => headers.push(r),
+ };
+ }
+ Ok(headers)
+ }
}
impl Iterator for Reader {
}
}
-/*
-#[derive(FromPrimitive)]
-enum ExtensionType {
- /// Machine integer info.
- Integer = 3,
- /// Machine floating-point info.
- Float = 4,
- /// Variable sets.
- VarSets = 5,
- /// DATE.
- Date = 6,
- /// Multiple response sets.
- Mrsets = 7,
- /// SPSS Data Entry.
- DataEntry = 8,
- /// Extra product info text.
- ProductInfo = 10,
- /// Variable display parameters.
- Display = 11,
- /// Long variable names.
- LongNames = 13,
- /// Long strings.
- LongStrings = 14,
- /// Extended number of cases.
- Ncases = 16,
- /// Data file attributes.
- FileAttrs = 17,
- /// Variable attributes.
- VarAttrs = 18,
- /// Multiple response sets (extended).
- Mrsets2 = 19,
- /// Character encoding.
- Encoding = 20,
- /// Value labels for long strings.
- LongLabels = 21,
- /// Missing values for long strings.
- LongMissing = 22,
- /// "Format properties in dataview table".
- Dataview = 24,
-}
- */
-
trait TextRecord
where
Self: Sized,
where
Self: Sized,
{
+ const SUBTYPE: u32;
const SIZE: Option<u32>;
const COUNT: Option<u32>;
const NAME: &'static str;
fn parse(ext: &Extension, endian: Endian, warn: impl Fn(Error)) -> Result<Self, Error>;
}
+#[derive(Clone, Debug)]
pub struct IntegerInfo {
pub version: (i32, i32, i32),
pub machine_code: i32,
}
impl ExtensionRecord for IntegerInfo {
+ const SUBTYPE: u32 = 3;
const SIZE: Option<u32> = Some(4);
const COUNT: Option<u32> = Some(8);
const NAME: &'static str = "integer record";
}
}
+#[derive(Clone, Debug)]
pub struct FloatInfo {
pub sysmis: f64,
pub highest: f64,
}
impl ExtensionRecord for FloatInfo {
+ const SUBTYPE: u32 = 4;
const SIZE: Option<u32> = Some(8);
const COUNT: Option<u32> = Some(3);
const NAME: &'static str = "floating point record";
}
}
+#[derive(Clone, Debug)]
pub enum CategoryLabels {
VarLabels,
CountedValues,
}
+#[derive(Clone, Debug)]
pub enum MultipleResponseType {
MultipleDichotomy {
value: UnencodedString,
},
MultipleCategory,
}
+#[derive(Clone, Debug)]
pub struct MultipleResponseSet {
pub name: UnencodedString,
pub label: UnencodedString,
}
}
-pub struct MultipleResponseSets(Vec<MultipleResponseSet>);
+#[derive(Clone, Debug)]
+pub struct MultipleResponseRecord(Vec<MultipleResponseSet>);
-impl ExtensionRecord for MultipleResponseSets {
+impl ExtensionRecord for MultipleResponseRecord {
+ const SUBTYPE: u32 = 7;
const SIZE: Option<u32> = Some(1);
const COUNT: Option<u32> = None;
const NAME: &'static str = "multiple response set record";
sets.push(set);
input = rest;
}
- Ok(MultipleResponseSets(sets))
+ Ok(MultipleResponseRecord(sets))
}
}
}
}
+#[derive(Clone, Debug)]
pub struct VarDisplayRecord(Vec<u32>);
impl ExtensionRecord for VarDisplayRecord {
+ const SUBTYPE: u32 = 11;
const SIZE: Option<u32> = Some(4);
const COUNT: Option<u32> = None;
const NAME: &'static str = "variable display record";
}
}
+#[derive(Clone, Debug)]
pub struct LongStringValueLabels {
pub var_name: UnencodedString,
pub width: u32,
pub labels: Vec<(UnencodedString, UnencodedString)>,
}
-pub struct LongStringValueLabelSet(Vec<LongStringValueLabels>);
+#[derive(Clone, Debug)]
+pub struct LongStringValueLabelRecord(Vec<LongStringValueLabels>);
-impl ExtensionRecord for LongStringValueLabelSet {
+impl ExtensionRecord for LongStringValueLabelRecord {
+ const SUBTYPE: u32 = 21;
const SIZE: Option<u32> = Some(1);
const COUNT: Option<u32> = None;
const NAME: &'static str = "long string value labels record";
labels,
})
}
- Ok(LongStringValueLabelSet(label_set))
+ Ok(LongStringValueLabelRecord(label_set))
}
}
pub struct LongStringMissingValueSet(Vec<LongStringMissingValues>);
impl ExtensionRecord for LongStringMissingValueSet {
+ const SUBTYPE: u32 = 22;
const SIZE: Option<u32> = Some(1);
const COUNT: Option<u32> = None;
const NAME: &'static str = "long string missing values record";
}
}
-pub struct Encoding(pub String);
+#[derive(Clone, Debug)]
+pub struct EncodingRecord(pub String);
-impl ExtensionRecord for Encoding {
+impl ExtensionRecord for EncodingRecord {
+ const SUBTYPE: u32 = 20;
const SIZE: Option<u32> = Some(1);
const COUNT: Option<u32> = None;
const NAME: &'static str = "encoding record";
fn parse(ext: &Extension, _endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
ext.check_size::<Self>()?;
- Ok(Encoding(String::from_utf8(ext.data.clone()).map_err(
+ Ok(EncodingRecord(String::from_utf8(ext.data.clone()).map_err(
|_| Error::BadEncodingName { offset: ext.offset },
)?))
}
}
}
+#[derive(Clone, Debug)]
pub struct NumberOfCasesRecord {
/// Always observed as 1.
pub one: u64,
}
impl ExtensionRecord for NumberOfCasesRecord {
+ const SUBTYPE: u32 = 16;
const SIZE: Option<u32> = Some(8);
const COUNT: Option<u32> = Some(2);
const NAME: &'static str = "extended number of cases record";
}
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum TextExtensionSubtype {
+ VariableSets = 5,
+ ProductInfo = 10,
+ LongNames = 13,
+ LongStrings = 14,
+ FileAttributes = 17,
+ VariableAttributes = 18,
+}
+
+#[derive(Clone, Debug)]
+pub struct TextExtension {
+ pub subtype: TextExtensionSubtype,
+ pub string: UnencodedString,
+}
+
#[derive(Clone, Debug)]
pub struct Extension {
/// Offset from the start of the file to the start of the record.
Ok(())
}
- fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Extension, Error> {
+ fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Record, Error> {
let subtype = endian.parse(read_bytes(r)?);
let offset = r.stream_position()?;
let size: u32 = endian.parse(read_bytes(r)?);
};
let offset = r.stream_position()?;
let data = read_vec(r, product as usize)?;
- Ok(Extension {
+ let extension = Extension {
offset,
subtype,
size,
count,
data,
- })
+ };
+ match subtype {
+ IntegerInfo::SUBTYPE => Ok(Record::IntegerInfo(IntegerInfo::parse(&extension, endian, |_| ())?)),
+ FloatInfo::SUBTYPE => Ok(Record::FloatInfo(FloatInfo::parse(&extension, endian, |_| ())?)),
+ VarDisplayRecord::SUBTYPE => Ok(Record::VarDisplay(VarDisplayRecord::parse(&extension, endian, |_| ())?)),
+ MultipleResponseRecord::SUBTYPE | 19 => Ok(Record::MultipleResponse(MultipleResponseRecord::parse(&extension, endian, |_| ())?)),
+ LongStringValueLabelRecord::SUBTYPE => Ok(Record::LongStringValueLabels(LongStringValueLabelRecord::parse(&extension, endian, |_| ())?)),
+ EncodingRecord::SUBTYPE => Ok(Record::Encoding(EncodingRecord::parse(&extension, endian, |_| ())?)),
+ NumberOfCasesRecord::SUBTYPE => Ok(Record::NumberOfCases(NumberOfCasesRecord::parse(&extension, endian, |_| ())?)),
+ x if x == TextExtensionSubtype::VariableSets as u32 => Ok(Record::VariableSets(UnencodedString(extension.data))),
+ x if x == TextExtensionSubtype::ProductInfo as u32 => Ok(Record::ProductInfo(UnencodedString(extension.data))),
+ x if x == TextExtensionSubtype::LongNames as u32 => Ok(Record::LongNames(UnencodedString(extension.data))),
+ x if x == TextExtensionSubtype::LongStrings as u32 => Ok(Record::LongStrings(UnencodedString(extension.data))),
+ x if x == TextExtensionSubtype::FileAttributes as u32 => Ok(Record::FileAttributes(UnencodedString(extension.data))),
+ x if x == TextExtensionSubtype::VariableAttributes as u32 => Ok(Record::VariableAttributes(UnencodedString(extension.data))),
+ _ => Ok(Record::Extension(extension))
+ }
}
}