1use std::borrow::Cow;
27use std::collections::BTreeMap;
28
29use super::part::PartName;
30use crate::oxml::ns::NS_RELATIONSHIPS;
31
32#[derive(Clone, Debug, Eq, PartialEq, Hash)]
37pub enum RelType {
38 OfficeDocument,
40 Slide,
42 SlideLayout,
44 SlideMaster,
46 Theme,
48 Image,
50 NotesSlide,
52 NotesMaster,
57 Comments,
59 CommentAuthors,
61 Hyperlink,
63 SlideRel,
66 Chart,
68 CustomXml,
70 PresProps,
72 ViewProps,
74 TableStyles,
76 OleObject,
81 Video,
87 Audio,
92 Media,
97 DiagramData,
103 DiagramLayout,
107 DiagramQuickStyle,
111 DiagramColors,
115 Package,
122 Other(String),
124}
125
126impl RelType {
127 pub fn uri(&self) -> Cow<'_, str> {
129 match self {
130 RelType::OfficeDocument =>
131 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"),
132 RelType::Slide =>
133 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"),
134 RelType::SlideLayout =>
135 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"),
136 RelType::SlideMaster =>
137 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"),
138 RelType::Theme =>
139 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"),
140 RelType::Image =>
141 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"),
142 RelType::NotesSlide =>
143 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide"),
144 RelType::NotesMaster =>
145 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster"),
146 RelType::Comments =>
147 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"),
148 RelType::CommentAuthors =>
149 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors"),
150 RelType::Hyperlink =>
151 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"),
152 RelType::SlideRel =>
153 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"),
154 RelType::Chart =>
155 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"),
156 RelType::CustomXml =>
157 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml"),
158 RelType::PresProps =>
159 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps"),
160 RelType::ViewProps =>
161 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps"),
162 RelType::TableStyles =>
163 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles"),
164 RelType::OleObject =>
165 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"),
166 RelType::Video =>
167 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"),
168 RelType::Audio =>
169 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio"),
170 RelType::Media =>
171 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/media"),
172 RelType::DiagramData =>
173 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData"),
174 RelType::DiagramLayout =>
175 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout"),
176 RelType::DiagramQuickStyle =>
177 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle"),
178 RelType::DiagramColors =>
179 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors"),
180 RelType::Package =>
181 Cow::Borrowed("http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"),
182 RelType::Other(s) => Cow::Borrowed(s.as_str()),
183 }
184 }
185}
186
187impl std::fmt::Display for RelType {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.write_str(&self.uri())
190 }
191}
192
193#[derive(Debug, Clone)]
195pub struct Relationship {
196 pub id: String,
198 pub reltype: RelType,
200 pub target: Target,
202 pub is_external: bool,
204}
205
206impl Relationship {
207 pub fn internal(id: impl Into<String>, reltype: RelType, target: PartName) -> Self {
209 Relationship {
210 id: id.into(),
211 reltype,
212 target: Target::Internal(target),
213 is_external: false,
214 }
215 }
216 pub fn internal_str(
218 id: impl Into<String>,
219 reltype: RelType,
220 target: impl Into<String>,
221 ) -> Self {
222 Relationship {
223 id: id.into(),
224 reltype,
225 target: Target::InternalStr(target.into()),
226 is_external: false,
227 }
228 }
229 pub fn external(id: impl Into<String>, reltype: RelType, url: impl Into<String>) -> Self {
231 Relationship {
232 id: id.into(),
233 reltype,
234 target: Target::External(url.into()),
235 is_external: true,
236 }
237 }
238}
239
240#[derive(Debug, Clone)]
242pub enum Target {
243 Internal(PartName),
245 InternalStr(String),
247 External(String),
249}
250
251impl Target {
252 pub fn as_str(&self) -> &str {
254 match self {
255 Target::Internal(p) => p.as_str(),
256 Target::InternalStr(s) => s.as_str(),
257 Target::External(s) => s.as_str(),
258 }
259 }
260}
261
262#[derive(Debug, Clone, Default)]
267pub struct Relationships {
268 items: Vec<Relationship>,
269 by_id: BTreeMap<String, usize>,
270}
271
272impl Relationships {
273 pub fn new() -> Self {
275 Relationships::default()
276 }
277
278 pub fn add(&mut self, r: Relationship) -> crate::Result<&Relationship> {
283 if self.by_id.contains_key(&r.id) {
284 return Err(crate::Error::opc(format!(
285 "duplicate relationship id: {}",
286 r.id
287 )));
288 }
289 let idx = self.items.len();
290 self.by_id.insert(r.id.clone(), idx);
291 self.items.push(r);
292 Ok(&self.items[idx])
293 }
294
295 pub fn iter(&self) -> impl Iterator<Item = &Relationship> {
297 self.items.iter()
298 }
299
300 pub fn len(&self) -> usize {
302 self.items.len()
303 }
304 pub fn is_empty(&self) -> bool {
306 self.items.is_empty()
307 }
308
309 pub fn get(&self, id: &str) -> Option<&Relationship> {
311 self.by_id.get(id).map(|&i| &self.items[i])
312 }
313
314 pub fn of_type<'a>(&'a self, t: RelType) -> impl Iterator<Item = &'a Relationship> + 'a {
316 self.items.iter().filter(move |r| r.reltype == t)
317 }
318
319 pub fn to_xml(&self) -> String {
321 let mut s = String::with_capacity(256);
322 s.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
323 s.push_str(&format!("<Relationships xmlns=\"{}\">", NS_RELATIONSHIPS));
324 for r in &self.items {
325 s.push_str(&format!(
326 "<Relationship Id=\"{}\" Type=\"{}\" Target=\"{}\"{}/>",
327 xml_escape(&r.id),
328 xml_escape(&r.reltype.uri()),
329 xml_escape(r.target.as_str()),
330 if r.is_external {
331 " TargetMode=\"External\""
332 } else {
333 ""
334 },
335 ));
336 }
337 s.push_str("</Relationships>");
338 s
339 }
340
341 pub fn from_xml(xml: &str) -> crate::Result<Self> {
347 use quick_xml::events::Event;
348 use quick_xml::reader::Reader;
349
350 let mut r = Relationships::new();
351 let mut rd = Reader::from_str(xml);
352 rd.config_mut().trim_text(true);
353 let mut buf = Vec::new();
354 loop {
355 match rd.read_event_into(&mut buf) {
356 Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
357 let name = e.name();
358 if name.as_ref() != b"Relationship" {
359 continue;
360 }
361 let mut id = None;
362 let mut rtype = None;
363 let mut target = None;
364 let mut external = false;
365 for attr in e.attributes().flatten() {
366 let key = attr.key.as_ref();
367 let v = attr
368 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
369 .unwrap_or_default()
370 .to_string();
371 match key {
372 b"Id" => id = Some(v),
373 b"Type" => rtype = Some(v),
374 b"Target" => target = Some(v),
375 b"TargetMode" if v == "External" => external = true,
376 _ => {}
377 }
378 }
379 let (Some(id), Some(rtype), Some(target)) = (id, rtype, target) else {
380 return Err(crate::Error::opc("malformed <Relationship> element"));
381 };
382 let reltype = match rtype.as_str() {
383 x if x == RelType::OfficeDocument.uri() => RelType::OfficeDocument,
384 x if x == RelType::Slide.uri() => RelType::Slide,
385 x if x == RelType::SlideLayout.uri() => RelType::SlideLayout,
386 x if x == RelType::SlideMaster.uri() => RelType::SlideMaster,
387 x if x == RelType::Theme.uri() => RelType::Theme,
388 x if x == RelType::Image.uri() => RelType::Image,
389 x if x == RelType::NotesSlide.uri() => RelType::NotesSlide,
390 x if x == RelType::NotesMaster.uri() => RelType::NotesMaster,
391 x if x == RelType::Comments.uri() => RelType::Comments,
392 x if x == RelType::CommentAuthors.uri() => RelType::CommentAuthors,
393 x if x == RelType::Hyperlink.uri() => RelType::Hyperlink,
394 x if x == RelType::Chart.uri() => RelType::Chart,
395 x if x == RelType::CustomXml.uri() => RelType::CustomXml,
396 x if x == RelType::PresProps.uri() => RelType::PresProps,
397 x if x == RelType::ViewProps.uri() => RelType::ViewProps,
398 x if x == RelType::TableStyles.uri() => RelType::TableStyles,
399 x if x == RelType::OleObject.uri() => RelType::OleObject,
400 x if x == RelType::Video.uri() => RelType::Video,
401 x if x == RelType::Audio.uri() => RelType::Audio,
402 x if x == RelType::Media.uri() => RelType::Media,
403 x if x == RelType::DiagramData.uri() => RelType::DiagramData,
404 x if x == RelType::DiagramLayout.uri() => RelType::DiagramLayout,
405 x if x == RelType::DiagramQuickStyle.uri() => RelType::DiagramQuickStyle,
406 x if x == RelType::DiagramColors.uri() => RelType::DiagramColors,
407 x if x == RelType::Package.uri() => RelType::Package,
408 other => RelType::Other(other.to_string()),
411 };
412 let rel = if external {
416 Relationship::external(id, reltype, target)
417 } else {
418 Relationship::internal_str(id, reltype, target)
419 };
420 r.add(rel)?;
421 }
422 Ok(Event::Eof) => break,
423 Err(e) => return Err(crate::Error::Xml(format!("relationships parse: {e}"))),
424 _ => {}
425 }
426 buf.clear();
427 }
428 Ok(r)
429 }
430}
431
432pub(crate) fn xml_escape(s: &str) -> String {
437 crate::escape::xml_escape(s)
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use crate::opc::part::new_part_name;
444
445 #[test]
447 fn round_trip() {
448 let mut r = Relationships::new();
449 r.add(Relationship::internal(
450 "rId1",
451 RelType::Slide,
452 new_part_name("/ppt/slides/slide1.xml"),
453 ))
454 .unwrap();
455 r.add(Relationship::internal(
456 "rId2",
457 RelType::SlideLayout,
458 new_part_name("/ppt/slideLayouts/slideLayout1.xml"),
459 ))
460 .unwrap();
461 let xml = r.to_xml();
462 let r2 = Relationships::from_xml(&xml).unwrap();
463 assert_eq!(r2.len(), 2);
464 assert!(r2.get("rId1").is_some());
465 }
466
467 #[test]
471 fn diagram_reltype_uri_correct() {
472 assert_eq!(
473 RelType::DiagramData.uri(),
474 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData"
475 );
476 assert_eq!(
477 RelType::DiagramLayout.uri(),
478 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout"
479 );
480 assert_eq!(
481 RelType::DiagramQuickStyle.uri(),
482 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle"
483 );
484 assert_eq!(
485 RelType::DiagramColors.uri(),
486 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors"
487 );
488 }
489
490 #[test]
492 fn from_xml_recognizes_diagram_reltypes() {
493 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
494<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
495<Relationship Id="rIdDgmData1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData" Target="../diagrams/data1.xml"/>
496<Relationship Id="rIdDgmLayout1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout" Target="../diagrams/layout1.xml"/>
497<Relationship Id="rIdDgmQs1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle" Target="../diagrams/quickStyles1.xml"/>
498<Relationship Id="rIdDgmColors1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors" Target="../diagrams/colors1.xml"/>
499</Relationships>"#;
500 let r = Relationships::from_xml(xml).expect("parse ok");
501 assert_eq!(r.len(), 4);
502
503 let data = r.get("rIdDgmData1").expect("data rel");
504 assert_eq!(data.reltype, RelType::DiagramData);
505
506 let layout = r.get("rIdDgmLayout1").expect("layout rel");
507 assert_eq!(layout.reltype, RelType::DiagramLayout);
508
509 let qs = r.get("rIdDgmQs1").expect("qs rel");
510 assert_eq!(qs.reltype, RelType::DiagramQuickStyle);
511
512 let colors = r.get("rIdDgmColors1").expect("colors rel");
513 assert_eq!(colors.reltype, RelType::DiagramColors);
514 }
515
516 #[test]
518 fn diagram_reltype_round_trip() {
519 let mut r = Relationships::new();
520 r.add(Relationship::internal_str(
521 "rIdDgmData1",
522 RelType::DiagramData,
523 "../diagrams/data1.xml",
524 ))
525 .unwrap();
526 r.add(Relationship::internal_str(
527 "rIdDgmLayout1",
528 RelType::DiagramLayout,
529 "../diagrams/layout1.xml",
530 ))
531 .unwrap();
532 r.add(Relationship::internal_str(
533 "rIdDgmQs1",
534 RelType::DiagramQuickStyle,
535 "../diagrams/quickStyles1.xml",
536 ))
537 .unwrap();
538 r.add(Relationship::internal_str(
539 "rIdDgmColors1",
540 RelType::DiagramColors,
541 "../diagrams/colors1.xml",
542 ))
543 .unwrap();
544
545 let xml = r.to_xml();
546 let r2 = Relationships::from_xml(&xml).expect("parse ok");
547 assert_eq!(r2.len(), 4);
548 assert_eq!(
550 r2.get("rIdDgmData1").unwrap().target.as_str(),
551 "../diagrams/data1.xml"
552 );
553 assert_eq!(
554 r2.get("rIdDgmColors1").unwrap().target.as_str(),
555 "../diagrams/colors1.xml"
556 );
557 }
558}