1use serde::{Deserialize, Serialize};
6
7use crate::namespaces;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename = "Relationships")]
12pub struct Relationships {
13 #[serde(rename = "@xmlns")]
14 pub xmlns: String,
15
16 #[serde(rename = "Relationship", default)]
17 pub relationships: Vec<Relationship>,
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct Relationship {
23 #[serde(rename = "@Id")]
24 pub id: String,
25
26 #[serde(rename = "@Type")]
27 pub rel_type: String,
28
29 #[serde(rename = "@Target")]
30 pub target: String,
31
32 #[serde(rename = "@TargetMode", skip_serializing_if = "Option::is_none")]
33 pub target_mode: Option<String>,
34}
35
36pub fn package_rels() -> Relationships {
41 Relationships {
42 xmlns: namespaces::PACKAGE_RELATIONSHIPS.to_string(),
43 relationships: vec![
44 Relationship {
45 id: "rId1".to_string(),
46 rel_type: rel_types::OFFICE_DOCUMENT.to_string(),
47 target: "xl/workbook.xml".to_string(),
48 target_mode: None,
49 },
50 Relationship {
51 id: "rId2".to_string(),
52 rel_type: rel_types::CORE_PROPERTIES.to_string(),
53 target: "docProps/core.xml".to_string(),
54 target_mode: None,
55 },
56 Relationship {
57 id: "rId3".to_string(),
58 rel_type: rel_types::EXTENDED_PROPERTIES.to_string(),
59 target: "docProps/app.xml".to_string(),
60 target_mode: None,
61 },
62 ],
63 }
64}
65
66pub fn workbook_rels() -> Relationships {
70 Relationships {
71 xmlns: namespaces::PACKAGE_RELATIONSHIPS.to_string(),
72 relationships: vec![
73 Relationship {
74 id: "rId1".to_string(),
75 rel_type: rel_types::WORKSHEET.to_string(),
76 target: "worksheets/sheet1.xml".to_string(),
77 target_mode: None,
78 },
79 Relationship {
80 id: "rId2".to_string(),
81 rel_type: rel_types::STYLES.to_string(),
82 target: "styles.xml".to_string(),
83 target_mode: None,
84 },
85 Relationship {
86 id: "rId3".to_string(),
87 rel_type: rel_types::SHARED_STRINGS.to_string(),
88 target: "sharedStrings.xml".to_string(),
89 target_mode: None,
90 },
91 ],
92 }
93}
94
95pub mod rel_types {
97 pub const OFFICE_DOCUMENT: &str =
99 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
100 pub const CORE_PROPERTIES: &str =
101 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
102 pub const EXTENDED_PROPERTIES: &str =
103 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
104
105 pub const WORKSHEET: &str =
107 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
108 pub const SHARED_STRINGS: &str =
109 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
110 pub const STYLES: &str =
111 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
112 pub const THEME: &str =
113 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
114 pub const CHARTSHEET: &str =
115 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet";
116 pub const CALC_CHAIN: &str =
117 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain";
118 pub const EXTERNAL_LINK: &str =
119 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink";
120 pub const PIVOT_CACHE_DEF: &str =
121 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition";
122 pub const PIVOT_TABLE: &str =
123 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable";
124 pub const PIVOT_CACHE_RECORDS: &str =
125 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords";
126
127 pub const COMMENTS: &str =
129 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
130 pub const DRAWING: &str =
131 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
132 pub const TABLE: &str =
133 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
134 pub const HYPERLINK: &str =
135 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
136 pub const PRINTER_SETTINGS: &str =
137 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings";
138
139 pub const VML_DRAWING: &str =
140 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
141
142 pub const CHART: &str =
144 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
145 pub const IMAGE: &str =
146 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
147
148 pub const CUSTOM_PROPERTIES: &str =
150 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
151
152 pub const SLICER: &str = "http://schemas.microsoft.com/office/2007/relationships/slicer";
154 pub const SLICER_CACHE: &str =
155 "http://schemas.microsoft.com/office/2007/relationships/slicerCache";
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_package_rels_factory() {
164 let rels = package_rels();
165 assert_eq!(rels.xmlns, namespaces::PACKAGE_RELATIONSHIPS);
166 assert_eq!(rels.relationships.len(), 3);
167
168 assert_eq!(rels.relationships[0].id, "rId1");
170 assert_eq!(rels.relationships[0].rel_type, rel_types::OFFICE_DOCUMENT);
171 assert_eq!(rels.relationships[0].target, "xl/workbook.xml");
172 assert!(rels.relationships[0].target_mode.is_none());
173
174 assert_eq!(rels.relationships[1].id, "rId2");
176 assert_eq!(rels.relationships[1].rel_type, rel_types::CORE_PROPERTIES);
177 assert_eq!(rels.relationships[1].target, "docProps/core.xml");
178
179 assert_eq!(rels.relationships[2].id, "rId3");
181 assert_eq!(
182 rels.relationships[2].rel_type,
183 rel_types::EXTENDED_PROPERTIES
184 );
185 assert_eq!(rels.relationships[2].target, "docProps/app.xml");
186 }
187
188 #[test]
189 fn test_workbook_rels_factory() {
190 let rels = workbook_rels();
191 assert_eq!(rels.xmlns, namespaces::PACKAGE_RELATIONSHIPS);
192 assert_eq!(rels.relationships.len(), 3);
193
194 assert_eq!(rels.relationships[0].id, "rId1");
196 assert_eq!(rels.relationships[0].rel_type, rel_types::WORKSHEET);
197 assert_eq!(rels.relationships[0].target, "worksheets/sheet1.xml");
198
199 assert_eq!(rels.relationships[1].id, "rId2");
201 assert_eq!(rels.relationships[1].rel_type, rel_types::STYLES);
202 assert_eq!(rels.relationships[1].target, "styles.xml");
203
204 assert_eq!(rels.relationships[2].id, "rId3");
206 assert_eq!(rels.relationships[2].rel_type, rel_types::SHARED_STRINGS);
207 assert_eq!(rels.relationships[2].target, "sharedStrings.xml");
208 }
209
210 #[test]
211 fn test_relationships_roundtrip() {
212 let rels = package_rels();
213 let xml = quick_xml::se::to_string(&rels).unwrap();
214 let parsed: Relationships = quick_xml::de::from_str(&xml).unwrap();
215 assert_eq!(rels.xmlns, parsed.xmlns);
216 assert_eq!(rels.relationships.len(), parsed.relationships.len());
217 assert_eq!(rels.relationships[0].id, parsed.relationships[0].id);
218 assert_eq!(
219 rels.relationships[0].rel_type,
220 parsed.relationships[0].rel_type
221 );
222 assert_eq!(rels.relationships[0].target, parsed.relationships[0].target);
223 }
224
225 #[test]
226 fn test_workbook_rels_roundtrip() {
227 let rels = workbook_rels();
228 let xml = quick_xml::se::to_string(&rels).unwrap();
229 let parsed: Relationships = quick_xml::de::from_str(&xml).unwrap();
230 assert_eq!(rels.relationships.len(), parsed.relationships.len());
231 for (orig, parsed) in rels.relationships.iter().zip(parsed.relationships.iter()) {
232 assert_eq!(orig.id, parsed.id);
233 assert_eq!(orig.rel_type, parsed.rel_type);
234 assert_eq!(orig.target, parsed.target);
235 }
236 }
237
238 #[test]
239 fn test_relationship_with_target_mode() {
240 let rel = Relationship {
241 id: "rId1".to_string(),
242 rel_type: rel_types::HYPERLINK.to_string(),
243 target: "https://example.com".to_string(),
244 target_mode: Some("External".to_string()),
245 };
246 let xml = quick_xml::se::to_string(&rel).unwrap();
247 assert!(xml.contains("TargetMode=\"External\""));
248
249 let parsed: Relationship = quick_xml::de::from_str(&xml).unwrap();
250 assert_eq!(parsed.target_mode, Some("External".to_string()));
251 }
252
253 #[test]
254 fn test_relationship_without_target_mode_omits_attr() {
255 let rel = Relationship {
256 id: "rId1".to_string(),
257 rel_type: rel_types::WORKSHEET.to_string(),
258 target: "worksheets/sheet1.xml".to_string(),
259 target_mode: None,
260 };
261 let xml = quick_xml::se::to_string(&rel).unwrap();
262 assert!(!xml.contains("TargetMode"));
263 }
264
265 #[test]
266 fn test_parse_real_excel_rels() {
267 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
268<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
269 <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
270</Relationships>"#;
271
272 let parsed: Relationships = quick_xml::de::from_str(xml).unwrap();
273 assert_eq!(parsed.xmlns, namespaces::PACKAGE_RELATIONSHIPS);
274 assert_eq!(parsed.relationships.len(), 1);
275 assert_eq!(parsed.relationships[0].id, "rId1");
276 assert_eq!(parsed.relationships[0].target, "xl/workbook.xml");
277 }
278
279 #[test]
280 fn test_parse_real_excel_workbook_rels() {
281 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
282<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
283 <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
284 <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
285 <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
286</Relationships>"#;
287
288 let parsed: Relationships = quick_xml::de::from_str(xml).unwrap();
289 assert_eq!(parsed.relationships.len(), 3);
290 assert_eq!(parsed.relationships[0].rel_type, rel_types::WORKSHEET);
291 assert_eq!(parsed.relationships[1].rel_type, rel_types::STYLES);
292 assert_eq!(parsed.relationships[2].rel_type, rel_types::SHARED_STRINGS);
293 }
294
295 #[test]
296 fn test_serialize_structure() {
297 let rels = package_rels();
298 let xml = quick_xml::se::to_string(&rels).unwrap();
299 assert!(xml.contains("<Relationships"));
300 assert!(xml.contains("<Relationship"));
301 assert!(xml.contains("Id="));
302 assert!(xml.contains("Type="));
303 assert!(xml.contains("Target="));
304 }
305
306 #[test]
307 fn test_empty_relationships() {
308 let rels = Relationships {
309 xmlns: namespaces::PACKAGE_RELATIONSHIPS.to_string(),
310 relationships: vec![],
311 };
312 let xml = quick_xml::se::to_string(&rels).unwrap();
313 let parsed: Relationships = quick_xml::de::from_str(&xml).unwrap();
314 assert!(parsed.relationships.is_empty());
315 }
316}