Skip to main content

ppt_rs/parts/
slide_master.rs

1//! Slide master part
2//!
3//! Represents a slide master template (ppt/slideMasters/slideMasterN.xml).
4
5use super::base::{ContentType, Part, PartType};
6use crate::exc::PptxError;
7
8/// Slide master part (ppt/slideMasters/slideMasterN.xml)
9#[derive(Debug, Clone)]
10pub struct SlideMasterPart {
11    path: String,
12    master_number: usize,
13    name: String,
14    theme_rel_id: String,
15    layout_rel_ids: Vec<String>,
16    xml_content: Option<String>,
17}
18
19impl SlideMasterPart {
20    /// Create a new slide master part
21    pub fn new(master_number: usize) -> Self {
22        SlideMasterPart {
23            path: format!("ppt/slideMasters/slideMaster{}.xml", master_number),
24            master_number,
25            name: "Office Theme".to_string(),
26            theme_rel_id: "rId1".to_string(),
27            layout_rel_ids: vec![],
28            xml_content: None,
29        }
30    }
31
32    /// Get master number
33    pub fn master_number(&self) -> usize {
34        self.master_number
35    }
36
37    /// Get master name
38    pub fn name(&self) -> &str {
39        &self.name
40    }
41
42    /// Set master name
43    pub fn set_name(&mut self, name: impl Into<String>) {
44        self.name = name.into();
45    }
46
47    /// Add a layout relationship ID
48    pub fn add_layout_rel_id(&mut self, rel_id: impl Into<String>) {
49        self.layout_rel_ids.push(rel_id.into());
50    }
51
52    /// Get layout relationship IDs
53    pub fn layout_rel_ids(&self) -> &[String] {
54        &self.layout_rel_ids
55    }
56
57    /// Set theme relationship ID
58    pub fn set_theme_rel_id(&mut self, rel_id: impl Into<String>) {
59        self.theme_rel_id = rel_id.into();
60    }
61
62    /// Get relative path for relationships
63    pub fn rel_target(&self) -> String {
64        format!("slideMasters/slideMaster{}.xml", self.master_number)
65    }
66
67    fn generate_xml(&self) -> String {
68        let layout_ids: String = self
69            .layout_rel_ids
70            .iter()
71            .map(|id| {
72                format!(
73                    r#"<p:sldLayoutId id="{}" r:id="{}"/>"#,
74                    2147483649 + self.layout_rel_ids.iter().position(|x| x == id).unwrap() as u64,
75                    id
76                )
77            })
78            .collect::<Vec<_>>()
79            .join("\n      ");
80
81        format!(
82            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
83<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
84  <p:cSld>
85    <p:bg>
86      <p:bgRef idx="1001">
87        <a:schemeClr val="bg1"/>
88      </p:bgRef>
89    </p:bg>
90    <p:spTree>
91      <p:nvGrpSpPr>
92        <p:cNvPr id="1" name=""/>
93        <p:cNvGrpSpPr/>
94        <p:nvPr/>
95      </p:nvGrpSpPr>
96      <p:grpSpPr>
97        <a:xfrm>
98          <a:off x="0" y="0"/>
99          <a:ext cx="0" cy="0"/>
100          <a:chOff x="0" y="0"/>
101          <a:chExt cx="0" cy="0"/>
102        </a:xfrm>
103      </p:grpSpPr>
104    </p:spTree>
105  </p:cSld>
106  <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
107  <p:sldLayoutIdLst>
108    {}</p:sldLayoutIdLst>
109  <p:txStyles>
110    <p:titleStyle/>
111    <p:bodyStyle/>
112    <p:otherStyle/>
113  </p:txStyles>
114</p:sldMaster>"#,
115            if layout_ids.is_empty() {
116                "".to_string()
117            } else {
118                format!("\n      {}\n  ", layout_ids)
119            }
120        )
121    }
122}
123
124impl Part for SlideMasterPart {
125    fn path(&self) -> &str {
126        &self.path
127    }
128
129    fn part_type(&self) -> PartType {
130        PartType::SlideMaster
131    }
132
133    fn content_type(&self) -> ContentType {
134        ContentType::SlideMaster
135    }
136
137    fn to_xml(&self) -> Result<String, PptxError> {
138        if let Some(ref xml) = self.xml_content {
139            return Ok(xml.clone());
140        }
141        Ok(self.generate_xml())
142    }
143
144    fn from_xml(xml: &str) -> Result<Self, PptxError> {
145        Ok(SlideMasterPart {
146            path: "ppt/slideMasters/slideMaster1.xml".to_string(),
147            master_number: 1,
148            name: "Office Theme".to_string(),
149            theme_rel_id: "rId1".to_string(),
150            layout_rel_ids: vec![],
151            xml_content: Some(xml.to_string()),
152        })
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_slide_master_new() {
162        let master = SlideMasterPart::new(1);
163        assert_eq!(master.master_number(), 1);
164        assert_eq!(master.path(), "ppt/slideMasters/slideMaster1.xml");
165    }
166
167    #[test]
168    fn test_slide_master_name() {
169        let mut master = SlideMasterPart::new(1);
170        assert_eq!(master.name(), "Office Theme");
171        master.set_name("Custom Theme");
172        assert_eq!(master.name(), "Custom Theme");
173    }
174
175    #[test]
176    fn test_slide_master_layouts() {
177        let mut master = SlideMasterPart::new(1);
178        master.add_layout_rel_id("rId2");
179        master.add_layout_rel_id("rId3");
180        assert_eq!(master.layout_rel_ids().len(), 2);
181    }
182
183    #[test]
184    fn test_slide_master_to_xml() {
185        let master = SlideMasterPart::new(1);
186        let xml = master.to_xml().unwrap();
187        assert!(xml.contains("p:sldMaster"));
188        assert!(xml.contains("p:clrMap"));
189    }
190
191    #[test]
192    fn test_slide_master_rel_target() {
193        let master = SlideMasterPart::new(2);
194        assert_eq!(master.rel_target(), "slideMasters/slideMaster2.xml");
195    }
196}