+ max: Self::MAX_LINES,
+ })
+ } else {
+ let pos = r.stream_position()?;
+ let mut lines = Vec::with_capacity(n);
+ for _ in 0..n {
+ lines.push(UnencodedStr::<{ DocumentRecord::LINE_LEN }>(read_bytes(r)?));
+ }
+ Ok(DocumentRecord { pos, lines })
+ }
+ }
+}
+
+trait ExtensionRecord
+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 IntegerInfoRecord {
+ pub version: (i32, i32, i32),
+ pub machine_code: i32,
+ pub floating_point_rep: i32,
+ pub compression_code: i32,
+ pub endianness: i32,
+ pub character_code: i32,
+}
+
+impl ExtensionRecord for IntegerInfoRecord {
+ const SUBTYPE: u32 = 3;
+ const SIZE: Option<u32> = Some(4);
+ const COUNT: Option<u32> = Some(8);
+ const NAME: &'static str = "integer record";
+
+ fn parse(ext: &Extension, endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ ext.check_size::<Self>()?;
+
+ let mut input = &ext.data[..];
+ let data: Vec<i32> = (0..8)
+ .map(|_| endian.parse(read_bytes(&mut input).unwrap()))
+ .collect();
+ Ok(IntegerInfoRecord {
+ version: (data[0], data[1], data[2]),
+ machine_code: data[3],
+ floating_point_rep: data[4],
+ compression_code: data[5],
+ endianness: data[6],
+ character_code: data[7],
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct FloatInfoRecord {
+ pub sysmis: f64,
+ pub highest: f64,
+ pub lowest: f64,
+}
+
+impl ExtensionRecord for FloatInfoRecord {
+ const SUBTYPE: u32 = 4;
+ const SIZE: Option<u32> = Some(8);
+ const COUNT: Option<u32> = Some(3);
+ const NAME: &'static str = "floating point record";
+
+ fn parse(ext: &Extension, endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ ext.check_size::<Self>()?;
+
+ let mut input = &ext.data[..];
+ let data: Vec<f64> = (0..3)
+ .map(|_| endian.parse(read_bytes(&mut input).unwrap()))
+ .collect();
+ Ok(FloatInfoRecord {
+ sysmis: data[0],
+ highest: data[1],
+ lowest: data[2],
+ })
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum CategoryLabels {
+ VarLabels,
+ CountedValues,
+}
+
+#[derive(Clone, Debug)]
+pub enum MultipleResponseType {
+ MultipleDichotomy {
+ value: UnencodedString,
+ labels: CategoryLabels,
+ },
+ MultipleCategory,
+}
+
+impl MultipleResponseType {
+ fn parse(input: &[u8]) -> Result<(MultipleResponseType, &[u8]), Error> {
+ let (mr_type, input) = match input.get(0) {
+ Some(b'C') => (MultipleResponseType::MultipleCategory, &input[1..]),
+ Some(b'D') => {
+ let (value, input) = parse_counted_string(&input[1..])?;
+ (
+ MultipleResponseType::MultipleDichotomy {
+ value: value.into(),
+ labels: CategoryLabels::VarLabels,
+ },
+ input,
+ )
+ }
+ Some(b'E') => {
+ let Some(b' ') = input.get(1) else {
+ return Err(Error::TBD);
+ };
+ let input = &input[2..];
+ let (labels, input) = if let Some(rest) = input.strip_prefix(b" 1 ") {
+ (CategoryLabels::CountedValues, rest)
+ } else if let Some(rest) = input.strip_prefix(b" 11 ") {
+ (CategoryLabels::VarLabels, rest)
+ } else {
+ return Err(Error::TBD);
+ };
+ let (value, input) = parse_counted_string(input)?;
+ (
+ MultipleResponseType::MultipleDichotomy {
+ value: value.into(),
+ labels,
+ },
+ input,
+ )
+ }
+ _ => return Err(Error::TBD),
+ };
+ Ok((mr_type, input))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct MultipleResponseSet {
+ pub name: UnencodedString,
+ pub label: UnencodedString,
+ pub mr_type: MultipleResponseType,
+ pub short_names: Vec<UnencodedString>,
+}
+
+impl MultipleResponseSet {
+ fn parse(input: &[u8]) -> Result<(MultipleResponseSet, &[u8]), Error> {
+ let Some(equals) = input.iter().position(|&b| b == b'=') else {
+ return Err(Error::TBD);
+ };
+ let (name, input) = input.split_at(equals);
+ let (mr_type, input) = MultipleResponseType::parse(input)?;
+ let Some(b' ') = input.get(0) else {
+ return Err(Error::TBD);
+ };
+ let (label, mut input) = parse_counted_string(&input[1..])?;
+ let mut vars = Vec::new();
+ while input.get(0) == Some(&b' ') {
+ input = &input[1..];
+ let Some(length) = input.iter().position(|b| b" \n".contains(b)) else {
+ return Err(Error::TBD);
+ };
+ if length > 0 {
+ vars.push(input[..length].into());
+ }
+ input = &input[length..];
+ }
+ if input.get(0) != Some(&b'\n') {
+ return Err(Error::TBD);
+ }
+ while input.get(0) == Some(&b'\n') {
+ input = &input[1..];
+ }
+ Ok((
+ MultipleResponseSet {
+ name: name.into(),
+ label: label.into(),
+ mr_type,
+ short_names: vars,
+ },
+ input,
+ ))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct MultipleResponseRecord(pub Vec<MultipleResponseSet>);
+
+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";
+
+ fn parse(ext: &Extension, _endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ ext.check_size::<Self>()?;
+
+ let mut input = &ext.data[..];
+ let mut sets = Vec::new();
+ while !input.is_empty() {
+ let (set, rest) = MultipleResponseSet::parse(input)?;
+ sets.push(set);
+ input = rest;