use std::io::Write;
use zip::write::SimpleFileOptions;
use zip::ZipWriter;
use super::esc_attr;
const CT_NS: &str = "http://schemas.openxmlformats.org/package/2006/content-types";
const REL_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
const CT_RELS: &str = "application/vnd.openxmlformats-package.relationships+xml";
const XML_DECL: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#;
pub(crate) struct Rel {
pub id: String,
pub rel_type: String,
pub target: String,
pub external: bool,
}
pub(crate) struct Package {
parts: Vec<(String, Vec<u8>)>,
defaults: Vec<(String, String)>,
overrides: Vec<(String, String)>,
rels: Vec<(String, Vec<Rel>)>,
}
impl Package {
pub(crate) fn new() -> Self {
Package {
parts: Vec::new(),
defaults: vec![
("rels".into(), CT_RELS.into()),
("xml".into(), "application/xml".into()),
],
overrides: Vec::new(),
rels: Vec::new(),
}
}
pub(crate) fn add_default(&mut self, ext: &str, content_type: &str) {
if !self.defaults.iter().any(|(e, _)| e == ext) {
self.defaults
.push((ext.to_string(), content_type.to_string()));
}
}
pub(crate) fn add_part(&mut self, path: &str, content_type: Option<&str>, bytes: Vec<u8>) {
if let Some(ct) = content_type {
self.overrides.push((format!("/{path}"), ct.to_string()));
}
self.parts.push((path.to_string(), bytes));
}
pub(crate) fn add_rels(&mut self, rels_path: &str, rels: Vec<Rel>) {
self.rels.push((rels_path.to_string(), rels));
}
pub(crate) fn try_into_zip(self) -> std::io::Result<Vec<u8>> {
let mut zw = ZipWriter::new(std::io::Cursor::new(Vec::new()));
let opt = SimpleFileOptions::default();
let mut ct = String::new();
ct.push_str(XML_DECL);
ct.push_str(&format!(r#"<Types xmlns="{CT_NS}">"#));
for (ext, c) in &self.defaults {
ct.push_str(&format!(
r#"<Default Extension="{}" ContentType="{}"/>"#,
esc_attr(ext),
esc_attr(c)
));
}
for (pn, c) in &self.overrides {
ct.push_str(&format!(
r#"<Override PartName="{}" ContentType="{}"/>"#,
esc_attr(pn),
esc_attr(c)
));
}
ct.push_str("</Types>");
zw.start_file("[Content_Types].xml", opt)?;
zw.write_all(ct.as_bytes())?;
for (path, rels) in &self.rels {
let mut x = String::new();
x.push_str(XML_DECL);
x.push_str(&format!(r#"<Relationships xmlns="{REL_NS}">"#));
for rel in rels {
x.push_str(&format!(
r#"<Relationship Id="{}" Type="{}" Target="{}""#,
esc_attr(&rel.id),
esc_attr(&rel.rel_type),
esc_attr(&rel.target)
));
if rel.external {
x.push_str(r#" TargetMode="External""#);
}
x.push_str("/>");
}
x.push_str("</Relationships>");
zw.start_file(path.as_str(), opt)?;
zw.write_all(x.as_bytes())?;
}
for (path, bytes) in &self.parts {
zw.start_file(path.as_str(), opt)?;
zw.write_all(bytes)?;
}
Ok(zw.finish()?.into_inner())
}
}