use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, Write};
use std::path::Path;
use zip::CompressionMethod;
use zip::write::{SimpleFileOptions, ZipWriter};
use super::content_types::{ContentTypes, ContentTypesBuilder};
use super::error::Result;
use super::opc::PartName;
use super::relationships::{Relationships, RelationshipsBuilder};
pub struct EditablePackage {
parts: HashMap<PartName, Vec<u8>>,
content_types: ContentTypes,
package_rels: Relationships,
part_rels: HashMap<PartName, Relationships>,
}
impl EditablePackage {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let file = File::open(path)?;
Self::from_reader(file)
}
pub fn from_reader<R: Read + Seek>(reader: R) -> Result<Self> {
let mut opc = super::opc::OpcReader::new(reader)?;
let content_types = opc.content_types().clone();
let package_rels = opc.package_rels().clone();
let part_names = opc.part_names();
let mut parts = HashMap::new();
let mut part_rels = HashMap::new();
for name in &part_names {
let data = opc.read_part(name)?;
parts.insert(name.clone(), data);
let rels = opc.read_rels_for(name)?;
if !rels.all().is_empty() {
part_rels.insert(name.clone(), rels);
}
}
Ok(Self {
parts,
content_types,
package_rels,
part_rels,
})
}
pub fn get_part(&self, name: &PartName) -> Option<&[u8]> {
self.parts.get(name).map(|v| v.as_slice())
}
pub fn set_part(&mut self, name: PartName, data: Vec<u8>) {
self.parts.insert(name, data);
}
pub fn content_types(&self) -> &ContentTypes {
&self.content_types
}
pub fn package_rels(&self) -> &Relationships {
&self.package_rels
}
pub fn part_rels(&self, name: &PartName) -> Option<&Relationships> {
self.part_rels.get(name)
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let file = File::create(path)?;
self.write_to(file)
}
pub fn write_to<W: Write + Seek>(&self, writer: W) -> Result<()> {
let mut zip = ZipWriter::new(writer);
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
for (name, data) in &self.parts {
let zip_path = &name.as_str()[1..]; zip.start_file(zip_path, options)?;
zip.write_all(data)?;
}
for (source, rels) in &self.part_rels {
if rels.all().is_empty() {
continue;
}
let rels_path = source.rels_path();
let zip_path = &rels_path[1..];
let mut builder = RelationshipsBuilder::new();
for rel in rels.all() {
builder.add_with_id(&rel.id, &rel.rel_type, &rel.target, rel.target_mode);
}
let data = builder.serialize();
zip.start_file(zip_path, options)?;
zip.write_all(&data)?;
}
{
let mut builder = RelationshipsBuilder::new();
for rel in self.package_rels.all() {
builder.add_with_id(&rel.id, &rel.rel_type, &rel.target, rel.target_mode);
}
let data = builder.serialize();
zip.start_file("_rels/.rels", options)?;
zip.write_all(&data)?;
}
{
let mut ct_builder = ContentTypesBuilder::new();
for (ext, ct) in self.content_types.defaults() {
ct_builder.add_default(ext, ct);
}
for (pn, ct) in self.content_types.overrides() {
ct_builder.add_override(pn.clone(), ct);
}
let data = ct_builder.serialize();
zip.start_file("[Content_Types].xml", options)?;
zip.write_all(&data)?;
}
zip.finish()?;
Ok(())
}
}