#![allow(dead_code)]
use std::{
fmt::Display,
fs::File,
io::{BufReader, Read, Seek},
path::Path,
};
use displaydoc::Display;
use serde::Deserialize;
use zip::{ZipArchive, result::ZipError};
use crate::{
crypto::EncryptedReader,
output::{Item, page::PageSetup},
spv::{
legacy_bin::LegacyBinWarning,
read::{
graph::GraphWarning,
legacy_xml::LegacyXmlWarning,
light::LightWarning,
structure::{OutlineItem, StructureMember},
},
},
};
mod css;
#[allow(missing_docs)]
pub mod graph;
pub mod html;
pub mod legacy_bin;
#[allow(missing_docs)]
pub mod legacy_xml;
mod light;
pub mod structure;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug)]
pub struct Warning {
pub member: String,
pub details: WarningDetails,
}
impl Display for Warning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Warning reading member {:?}: {}",
&self.member, &self.details
)
}
}
#[derive(Clone, Debug, thiserror::Error, Display)]
pub enum WarningDetails {
LightWarning(LightWarning),
LegacyBinWarning(LegacyBinWarning),
LegacyXmlWarning(LegacyXmlWarning),
GraphWarning(GraphWarning),
UnknownOrientation(String),
}
pub trait ReadSeek: Read + Seek {}
impl<T> ReadSeek for T where T: Read + Seek {}
pub struct SpvArchive<R>(pub ZipArchive<R>);
impl SpvArchive<Box<dyn ReadSeek>> {
pub fn open_file<P>(path: P, password: Option<&str>) -> Result<Self, Error>
where
P: AsRef<Path>,
{
Self::open_reader(File::open(path)?, password)
}
pub fn open_reader<R>(reader: R, password: Option<&str>) -> Result<Self, Error>
where
R: Read + Seek + 'static,
{
let reader = if let Some(password) = password {
Box::new(EncryptedReader::open(reader, password)?)
} else {
Box::new(reader) as Box<dyn ReadSeek>
};
let mut archive = ZipArchive::new(reader).map_err(|error| match error {
ZipError::InvalidArchive(_) => Error::NotSpv,
other => other.into(),
})?;
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);
Ok(Self(archive))
}
}
impl<R> SpvArchive<R>
where
R: Read + Seek,
{
pub fn read_outline<F>(&mut self, mut warn: F) -> Result<SpvOutline, Error>
where
F: FnMut(Warning),
{
let mut items = Vec::new();
let mut page_setup = None;
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(self.0.by_index(i)?);
let mut member = StructureMember::read(member, &name, &mut warn)?;
page_setup = page_setup.or(member.page_setup);
items.append(&mut member.items);
}
}
Ok(SpvOutline { page_setup, items })
}
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)
}
}
#[derive(Clone, Debug)]
pub struct SpvOutline {
pub page_setup: Option<PageSetup>,
pub items: Vec<OutlineItem>,
}
impl SpvOutline {
fn read_items<F, R>(self, archive: &mut SpvArchive<R>, warn: &mut F) -> Result<SpvFile, Error>
where
R: Read + Seek,
F: FnMut(Warning),
{
Ok(SpvFile {
page_setup: self.page_setup,
items: self
.items
.into_iter()
.map(|member| member.read_item(&mut archive.0, warn))
.collect(),
})
}
}
pub struct SpvFile {
pub items: Vec<Item>,
pub page_setup: Option<PageSetup>,
}
impl SpvFile {
pub fn into_contents(self) -> (Vec<Item>, Option<PageSetup>) {
(self.items, self.page_setup)
}
pub fn into_items(self) -> Vec<Item> {
self.items
}
}
#[derive(Debug, Display, thiserror::Error)]
pub enum Error {
NotSpv,
EncryptionError(#[from] crate::crypto::Error),
ZipError(#[from] ZipError),
IoError(#[from] std::io::Error),
DeError(#[from] quick_xml::DeError),
BinrwError(#[from] binrw::Error),
CairoError(#[from] cairo::IoError),
DeserializeError {
member_name: String,
error: serde_path_to_error::Error<quick_xml::DeError>,
},
LegacyMissingGraph,
ModelTodo,
TreeTodo,
}
#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum TableType {
Table,
Note,
Warning,
}