use flate2::read::ZlibDecoder;
use num::Integer;
use std::borrow::Cow;
+use std::cmp::Ordering;
use std::fmt::{Debug, Formatter, Result as FmtResult};
+use std::ops::Range;
use std::str::from_utf8;
use std::{
collections::VecDeque,
#[error("At offset {offset:#x}, unrecognized record type {rec_type}.")]
BadRecordType { offset: u64, rec_type: u32 },
- #[error("At offset {offset:#x}, variable label code ({code}) is not 0 or 1.")]
- BadVariableLabelCode { offset: u64, code: u32 },
+ #[error("In variable record starting at offset {start_offset:#x}, variable label code {code} at offset {code_offset:#x} is not 0 or 1.")]
+ BadVariableLabelCode { start_offset: u64, code_offset: u64, code: u32 },
#[error(
"At offset {offset:#x}, numeric missing value code ({code}) is not -3, -2, 0, 1, 2, or 3."
// If `s` is valid UTF-8, returns it decoded as UTF-8, otherwise returns it
// decoded as Latin-1 (actually bytes interpreted as Unicode code points).
-fn default_decode<'a>(s: &'a [u8]) -> Cow<'a, str> {
+fn default_decode<>(s: &[u8]) -> Cow<str> {
from_utf8(s).map_or_else(|_| decode_latin1(s), Cow::from)
}
ZLib,
}
+trait Header {
+ fn offsets(&self) -> Range<u64>;
+}
+
#[derive(Clone)]
pub struct HeaderRecord {
+ /// Offset in file.
+ pub offsets: Range<u64>,
+
/// Magic number.
pub magic: Magic,
fn fmt(&self, f: &mut Formatter) -> FmtResult {
writeln!(f, "File header record:")?;
self.debug_field(f, "Magic", self.magic)?;
- self.debug_field(f, "Product name", &self.eye_catcher)?;
+ self.debug_field(f, "Product name", self.eye_catcher)?;
self.debug_field(f, "Layout code", self.layout_code)?;
self.debug_field(f, "Nominal case size", self.nominal_case_size)?;
self.debug_field(f, "Compression", self.compression)?;
self.debug_field(f, "Weight index", self.weight_index)?;
self.debug_field(f, "Number of cases", self.n_cases)?;
self.debug_field(f, "Compression bias", self.bias)?;
- self.debug_field(f, "Creation date", &self.creation_date)?;
- self.debug_field(f, "Creation time", &self.creation_time)?;
- self.debug_field(f, "File label", &self.file_label)?;
+ self.debug_field(f, "Creation date", self.creation_date)?;
+ self.debug_field(f, "Creation time", self.creation_time)?;
+ self.debug_field(f, "File label", self.file_label)?;
self.debug_field(f, "Endianness", self.endian)
}
}
impl HeaderRecord {
- fn read<R: Read>(r: &mut R) -> Result<HeaderRecord, Error> {
+ fn read<R: Read + Seek>(r: &mut R) -> Result<HeaderRecord, Error> {
+ let start = r.stream_position()?;
+
let magic: [u8; 4] = read_bytes(r)?;
let magic: Magic = magic.try_into().map_err(|_| Error::NotASystemFile)?;
let _: [u8; 3] = read_bytes(r)?;
Ok(HeaderRecord {
+ offsets: start..r.stream_position()?,
magic,
layout_code,
nominal_case_size,
}
}
+impl Header for HeaderRecord {
+ fn offsets(&self) -> Range<u64> {
+ self.offsets.clone()
+ }
+}
+
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Magic([u8; 4]);
impl Debug for Magic {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
- let s = match self {
- &Magic::SAV => "$FL2",
- &Magic::ZSAV => "$FL3",
- &Magic::EBCDIC => "($FL2 in EBCDIC)",
+ let s = match *self {
+ Magic::SAV => "$FL2",
+ Magic::ZSAV => "$FL3",
+ Magic::EBCDIC => "($FL2 in EBCDIC)",
_ => return write!(f, "{:?}", self.0),
};
write!(f, "{s}")
#[derive(Clone)]
pub struct VariableRecord {
- /// Offset from the start of the file to the start of the record.
- pub offset: u64,
+ /// Range of offsets in file.
+ pub offsets: Range<u64>,
/// Variable width, in the range -1..=255.
pub width: i32,
f,
"Width: {} ({})",
self.width,
- if self.width > 0 {
- "string"
- } else if self.width == 0 {
- "numeric"
- } else {
- "long string continuation record"
+ match self.width.cmp(&0) {
+ Ordering::Greater => "string",
+ Ordering::Equal => "numeric",
+ Ordering::Less => "long string continuation record",
}
)?;
writeln!(f, "Print format: {:?}", self.print_format)?;
impl VariableRecord {
fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<VariableRecord, Error> {
- let offset = r.stream_position()?;
+ let start_offset = r.stream_position()?;
let width: i32 = endian.parse(read_bytes(r)?);
+ let code_offset = r.stream_position()?;
let has_variable_label: u32 = endian.parse(read_bytes(r)?);
let missing_value_code: i32 = endian.parse(read_bytes(r)?);
let print_format = Spec(endian.parse(read_bytes(r)?));
}
_ => {
return Err(Error::BadVariableLabelCode {
- offset,
+ start_offset,
+ code_offset,
code: has_variable_label,
})
}
};
- let missing_values = MissingValues::read(r, offset, width, missing_value_code, endian)?;
+ let missing_values = MissingValues::read(r, start_offset, width, missing_value_code, endian)?;
+
+ let end_offset = r.stream_position()?;
Ok(VariableRecord {
- offset,
+ offsets: start_offset..end_offset,
width,
name,
print_format,
impl MultipleResponseType {
fn parse(input: &[u8]) -> Result<(MultipleResponseType, &[u8]), Error> {
- let (mr_type, input) = match input.get(0) {
+ let (mr_type, input) = match input.first() {
Some(b'C') => (MultipleResponseType::MultipleCategory, &input[1..]),
Some(b'D') => {
let (value, input) = parse_counted_string(&input[1..])?;
(
MultipleResponseType::MultipleDichotomy {
- value: value.into(),
+ value,
labels: CategoryLabels::VarLabels,
},
input,
let (value, input) = parse_counted_string(input)?;
(
MultipleResponseType::MultipleDichotomy {
- value: value.into(),
+ value,
labels,
},
input,
};
let (name, input) = input.split_at(equals);
let (mr_type, input) = MultipleResponseType::parse(input)?;
- let Some(b' ') = input.get(0) else {
+ let Some(b' ') = input.first() 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' ') {
+ while input.first() == Some(&b' ') {
input = &input[1..];
let Some(length) = input.iter().position(|b| b" \n".contains(b)) else {
return Err(Error::TBD);
}
input = &input[length..];
}
- if input.get(0) != Some(&b'\n') {
+ if input.first() != Some(&b'\n') {
return Err(Error::TBD);
}
- while input.get(0) == Some(&b'\n') {
+ while input.first() == Some(&b'\n') {
input = &input[1..];
}
Ok((
MultipleResponseSet {
name: name.into(),
- label: label.into(),
+ label,
mr_type,
short_names: vars,
},