use crate::error::OoxmlError;
use crate::packaging::app_property::AppProperties;
use crate::packaging::content_type::{ContentType, ContentTypes};
use crate::packaging::custom_property::CustomProperties;
use crate::packaging::element::*;
use crate::packaging::part::OpenXmlPart;
use crate::packaging::property::Properties;
pub use crate::packaging::relationship::Relationships;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use zip::ZipArchive;
use linked_hash_map::LinkedHashMap;
use crate::packaging::app_property::APP_PROPERTIES_URI;
use crate::packaging::content_type::CONTENT_TYPES_FILE;
use crate::packaging::custom_property::CUSTOM_PROPERTIES_URI;
use crate::packaging::property::CORE_PROPERTIES_URI;
use crate::packaging::relationship::RELATIONSHIPS_FILE;
#[derive(Debug, Clone, Default)]
pub struct OpenXmlPackage {
content_types: ContentTypes,
relationships: Relationships,
app_properties: AppProperties,
properties: Properties,
custom_properties: Option<CustomProperties>,
parts: LinkedHashMap<String, OpenXmlPart>,
}
impl OpenXmlPackage {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, OoxmlError> {
let file = std::fs::File::open(path)?;
Self::from_reader(file)
}
pub fn from_reader<R: Read + Seek>(reader: R) -> Result<Self, OoxmlError> {
let mut zip = ZipArchive::new(reader)?;
let mut package = OpenXmlPackage::default();
let mut content_types_id = None;
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
if file.is_dir() {
continue;
}
let filename = file.name().to_string();
if filename == CONTENT_TYPES_FILE {
content_types_id = Some(i);
let mut xml = String::new();
file.read_to_string(&mut xml)?;
package.content_types = ContentTypes::parse_from_xml_str(&xml);
continue;
} else if filename == RELATIONSHIPS_FILE {
let mut xml = String::new();
file.read_to_string(&mut xml)?;
package.relationships = Relationships::parse_from_xml_str(&xml);
continue;
} else if filename == CORE_PROPERTIES_URI {
let mut xml = String::new();
file.read_to_string(&mut xml)?;
package.properties = Properties::parse_from_xml_str(&xml);
continue;
} else if filename == CUSTOM_PROPERTIES_URI {
let mut xml = String::new();
file.read_to_string(&mut xml)?;
package.custom_properties = Some(CustomProperties::parse_from_xml_str(&xml));
} else if filename == APP_PROPERTIES_URI {
let mut xml = String::new();
file.read_to_string(&mut xml)?;
package.app_properties = OpenXmlDeserialize::from_xml_str(&xml)?;
} else {
let uri = std::path::PathBuf::from(&filename);
let part = OpenXmlPart::from_reader(uri, &mut file)?;
package.parts.insert(filename, part);
}
}
if content_types_id.is_none() {
return Err(OoxmlError::PackageContentTypeError);
}
assert!(package.has_content_types());
assert!(package.has_relationships());
Ok(package)
}
pub fn save_as<P: AsRef<Path>>(&self, path: P) -> Result<(), OoxmlError> {
let mut file = File::create(path)?;
self.write(&mut file)?;
Ok(())
}
pub fn write<W: Write + Seek>(&self, writer: W) -> Result<(), OoxmlError> {
let mut zip = zip::ZipWriter::new(writer);
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file(CONTENT_TYPES_FILE, options)?;
self.content_types.write(&mut zip)?;
zip.start_file(RELATIONSHIPS_FILE, options)?;
self.relationships.write(&mut zip)?;
zip.start_file(CORE_PROPERTIES_URI, options)?;
self.properties.write(&mut zip)?;
if let Some(custom_properties) = &self.custom_properties {
zip.start_file(CORE_PROPERTIES_URI, options)?;
custom_properties.write(&mut zip)?;
}
for (path, part) in self.parts.iter() {
zip.start_file(path, options)?;
zip.write(part.as_part_bytes())?;
}
zip.flush()?;
Ok(())
}
pub fn has_content_types(&self) -> bool {
!self.content_types.is_empty()
}
pub fn has_relationships(&self) -> bool {
!self.relationships.is_empty()
}
pub fn is_dirty(&self, _uri: &str) -> bool {
unimplemented!()
}
pub fn get_part(&self, uri: &str) -> Option<&OpenXmlPart> {
self.parts.get(uri)
}
pub fn create_part() {}
pub fn flush() {}
pub fn create_relationship() {}
pub fn delete_relationship() {}
pub fn get_relationships() {}
pub fn get_relationships_by_type(_relationship_type: String) {}
pub fn relationship_exist(&self, id: &str) -> bool {
self.relationships.contains(id)
}
pub fn create_part_core(&mut self, uri: &str, content_type: &ContentType) {
let part = OpenXmlPart::new_with_content_type(uri, content_type);
self.parts.insert(uri.into(), part);
}
pub fn create_part_core_with_data(
&mut self,
uri: &str,
content_type: &ContentType,
data: &[u8],
) -> Result<(), OoxmlError> {
let part = OpenXmlPart::new(uri, content_type, data)?;
self.parts.insert(uri.into(), part);
Ok(())
}
pub fn delete_part_core(&mut self, uri: &str) {
let part = self.parts.remove(uri);
if let Some(part) = part {
if let Some(content_type) = part.content_type() {
self.content_types.delete_content_type(content_type);
}
}
}
}
#[test]
fn open_and_save() {
let package = OpenXmlPackage::open("examples/excel-demo/demo.xlsx").unwrap();
package.save_as("tests/write-back.xlsx").unwrap();
let package = OpenXmlPackage::open("examples/docx-demo/rust-docx-rs.docx").unwrap();
package.save_as("tests/write-back.docx").unwrap();
}