use binrw::{BinWrite, Endian};
use chrono::Utc;
+use displaydoc::Display;
use enum_map::EnumMap;
use quick_xml::{
ElementWriter, Writer as XmlWriter,
events::{BytesText, attributes::Attribute},
};
-use zip::{ZipWriter, result::ZipResult, write::SimpleFileOptions};
+use zip::{ZipWriter, result::ZipError, write::SimpleFileOptions};
use crate::{
format::{Format, Type},
util::ToSmallString,
};
+/// An error writing an SPV file.
+#[derive(Debug, Display, thiserror::Error)]
+pub enum Error {
+ /// {0}
+ ZipError(#[from] ZipError),
+
+ /// {0}
+ IoError(#[from] std::io::Error),
+
+ /// {0}
+ BinrwError(#[from] binrw::Error),
+}
+
/// SPSS viewer (SPV) file writer.
pub struct Writer<W>
where
{
/// Creates a new `Writer` to write an SPV file to underlying stream
/// `writer`.
- pub fn for_writer(writer: W) -> Self {
+ pub fn for_writer(writer: W) -> Result<Self, Error> {
let mut writer = ZipWriter::new(writer);
- writer
- .start_file("META-INF/MANIFEST.MF", SimpleFileOptions::default())
- .unwrap();
- writer.write_all("allowPivoting=true".as_bytes()).unwrap();
- Self {
+ writer.start_file("META-INF/MANIFEST.MF", SimpleFileOptions::default())?;
+ writer.write_all("allowPivoting=true".as_bytes())?;
+ Ok(Self {
writer,
needs_page_break: false,
next_table_id: 1,
next_heading_id: 1,
page_setup: None,
- }
+ })
}
/// Returns this `Writer` with `page_setup` set up to be written with the
/// Closes the underlying file and returns the inner writer and the final
/// I/O result.
- pub fn close(mut self) -> ZipResult<W> {
+ pub fn close(mut self) -> Result<W, Error> {
self.writer
.start_file("META-INF/MANIFEST.MF", SimpleFileOptions::default())?;
write!(&mut self.writer, "allowPivoting=true")?;
- self.writer.finish()
+ Ok(self.writer.finish()?)
}
fn page_break_before(&mut self) -> bool {
item: &Item,
pivot_table: &PivotTable,
structure: &mut XmlWriter<X>,
- ) where
+ ) -> Result<(), Error>
+ where
X: Write,
{
let table_id = self.next_table_id;
let mut content = Vec::new();
let mut cursor = Cursor::new(&mut content);
- pivot_table.write_le(&mut cursor).unwrap();
+ pivot_table.write_le(&mut cursor)?;
let table_name = format!("{table_id:011}_lightTableData.bin");
self.writer
- .start_file(&table_name, SimpleFileOptions::default())
- .unwrap(); // XXX
- self.writer.write_all(&content).unwrap(); // XXX
+ .start_file(&table_name, SimpleFileOptions::default())?; // XXX
+ self.writer.write_all(&content)?; // XXX
self.container(structure, item, "vtb:table", |element| {
element
Ok(())
})?;
Ok(())
- })
- .unwrap();
- });
+ })?;
+ Ok(())
+ })?;
+ Ok(())
}
- fn write_text<X>(&mut self, item: &Item, text: &Text, structure: &mut XmlWriter<X>)
+ fn write_text<X>(
+ &mut self,
+ item: &Item,
+ text: &Text,
+ structure: &mut XmlWriter<X>,
+ ) -> Result<(), Error>
where
X: Write,
{
self.container(structure, item, "vtx:text", |w| {
w.with_attribute(("type", text.type_.as_xml_str()))
- .write_text_content(BytesText::new(&text.content.display(()).to_string()))
- .unwrap();
- });
+ .write_text_content(BytesText::new(&text.content.display(()).to_string()))?;
+ Ok(())
+ })
}
- fn write_item<X>(&mut self, item: &Item, structure: &mut XmlWriter<X>)
+ fn write_item<X>(&mut self, item: &Item, structure: &mut XmlWriter<X>) -> Result<(), Error>
where
X: Write,
{
w.create_element("label")
.write_text_content(BytesText::new(&item.label()))?;
for child in &children.0 {
- self.write_item(&child, w);
+ self.write_item(&child, w).map_err(std::io::Error::other)?;
}
Ok(())
- })
- .unwrap();
+ })?;
+ Ok(())
}
Details::Message(diagnostic) => {
self.write_text(item, &Text::from(diagnostic.as_ref()), structure)
}
Details::PageBreak => {
self.needs_page_break = true;
+ Ok(())
}
Details::Table(pivot_table) => self.write_table(item, pivot_table, structure),
Details::Text(text) => self.write_text(item, text, structure),
item: &Item,
inner_elem: &str,
closure: F,
- ) where
+ ) -> Result<(), Error>
+ where
X: Write,
- F: FnOnce(ElementWriter<X>),
+ F: FnOnce(ElementWriter<X>) -> Result<(), Error>,
{
writer
.create_element("container")
.write_inner_content(|w| {
let mut element = w
.create_element("label")
- .write_text_content(BytesText::new(&item.label()))
- .unwrap()
+ .write_text_content(BytesText::new(&item.label()))?
.create_element(inner_elem);
if let Some(command_name) = &item.command_name {
element = element.with_attribute(("commandName", command_name.as_str()));
};
closure(element);
Ok(())
- })
- .unwrap();
+ })?;
+ Ok(())
}
}
where
W: Write + Seek,
{
- pub fn write(&mut self, item: &Item) {
+ pub fn write(&mut self, item: &Item) -> Result<(), Error> {
if item.details.is_page_break() {
self.needs_page_break = true;
- return;
+ return Ok(());
}
let mut headings = XmlWriter::new(Cursor::new(Vec::new()));
))
.with_attribute(("xmlns:vtx", "http://xml.spss.com/spss/viewer/viewer-text"))
.with_attribute(("xmlns:vtb", "http://xml.spss.com/spss/viewer/viewer-table"));
- element
- .write_inner_content(|w| {
- w.create_element("label")
- .write_text_content(BytesText::new("Output"))?;
- if let Some(page_setup) = self.page_setup.take() {
- write_page_setup(&page_setup, w)?;
- }
- self.write_item(item, w);
- Ok(())
- })
- .unwrap();
+ element.write_inner_content(|w| {
+ w.create_element("label")
+ .write_text_content(BytesText::new("Output"))?;
+ if let Some(page_setup) = self.page_setup.take() {
+ write_page_setup(&page_setup, w)?;
+ }
+ self.write_item(item, w);
+ Ok(())
+ })?;
let headings = headings.into_inner().into_inner();
let heading_id = self.next_heading_id;
self.next_heading_id += 1;
- self.writer
- .start_file(
- format!(
- "outputViewer{heading_id:010}{}.xml",
- if item.details.is_heading() {
- "_heading"
- } else {
- ""
- }
- ),
- SimpleFileOptions::default(),
- )
- .unwrap(); // XXX
- self.writer.write_all(&headings).unwrap(); // XXX
+ self.writer.start_file(
+ format!(
+ "outputViewer{heading_id:010}{}.xml",
+ if item.details.is_heading() {
+ "_heading"
+ } else {
+ ""
+ }
+ ),
+ SimpleFileOptions::default(),
+ )?; // XXX
+ self.writer.write_all(&headings)?; // XXX
+ Ok(())
}
}
0u8,
0u8,
2u32,
- data_indexes.next().unwrap() as u32,
+ data_indexes
+ .next()
+ .expect("should have as many data indexes as leaves") as u32,
0u32,
)
.write_le(writer)