Skip to main content

ppt_rs/opc/
package.rs

1//! OPC Package handling
2
3use std::io::Read;
4use std::path::Path;
5use std::collections::HashMap;
6use crate::exc::Result;
7
8/// Represents an OPC package (ZIP file)
9pub struct Package {
10    /// Package parts stored as (path, content)
11    parts: HashMap<String, Vec<u8>>,
12}
13
14impl Package {
15    /// Create a new empty package
16    pub fn new() -> Self {
17        Package {
18            parts: HashMap::new(),
19        }
20    }
21
22    /// Open a package from a file path
23    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    /// Open a package from a reader
30    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    /// Save the package to a file
52    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    /// Save the package to a writer
59    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    /// Get a part by path
78    pub fn get_part(&self, path: &str) -> Option<&[u8]> {
79        self.parts.get(path).map(|v| v.as_slice())
80    }
81
82    /// Add or update a part
83    pub fn add_part(&mut self, path: String, content: Vec<u8>) {
84        self.parts.insert(path, content);
85    }
86
87    /// Remove a part by path
88    pub fn remove_part(&mut self, path: &str) -> Option<Vec<u8>> {
89        self.parts.remove(path)
90    }
91
92    /// Check if a part exists
93    pub fn has_part(&self, path: &str) -> bool {
94        self.parts.contains_key(path)
95    }
96
97    /// Get all part paths
98    pub fn part_paths(&self) -> Vec<&str> {
99        self.parts.keys().map(|s| s.as_str()).collect()
100    }
101
102    /// Get number of parts
103    pub fn part_count(&self) -> usize {
104        self.parts.len()
105    }
106
107    /// Get mutable reference to part content
108    pub fn get_part_mut(&mut self, path: &str) -> Option<&mut Vec<u8>> {
109        self.parts.get_mut(path)
110    }
111
112    /// Get part as string (for XML parts)
113    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}