more functions for reading tablelooks
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 11 Mar 2025 06:04:37 +0000 (23:04 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 11 Mar 2025 06:04:37 +0000 (23:04 -0700)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/pivot/tlo.rs

index 0978751d486f4d64dbe7479c3c59fd277d537917..8209a010fc6fc2390efcfc5a5cf931189381a2bf 100644 (file)
 use std::{
     collections::HashMap,
     fmt::{Debug, Display, Write},
+    io::Read,
     iter::{once, repeat},
     ops::{Index, IndexMut, Not, Range, RangeInclusive},
-    str::{from_utf8, FromStr},
+    str::{from_utf8, FromStr, Utf8Error},
     sync::{Arc, OnceLock, Weak},
 };
 
+use binrw::Error as BinError;
 use chrono::NaiveDateTime;
 pub use color::ParseError as ParseColorError;
 use color::{palette::css::TRANSPARENT, AlphaColor, Rgba8, Srgb};
@@ -74,6 +76,8 @@ use quick_xml::{de::from_str, DeError};
 use serde::{de::Visitor, Deserialize};
 use smallstr::SmallString;
 use smallvec::{smallvec, SmallVec};
+use thiserror::Error as ThisError;
+use tlo::parse_tlo;
 
 use crate::{
     dictionary::Value as DataValue,
@@ -575,14 +579,54 @@ impl Default for Look {
     }
 }
 
+#[derive(ThisError, Debug)]
+pub enum ParseLookError {
+    #[error("{0}")]
+    XmlError(#[from] DeError),
+
+    #[error("{0}")]
+    Utf8Error(#[from] Utf8Error),
+
+    #[error("{0}")]
+    BinError(#[from] BinError),
+
+    #[error("{0}")]
+    IoError(#[from] std::io::Error),
+}
+
 impl Look {
-    fn shared_default() -> Arc<Look> {
+    pub fn shared_default() -> Arc<Look> {
         static LOOK: OnceLock<Arc<Look>> = OnceLock::new();
         LOOK.get_or_init(|| Arc::new(Look::default())).clone()
     }
 
-    fn from_xml(xml: &str) -> Result<Self, DeError> {
-        Ok(from_str::<TableProperties>(xml)?.into())
+    pub fn from_xml(xml: &str) -> Result<Self, ParseLookError> {
+        Ok(from_str::<TableProperties>(xml)
+            .map_err(ParseLookError::from)?
+            .into())
+    }
+
+    pub fn from_binary(tlo: &[u8]) -> Result<Self, ParseLookError> {
+        parse_tlo(tlo).map_err(ParseLookError::from)
+    }
+
+    pub fn from_data(data: &[u8]) -> Result<Self, ParseLookError> {
+        if data.starts_with(b"\xff\xff\0\0") {
+            Self::from_binary(data)
+        } else {
+            Self::from_xml(from_utf8(data).map_err(ParseLookError::from)?)
+        }
+    }
+
+    pub fn from_reader<R>(mut reader: R) -> Result<Self, ParseLookError>
+    where
+        R: Read,
+    {
+        let mut buffer = Vec::new();
+        reader
+            .read_to_end(&mut buffer)
+            .map_err(ParseLookError::from)?;
+        Self::from_data(&buffer)
     }
 }
 
index 9dfd21aeffcff4a00486d9ae9a02981fc1ebd93f..198c1aad4e4c952de25f72518947cfe23f578153 100644 (file)
@@ -1,4 +1,4 @@
-use std::fmt::Debug;
+use std::{fmt::Debug, io::Cursor};
 
 use crate::output::pivot::{
     Axis2, Border, BoxBorder, FootnoteMarkerPosition, FootnoteMarkerType, HeadingRegion,
@@ -21,6 +21,18 @@ struct TableLook {
     v2_styles: V2Styles,
 }
 
+pub fn parse_tlo(input: &[u8]) -> BinResult<Look> {
+    let mut cursor = Cursor::new(input);
+    let tlo = TableLook::read(&mut cursor)?;
+    match input.len() as u64 - cursor.position() {
+        0 => Ok(tlo.into()),
+        extra => Err(BinError::AssertFail {
+            pos: cursor.position(),
+            message: format!("unexpected {extra} bytes following TLO data"),
+        }),
+    }
+}
+
 /// Points (72/inch) to pixels (96/inch).
 fn pt_to_px(pt: i32) -> usize {
     num::cast((pt as f64 * (96.0 / 72.0)).round()).unwrap_or_default()
@@ -530,18 +542,11 @@ impl Debug for U8String {
 
 #[cfg(test)]
 mod test {
-    use std::io::Cursor;
-
-    use binrw::BinRead;
-
-    use crate::output::pivot::{tlo::TableLook, Look};
+    use crate::output::pivot::tlo::parse_tlo;
 
     #[test]
     fn parse() {
-        let bytes = include_bytes!("test1.tlo");
-        let tlo = TableLook::read(&mut Cursor::new(bytes)).unwrap();
-        println!("{tlo:#?}");
-        let look = Look::from(tlo);
+        let look = parse_tlo(include_bytes!("test1.tlo"));
         println!("{look:#?}");
     }
 }