#[error("Text string contains invalid bytes for {encoding} encoding: {text}")]
MalformedString { encoding: String, text: String },
+ #[error("Invalid variable measurement level value {0}")]
+ InvalidMeasurement(u32),
+
+ #[error("Invalid variable display alignment value {0}")]
+ InvalidAlignment(u32),
+
#[error("Details TBD")]
TBD,
}
2 => Ok(Some(VariableRecord::read(reader, endian)?)),
3 => Ok(ValueLabelRecord::read(reader, endian, var_types, warn)?),
6 => Ok(Some(DocumentRecord::read(reader, endian)?)),
- 7 => Extension::read(reader, endian, warn),
+ 7 => Extension::read(reader, endian, var_types.len(), warn),
999 => Ok(Some(Record::EndOfHeaders(
endian.parse(read_bytes(reader)?),
))),
}
output
}
-
+
fn decode<'a>(&self, input: &'a RawString) -> Cow<'a, str> {
self.decode_slice(input.0.as_slice())
}
fn decode<'a>(&'a self, decoder: &Decoder) -> DocumentRecord<Cow<'a, str>> {
DocumentRecord {
offsets: self.offsets.clone(),
- lines: self.lines.iter().map(|s| decoder.decode_slice(&s.0)).collect(),
+ lines: self
+ .lines
+ .iter()
+ .map(|s| decoder.decode_slice(&s.0))
+ .collect(),
}
}
}
Ok((string.into(), rest))
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Measure {
+ Nominal,
+ Ordinal,
+ Scale,
+}
+
+impl Measure {
+ fn try_decode(source: u32) -> Result<Option<Measure>, Error> {
+ match source {
+ 0 => Ok(None),
+ 1 => Ok(Some(Measure::Nominal)),
+ 2 => Ok(Some(Measure::Ordinal)),
+ 3 => Ok(Some(Measure::Scale)),
+ _ => Err(Error::InvalidMeasurement(source)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Alignment {
+ Left,
+ Right,
+ Center,
+}
+
+impl Alignment {
+ fn try_decode(source: u32) -> Result<Option<Alignment>, Error> {
+ match source {
+ 0 => Ok(None),
+ 1 => Ok(Some(Alignment::Left)),
+ 2 => Ok(Some(Alignment::Right)),
+ 3 => Ok(Some(Alignment::Center)),
+ _ => Err(Error::InvalidAlignment(source)),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct VarDisplay {
+ pub measure: Option<Measure>,
+ pub width: Option<u32>,
+ pub alignment: Option<Alignment>,
+}
+
#[derive(Clone, Debug)]
-pub struct VarDisplayRecord(pub Vec<u32>);
+pub struct VarDisplayRecord(pub Vec<VarDisplay>);
-impl ExtensionRecord for VarDisplayRecord {
+impl 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) -> Result<Record, Error> {
- ext.check_size::<Self>()?;
+ fn parse(
+ ext: &Extension,
+ n_vars: usize,
+ endian: Endian,
+ warn: &Box<dyn Fn(Error)>,
+ ) -> Result<Record, Error> {
+ if ext.size != 4 {
+ return Err(Error::BadRecordSize {
+ offset: ext.offsets.start,
+ record: String::from("variable display record"),
+ size: ext.size,
+ expected_size: 4,
+ });
+ }
+ let has_width = if ext.count as usize == 3 * n_vars {
+ true
+ } else if ext.count as usize == 2 * n_vars {
+ false
+ } else {
+ return Err(Error::TBD);
+ };
+
+ let mut var_displays = Vec::new();
let mut input = &ext.data[..];
- let display = (0..ext.count)
- .map(|_| endian.parse(read_bytes(&mut input).unwrap()))
- .collect();
- Ok(Record::VarDisplay(VarDisplayRecord(display)))
+ for _ in 0..n_vars {
+ let measure = Measure::try_decode(endian.parse(read_bytes(&mut input).unwrap()))
+ .warn_on_error(&warn)
+ .flatten();
+ let width = has_width.then(|| endian.parse(read_bytes(&mut input).unwrap()));
+ let alignment = Alignment::try_decode(endian.parse(read_bytes(&mut input).unwrap()))
+ .warn_on_error(&warn)
+ .flatten();
+ var_displays.push(VarDisplay {
+ measure,
+ width,
+ alignment,
+ });
+ }
+ Ok(Record::VarDisplay(VarDisplayRecord(var_displays)))
}
}
#[derive(Clone, Debug)]
pub struct VariableSetRecord {
pub offsets: Range<u64>,
- pub sets: Vec<VariableSet>
+ pub sets: Vec<VariableSet>,
}
impl VariableSetRecord {
sets.push(set)
}
}
- VariableSetRecord { offsets: source.offsets.clone(), sets }
+ VariableSetRecord {
+ offsets: source.offsets.clone(),
+ sets,
+ }
}
}
fn read<R: Read + Seek>(
r: &mut R,
endian: Endian,
+ n_vars: usize,
warn: &Box<dyn Fn(Error)>,
) -> Result<Option<Record>, Error> {
let subtype = endian.parse(read_bytes(r)?);
let result = match subtype {
IntegerInfoRecord::SUBTYPE => IntegerInfoRecord::parse(&extension, endian),
FloatInfoRecord::SUBTYPE => FloatInfoRecord::parse(&extension, endian),
- VarDisplayRecord::SUBTYPE => VarDisplayRecord::parse(&extension, endian),
+ VarDisplayRecord::SUBTYPE => VarDisplayRecord::parse(&extension, n_vars, endian, warn),
MultipleResponseRecord::SUBTYPE | 19 => {
MultipleResponseRecord::parse(&extension, endian)
}