1use std::io::Read;
4use std::path::Path;
5use std::collections::HashMap;
6use crate::exc::Result;
7
8pub struct Package {
10 parts: HashMap<String, Vec<u8>>,
12}
13
14impl Package {
15 pub fn new() -> Self {
17 Package {
18 parts: HashMap::new(),
19 }
20 }
21
22 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
24 let path = path.as_ref();
25 let file = std::fs::File::open(path)?;
26 Self::open_reader(file)
27 }
28
29 pub fn open_reader<R: Read + std::io::Seek>(reader: R) -> Result<Self> {
31 let mut archive = zip::ZipArchive::new(reader)
32 .map_err(|e| crate::exc::PptxError::Zip(e.to_string()))?;
33
34 let mut parts = HashMap::new();
35
36 for i in 0..archive.len() {
37 let mut file = archive
38 .by_index(i)
39 .map_err(|e| crate::exc::PptxError::Zip(e.to_string()))?;
40
41 if !file.is_dir() {
42 let mut content = Vec::new();
43 file.read_to_end(&mut content)?;
44 parts.insert(file.name().to_string(), content);
45 }
46 }
47
48 Ok(Package { parts })
49 }
50
51 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
53 let path = path.as_ref();
54 let file = std::fs::File::create(path)?;
55 self.save_writer(file)
56 }
57
58 pub fn save_writer<W: std::io::Write + std::io::Seek>(&self, writer: W) -> Result<()> {
60 let mut archive = zip::ZipWriter::new(writer);
61
62 for (path, content) in &self.parts {
63 let options = zip::write::FileOptions::default();
64 archive
65 .start_file(path, options)
66 .map_err(|e| crate::exc::PptxError::Zip(e.to_string()))?;
67 std::io::Write::write_all(&mut archive, content)?;
68 }
69
70 archive
71 .finish()
72 .map_err(|e| crate::exc::PptxError::Zip(e.to_string()))?;
73
74 Ok(())
75 }
76
77 pub fn get_part(&self, path: &str) -> Option<&[u8]> {
79 self.parts.get(path).map(|v| v.as_slice())
80 }
81
82 pub fn add_part(&mut self, path: String, content: Vec<u8>) {
84 self.parts.insert(path, content);
85 }
86
87 pub fn remove_part(&mut self, path: &str) -> Option<Vec<u8>> {
89 self.parts.remove(path)
90 }
91
92 pub fn has_part(&self, path: &str) -> bool {
94 self.parts.contains_key(path)
95 }
96
97 pub fn part_paths(&self) -> Vec<&str> {
99 self.parts.keys().map(|s| s.as_str()).collect()
100 }
101
102 pub fn part_count(&self) -> usize {
104 self.parts.len()
105 }
106
107 pub fn get_part_mut(&mut self, path: &str) -> Option<&mut Vec<u8>> {
109 self.parts.get_mut(path)
110 }
111
112 pub fn get_part_string(&self, path: &str) -> Option<String> {
114 self.parts.get(path).map(|v| String::from_utf8_lossy(v).to_string())
115 }
116}
117
118impl Default for Package {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_package_creation() {
130 let package = Package::new();
131 assert_eq!(package.part_count(), 0);
132 }
133
134 #[test]
135 fn test_add_part() {
136 let mut package = Package::new();
137 package.add_part("test.txt".to_string(), b"content".to_vec());
138 assert_eq!(package.part_count(), 1);
139 assert_eq!(package.get_part("test.txt"), Some(b"content".as_slice()));
140 }
141
142 #[test]
143 fn test_part_paths() {
144 let mut package = Package::new();
145 package.add_part("file1.txt".to_string(), b"content1".to_vec());
146 package.add_part("file2.txt".to_string(), b"content2".to_vec());
147 let paths = package.part_paths();
148 assert_eq!(paths.len(), 2);
149 }
150}