ppt_rs/parts/
relationships.rs1use crate::core::ToXml;
6use crate::exc::PptxError;
7use crate::oxml::XmlParser;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum RelationshipType {
12 OfficeDocument,
13 Slide,
14 SlideLayout,
15 SlideMaster,
16 Theme,
17 Image,
18 Chart,
19 CoreProperties,
20 ExtendedProperties,
21 Custom(String),
22}
23
24impl RelationshipType {
25 pub fn uri(&self) -> &str {
27 match self {
28 RelationshipType::OfficeDocument => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
29 RelationshipType::Slide => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide",
30 RelationshipType::SlideLayout => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout",
31 RelationshipType::SlideMaster => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster",
32 RelationshipType::Theme => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
33 RelationshipType::Image => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
34 RelationshipType::Chart => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
35 RelationshipType::CoreProperties => "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
36 RelationshipType::ExtendedProperties => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
37 RelationshipType::Custom(uri) => uri,
38 }
39 }
40
41 pub fn from_uri(uri: &str) -> Self {
43 if uri.contains("/slide") && !uri.contains("Layout") && !uri.contains("Master") {
44 RelationshipType::Slide
45 } else if uri.contains("/slideLayout") {
46 RelationshipType::SlideLayout
47 } else if uri.contains("/slideMaster") {
48 RelationshipType::SlideMaster
49 } else if uri.contains("/theme") {
50 RelationshipType::Theme
51 } else if uri.contains("/image") {
52 RelationshipType::Image
53 } else if uri.contains("/chart") {
54 RelationshipType::Chart
55 } else if uri.contains("/officeDocument") {
56 RelationshipType::OfficeDocument
57 } else if uri.contains("core-properties") {
58 RelationshipType::CoreProperties
59 } else if uri.contains("extended-properties") {
60 RelationshipType::ExtendedProperties
61 } else {
62 RelationshipType::Custom(uri.to_string())
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct Relationship {
70 pub id: String,
71 pub rel_type: RelationshipType,
72 pub target: String,
73}
74
75impl Relationship {
76 pub fn new(id: &str, rel_type: RelationshipType, target: &str) -> Self {
78 Relationship {
79 id: id.to_string(),
80 rel_type,
81 target: target.to_string(),
82 }
83 }
84
85 pub fn to_xml(&self) -> String {
87 format!(
88 r#"<Relationship Id="{}" Type="{}" Target="{}"/>"#,
89 self.id,
90 self.rel_type.uri(),
91 self.target
92 )
93 }
94}
95
96impl ToXml for Relationship {
97 fn to_xml(&self) -> String {
98 Relationship::to_xml(self)
99 }
100}
101
102#[derive(Debug, Clone, Default)]
104pub struct Relationships {
105 relationships: Vec<Relationship>,
106 next_id: u32,
107}
108
109impl Relationships {
110 pub fn new() -> Self {
112 Relationships {
113 relationships: Vec::new(),
114 next_id: 1,
115 }
116 }
117
118 pub fn add(&mut self, rel_type: RelationshipType, target: &str) -> String {
120 let id = format!("rId{}", self.next_id);
121 self.next_id += 1;
122 self.relationships
123 .push(Relationship::new(&id, rel_type, target));
124 id
125 }
126
127 pub fn add_with_id(&mut self, id: &str, rel_type: RelationshipType, target: &str) {
129 self.relationships
130 .push(Relationship::new(id, rel_type, target));
131 if let Some(num) = id.strip_prefix("rId").and_then(|s| s.parse::<u32>().ok()) {
133 if num >= self.next_id {
134 self.next_id = num + 1;
135 }
136 }
137 }
138
139 pub fn get(&self, id: &str) -> Option<&Relationship> {
141 self.relationships.iter().find(|r| r.id == id)
142 }
143
144 pub fn get_by_type(&self, rel_type: &RelationshipType) -> Vec<&Relationship> {
146 self.relationships
147 .iter()
148 .filter(|r| &r.rel_type == rel_type)
149 .collect()
150 }
151
152 pub fn all(&self) -> &[Relationship] {
154 &self.relationships
155 }
156
157 pub fn len(&self) -> usize {
159 self.relationships.len()
160 }
161
162 pub fn is_empty(&self) -> bool {
164 self.relationships.is_empty()
165 }
166
167 pub fn to_xml(&self) -> String {
169 let mut xml = String::from(
170 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
171<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">"#,
172 );
173
174 for rel in &self.relationships {
175 xml.push_str("\n ");
176 xml.push_str(&rel.to_xml());
177 }
178
179 xml.push_str("\n</Relationships>");
180 xml
181 }
182}
183
184impl ToXml for Relationships {
185 fn to_xml(&self) -> String {
186 Relationships::to_xml(self)
187 }
188}
189
190impl Relationships {
191 pub fn from_xml(xml: &str) -> Result<Self, PptxError> {
193 let root = XmlParser::parse_str(xml)?;
194 let mut rels = Relationships::new();
195
196 for rel_elem in root.find_all("Relationship") {
197 if let (Some(id), Some(rel_type), Some(target)) = (
198 rel_elem.attr("Id"),
199 rel_elem.attr("Type"),
200 rel_elem.attr("Target"),
201 ) {
202 rels.add_with_id(id, RelationshipType::from_uri(rel_type), target);
203 }
204 }
205
206 Ok(rels)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_relationship_type_uri() {
216 assert!(RelationshipType::Slide.uri().contains("/slide"));
217 assert!(RelationshipType::Image.uri().contains("/image"));
218 }
219
220 #[test]
221 fn test_relationship_type_from_uri() {
222 let slide = RelationshipType::from_uri(
223 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide",
224 );
225 assert_eq!(slide, RelationshipType::Slide);
226 }
227
228 #[test]
229 fn test_relationships_add() {
230 let mut rels = Relationships::new();
231 let id1 = rels.add(RelationshipType::Slide, "slides/slide1.xml");
232 let id2 = rels.add(RelationshipType::Slide, "slides/slide2.xml");
233
234 assert_eq!(id1, "rId1");
235 assert_eq!(id2, "rId2");
236 assert_eq!(rels.len(), 2);
237 }
238
239 #[test]
240 fn test_relationships_to_xml() {
241 let mut rels = Relationships::new();
242 rels.add(
243 RelationshipType::SlideMaster,
244 "slideMasters/slideMaster1.xml",
245 );
246 rels.add(RelationshipType::Theme, "theme/theme1.xml");
247
248 let xml = rels.to_xml();
249 assert!(xml.contains("rId1"));
250 assert!(xml.contains("slideMaster"));
251 assert!(xml.contains("theme"));
252 }
253
254 #[test]
255 fn test_relationships_from_xml() {
256 let xml = r#"<?xml version="1.0"?>
257 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
258 <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
259 <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
260 </Relationships>"#;
261
262 let rels = Relationships::from_xml(xml).unwrap();
263 assert_eq!(rels.len(), 2);
264 assert!(rels.get("rId1").is_some());
265 }
266}