use flate2::read::ZlibDecoder;
use num::Integer;
+use std::str::from_utf8;
use std::{
collections::VecDeque,
io::{Error as IoError, Read, Seek, SeekFrom},
}
*/
+trait TextRecord
+where
+ Self: Sized,
+{
+ const NAME: &'static str;
+ fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error>;
+}
+
trait ExtensionRecord
where
Self: Sized,
}
}
+pub enum CategoryLabels {
+ VarLabels,
+ CountedValues,
+}
+pub enum MultipleResponseType {
+ MultipleDichotomy {
+ value: Vec<u8>,
+ labels: CategoryLabels,
+ },
+ MultipleCategory,
+}
+pub struct MultipleResponseSet {
+ pub name: Vec<u8>,
+ pub label: Vec<u8>,
+ pub mr_type: MultipleResponseType,
+ pub vars: Vec<Vec<u8>>,
+}
+
+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) = 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),
+ };
+ 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,
+ vars,
+ },
+ input,
+ ))
+ }
+}
+
+pub struct MultipleResponseSets(Vec<MultipleResponseSet>);
+
+impl ExtensionRecord for MultipleResponseSets {
+ 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(MultipleResponseSets(sets))
+ }
+}
+
+fn parse_counted_string(input: &[u8]) -> Result<(&[u8], &[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, rest))
+}
+
+pub struct ExtraProductInfo(String);
+
+impl TextRecord for ExtraProductInfo {
+ const NAME: &'static str = "extra product info";
+ fn parse(input: &str, _warn: impl Fn(Error)) -> Result<Self, Error> {
+ Ok(ExtraProductInfo(input.into()))
+ }
+}
+
pub struct VarDisplayRecord(Vec<u32>);
impl ExtensionRecord for VarDisplayRecord {
}
}
+pub struct VariableSet {
+ pub name: String,
+ pub vars: Vec<String>,
+}
+
+impl VariableSet {
+ fn parse(input: &str) -> Result<Self, Error> {
+ let (name, input) = input.split_once('=').ok_or(Error::TBD)?;
+ let vars = input.split_ascii_whitespace().map(String::from).collect();
+ Ok(VariableSet {
+ name: name.into(),
+ vars,
+ })
+ }
+}
+
+pub struct VariableSetRecord(Vec<VariableSet>);
+
+impl TextRecord for VariableSetRecord {
+ const NAME: &'static str = "variable set";
+ fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
+ let mut sets = Vec::new();
+ for line in input.lines() {
+ match VariableSet::parse(line) {
+ Ok(set) => sets.push(set),
+ Err(error) => warn(error),
+ }
+ }
+ Ok(VariableSetRecord(sets))
+ }
+}
+
+pub struct LongVariableName {
+ pub short_name: String,
+ pub long_name: String,
+}
+
+pub struct LongVariableNameRecord(Vec<LongVariableName>);
+
+impl TextRecord for LongVariableNameRecord {
+ const NAME: &'static str = "long variable names";
+ fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
+ let mut names = Vec::new();
+ for pair in input.split('\t').filter(|s| !s.is_empty()) {
+ if let Some((short_name, long_name)) = pair.split_once('=') {
+ let name = LongVariableName {
+ short_name: short_name.into(),
+ long_name: long_name.into(),
+ };
+ names.push(name);
+ } else {
+ warn(Error::TBD)
+ }
+ }
+ Ok(LongVariableNameRecord(names))
+ }
+}
+
+pub struct VeryLongString {
+ short_name: String,
+ length: usize,
+}
+
+impl VeryLongString {
+ fn parse(input: &str) -> Result<VeryLongString, Error> {
+ let Some((short_name, length)) = input.split_once('=') else {
+ return Err(Error::TBD);
+ };
+ let length: usize = length.parse().map_err(|_| Error::TBD)?;
+ Ok(VeryLongString {
+ short_name: short_name.into(),
+ length,
+ })
+ }
+}
+
+pub struct VeryLongStringRecord(Vec<VeryLongString>);
+
+impl TextRecord for VeryLongStringRecord {
+ const NAME: &'static str = "very long strings";
+ fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
+ let mut very_long_strings = Vec::new();
+ for tuple in input
+ .split('\0')
+ .map(|s| s.trim_end_matches('\t'))
+ .filter(|s| !s.is_empty())
+ {
+ match VeryLongString::parse(tuple) {
+ Ok(vls) => very_long_strings.push(vls),
+ Err(error) => warn(error),
+ }
+ }
+ Ok(VeryLongStringRecord(very_long_strings))
+ }
+}
+
pub struct LongStringValueLabels {
pub var_name: Vec<u8>,
pub width: u32,