- #[error("At offset {offset:#x}, variable index record (type 4) does not immediately follow value label record (type 3) as it should.")]
- MissingVariableIndexRecord { offset: u64 },
-
- #[error("At offset {offset:#x}, number of variables associated with a value label ({n}) is not between 1 and the number of variables ({max}).")]
- BadNumberOfValueLabelVariables { offset: u64, n: u32, max: u32 },
-}
-
-#[derive(Error, Debug)]
-pub enum Warning {
- #[error("Unexpected floating-point bias {0} or unrecognized floating-point format.")]
- UnexpectedBias(f64),
-
- #[error("Duplicate type 6 (document) record.")]
- DuplicateDocumentRecord,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum Compression {
- Simple,
- ZLib,
-}
-
-pub struct Reader<R: Read> {
- r: BufReader<R>,
-
- document_record: Option<DocumentRecord>,
-
- variables: Vec<VariableRecord>,
-
- value_labels: Vec<ValueLabelRecord>,
-}
-
-/// Magic number for a regular system file.
-pub const ASCII_MAGIC: &[u8; 4] = b"$FL2";
-
-/// Magic number for a system file that contains zlib-compressed data.
-pub const ASCII_ZMAGIC: &[u8; 4] = b"$FL3";
-
-/// Magic number for an EBDIC-encoded system file. This is `$FL2` encoded in
-/// EBCDIC.
-pub const EBCDIC_MAGIC: &[u8; 4] = &[0x5b, 0xc6, 0xd3, 0xf2];
-
-pub struct FileHeader {
- /// First 4 bytes of the file, one of `ASCII_MAGIC`, `ASCII_ZMAGIC`, and
- /// `EBCDIC_MAGIC`.
- pub magic: [u8; 4],
-
- /// True if `magic` indicates that this file contained zlib-compressed data.
- pub is_zsav: bool,
-
- /// True if `magic` indicates that this file contained EBCDIC data.
- pub is_ebcdic: bool,
-
- /// Endianness of the data in the file header.
- pub endianness: Endian,
-
- /// 0-based variable index of the weight variable, or `None` if the file is
- /// unweighted.
- pub weight_index: Option<u32>,
-
- /// Number of variable positions, or `None` if the value in the file is
- /// questionably trustworthy.
- pub nominal_case_size: Option<u32>,
-
- /// `dd mmm yy` in the file's encoding.
- pub creation_date: [u8; 9],
-
- /// `HH:MM:SS` in the file's encoding.
- pub creation_time: [u8; 8],
-
- /// Eye-catcher string, then product name, in the file's encoding. Padded
- /// on the right with spaces.
- pub eye_catcher: [u8; 60],
-
- /// File label, in the file's encoding. Padded on the right with spaces.
- pub file_label: [u8; 64],
-}
-
-pub const DOC_LINE_LEN: u32 = 80;
-pub const DOC_MAX_LINES: u32 = i32::MAX as u32 / DOC_LINE_LEN;
-
-impl<R: Read + Seek> Reader<R> {
- pub fn new(r: R, warn: impl Fn(Warning)) -> Result<Reader<R>, Error> {
- let mut r = BufReader::new(r);
-
- let header = read_header(&mut r, &warn)?;
- let e = header.endianness;
- let mut document_record = None;
- let mut variables = Vec::new();
- let mut value_labels = Vec::new();
- loop {
- let offset = r.stream_position()?;
- let rec_type: u32 = e.parse(read_bytes(&mut r)?);
- match rec_type {
- 2 => variables.push(read_variable_record(&mut r, e)?),
- 3 => value_labels.push(read_value_label_record(&mut r, e, variables.len())?),
- // A Type 4 record is always immediately after a type 3 record,
- // the code for type 3 records reads the type 4 record too.
- 4 => return Err(Error::MisplacedType4Record(offset)),
-
- 6 => {
- let d = read_document_record(&mut r, e)?;
- if document_record.is_some() {
- warn(Warning::DuplicateDocumentRecord);
- } else {
- document_record = d;
- }
- }
- /*
- 7 => d.read_extension_record()?,
- */
- 999 => break,
- _ => return Err(Error::BadRecordType { offset, rec_type }),
- }
- }
-
- Ok(Reader {
- r,
- document_record,
- variables,
- value_labels,
- })
- }
-}
-
-fn read_header<R: Read>(r: &mut R, warn: impl Fn(Warning)) -> Result<FileHeader, Error> {
- let magic: [u8; 4] = read_bytes(r)?;
- let (is_zsav, is_ebcdic) = match &magic {
- ASCII_MAGIC => (false, false),
- ASCII_ZMAGIC => (true, false),
- EBCDIC_MAGIC => (false, true),
- _ => return Err(Error::NotASystemFile),
- };
-
- let eye_catcher: [u8; 60] = read_bytes(r)?;
- let layout_code: [u8; 4] = read_bytes(r)?;
- let endianness = Endian::identify_u32(2, layout_code)
- .or_else(|| Endian::identify_u32(2, layout_code))
- .ok_or_else(|| Error::NotASystemFile)?;