From: Ben Pfaff Date: Wed, 10 Dec 2025 19:25:10 +0000 (-0800) Subject: Move spv reader and writer to top-level module. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5bd7fb2785dfd48bb2c5b20f8cf0f3249a821109;p=pspp Move spv reader and writer to top-level module. --- diff --git a/rust/pspp/src/cli/convert.rs b/rust/pspp/src/cli/convert.rs index 26dc202964..3df207134d 100644 --- a/rust/pspp/src/cli/convert.rs +++ b/rust/pspp/src/cli/convert.rs @@ -23,7 +23,7 @@ use pspp::{ data::{ByteString, Case, Datum}, dictionary::Dictionary, file::FileType, - output::{Criteria, drivers::Driver, spv}, + output::{Criteria, drivers::Driver}, pc::PcFile, por::PortableFile, sys::ReadOptions, @@ -143,7 +143,7 @@ impl Convert { self.write_data(dictionary, cases) } Some(FileType::Viewer { .. }) => { - let (items, page_setup) = spv::ReadOptions::new() + let (items, page_setup) = pspp::spv::read::ReadOptions::new() .with_password(self.password.clone()) .open_file(&self.input)? .into_parts(); diff --git a/rust/pspp/src/cli/show_spv.rs b/rust/pspp/src/cli/show_spv.rs index 631c3177c8..2a068602e6 100644 --- a/rust/pspp/src/cli/show_spv.rs +++ b/rust/pspp/src/cli/show_spv.rs @@ -16,7 +16,7 @@ use anyhow::Result; use clap::{Args, ValueEnum}; -use pspp::output::{Criteria, Item, spv}; +use pspp::output::{Criteria, Item}; use std::{fmt::Display, path::PathBuf}; /// Show information about SPSS viewer files (SPV files). @@ -90,7 +90,7 @@ impl ShowSpv { pub fn run(self) -> Result<()> { match self.mode { Mode::Directory => { - let item = spv::ReadOptions::new() + let item = pspp::spv::read::ReadOptions::new() .with_password(self.password) .open_file(&self.input)? .into_items(); @@ -101,7 +101,7 @@ impl ShowSpv { Ok(()) } Mode::View => { - let item = spv::ReadOptions::new() + let item = pspp::spv::read::ReadOptions::new() .with_password(self.password) .open_file(&self.input)? .into_items(); diff --git a/rust/pspp/src/output.rs b/rust/pspp/src/output.rs index 1253116210..225051e6e2 100644 --- a/rust/pspp/src/output.rs +++ b/rust/pspp/src/output.rs @@ -46,7 +46,6 @@ pub mod drivers; pub mod page; pub mod pivot; pub mod render; -pub mod spv; pub mod table; /// A single output item. diff --git a/rust/pspp/src/output/drivers/cairo/driver.rs b/rust/pspp/src/output/drivers/cairo/driver.rs index 1fa4c3e019..8b8b29089f 100644 --- a/rust/pspp/src/output/drivers/cairo/driver.rs +++ b/rust/pspp/src/output/drivers/cairo/driver.rs @@ -27,18 +27,20 @@ use pango::SCALE; use paper_sizes::Unit; use serde::{Deserialize, Serialize}; -use crate::output::{ - Details, Item, ItemCursor, TextType, - drivers::{ - Driver, - cairo::{ - fsm::{CairoFsmStyle, parse_font_style}, - pager::{CairoPageStyle, CairoPager}, +use crate::{ + output::{ + Details, Item, ItemCursor, TextType, + drivers::{ + Driver, + cairo::{ + fsm::{CairoFsmStyle, parse_font_style}, + pager::{CairoPageStyle, CairoPager}, + }, }, + page::PageSetup, + pivot::{Color, Coord2, FontStyle}, }, - page::PageSetup, - pivot::{Color, Coord2, FontStyle}, - spv::html::Variable, + spv::read::html::Variable, }; use crate::output::pivot::Axis2; diff --git a/rust/pspp/src/output/drivers/cairo/fsm.rs b/rust/pspp/src/output/drivers/cairo/fsm.rs index 5c411ec1d9..1d92e12ec4 100644 --- a/rust/pspp/src/output/drivers/cairo/fsm.rs +++ b/rust/pspp/src/output/drivers/cairo/fsm.rs @@ -29,10 +29,10 @@ use smallvec::{SmallVec, smallvec}; use crate::output::drivers::cairo::{px_to_xr, xr_to_pt}; use crate::output::pivot::{Axis2, BorderStyle, Coord2, FontStyle, HorzAlign, Rect2, Stroke}; use crate::output::render::{Device, Extreme, Pager, Params}; -use crate::output::spv::html::Markup; use crate::output::table::DrawCell; use crate::output::{Details, Item}; use crate::output::{pivot::Color, table::Content}; +use crate::spv::read::html::Markup; /// Width of an ordinary line. const LINE_WIDTH: isize = LINE_SPACE / 2; diff --git a/rust/pspp/src/output/drivers/cairo/pager.rs b/rust/pspp/src/output/drivers/cairo/pager.rs index 6cb1152992..6811646c26 100644 --- a/rust/pspp/src/output/drivers/cairo/pager.rs +++ b/rust/pspp/src/output/drivers/cairo/pager.rs @@ -20,16 +20,12 @@ use cairo::{Context, RecordingSurface}; use enum_map::EnumMap; use pango::Layout; -use crate::output::{ - Item, +use crate::{output::{ drivers::cairo::{ fsm::{CairoFsm, CairoFsmStyle}, xr_to_pt, - }, - pivot::{Axis2, CellStyle, FontStyle, Rect2, ValueOptions}, - spv::html::{Document, Variable}, - table::DrawCell, -}; + }, pivot::{Axis2, CellStyle, FontStyle, Rect2, ValueOptions}, table::DrawCell, Item +}, spv::read::html::{Document, Variable}}; #[derive(Clone, Debug)] pub struct CairoPageStyle { diff --git a/rust/pspp/src/output/page.rs b/rust/pspp/src/output/page.rs index d8ea2e67d9..611a70442a 100644 --- a/rust/pspp/src/output/page.rs +++ b/rust/pspp/src/output/page.rs @@ -20,7 +20,7 @@ use enum_map::{EnumMap, enum_map}; use paper_sizes::{Catalog, Length, PaperSize, Unit}; use serde::{Deserialize, Deserializer, Serialize, de::Error}; -use crate::output::spv::html::Document; +use crate::spv::read::html::Document; use super::pivot::Axis2; diff --git a/rust/pspp/src/output/pivot.rs b/rust/pspp/src/output/pivot.rs index 3a60b2d6d9..2b588301c4 100644 --- a/rust/pspp/src/output/pivot.rs +++ b/rust/pspp/src/output/pivot.rs @@ -72,16 +72,9 @@ pub use tlo::parse_bool; use tlo::parse_tlo; use crate::{ - calendar::date_time_to_pspp, - data::{ByteString, Datum, EncodedString}, - format::{ - DATETIME40_0, Decimal, F8_2, F40, F40_2, F40_3, Format, PCT40_1, - Settings as FormatSettings, Type, UncheckedFormat, - }, - output::spv::html::Markup, - settings::{Settings, Show}, - util::ToSmallString, - variable::{VarType, Variable}, + calendar::date_time_to_pspp, data::{ByteString, Datum, EncodedString}, format::{ + Decimal, Format, Settings as FormatSettings, Type, UncheckedFormat, DATETIME40_0, F40, F40_2, F40_3, F8_2, PCT40_1 + }, settings::{Settings, Show}, spv::read::html::Markup, util::ToSmallString, variable::{VarType, Variable} }; pub mod output; diff --git a/rust/pspp/src/output/spv.rs b/rust/pspp/src/output/spv.rs deleted file mode 100644 index 7be381b96b..0000000000 --- a/rust/pspp/src/output/spv.rs +++ /dev/null @@ -1,774 +0,0 @@ -// 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::{ - fs::File, - io::{BufReader, Cursor, Read, Seek}, - path::Path, -}; - -use anyhow::{Context, anyhow}; -use binrw::{BinRead, error::ContextExt}; -use cairo::ImageSurface; -use displaydoc::Display; -use paper_sizes::PaperSize; -use serde::Deserialize; -use zip::{ZipArchive, result::ZipError}; - -use crate::{ - crypto::EncryptedFile, - output::{ - Details, Item, SpvInfo, SpvMembers, Text, - page::{self}, - pivot::{Axis2, Length, Look, TableProperties, Value}, - spv::{ - html::Document, - legacy_bin::LegacyBin, - legacy_xml::Visualization, - light::{LightError, LightTable}, - }, - }, -}; - -mod css; -pub mod html; -mod legacy_bin; -mod legacy_xml; -mod light; - -/// Options for reading an SPV file. -#[derive(Clone, Debug, Default)] -pub struct ReadOptions { - /// Password to use to unlock an encrypted SPV file. - /// - /// For an encrypted SPV file, this must be set to the (encoded or - /// unencoded) password. - /// - /// For a plaintext SPV file, this must be None. - pub password: Option, -} - -impl ReadOptions { - /// Construct a new [ReadOptions] without a password. - pub fn new() -> Self { - Self::default() - } - - /// Causes the file to be read by decrypting it with the given `password` or - /// without decrypting if `password` is None. - pub fn with_password(self, password: Option) -> Self { - Self { password } - } - - /// Opens the file at `path`. - pub fn open_file

(mut self, path: P) -> Result - where - P: AsRef, - { - let file = File::open(path)?; - if let Some(password) = self.password.take() { - self.open_reader_encrypted(file, password) - } else { - Self::open_reader_inner(file) - } - } - - /// Opens the file read from `reader`. - fn open_reader_encrypted(self, reader: R, password: String) -> Result - where - R: Read + Seek + 'static, - { - Self::open_reader_inner( - EncryptedFile::new(reader)? - .unlock(password.as_bytes()) - .map_err(|_| anyhow!("Incorrect password."))?, - ) - } - - /// Opens the file read from `reader`. - pub fn open_reader(mut self, reader: R) -> Result - where - R: Read + Seek + 'static, - { - if let Some(password) = self.password.take() { - self.open_reader_encrypted(reader, password) - } else { - Self::open_reader_inner(reader) - } - } - - fn open_reader_inner(reader: R) -> Result - where - R: Read + Seek + 'static, - { - // Open archive. - let mut archive = ZipArchive::new(reader).map_err(|error| match error { - ZipError::InvalidArchive(_) => Error::NotSpv, - other => other.into(), - })?; - Ok(Self::from_spv_zip_archive(&mut archive)?) - } - - fn from_spv_zip_archive(archive: &mut ZipArchive) -> Result - where - R: Read + Seek, - { - // Check manifest. - let mut file = archive - .by_name("META-INF/MANIFEST.MF") - .map_err(|_| Error::NotSpv)?; - let mut string = String::new(); - file.read_to_string(&mut string)?; - if string.trim() != "allowPivoting=true" { - return Err(Error::NotSpv); - } - drop(file); - - let mut items = Vec::new(); - let mut page_setup = None; - for i in 0..archive.len() { - let name = String::from(archive.name_for_index(i).unwrap()); - if name.starts_with("outputViewer") && name.ends_with(".xml") { - let (mut new_items, ps) = read_heading(archive, i, &name)?; - items.append(&mut new_items); - page_setup = page_setup.or(ps); - } - } - - Ok(SpvFile { - item: items.into_iter().collect(), - page_setup, - }) - } -} - -pub struct SpvFile { - /// SPV file contents. - pub item: Vec, - - /// The page setup in the SPV file, if any. - pub page_setup: Option, -} - -impl SpvFile { - pub fn into_parts(self) -> (Vec, Option) { - (self.item, self.page_setup) - } - - pub fn into_items(self) -> Vec { - self.item - } -} - -#[derive(Debug, Display, thiserror::Error)] -pub enum Error { - /// Not an SPV file. - NotSpv, - - /// {0} - ZipError(#[from] ZipError), - - /// {0} - IoError(#[from] std::io::Error), - - /// {0} - DeError(#[from] quick_xml::DeError), - - /// {0} - BinrwError(#[from] binrw::Error), - - /// {0} - LightError(#[from] LightError), - - /// {0} - CairoError(#[from] cairo::IoError), -} - -fn new_error_item(message: impl Into) -> Item { - Text::new_log(message).into_item().with_label("Error") -} - -fn read_heading( - archive: &mut ZipArchive, - file_number: usize, - structure_member: &str, -) -> Result<(Vec, Option), Error> -where - R: Read + Seek, -{ - let member = BufReader::new(archive.by_index(file_number)?); - let mut heading: Heading = match serde_path_to_error::deserialize( - &mut quick_xml::de::Deserializer::from_reader(member), - ) - .with_context(|| format!("Failed to parse {structure_member}")) - { - Ok(result) => result, - Err(error) => panic!("{error:?}"), - }; - let _page_setup = heading.page_setup.take(); - // XXX convert page_setup to the internal format - Ok((heading.decode(archive, structure_member)?, None)) -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Heading { - #[serde(rename = "@visibility")] - visibility: Option, - #[serde(rename = "@commandName")] - command_name: Option, - label: Label, - page_setup: Option, - - #[serde(rename = "$value")] - #[serde(default)] - children: Vec, -} - -impl Heading { - fn decode( - self, - archive: &mut ZipArchive, - structure_member: &str, - ) -> Result, Error> - where - R: Read + Seek, - { - let mut items = Vec::new(); - for child in self.children { - match child { - HeadingContent::Container(container) => { - if container.page_break_before == PageBreakBefore::Always { - items.push( - Details::PageBreak - .into_item() - .with_spv_info(SpvInfo::new(structure_member)), - ); - } - let item = match container.content { - ContainerContent::Table(table) => { - table.decode(archive, structure_member).unwrap() /* XXX*/ - } - ContainerContent::Graph(graph) => graph.decode(structure_member), - ContainerContent::Text(container_text) => Text::new( - match container_text.text_type { - TextType::Title => crate::output::TextType::Title, - TextType::Log | TextType::Text => crate::output::TextType::Log, - TextType::PageTitle => crate::output::TextType::PageTitle, - }, - container_text.decode(), - ) - .into_item() - .with_command_name(container_text.command_name) - .with_spv_info(SpvInfo::new(structure_member)), - ContainerContent::Image(image) => { - image.decode(archive, structure_member).unwrap() - } /*XXX*/, - ContainerContent::Object(object) => { - object.decode(archive, structure_member).unwrap() - } /*XXX*/, - ContainerContent::Model => new_error_item("models not yet implemented") - .with_spv_info(SpvInfo::new(structure_member).with_error()), - ContainerContent::Tree => new_error_item("trees not yet implemented") - .with_spv_info(SpvInfo::new(structure_member).with_error()), - }; - items.push(item.with_show(container.visibility == Visibility::Visible)); - } - HeadingContent::Heading(mut heading) => { - let show = !heading.visibility.is_some(); - let label = std::mem::take(&mut heading.label.text); - let command_name = heading.command_name.take(); - items.push( - heading - .decode(archive, structure_member)? - .into_iter() - .collect::() - .with_show(show) - .with_label(label) - .with_command_name(command_name) - .with_spv_info(SpvInfo::new(structure_member)), - ); - } - } - } - Ok(items) - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PageSetup { - #[serde(rename = "@initial-page-number")] - pub initial_page_number: Option, - #[serde(rename = "@chart-size")] - pub chart_size: Option, - #[serde(rename = "@margin-left")] - pub margin_left: Option, - #[serde(rename = "@margin-right")] - pub margin_right: Option, - #[serde(rename = "@margin-top")] - pub margin_top: Option, - #[serde(rename = "@margin-bottom")] - pub margin_bottom: Option, - #[serde(rename = "@paper-height")] - pub paper_height: Option, - #[serde(rename = "@paper-width")] - pub paper_width: Option, - #[serde(rename = "@reference-orientation")] - pub reference_orientation: Option, - #[serde(rename = "@space-after")] - pub space_after: Option, - pub page_header: PageHeader, - pub page_footer: PageFooter, -} - -impl PageSetup { - fn decode(&self) -> page::PageSetup { - let mut setup = page::PageSetup::default(); - if let Some(initial_page_number) = self.initial_page_number { - setup.initial_page_number = initial_page_number; - } - if let Some(chart_size) = self.chart_size { - setup.chart_size = chart_size.into(); - } - if let Some(margin_left) = self.margin_left { - setup.margins.0[Axis2::X][0] = margin_left.into(); - } - if let Some(margin_right) = self.margin_right { - setup.margins.0[Axis2::X][1] = margin_right.into(); - } - if let Some(margin_top) = self.margin_top { - setup.margins.0[Axis2::Y][0] = margin_top.into(); - } - if let Some(margin_bottom) = self.margin_bottom { - setup.margins.0[Axis2::Y][1] = margin_bottom.into(); - } - match (self.paper_width, self.paper_height) { - (Some(width), Some(height)) => { - setup.paper = PaperSize::new(width.0, height.0, paper_sizes::Unit::Inch) - } - (Some(length), None) | (None, Some(length)) => { - setup.paper = PaperSize::new(length.0, length.0, paper_sizes::Unit::Inch) - } - (None, None) => (), - } - if let Some(reference_orientation) = self.reference_orientation { - setup.orientation = reference_orientation.into(); - } - if let Some(space_after) = self.space_after { - setup.object_spacing = space_after.into(); - } - if let Some(PageParagraph { text }) = &self.page_header.page_paragraph { - setup.header = text.decode(); - } - if let Some(PageParagraph { text }) = &self.page_footer.page_paragraph { - setup.footer = text.decode(); - } - setup - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PageHeader { - page_paragraph: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PageFooter { - page_paragraph: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PageParagraph { - text: PageParagraphText, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PageParagraphText { - #[serde(default, rename = "$text")] - text: String, -} - -impl PageParagraphText { - fn decode(&self) -> Document { - Document::from_html(&self.text) - } -} - -#[derive(Copy, Clone, Debug, Default, Deserialize)] -#[serde(rename = "snake_case")] -pub enum ReferenceOrientation { - #[serde(alias = "0")] - #[serde(alias = "0deg")] - #[serde(alias = "inherit")] - #[default] - Portrait, - - #[serde(alias = "90")] - #[serde(alias = "90deg")] - #[serde(alias = "-270")] - #[serde(alias = "-270deg")] - Landscape, - - #[serde(alias = "180")] - #[serde(alias = "180deg")] - #[serde(alias = "-1280")] - #[serde(alias = "-180deg")] - ReversePortrait, - - #[serde(alias = "270")] - #[serde(alias = "270deg")] - #[serde(alias = "-90")] - #[serde(alias = "-90deg")] - Seascape, -} - -impl From for page::Orientation { - fn from(value: ReferenceOrientation) -> Self { - match value { - ReferenceOrientation::Portrait | ReferenceOrientation::ReversePortrait => { - page::Orientation::Portrait - } - ReferenceOrientation::Landscape | ReferenceOrientation::Seascape => { - page::Orientation::Landscape - } - } - } -} - -/// Chart size. -#[derive(Copy, Clone, Debug, Default, Deserialize)] -pub enum ChartSize { - #[default] - #[serde(rename = "as-is")] - AsIs, - - #[serde(rename = "full-height")] - FullHeight, - - #[serde(rename = "half-height")] - HalfHeight, - - #[serde(rename = "quarter-height")] - QuarterHeight, -} - -impl From for page::ChartSize { - fn from(value: ChartSize) -> Self { - match value { - ChartSize::AsIs => page::ChartSize::AsIs, - ChartSize::FullHeight => page::ChartSize::FullHeight, - ChartSize::HalfHeight => page::ChartSize::HalfHeight, - ChartSize::QuarterHeight => page::ChartSize::QuarterHeight, - } - } -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -enum HeadingContent { - Container(Container), - Heading(Box), -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Label { - #[serde(default, rename = "$text")] - text: String, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Container { - #[serde(default, rename = "@visibility")] - visibility: Visibility, - #[serde(rename = "@page-break-before")] - #[serde(default)] - page_break_before: PageBreakBefore, - #[serde(rename = "@text-align")] - text_align: Option, - #[serde(rename = "@width")] - width: Option, - label: Label, - - #[serde(rename = "$value")] - content: ContainerContent, -} - -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase")] -enum PageBreakBefore { - #[default] - Auto, - Always, - Avoid, - Left, - Right, - Inherit, -} - -#[derive(Deserialize, Debug, Default, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -enum Visibility { - #[default] - Visible, - Hidden, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -enum TextAlign { - Left, - Center, - Right, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -enum ContainerContent { - Table(Table), - Text(ContainerText), - Graph(Graph), - Model, - Object(Object), - Image(Image), - Tree, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Graph { - #[serde(rename = "@commandName")] - command_name: String, - data_path: Option, - path: String, - csv_path: Option, -} - -impl Graph { - fn decode(&self, structure_member: &str) -> Item { - crate::output::Chart - .into_item() - .with_spv_info( - SpvInfo::new(structure_member).with_members(SpvMembers::Graph { - data: self.data_path.clone(), - xml: self.path.clone(), - csv: self.csv_path.clone(), - }), - ) - } -} - -fn decode_image( - archive: &mut ZipArchive, - structure_member: &str, - command_name: &Option, - image_name: &str, -) -> Result -where - R: Read + Seek, -{ - let mut png = archive.by_name(image_name)?; - let image = ImageSurface::create_from_png(&mut png)?; - Ok(Details::Image(image) - .into_item() - .with_command_name(command_name.clone()) - .with_spv_info( - SpvInfo::new(structure_member).with_members(SpvMembers::Image(image_name.into())), - )) -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Image { - #[serde(rename = "@commandName")] - command_name: Option, - data_path: String, -} - -impl Image { - fn decode(&self, archive: &mut ZipArchive, structure_member: &str) -> Result - where - R: Read + Seek, - { - decode_image( - archive, - structure_member, - &self.command_name, - &self.data_path, - ) - } -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Object { - #[serde(rename = "@commandName")] - command_name: Option, - #[serde(rename = "@uri")] - uri: String, -} - -impl Object { - fn decode(&self, archive: &mut ZipArchive, structure_member: &str) -> Result - where - R: Read + Seek, - { - decode_image(archive, structure_member, &self.command_name, &self.uri) - } -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct Table { - #[serde(rename = "@commandName")] - command_name: String, - #[serde(rename = "@subType")] - sub_type: String, - #[serde(rename = "@tableId")] - table_id: Option, - #[serde(rename = "@type")] - table_type: TableType, - properties: Option, - table_structure: TableStructure, -} - -impl Table { - fn decode(&self, archive: &mut ZipArchive, structure_member: &str) -> Result - where - R: Read + Seek, - { - 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())), - )) - } - Some(xml_member_name) => { - let bin_member_name = &self.table_structure.data_path; - let mut bin_member = archive.by_name(bin_member_name)?; - let mut bin_data = Vec::with_capacity(bin_member.size() as usize); - bin_member.read_to_end(&mut bin_data)?; - let mut cursor = Cursor::new(bin_data); - let legacy_bin = LegacyBin::read(&mut cursor).map_err(|e| { - e.with_message(format!( - "While parsing {bin_member_name:?} as legacy binary SPV member" - )) - })?; - let data = legacy_bin.decode(); - drop(bin_member); - - 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:?}"), - }; - let pivot_table = visualization.decode( - data, - self.properties - .as_ref() - .map_or_else(Look::default, |properties| properties.clone().into()), - )?; - - Ok(pivot_table.into_item().with_spv_info( - SpvInfo::new(structure_member).with_members(SpvMembers::Legacy { - xml: xml_member_name.clone(), - binary: bin_member_name.clone(), - }), - )) - } - } - } -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -enum TableType { - Table, - Note, - Warning, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ContainerText { - #[serde(rename = "@type")] - text_type: TextType, - #[serde(rename = "@commandName")] - command_name: Option, - html: String, -} - -impl ContainerText { - fn decode(&self) -> Value { - html::Document::from_html(&self.html).into_value() - } -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -enum TextType { - Title, - Log, - Text, - #[serde(rename = "page-title")] - PageTitle, -} - -#[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, -} - -#[cfg(test)] -#[test] -fn test_spv() { - let items = ReadOptions::new() - .open_file("/home/blp/pspp/rust/tests/utilities/regress.spv") - .unwrap() - .into_items(); - for item in items { - println!("{item}"); - } - todo!() -} diff --git a/rust/pspp/src/output/spv/css.rs b/rust/pspp/src/output/spv/css.rs deleted file mode 100644 index 17fbe92435..0000000000 --- a/rust/pspp/src/output/spv/css.rs +++ /dev/null @@ -1,377 +0,0 @@ -use std::{ - borrow::Cow, - fmt::{Display, Write}, - mem::discriminant, - ops::Not, -}; - -use itertools::Itertools; - -use crate::output::{ - pivot::{FontStyle, HorzAlign}, - spv::html::Style, -}; - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Token<'a> { - Id(Cow<'a, str>), - LeftCurly, - RightCurly, - Colon, - Semicolon, - Error, -} - -struct Lexer<'a>(&'a str); - -impl<'a> Iterator for Lexer<'a> { - type Item = Token<'a>; - - fn next(&mut self) -> Option { - let mut s = self.0; - loop { - s = s.trim_start(); - if let Some(rest) = s.strip_prefix("") { - s = rest; - } else { - break; - } - } - let mut iter = s.chars(); - let (c, mut rest) = (iter.next()?, iter.as_str()); - let (token, rest) = match c { - '{' => (Token::LeftCurly, rest), - '}' => (Token::RightCurly, rest), - ':' => (Token::Colon, rest), - ';' => (Token::Semicolon, rest), - '\'' | '"' => { - let quote = c; - let mut s = String::new(); - while let Some(c) = iter.next() { - if c == quote { - break; - } else if c != '\\' { - s.push(c); - } else { - let start = iter.as_str(); - match iter.next() { - None => break, - Some(a) if a.is_ascii_alphanumeric() => { - let n = start - .chars() - .take_while(|c| c.is_ascii_alphanumeric()) - .take(6) - .count(); - iter = start[n..].chars(); - if let Ok(code_point) = u32::from_str_radix(&start[..n], 16) - && let Ok(c) = char::try_from(code_point) - { - s.push(c); - } - } - Some('\n') => (), - Some(other) => s.push(other), - } - } - } - (Token::Id(Cow::from(s)), iter.as_str()) - } - _ => { - while !iter.as_str().starts_with("-->") - && let Some(c) = iter.next() - && !c.is_whitespace() - && c != '{' - && c != '}' - && c != ':' - && c != ';' - { - rest = iter.as_str(); - } - let id_len = s.len() - rest.len(); - let (id, rest) = s.split_at(id_len); - (Token::Id(Cow::from(id)), rest) - } - }; - self.0 = rest; - Some(token) - } -} - -impl HorzAlign { - pub fn from_css(s: &str) -> Option { - let mut lexer = Lexer(s); - while let Some(token) = lexer.next() { - if let Token::Id(key) = token - && let Some(Token::Colon) = lexer.next() - && let Some(Token::Id(value)) = lexer.next() - && key.as_ref() == "text-align" - && let Ok(align) = value.parse() - { - return Some(align); - } - } - None - } -} - -impl Style { - pub fn parse_css(styles: &mut Vec