Skip to main content

ppt_rs/opc/
package.rs

1//! OPC Package handling
2
3use crate::exc::Result;
4use std::collections::HashMap;
5use std::io::Read;
6use std::path::Path;
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
33        let mut parts = HashMap::new();
34
35        for i in 0..archive.len() {
36            let mut file = archive.by_index(i)?;
37
38            if !file.is_dir() {
39                let mut content = Vec::new();
40                file.read_to_end(&mut content)?;
41                parts.insert(file.name().to_string(), content);
42            }
43        }
44
45        Ok(Package { parts })
46    }
47
48    /// Save the package to a file
49    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
50        let path = path.as_ref();
51        let file = std::fs::File::create(path)?;
52        self.save_writer(file)
53    }
54
55    /// Save the package to a writer
56    pub fn save_writer<W: std::io::Write + std::io::Seek>(&self, writer: W) -> Result<()> {
57        let mut archive = zip::ZipWriter::new(writer);
58
59        for (path, content) in &self.parts {
60            let options = zip::write::FileOptions::default();
61            archive.start_file(path, options)?;
62            std::io::Write::write_all(&mut archive, content)?;
63        }
64
65        archive.finish()?;
66
67        Ok(())
68    }
69
70    /// Get a part by path
71    pub fn get_part(&self, path: &str) -> Option<&[u8]> {
72        self.parts.get(path).map(|v| v.as_slice())
73    }
74
75    /// Add or update a part
76    pub fn add_part(&mut self, path: String, content: Vec<u8>) {
77        self.parts.insert(path, content);
78    }
79
80    /// Remove a part by path
81    pub fn remove_part(&mut self, path: &str) -> Option<Vec<u8>> {
82        self.parts.remove(path)
83    }
84
85    /// Check if a part exists
86    pub fn has_part(&self, path: &str) -> bool {
87        self.parts.contains_key(path)
88    }
89
90    /// Get all part paths
91    pub fn part_paths(&self) -> Vec<&str> {
92        self.parts.keys().map(|s| s.as_str()).collect()
93    }
94
95    /// Get number of parts
96    pub fn part_count(&self) -> usize {
97        self.parts.len()
98    }
99
100    /// Get mutable reference to part content
101    pub fn get_part_mut(&mut self, path: &str) -> Option<&mut Vec<u8>> {
102        self.parts.get_mut(path)
103    }
104
105    /// Get part as string (for XML parts)
106    pub fn get_part_string(&self, path: &str) -> Option<String> {
107        self.parts
108            .get(path)
109            .map(|v| String::from_utf8_lossy(v).to_string())
110    }
111}
112
113impl Default for Package {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_package_creation() {
125        let package = Package::new();
126        assert_eq!(package.part_count(), 0);
127    }
128
129    #[test]
130    fn test_add_part() {
131        let mut package = Package::new();
132        package.add_part("test.txt".to_string(), b"content".to_vec());
133        assert_eq!(package.part_count(), 1);
134        assert_eq!(package.get_part("test.txt"), Some(b"content".as_slice()));
135    }
136
137    #[test]
138    fn test_part_paths() {
139        let mut package = Package::new();
140        package.add_part("file1.txt".to_string(), b"content1".to_vec());
141        package.add_part("file2.txt".to_string(), b"content2".to_vec());
142        let paths = package.part_paths();
143        assert_eq!(paths.len(), 2);
144    }
145}