+#[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;
+ }
+ Ok(MultipleResponseRecord(sets))
+ }
+}
+
+fn parse_counted_string(input: &[u8]) -> Result<(UnencodedString, &[u8]), Error> {
+ let Some(space) = input.iter().position(|&b| b == b' ') else {
+ return Err(Error::TBD);
+ };
+ let Ok(length) = from_utf8(&input[..space]) else {
+ return Err(Error::TBD);
+ };
+ let Ok(length): Result<usize, _> = length.parse() else {
+ return Err(Error::TBD);
+ };
+
+ let input = &input[space + 1..];
+ if input.len() < length {
+ return Err(Error::TBD);
+ };
+
+ let (string, rest) = input.split_at(length);
+ Ok((string.into(), rest))
+}
+
+#[derive(Clone, Debug)]
+pub struct VarDisplayRecord(pub 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";
+
+ fn parse(ext: &Extension, endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ ext.check_size::<Self>()?;
+
+ let mut input = &ext.data[..];
+ let display = (0..ext.count)
+ .map(|_| endian.parse(read_bytes(&mut input).unwrap()))
+ .collect();
+ Ok(VarDisplayRecord(display))
+ }
+}
+
+pub struct LongStringMissingValues {
+ /// Variable name.
+ pub var_name: UnencodedString,
+
+ /// Missing values.
+ pub missing_values: MissingValues,
+}
+
+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";
+
+ fn parse(ext: &Extension, endian: Endian, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ ext.check_size::<Self>()?;
+
+ let mut input = &ext.data[..];
+ let mut missing_value_set = Vec::new();
+ while !input.is_empty() {
+ let var_name = read_string(&mut input, endian)?;
+ let n_missing_values: u8 = endian.parse(read_bytes(&mut input)?);
+ let value_len: u32 = endian.parse(read_bytes(&mut input)?);
+ if value_len != 8 {
+ let offset = (ext.data.len() - input.len() - 8) as u64 + ext.offset;
+ return Err(Error::BadLongMissingValueLength {
+ record_offset: ext.offset,
+ offset,
+ value_len,
+ });
+ }
+ let mut values = Vec::new();
+ for i in 0..n_missing_values {
+ let value: [u8; 8] = read_bytes(&mut input)?;
+ let numeric_value: u64 = endian.parse(value);
+ let value = if i > 0 && numeric_value == 8 {
+ // Tolerate files written by old, buggy versions of PSPP
+ // where we believed that the value_length was repeated
+ // before each missing value.
+ read_bytes(&mut input)?
+ } else {
+ value
+ };
+ values.push(Value::String(UnencodedStr(value)));
+ }
+ let missing_values = MissingValues {
+ values,
+ range: None,
+ };
+ missing_value_set.push(LongStringMissingValues {
+ var_name,
+ missing_values,
+ });
+ }
+ Ok(LongStringMissingValueSet(missing_value_set))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct EncodingRecord(pub String);