output::{Criteria, drivers::Driver},
pc::PcFile,
por::PortableFile,
+ spv::SpvArchive,
sys::ReadOptions,
};
self.write_data(dictionary, cases)
}
Some(FileType::Viewer { .. }) => {
- let (items, page_setup) = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
- .with_password(self.password.clone())
- .open_file(&self.input)?
- .into_contents();
+ let (items, page_setup) =
+ SpvArchive::open_file(&self.input, self.password.as_deref())?
+ .read(|e| eprintln!("{e}"))?
+ .into_contents();
let mut output = self.open_driver("text")?;
if let Some(page_setup) = &page_setup {
output.setup(page_setup);
// 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, ItemRefIterator, SpvMembers,
- pivot::{Axis3, Dimension, Group, Leaf, PivotTable, value::Value},
- },
- spv::legacy_bin::LegacyBin,
-};
-use std::{
- collections::HashMap,
- fmt::Display,
- io::{Cursor, Read},
- path::PathBuf,
+ output::{Criteria, Item},
+ spv::SpvArchive,
};
+use std::{fmt::Display, path::PathBuf, sync::Arc};
/// Show information about SPSS viewer files (SPV files).
#[derive(Args, Clone, Debug)]
}
}
+ fn read(&self) -> Result<Vec<Arc<Item>>> {
+ Ok(self.criteria.apply(
+ SpvArchive::open_file(&self.input, self.password.as_deref())?
+ .read(|e| eprintln!("{e}"))?
+ .into_items(),
+ ))
+ }
+
fn directory(self) -> Result<()> {
- let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
- .with_password(self.password)
- .open_file(&self.input)?
- .into_items();
- let items = self.criteria.apply(item);
- for child in items {
+ for child in self.read()? {
print_item_directory(&child, 0, self.show_member_names);
}
Ok(())
}
fn view(self) -> Result<()> {
- let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
- .with_password(self.password)
- .open_file(&self.input)?
- .into_items();
- let items = self.criteria.apply(item);
- for child in items {
+ for child in self.read()? {
println!("{child}");
}
Ok(())
}
fn legacy_data(self) -> Result<()> {
- let mut spv_file = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
- .with_password(self.password)
- .open_file(&self.input)?;
-
- let items = self.criteria.apply(spv_file.items);
- for item in items {
- for item in ItemRefIterator::with_hidden(&item) {
- if let Some(spv_info) = dbg!(&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}");
- }
- }
+ 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);
}
- Ok(())
+ }
+ println!("{pivot_table}");
+ }
+ }
+ }
+ Ok(())*/
}
}
pub fn is_shown(&self) -> bool {
self.details.is_heading() || self.show
}
+
+ pub fn iter_in_order(&self) -> ItemRefIterator<'_> {
+ ItemRefIterator::new(self)
+ }
}
impl<T> From<T> for Item
}
impl<'a> ItemRefIterator<'a> {
- pub fn without_hidden(start: &'a Item) -> impl Iterator<Item = &'a Item> {
- Self::with_hidden(start).filter(|item| item.is_shown())
+ pub fn without_hidden(self) -> impl Iterator<Item = &'a Item> {
+ self.filter(|item| item.is_shown())
}
- pub fn with_hidden(start: &'a Item) -> Self {
+ pub fn new(start: &'a Item) -> Self {
Self {
next: Some(start),
stack: Vec::new(),
use unicode_linebreak::{BreakOpportunity, linebreaks};
use unicode_width::UnicodeWidthStr;
-use crate::output::{ItemRefIterator, render::Extreme, table::DrawCell};
+use crate::output::{render::Extreme, table::DrawCell};
use crate::output::{
Details, Item,
where
W: FmtWrite,
{
- for item in ItemRefIterator::without_hidden(item).filter(|item| !item.details.is_heading())
+ for item in item
+ .iter_in_order()
+ .without_hidden()
+ .filter(|item| !item.details.is_heading())
{
match &item.details {
Details::Graph => {
mod read;
mod write;
-pub use read::{Error, ReadOptions, SpvFile, html, legacy_bin};
+pub use read::{Error, SpvArchive, SpvFile, SpvOutline, html, legacy_bin};
pub use write::Writer;
// this program. If not, see <http://www.gnu.org/licenses/>.
#![allow(dead_code)]
use std::{
- cell::RefCell,
fmt::Display,
fs::File,
io::{BufReader, Read, Seek},
+ mem::take,
path::Path,
- rc::Rc,
};
use displaydoc::Display;
use crate::{
crypto::EncryptedReader,
output::{Item, page::PageSetup},
- spv::read::light::LightWarning,
+ spv::read::{light::LightWarning, structure::StructureMember},
};
mod css;
UnknownOrientation(String),
}
-/// Options for reading an SPV file.
-#[derive(Clone, Debug)]
-pub struct ReadOptions<F> {
- /// Function called to report warnings.
- pub warn: F,
-
- /// 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<String>,
-}
-
-impl<F> ReadOptions<F> {
- /// Construct a new [ReadOptions] without a password.
- pub fn new(warn: F) -> Self {
- Self {
- warn,
- password: None,
- }
- }
-
- /// 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<String>) -> Self {
- Self { password, ..self }
- }
-}
-
pub trait ReadSeek: Read + Seek {}
impl<T> ReadSeek for T where T: Read + Seek {}
-impl<F> ReadOptions<F>
-where
- F: FnMut(Warning) + 'static,
-{
+/// A ZIP archive that contains an SPV file.
+pub struct SpvArchive<R>(pub ZipArchive<R>);
+
+impl SpvArchive<Box<dyn ReadSeek>> {
/// Opens the file at `path`.
- pub fn open_file<P>(self, path: P) -> Result<SpvFile, Error>
+ pub fn open_file<P>(path: P, password: Option<&str>) -> Result<Self, Error>
where
P: AsRef<Path>,
{
- self.open_reader(File::open(path)?)
+ Self::open_reader(File::open(path)?, password)
}
- /// Opens the file read from `reader`.
- pub fn open_reader<R>(self, reader: R) -> Result<SpvFile, Error>
+ /// Opens the provided Zip `archive`.
+ ///
+ /// Any password provided for reading the file is unused, because if one was
+ /// needed then it must have already been used to open the archive.
+ pub fn open_reader<R>(reader: R, password: Option<&str>) -> Result<Self, Error>
where
R: Read + Seek + 'static,
{
- let reader = match &self.password {
- None => Box::new(reader) as Box<dyn ReadSeek>,
- Some(password) => Box::new(EncryptedReader::open(reader, password)?),
+ let reader = if let Some(password) = password {
+ Box::new(EncryptedReader::open(reader, password)?)
+ } else {
+ Box::new(reader) as Box<dyn ReadSeek>
};
- self.open_reader_inner(Box::new(reader))
- }
-
- fn open_reader_inner(self, reader: Box<dyn ReadSeek>) -> Result<SpvFile, Error> {
- let archive = ZipArchive::new(reader).map_err(|error| match error {
+ let mut archive = ZipArchive::new(reader).map_err(|error| match error {
ZipError::InvalidArchive(_) => Error::NotSpv,
other => other.into(),
})?;
- Ok(self.open_archive(archive)?)
- }
-
- /// Opens the provided Zip `archive`.
- ///
- /// Any password provided for reading the file is unused, because if one was
- /// needed then it must have already been used to open the archive.
- pub fn open_archive(
- self,
- mut archive: ZipArchive<Box<dyn ReadSeek>>,
- ) -> Result<SpvFile, Error> {
- // Check manifest.
let mut file = archive
.by_name("META-INF/MANIFEST.MF")
.map_err(|_| Error::NotSpv)?;
}
drop(file);
+ Ok(Self(archive))
+ }
+}
+
+impl<R> SpvArchive<R>
+where
+ R: Read + Seek,
+{
+ /// Reads and returns an outline of the contents of the SPV file.
+ ///
+ /// Most callers want to use [read](Self::read), instead, to read the full
+ /// contents of the SPV file.
+ pub fn read_outline<F>(&mut self, mut warn: F) -> Result<SpvOutline, Error>
+ where
+ F: FnMut(Warning),
+ {
// Read all the items.
- let warn = Rc::new(RefCell::new(Box::new(self.warn) as Box<dyn FnMut(Warning)>));
- 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());
+ let mut members = Vec::new();
+ for i in 0..self.0.len() {
+ let name = String::from(self.0.name_for_index(i).unwrap());
if name.starts_with("outputViewer") && name.ends_with(".xml") {
- let member = BufReader::new(archive.by_index(i)?);
- let member = structure::StructureMember::read(member, &name, &warn)?;
- items.extend(member.root.read_items(&mut archive, &name, &warn));
- page_setup = page_setup.or(member.page_setup);
+ let member = BufReader::new(self.0.by_index(i)?);
+ members.push(StructureMember::read(member, &name, &mut warn)?);
}
}
+ Ok(SpvOutline { members })
+ }
- Ok(SpvFile {
- items: items.into_iter().collect(),
- page_setup,
- archive,
- })
+ /// Reads and returns the whole SPV file contents.
+ pub fn read<F>(&mut self, mut warn: F) -> Result<SpvFile, Error>
+ where
+ F: FnMut(Warning),
+ {
+ self.read_outline(&mut warn)?.read_items(self, &mut warn)
+ }
+}
+
+/// An outline of an SPV file.
+///
+/// Reading the outline only requires reading some of the SPV file. It provides
+/// a "table of contents" view of the SPV, without reading the details of tables
+/// and graphs.
+#[derive(Clone, Debug)]
+pub struct SpvOutline {
+ /// The table of contents.
+ pub members: Vec<StructureMember>,
+}
+
+impl SpvOutline {
+ fn read_items<F, R>(
+ mut self,
+ archive: &mut SpvArchive<R>,
+ warn: &mut F,
+ ) -> Result<SpvFile, Error>
+ where
+ R: Read + Seek,
+ F: FnMut(Warning),
+ {
+ let page_setup = self
+ .members
+ .get_mut(0)
+ .and_then(|member| take(&mut member.page_setup));
+ let items = self
+ .members
+ .into_iter()
+ .flat_map(|member| {
+ member
+ .root
+ .read_items(&mut archive.0, &member.member_name, warn)
+ })
+ .collect();
+ Ok(SpvFile { items, page_setup })
}
}
/// The page setup in the SPV file, if any.
pub page_setup: Option<PageSetup>,
-
- /// The Zip archive that the file was read from.
- pub archive: ZipArchive<Box<dyn ReadSeek>>,
}
impl SpvFile {
TreeTodo,
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum TableType {
Table,
use std::{
- cell::RefCell,
fmt::Debug,
io::{Read, Seek},
ops::Deref,
- rc::Rc,
str::FromStr,
sync::Arc,
};
use crate::{
data::Datum,
format::{
- CC, Decimal, Decimals, Epoch, F40, F40_2, Format, NumberStyle, Settings, Type,
+ self, CC, Decimal, Decimals, Epoch, F40, F40_2, NumberStyle, Settings, Type,
UncheckedFormat, Width,
},
output::pivot::{
parse_bool,
value::{self, DatumValue, TemplateValue, ValueStyle, VariableValue},
},
- settings::Show,
+ settings,
};
/// A warning decoding a light detail member.
struct Context {
version: Version,
- warn: Rc<RefCell<Box<dyn FnMut(LightWarning)>>>,
}
impl Context {
- fn new(version: Version, warn: Rc<RefCell<Box<dyn FnMut(LightWarning)>>>) -> Self {
- Self { version, warn }
- }
- fn warn(&self, warning: LightWarning) {
- (self.warn.borrow_mut())(warning);
+ fn new(version: Version) -> Self {
+ Self { version }
}
}
#[binread]
-#[br(little, import(warn: Rc<RefCell<Box<dyn FnMut(LightWarning)>>>))]
+#[br(little)]
#[derive(Debug)]
pub struct LightTable {
header: Header,
- #[br(temp, calc(Context::new(header.version, warn)))]
+ #[br(temp, calc(Context::new(header.version)))]
context: Context,
#[br(args(&context))]
titles: Titles,
}
}
- pub fn decode(&self, warn: &mut dyn FnMut(LightWarning)) -> PivotTable {
+ pub fn decode(&self, mut warn: &mut dyn FnMut(LightWarning)) -> PivotTable {
let encoding = self.formats.encoding(warn);
let n1 = self.formats.n1();
let footnotes = self
.footnotes
.iter()
- .map(|f| f.decode(encoding, &Footnotes::new()))
+ .map(|f| f.decode(encoding, &Footnotes::new(), warn))
.collect();
let cells = self
.cells
.map(|cell| {
(
PrecomputedIndex(cell.index as usize),
- cell.value.decode(encoding, &footnotes),
+ cell.value.decode(encoding, &footnotes, warn),
)
})
.collect::<Vec<_>>();
.dimensions
.iter()
.map(|d| {
- let mut root = Group::new(d.name.decode(encoding, &footnotes))
+ let mut root = Group::new(d.name.decode(encoding, &footnotes, warn))
.with_show_label(!d.hide_dim_label);
for category in &d.categories {
- category.decode(encoding, &footnotes, &mut root);
+ category.decode(encoding, &footnotes, &mut root, warn);
}
pivot::Dimension {
presentation_order: (0..root.len()).collect(), /*XXX*/
show_grid_lines: self.borders.show_grid_lines,
show_title: n1.map_or(true, |x1| x1.show_title != 10),
show_caption: n1.map_or(true, |x1| x1.show_caption),
- show_values: n1.map_or(None, |x1| x1.show_values),
- show_variables: n1.map_or(None, |x1| x1.show_variables),
+ show_values: n1.map_or(None, |x1| x1.show_values.decode(&mut warn)),
+ show_variables: n1.map_or(None, |x1| x1.show_variables.decode(&mut warn)),
sizing: self.table_settings.sizing.decode(
&self.formats.column_widths,
n2.map_or(&[], |x2| &x2.row_heights),
None
}
}),
- title: Some(Box::new(self.titles.title.decode(encoding, &footnotes))),
- subtype: Some(Box::new(self.titles.subtype.decode(encoding, &footnotes))),
+ title: Some(Box::new(
+ self.titles.title.decode(encoding, &footnotes, warn),
+ )),
+ subtype: Some(Box::new(
+ self.titles.subtype.decode(encoding, &footnotes, warn),
+ )),
corner_text: self
.titles
.corner_text
.as_ref()
- .map(|corner| Box::new(corner.decode(encoding, &footnotes))),
+ .map(|corner| Box::new(corner.decode(encoding, &footnotes, warn))),
caption: self
.titles
.caption
.as_ref()
- .map(|caption| Box::new(caption.decode(encoding, &footnotes))),
+ .map(|caption| Box::new(caption.decode(encoding, &footnotes, warn))),
notes: self.table_settings.notes.decode_optional(encoding),
notes_unexpanded: n3_inner
.and_then(|inner| inner.notes_unexpanded.decode_optional(encoding)),
}
impl Footnote {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> pivot::Footnote {
- pivot::Footnote::new(self.text.decode(encoding, footnotes))
- .with_marker(self.marker.as_ref().map(|m| m.decode(encoding, footnotes)))
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> pivot::Footnote {
+ pivot::Footnote::new(self.text.decode(encoding, footnotes, warn))
+ .with_marker(
+ self.marker
+ .as_ref()
+ .map(|m| m.decode(encoding, footnotes, warn)),
+ )
.with_show(self.show > 0)
}
}
#[br(temp, parse_with(parse_bool))]
_x16: bool,
_lang: u8,
- #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
- show_variables: Option<Show>,
- #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
- show_values: Option<Show>,
+ show_variables: Show,
+ show_values: Show,
#[br(temp)]
_x18: i32,
#[br(temp)]
show_caption: bool,
}
-#[binrw::parser(reader, endian)]
-fn parse_show<F>(mut warn: F) -> BinResult<Option<Show>>
-where
- F: FnMut(LightWarning),
-{
- match <u8>::read_options(reader, endian, ())? {
- 0 => Ok(None),
- 1 => Ok(Some(Show::Value)),
- 2 => Ok(Some(Show::Label)),
- 3 => Ok(Some(Show::Both)),
- other => {
- warn(LightWarning::UnknownShow(other));
- Ok(None)
+#[binread]
+#[br(little)]
+#[derive(Debug)]
+struct Show(u8);
+
+impl Show {
+ fn decode<F>(&self, mut warn: F) -> Option<settings::Show>
+ where
+ F: FnMut(LightWarning),
+ {
+ match self.0 {
+ 0 => None,
+ 1 => Some(settings::Show::Value),
+ 2 => Some(settings::Show::Label),
+ 3 => Some(settings::Show::Both),
+ other => {
+ warn(LightWarning::UnknownShow(other));
+ None
+ }
}
}
}
struct ValueNumber {
#[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format), args(context))]
format: Format,
x: f64,
}
+#[binread]
+#[br(little)]
+#[derive(Debug)]
+struct Format(u32);
+
+impl Format {
+ pub fn decode(&self, warn: &mut dyn FnMut(LightWarning)) -> format::Format {
+ decode_format(self.0, warn)
+ }
+}
+
#[binread]
#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueVarNumber {
#[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format), args(context))]
format: Format,
x: f64,
var_name: U32String,
value_label: U32String,
- #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
- show: Option<Show>,
+ show: Show,
}
#[binread]
struct ValueString {
#[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format), args(context))]
format: Format,
value_label: U32String,
var_name: U32String,
- #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
- show: Option<Show>,
+ show: Show,
s: U32String,
}
mods: Option<ValueMods>,
var_name: U32String,
var_label: U32String,
- #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
- show: Option<Show>,
+ show: Show,
}
#[binread]
}
}
-pub(super) fn decode_format(raw: u32, warn: &mut dyn FnMut(LightWarning)) -> Format {
+pub(super) fn decode_format(raw: u32, warn: &mut dyn FnMut(LightWarning)) -> format::Format {
if raw == 0 || raw == 0x10000 || raw == 1 {
return F40_2;
}
UncheckedFormat::new(type_, w, d).fix()
}
-#[binrw::parser(reader, endian)]
-fn parse_format(context: &Context) -> BinResult<Format> {
- Ok(decode_format(
- u32::read_options(reader, endian, ())?,
- &mut *context.warn.borrow_mut(),
- ))
-}
-
impl ValueNumber {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
- .with_format(self.format)
+ .with_format(self.format.decode(warn))
.with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
}
}
impl ValueVarNumber {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
- .with_format(self.format)
+ .with_format(self.format.decode(warn))
.with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
.with_value_label(self.value_label.decode_optional(encoding))
.with_variable_name(Some(self.var_name.decode(encoding)))
- .with_show_value_label(self.show)
+ .with_show_value_label(self.show.decode(warn))
}
}
}
impl ValueString {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
value::Value::new(pivot::value::ValueInner::Datum(DatumValue {
datum: Datum::new_utf8(self.s.decode(encoding)),
- format: self.format,
- show: self.show,
+ format: self.format.decode(warn),
+ show: self.show.decode(warn),
honor_small: false,
variable: self.var_name.decode_optional(encoding),
value_label: self.value_label.decode_optional(encoding),
}
impl ValueVarName {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
value::Value::new(pivot::value::ValueInner::Variable(VariableValue {
- show: self.show,
+ show: self.show.decode(warn),
var_name: self.var_name.decode(encoding),
variable_label: self.var_label.decode_optional(encoding),
}))
}
impl ValueTemplate {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
value::Value::new(pivot::value::ValueInner::Template(TemplateValue {
args: self
.args
.iter()
- .map(|argument| argument.decode(encoding, footnotes))
+ .map(|argument| argument.decode(encoding, footnotes, warn))
.collect(),
localized: self.template.decode(encoding),
id: self
}
impl Value {
- fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> value::Value {
match self {
- Value::Number(number) => number.decode(encoding, footnotes),
- Value::VarNumber(var_number) => var_number.decode(encoding, footnotes),
+ Value::Number(number) => number.decode(encoding, footnotes, warn),
+ Value::VarNumber(var_number) => var_number.decode(encoding, footnotes, warn),
Value::Text(text) => text.decode(encoding, footnotes),
- Value::String(string) => string.decode(encoding, footnotes),
- Value::VarName(var_name) => var_name.decode(encoding, footnotes),
+ Value::String(string) => string.decode(encoding, footnotes, warn),
+ Value::VarName(var_name) => var_name.decode(encoding, footnotes, warn),
Value::FixedText(fixed_text) => fixed_text.decode(encoding, footnotes),
- Value::Template(template) => template.decode(encoding, footnotes),
+ Value::Template(template) => template.decode(encoding, footnotes, warn),
}
}
}
&self,
encoding: &'static Encoding,
footnotes: &pivot::Footnotes,
+ warn: &mut dyn FnMut(LightWarning),
) -> Vec<value::Value> {
self.0
.iter()
- .map(|value| value.decode(encoding, footnotes))
+ .map(|value| value.decode(encoding, footnotes, warn))
.collect()
}
}
}
impl Category {
- fn decode(&self, encoding: &'static Encoding, footnotes: &Footnotes, group: &mut pivot::Group) {
- let name = self.name.decode(encoding, footnotes);
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ footnotes: &Footnotes,
+ group: &mut pivot::Group,
+ warn: &mut dyn FnMut(LightWarning),
+ ) {
+ let name = self.name.decode(encoding, footnotes, warn);
match &self.child {
Child::Leaf { leaf_index: _ } => {
group.push(pivot::Leaf::new(name));
subcategories,
} => {
for subcategory in subcategories {
- subcategory.decode(encoding, footnotes, group);
+ subcategory.decode(encoding, footnotes, group, warn);
}
}
Child::Group {
} => {
let mut subgroup = Group::new(name).with_label_shown();
for subcategory in subcategories {
- subcategory.decode(encoding, footnotes, &mut subgroup);
+ subcategory.decode(encoding, footnotes, &mut subgroup, warn);
}
group.push(subgroup);
}
use std::{
- cell::RefCell,
io::{BufRead, BufReader, Cursor, Read, Seek},
mem::take,
- rc::Rc,
};
use anyhow::Context;
spv::{
Error,
legacy_bin::LegacyBin,
- read::{
- TableType, Warning, WarningDetails,
- legacy_xml::Visualization,
- light::{LightTable, LightWarning},
- },
+ read::{TableType, Warning, WarningDetails, legacy_xml::Visualization, light::LightTable},
},
};
+/// Part of the outline of an SPV file.
+#[derive(Clone, Debug)]
pub struct StructureMember {
+ /// The name of the file inside the Zip archive that contains this
+ /// [StructureMember].
+ ///
+ /// This is useful for user messages.
+ pub member_name: String,
+
+ /// The [PageSetup] in the file, if any.
pub page_setup: Option<PageSetup>,
+
+ /// The contents.
+ ///
+ /// `root` itself is not interesting (it is normally just named `Output`)
+ /// and should be ignored. The contents are its children, which are
+ /// siblings of the children of the roots in all the other
+ /// [StructureMember]s in the SPV file.
pub root: Heading,
}
pub fn read<R>(
reader: R,
member_name: &str,
- warn: &Rc<RefCell<Box<dyn FnMut(Warning)>>>,
+ warn: &mut dyn FnMut(Warning),
) -> Result<Self, Error>
where
R: BufRead,
error,
})?;
Ok(Self {
+ member_name: member_name.into(),
page_setup: heading
.page_setup
.take()
}
}
+#[derive(Clone, Debug)]
pub struct Heading {
page_setup: Option<PageSetup>,
expand: bool,
}
impl Heading {
- pub fn read_items<R>(
+ pub fn read_items<R, F>(
self,
archive: &mut ZipArchive<R>,
structure_member: &str,
- warn: &Rc<RefCell<Box<dyn FnMut(Warning)>>>,
+ warn: &mut F,
) -> Vec<Item>
where
R: Read + Seek,
+ F: FnMut(Warning),
{
let mut items = Vec::new();
for child in self.children {
);
}
let result = match container.content {
- Content::Table(table) => table.decode(archive, warn),
+ Content::Table(table) => table.decode(archive, &mut *warn),
Content::Graph(_) => Err(Error::GraphTodo),
Content::Text(container_text) => Ok(container_text.into_item()),
Content::Image(image) => image.decode(archive),
let label = take(&mut heading.label);
let command_name = take(&mut heading.command_name);
heading
- .read_items(archive, structure_member, warn)
+ .read_items(archive, structure_member, &mut *warn)
.into_iter()
.collect::<Item>()
.with_show(expand)
}
}
+#[derive(Clone, Debug)]
pub enum HeadingChild {
Heading(Heading),
Container(Container),
}
}
+#[derive(Clone, Debug)]
pub struct Container {
show: bool,
page_break_before: bool,
content: Content,
}
+#[derive(Clone, Debug)]
pub enum Content {
Text(Text),
Table(Table),
}
}
+#[derive(Clone, Debug)]
pub struct Table {
subtype: String,
table_type: TableType,
}
}
- fn decode<R>(
- self,
- archive: &mut ZipArchive<R>,
- warn: &Rc<RefCell<Box<dyn FnMut(Warning)>>>,
- ) -> Result<Item, Error>
+ fn decode<R, F>(self, archive: &mut ZipArchive<R>, mut warn: F) -> Result<Item, Error>
where
R: Read + Seek,
+ F: FnMut(Warning),
{
if let Some(xml_member_name) = &self.xml_member {
let bin_member_name = &self.bin_member;
light.read_to_end(&mut data)?;
let mut cursor = Cursor::new(data);
- let warn = warn.clone();
- let warning = Rc::new(RefCell::new(Box::new({
- let warn = warn.clone();
- let member_name = member_name.clone();
- move |w| {
- (warn.borrow_mut())(Warning {
- member: member_name.clone(),
- details: WarningDetails::LightWarning(w),
- })
- }
- }) as Box<dyn FnMut(LightWarning)>));
- let table = LightTable::read_args(&mut cursor, (warning.clone(),)).map_err(|e| {
+ 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(&mut *warning.borrow_mut());
+ let pivot_table = table.decode(&mut |warning| {
+ warn(Warning {
+ member: member_name.clone(),
+ details: WarningDetails::LightWarning(warning),
+ })
+ });
Ok(pivot_table.into_item())
}
}
}
+#[derive(Clone, Debug)]
pub struct Image {
member: String,
}
}
}
+#[derive(Clone, Debug)]
pub struct Graph {
xml_member: String,
data_member: Option<String>,
}
mod raw {
- use std::{cell::RefCell, rc::Rc};
-
use paper_sizes::PaperSize;
use serde::Deserialize;
pub fn decode(
self,
structure_member: &str,
- warn: &Rc<RefCell<Box<dyn FnMut(Warning)>>>,
+ warn: &mut dyn FnMut(Warning),
) -> super::Heading {
let mut children = Vec::new();
for child in self.children {
impl PageSetup {
pub fn decode(
self,
- warn: &Rc<RefCell<Box<dyn FnMut(Warning)>>>,
+ warn: &mut dyn FnMut(Warning),
structure_member: &str,
) -> page::PageSetup {
let mut setup = page::PageSetup::default();
} else if reference_orientation.starts_with("90") {
setup.orientation = Orientation::Landscape;
} else {
- (warn.borrow_mut())(Warning {
+ warn(Warning {
member: structure_member.into(),
details: WarningDetails::UnknownOrientation(reference_orientation.clone()),
});
use crate::{
output::{Text, pivot::tests::assert_lines_eq},
- spv::ReadOptions,
+ spv::SpvArchive,
};
#[test]
R: BufRead + Seek + 'static,
{
let mut warnings = Vec::new();
- let output = match ReadOptions::new(move |warning| warnings.push(warning)).open_reader(spvfile)
- {
+ let result = match SpvArchive::open_reader(spvfile, None) {
+ Ok(mut archive) => archive.read(|w| warnings.push(w)),
+ Err(error) => Err(error),
+ };
+
+ let output = match result {
Ok(spv_file) => {
let (items, _page_setup /*XXX*/) = spv_file.into_contents();