use std::collections::HashMap;
use std::io::Read;
use crate::docx::error::{ParseError, Result};
pub struct PackageContents {
pub parts: HashMap<String, Vec<u8>>,
}
impl PackageContents {
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let cursor = std::io::Cursor::new(data);
let mut archive = zip::ZipArchive::new(cursor)?;
let mut parts = HashMap::with_capacity(archive.len());
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let name = normalize_path(file.name());
let mut buf = Vec::with_capacity(file.size() as usize);
file.read_to_end(&mut buf)?;
parts.insert(name, buf);
}
Ok(Self { parts })
}
pub fn get_part(&self, path: &str) -> Option<&[u8]> {
let normalized = normalize_path(path);
self.parts.get(&normalized).map(|v| v.as_slice())
}
pub fn require_part(&self, path: &str) -> Result<&[u8]> {
self.get_part(path)
.ok_or_else(|| ParseError::MissingPart(path.to_string()))
}
pub fn take_part(&mut self, path: &str) -> Option<Vec<u8>> {
let normalized = normalize_path(path);
self.parts.remove(&normalized)
}
}
fn normalize_path(path: &str) -> String {
path.trim_start_matches('/').to_lowercase()
}
pub fn resolve_target(base_dir: &str, target: &str) -> String {
if target.starts_with('/') {
normalize_path(target)
} else {
let mut path = if base_dir.is_empty() {
target.to_string()
} else {
format!("{}/{}", base_dir, target)
};
while let Some(pos) = path.find("/../") {
if let Some(parent_start) = path[..pos].rfind('/') {
path = format!("{}{}", &path[..parent_start], &path[pos + 3..]);
} else {
path = path[pos + 4..].to_string();
}
}
normalize_path(&path)
}
}
pub fn rels_path_for(part_path: &str) -> String {
let normalized = normalize_path(part_path);
if let Some(slash_pos) = normalized.rfind('/') {
format!(
"{}/_rels/{}.rels",
&normalized[..slash_pos],
&normalized[slash_pos + 1..]
)
} else {
format!("_rels/{}.rels", normalized)
}
}
pub fn part_directory(part_path: &str) -> &str {
match part_path.rfind('/') {
Some(pos) => &part_path[..pos],
None => "",
}
}