use std::io::{Cursor, Write};
use rdocx_opc::OpcPackage;
pub use rdocx_opc::{OpcError, Relationship, Relationships};
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipWriter};
fn norm(name: &str) -> String {
if name.starts_with('/') {
name.to_string()
} else {
format!("/{name}")
}
}
fn denorm(name: &str) -> String {
name.trim_start_matches('/').to_string()
}
fn part_name_to_rels_path(part_name: &str) -> String {
let name = part_name.strip_prefix('/').unwrap_or(part_name);
if let Some(pos) = name.rfind('/') {
let dir = &name[..pos];
let file = &name[pos + 1..];
format!("{dir}/_rels/{file}.rels")
} else {
format!("_rels/{name}.rels")
}
}
pub struct PartFs {
pkg: OpcPackage,
}
impl PartFs {
pub fn open(bytes: &[u8]) -> Result<Self, OpcError> {
Ok(PartFs {
pkg: OpcPackage::from_reader(Cursor::new(bytes.to_vec()))?,
})
}
pub fn part_bytes(&self, name: &str) -> Option<&[u8]> {
self.pkg.get_part(&norm(name))
}
pub fn part_string(&self, name: &str) -> Option<String> {
self.part_bytes(name)
.map(|b| String::from_utf8_lossy(b).into_owned())
}
pub fn set_part(&mut self, name: &str, data: Vec<u8>) {
self.pkg.set_part(&norm(name), data);
}
pub fn remove_part(&mut self, name: &str) {
self.pkg.parts.remove(&norm(name));
}
pub fn parts(&self) -> Vec<String> {
let mut v: Vec<String> = self.pkg.parts.keys().map(|k| denorm(k)).collect();
v.sort();
v
}
pub fn to_zip(&self) -> Result<Vec<u8>, OpcError> {
let mut zip = ZipWriter::new(Cursor::new(Vec::new()));
let options = SimpleFileOptions::default()
.compression_method(CompressionMethod::Deflated)
.compression_level(Some(1));
let ct_xml = self.pkg.content_types.to_xml()?;
zip.start_file("[Content_Types].xml", options)?;
zip.write_all(&ct_xml)?;
let pkg_rels_xml = self.pkg.package_rels.to_xml()?;
zip.start_file("_rels/.rels", options)?;
zip.write_all(&pkg_rels_xml)?;
for (part_name, rels) in &self.pkg.part_rels {
let rels_path = part_name_to_rels_path(part_name);
let rels_xml = rels.to_xml()?;
zip.start_file(&rels_path, options)?;
zip.write_all(&rels_xml)?;
}
for (name, data) in &self.pkg.parts {
let zip_name = name.strip_prefix('/').unwrap_or(name);
zip.start_file(zip_name, options)?;
zip.write_all(data)?;
}
Ok(zip.finish()?.into_inner())
}
pub fn resolve_rel_target(&self, source_part: &str, rel_target: &str) -> String {
OpcPackage::resolve_rel_target(source_part, rel_target)
}
pub fn content_type_for(&self, name: &str) -> Option<String> {
self.pkg
.content_types
.content_type_for(&norm(name))
.map(|s| s.to_string())
}
pub fn add_content_type_override(&mut self, part_name: &str, content_type: &str) {
self.pkg
.content_types
.add_override(&norm(part_name), content_type);
}
pub fn add_content_type_default(&mut self, ext: &str, content_type: &str) {
self.pkg.content_types.add_default(ext, content_type);
}
pub fn remove_content_type_override(&mut self, part_name: &str) {
self.pkg.content_types.overrides.remove(&norm(part_name));
}
pub fn remove_relationships_by_type(&mut self, source_part: &str, rel_type: &str) {
let key = norm(source_part);
if self.pkg.get_part_rels(&key).is_none() {
return;
}
let rels = self.pkg.get_or_create_part_rels(&key);
rels.items.retain(|r| r.rel_type != rel_type);
}
pub fn read_rels_for(&self, part_name: &str) -> Option<&Relationships> {
self.pkg.get_part_rels(&norm(part_name))
}
pub fn add_document_relationship(
&mut self,
source_part: &str,
rel_type: &str,
target: &str,
) -> String {
self.pkg
.get_or_create_part_rels(&norm(source_part))
.add(rel_type, target)
}
pub fn add_document_relationship_external(
&mut self,
source_part: &str,
rel_type: &str,
target: &str,
) -> String {
let rels = self.pkg.get_or_create_part_rels(&norm(source_part));
let id = rels.add(rel_type, target);
if let Some(r) = rels.items.iter_mut().find(|r| r.id == id) {
r.target_mode = Some("External".to_string());
}
id
}
pub fn set_rel_target_mode_external(&mut self, source_part: &str, rel_id: &str) {
let rels = self.pkg.get_or_create_part_rels(&norm(source_part));
if let Some(r) = rels.items.iter_mut().find(|r| r.id == rel_id) {
r.target_mode = Some("External".to_string());
}
}
pub fn main_document_part(&self) -> Option<String> {
self.pkg.main_document_part().map(|s| denorm(&s))
}
}