From 640ed7e35c2a70d7878d157944fd06beb8b4b69a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 10 Mar 2025 23:04:37 -0700 Subject: [PATCH] more functions for reading tablelooks --- rust/pspp/src/output/pivot/mod.rs | 52 ++++++++++++++++++++++++++++--- rust/pspp/src/output/pivot/tlo.rs | 25 +++++++++------ 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index 0978751d48..8209a010fc 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -58,12 +58,14 @@ 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 { + pub fn shared_default() -> Arc { static LOOK: OnceLock> = OnceLock::new(); LOOK.get_or_init(|| Arc::new(Look::default())).clone() } - fn from_xml(xml: &str) -> Result { - Ok(from_str::(xml)?.into()) + pub fn from_xml(xml: &str) -> Result { + Ok(from_str::(xml) + .map_err(ParseLookError::from)? + .into()) + } + + pub fn from_binary(tlo: &[u8]) -> Result { + parse_tlo(tlo).map_err(ParseLookError::from) + } + + pub fn from_data(data: &[u8]) -> Result { + 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(mut reader: R) -> Result + where + R: Read, + { + let mut buffer = Vec::new(); + reader + .read_to_end(&mut buffer) + .map_err(ParseLookError::from)?; + Self::from_data(&buffer) } } diff --git a/rust/pspp/src/output/pivot/tlo.rs b/rust/pspp/src/output/pivot/tlo.rs index 9dfd21aeff..198c1aad4e 100644 --- a/rust/pspp/src/output/pivot/tlo.rs +++ b/rust/pspp/src/output/pivot/tlo.rs @@ -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 { + 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:#?}"); } } -- 2.30.2