From 98a435fcc04a5adfb6bd3b85627b4a597418f225 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 17 Oct 2025 13:44:33 -0700 Subject: [PATCH] work on legacy spvs --- rust/doc/src/spv/legacy-detail-xml.md | 2 + rust/pspp/src/output/spv.rs | 55 +- rust/pspp/src/output/spv/legacy.rs | 1104 +++++++++++++++++++++++++ 3 files changed, 1143 insertions(+), 18 deletions(-) create mode 100644 rust/pspp/src/output/spv/legacy.rs diff --git a/rust/doc/src/spv/legacy-detail-xml.md b/rust/doc/src/spv/legacy-detail-xml.md index 5f0fe3399a..f7e77f0c32 100644 --- a/rust/doc/src/spv/legacy-detail-xml.md +++ b/rust/doc/src/spv/legacy-detail-xml.md @@ -67,6 +67,8 @@ categoricalDomain => variableReference simpleSort simpleSort :method[sort_method]=(custom) => categoryOrder +categoryOrder => TEXT + container :style=ref style => container_extension? location+ labelFrame* extension[container_extension] :combinedFootnotes=(true) => EMPTY diff --git a/rust/pspp/src/output/spv.rs b/rust/pspp/src/output/spv.rs index 39afb651b3..292d91926e 100644 --- a/rust/pspp/src/output/spv.rs +++ b/rust/pspp/src/output/spv.rs @@ -31,11 +31,15 @@ use crate::output::{ Details, Item, SpvInfo, SpvMembers, Text, page::PageSetup, pivot::{PivotTable, TableProperties, Value}, - spv::light::{LightError, LightTable}, + spv::{ + legacy::Visualization, + light::{LightError, LightTable}, + }, }; mod css; pub mod html; +mod legacy; mod light; #[derive(Debug, Display, thiserror::Error)] @@ -396,24 +400,36 @@ impl Table { where R: Read + Seek, { - if self.table_structure.path.is_none() { - let member_name = &self.table_structure.data_path; - let mut light = archive.by_name(member_name)?; - let mut data = Vec::with_capacity(light.size() as usize); - light.read_to_end(&mut data)?; - let mut cursor = Cursor::new(data); - let table = LightTable::read(&mut cursor).map_err(|e| { - e.with_message(format!( - "While parsing {member_name:?} as light binary SPV member" + match &self.table_structure.path { + None => { + let member_name = &self.table_structure.data_path; + let mut light = archive.by_name(member_name)?; + let mut data = Vec::with_capacity(light.size() as usize); + light.read_to_end(&mut data)?; + let mut cursor = Cursor::new(data); + let table = LightTable::read(&mut cursor).map_err(|e| { + e.with_message(format!( + "While parsing {member_name:?} as light binary SPV member" + )) + })?; + let pivot_table = table.decode()?; + Ok(pivot_table.into_item().with_spv_info( + SpvInfo::new(structure_member) + .with_members(SpvMembers::Light(self.table_structure.data_path.clone())), )) - })?; - let pivot_table = table.decode()?; - Ok(pivot_table.into_item().with_spv_info( - SpvInfo::new(structure_member) - .with_members(SpvMembers::Light(self.table_structure.data_path.clone())), - )) - } else { - Ok(PivotTable::new([]).into_item()) + } + Some(xml_member_name) => { + let member = BufReader::new(archive.by_name(&xml_member_name)?); + let _visualization: Visualization = match serde_path_to_error::deserialize( + &mut quick_xml::de::Deserializer::from_reader(member), + ) + .with_context(|| format!("Failed to parse {xml_member_name}")) + { + Ok(result) => result, + Err(error) => panic!("{error:?}"), + }; + Ok(PivotTable::new([]).into_item()) + } } } } @@ -455,8 +471,11 @@ enum TextType { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct TableStructure { + /// The `.xml` member name, for legacy members only. path: Option, + /// The `.bin` member name. data_path: String, + /// Rarely used, not understood. csv_path: Option, } diff --git a/rust/pspp/src/output/spv/legacy.rs b/rust/pspp/src/output/spv/legacy.rs new file mode 100644 index 0000000000..209f553e59 --- /dev/null +++ b/rust/pspp/src/output/spv/legacy.rs @@ -0,0 +1,1104 @@ +// PSPP - a program for statistical analysis. +// Copyright (C) 2025 Free Software Foundation, Inc. +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +use std::marker::PhantomData; + +use serde::Deserialize; + +use crate::output::pivot::Color; + +#[derive(Debug)] +struct Ref { + references: String, + _phantom: PhantomData, +} + +impl<'de, T> Deserialize<'de> for Ref { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self { + references: String::deserialize(deserializer)?, + _phantom: PhantomData, + }) + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Visualization { + /// In format `YYYY-MM-DD`. + #[serde(rename = "@date")] + date: String, + // Locale used for output, e.g. `en-US`. + #[serde(rename = "@lang")] + lang: String, + /// Localized title of the pivot table. + #[serde(rename = "@name")] + name: String, + /// Base style for the pivot table. + #[serde(rename = "@style")] + style: Ref