// this program. If not, see <http://www.gnu.org/licenses/>.
use anyhow::Result;
+use binrw::{BinRead, error::ContextExt};
use clap::{Args, ValueEnum};
use pspp::{
- output::{Criteria, Item, Itemlike},
- spv::SpvArchive,
+ output::{
+ Criteria, Item, Itemlike, SpvMembers,
+ pivot::{Axis3, Dimension, Group, Leaf, PivotTable, value::Value},
+ },
+ spv::{SpvArchive, legacy_bin::LegacyBin},
+};
+use std::{
+ collections::HashMap,
+ fmt::Display,
+ io::{Cursor, Read},
+ path::PathBuf,
+ sync::Arc,
};
-use std::{fmt::Display, path::PathBuf, sync::Arc};
/// Show information about SPSS viewer files (SPV files).
#[derive(Args, Clone, Debug)]
}
fn legacy_data(self) -> Result<()> {
- todo!() /*
- let spv = SpvArchive::open_file(&self.input, self.password.as_deref())?;
- let outline = spv.read_outline(|w| eprintln!("{w}"))?;
- for item in self.read()? {
- for item in ItemRefIterator::new(&item) {
- if let Some(spv_info) = &item.spv_info
- && let Some(members) = &spv_info.members
- && let SpvMembers::LegacyTable { xml: _, binary } = &members
- {
- let mut bin_member = spv_file.archive.by_name(&binary)?;
- 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 {binary:?} as legacy binary SPV member"
- ))
- })?;
- let data = legacy_bin.decode();
- let n_values = data
- .values()
- .flat_map(|map| map.values())
- .map(|values| values.len())
- .max()
- .unwrap_or(0);
- let index = Dimension::new(
- Group::new("Index")
- .with_multiple(Leaf::numbers(0..n_values))
- .with_label_shown(),
- );
- let variables = Dimension::new(Group::new("Variables").with_multiple(
- data.iter().map(|(name, contents)| {
- Group::new(name.as_str()).with_multiple(contents.keys().map(|name| {
- name.replace("categories", "\ncategories")
- .replace("labels", "\nlabels")
- .replace("group", "\ngroup")
- .replace("Label", "\nLabel")
- }))
- }),
- ));
- let mut pivot_table =
- PivotTable::new([(Axis3::Y, index), (Axis3::X, variables)]);
- let formats = HashMap::new();
- for (variable_index, (variable_name, values)) in
- data.values().flat_map(|map| map.iter()).enumerate()
- {
- for (value_index, data_value) in values.iter().enumerate() {
- let value = Value::new_datum(&data_value.value).with_value_label(
- (variable_name == "cellFormat")
- .then(|| data_value.as_format(&formats).to_string()),
- );
- pivot_table.insert([value_index, variable_index], value);
- }
- }
- println!("{pivot_table}");
+ let mut archive = SpvArchive::open_file(&self.input, self.password.as_deref())?;
+ let outline = archive.read_outline(|w| eprintln!("{w}"))?;
+ for item in outline.items {
+ for item in item.iter_in_order() {
+ if let Some(spv_info) = item.spv_info()
+ && let Some(members) = &spv_info.members
+ && let SpvMembers::LegacyTable { xml: _, binary } = &members
+ {
+ let mut bin_member = archive.0.by_name(&binary)?;
+ 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 {binary:?} as legacy binary SPV member"
+ ))
+ })?;
+ let data = legacy_bin.decode();
+ let n_values = data
+ .values()
+ .flat_map(|map| map.values())
+ .map(|values| values.len())
+ .max()
+ .unwrap_or(0);
+ let index = Dimension::new(
+ Group::new("Index")
+ .with_multiple(Leaf::numbers(0..n_values))
+ .with_label_shown(),
+ );
+ let variables = Dimension::new(Group::new("Variables").with_multiple(
+ data.iter().map(|(name, contents)| {
+ Group::new(name.as_str()).with_multiple(contents.keys().map(|name| {
+ name.replace("categories", "\ncategories")
+ .replace("labels", "\nlabels")
+ .replace("group", "\ngroup")
+ .replace("Label", "\nLabel")
+ }))
+ }),
+ ));
+ let mut pivot_table =
+ PivotTable::new([(Axis3::Y, index), (Axis3::X, variables)]);
+ let formats = HashMap::new();
+ for (variable_index, (variable_name, values)) in
+ data.values().flat_map(|map| map.iter()).enumerate()
+ {
+ for (value_index, data_value) in values.iter().enumerate() {
+ let value = Value::new_datum(&data_value.value).with_value_label(
+ (variable_name == "cellFormat")
+ .then(|| data_value.as_format(&formats).to_string()),
+ );
+ pivot_table.insert([value_index, variable_index], value);
+ }
+ }
+ println!("{pivot_table}");
+ }
+ }
}
- }
- }
- Ok(())*/
+ Ok(())
}
}
}
pub trait Itemlike {
- fn label(&self) -> Cow<'static, str>;
+ fn label(&self) -> Cow<'_, str>;
fn command_name(&self) -> Option<&str>;
fn subtype(&self) -> Option<String>;
fn is_shown(&self) -> bool;
fn page_break_before(&self) -> bool;
- fn spv_info(&self) -> Option<&SpvInfo>;
+ fn spv_info(&self) -> Option<Cow<'_, SpvInfo>>;
fn iter_in_order(&self) -> ItemRefIterator<'_, Self>
where
Self: Sized;
"Page Title" => Class::PageTitle,
_ => Class::Texts,
},
+ ItemKind::Model => Class::Models,
+ ItemKind::Tree => Class::Trees,
}
}
}
impl Itemlike for Item {
- fn label(&self) -> Cow<'static, str> {
+ fn label(&self) -> Cow<'_, str> {
match &self.label {
Some(label) => Cow::from(label.clone()),
None => self.details.label(),
self.page_break_before
}
- fn spv_info(&self) -> Option<&SpvInfo> {
- self.spv_info.as_deref()
+ fn spv_info(&self) -> Option<Cow<'_, SpvInfo>> {
+ self.spv_info
+ .as_ref()
+ .map(|spv_info| Cow::Borrowed(&**spv_info))
}
fn iter_in_order(&self) -> ItemRefIterator<'_, Item> {
Message(Severity),
Table,
Text,
+ Model,
+ Tree,
}
impl ItemKind {
ItemKind::Message(_) => "message",
ItemKind::Table => "table",
ItemKind::Text => "text",
+ ItemKind::Model => "model",
+ ItemKind::Tree => "tree",
}
}
}
// Returns the label for the heading with the given `level` in the stack
// above the current item. Level 0 is the top level. Levels without a
// label are skipped.
- pub fn heading(&self, level: usize) -> Option<Cow<'static, str>> {
+ pub fn heading(&self, level: usize) -> Option<Cow<'_, str>> {
self.stack
.iter()
.map(|(item, _index)| item.as_ref().label())
use std::{
+ borrow::Cow,
io::{BufRead, BufReader, Cursor, Read, Seek},
mem::take,
+ sync::Arc,
};
use anyhow::Context;
use crate::{
output::{
- Details, Item, SpvInfo, SpvMembers, Text,
+ Details, Item, ItemKind, ItemRefIterator, Itemlike, SpvInfo, SpvMembers, Text,
page::PageSetup,
pivot::{
Length,
structure_member: String,
expand: bool,
label: String,
- children: Vec<OutlineItem>,
+ children: Vec<Arc<OutlineItem>>,
command_name: Option<String>,
}
{
match self {
OutlineItem::Container(container) => {
- let mut spv_info = container.spv_info();
+ let mut spv_info = container.spv_info().clone();
let result = match container.content {
Content::Table(table) => table.decode(archive, &mut *warn),
Content::Graph(_) => Err(Error::GraphTodo),
heading
.children
.into_iter()
- .map(|child| child.read_item(archive, &mut *warn))
+ .map(|child| Arc::unwrap_or_clone(child).read_item(archive, &mut *warn))
.collect::<Item>()
.with_show(expand)
.with_label(label)
}
}
+impl Itemlike for OutlineItem {
+ fn label(&self) -> Cow<'_, str> {
+ match self {
+ OutlineItem::Heading(outline_heading) => Cow::from(outline_heading.label.as_str()),
+ OutlineItem::Container(container) => Cow::from(container.label.as_str()),
+ }
+ }
+
+ fn command_name(&self) -> Option<&str> {
+ match self {
+ OutlineItem::Heading(outline_heading) => outline_heading.command_name.as_deref(),
+ OutlineItem::Container(container) => Some(&container.command_name),
+ }
+ }
+
+ fn subtype(&self) -> Option<String> {
+ match self {
+ OutlineItem::Heading(_) => None,
+ OutlineItem::Container(container) => container.content.subtype().map(|s| s.into()),
+ }
+ }
+
+ fn is_shown(&self) -> bool {
+ match self {
+ OutlineItem::Heading(_) => true,
+ OutlineItem::Container(container) => container.show,
+ }
+ }
+
+ fn page_break_before(&self) -> bool {
+ match self {
+ OutlineItem::Heading(_) => false,
+ OutlineItem::Container(container) => container.page_break_before,
+ }
+ }
+
+ fn spv_info(&self) -> Option<Cow<'_, SpvInfo>> {
+ let spv_info = match self {
+ OutlineItem::Heading(outline_heading) => outline_heading.spv_info(),
+ OutlineItem::Container(container) => container.spv_info(),
+ };
+ Some(Cow::Owned(spv_info))
+ }
+
+ fn iter_in_order(&self) -> ItemRefIterator<'_, Self>
+ where
+ Self: Sized,
+ {
+ ItemRefIterator::new(self)
+ }
+
+ fn children(&self) -> &[Arc<Self>] {
+ match self {
+ OutlineItem::Heading(outline_heading) => &outline_heading.children,
+ OutlineItem::Container(_) => &[],
+ }
+ }
+
+ fn children_mut(&mut self) -> Option<&mut Vec<Arc<Self>>> {
+ match self {
+ OutlineItem::Heading(outline_heading) => Some(&mut outline_heading.children),
+ OutlineItem::Container(_) => None,
+ }
+ }
+
+ fn kind(&self) -> ItemKind {
+ match self {
+ OutlineItem::Heading(_) => ItemKind::Heading,
+ OutlineItem::Container(container) => container.content.kind(),
+ }
+ }
+}
+
#[derive(Clone, Debug)]
pub struct Container {
structure_member: String,
}
impl Content {
+ fn kind(&self) -> ItemKind {
+ match self {
+ Content::Text(_) => ItemKind::Text,
+ Content::Table(_) => ItemKind::Table,
+ Content::Image(_) => ItemKind::Image,
+ Content::Graph(_) => ItemKind::Graph,
+ Content::Tree => ItemKind::Tree,
+ Content::Model => ItemKind::Model,
+ }
+ }
+
+ fn subtype(&self) -> Option<&str> {
+ match self {
+ Content::Table(table) => Some(&table.subtype),
+ _ => None,
+ }
+ }
+
fn members(&self) -> Option<SpvMembers> {
match self {
Content::Text(_text) => None,
}
mod raw {
- use std::mem::take;
+ use std::{mem::take, sync::Arc};
use paper_sizes::PaperSize;
use serde::Deserialize;
expand: !heading.visibility.is_some(),
label: take(&mut heading.label.text),
command_name: heading.command_name.take(),
- children: heading.decode(structure_member, warn),
+ children: heading
+ .decode(structure_member, warn)
+ .into_iter()
+ .map(Arc::new)
+ .collect(),
}));
}
}