ppt_rs/parts/
notes_slide.rs1use super::base::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7use crate::core::escape_xml;
8
9#[derive(Debug, Clone)]
11pub struct NotesSlidePart {
12 path: String,
13 notes_number: usize,
14 slide_rel_id: String,
15 notes_text: String,
16 xml_content: Option<String>,
17}
18
19impl NotesSlidePart {
20 pub fn new(notes_number: usize) -> Self {
22 NotesSlidePart {
23 path: format!("ppt/notesSlides/notesSlide{}.xml", notes_number),
24 notes_number,
25 slide_rel_id: "rId1".to_string(),
26 notes_text: String::new(),
27 xml_content: None,
28 }
29 }
30
31 pub fn with_text(notes_number: usize, text: impl Into<String>) -> Self {
33 let mut part = Self::new(notes_number);
34 part.notes_text = text.into();
35 part
36 }
37
38 pub fn notes_number(&self) -> usize {
40 self.notes_number
41 }
42
43 pub fn notes_text(&self) -> &str {
45 &self.notes_text
46 }
47
48 pub fn set_notes_text(&mut self, text: impl Into<String>) {
50 self.notes_text = text.into();
51 self.xml_content = None;
52 }
53
54 pub fn set_slide_rel_id(&mut self, rel_id: impl Into<String>) {
56 self.slide_rel_id = rel_id.into();
57 }
58
59 pub fn rel_target(&self) -> String {
61 format!("../notesSlides/notesSlide{}.xml", self.notes_number)
62 }
63
64 fn generate_xml(&self) -> String {
65 let paragraphs: String = if self.notes_text.is_empty() {
66 "<a:p><a:endParaRPr lang=\"en-US\"/></a:p>".to_string()
67 } else {
68 self.notes_text
69 .lines()
70 .map(|line| {
71 format!(
72 "<a:p><a:r><a:rPr lang=\"en-US\"/><a:t>{}</a:t></a:r></a:p>",
73 escape_xml(line)
74 )
75 })
76 .collect::<Vec<_>>()
77 .join("\n ")
78 };
79
80 format!(
81 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
82<p:notes 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">
83 <p:cSld>
84 <p:spTree>
85 <p:nvGrpSpPr>
86 <p:cNvPr id="1" name=""/>
87 <p:cNvGrpSpPr/>
88 <p:nvPr/>
89 </p:nvGrpSpPr>
90 <p:grpSpPr>
91 <a:xfrm>
92 <a:off x="0" y="0"/>
93 <a:ext cx="0" cy="0"/>
94 <a:chOff x="0" y="0"/>
95 <a:chExt cx="0" cy="0"/>
96 </a:xfrm>
97 </p:grpSpPr>
98 <p:sp>
99 <p:nvSpPr>
100 <p:cNvPr id="2" name="Slide Image Placeholder 1"/>
101 <p:cNvSpPr><a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/></p:cNvSpPr>
102 <p:nvPr><p:ph type="sldImg"/></p:nvPr>
103 </p:nvSpPr>
104 <p:spPr/>
105 </p:sp>
106 <p:sp>
107 <p:nvSpPr>
108 <p:cNvPr id="3" name="Notes Placeholder 2"/>
109 <p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>
110 <p:nvPr><p:ph type="body" idx="1"/></p:nvPr>
111 </p:nvSpPr>
112 <p:spPr/>
113 <p:txBody>
114 <a:bodyPr/>
115 <a:lstStyle/>
116 {}
117 </p:txBody>
118 </p:sp>
119 </p:spTree>
120 </p:cSld>
121 <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
122</p:notes>"#,
123 paragraphs
124 )
125 }
126}
127
128impl Part for NotesSlidePart {
129 fn path(&self) -> &str {
130 &self.path
131 }
132
133 fn part_type(&self) -> PartType {
134 PartType::Slide }
136
137 fn content_type(&self) -> ContentType {
138 ContentType::Xml }
140
141 fn to_xml(&self) -> Result<String, PptxError> {
142 if let Some(ref xml) = self.xml_content {
143 return Ok(xml.clone());
144 }
145 Ok(self.generate_xml())
146 }
147
148 fn from_xml(xml: &str) -> Result<Self, PptxError> {
149 Ok(NotesSlidePart {
150 path: "ppt/notesSlides/notesSlide1.xml".to_string(),
151 notes_number: 1,
152 slide_rel_id: "rId1".to_string(),
153 notes_text: String::new(),
154 xml_content: Some(xml.to_string()),
155 })
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_notes_slide_new() {
165 let notes = NotesSlidePart::new(1);
166 assert_eq!(notes.notes_number(), 1);
167 assert_eq!(notes.path(), "ppt/notesSlides/notesSlide1.xml");
168 }
169
170 #[test]
171 fn test_notes_slide_with_text() {
172 let notes = NotesSlidePart::with_text(1, "Speaker notes here");
173 assert_eq!(notes.notes_text(), "Speaker notes here");
174 }
175
176 #[test]
177 fn test_notes_slide_set_text() {
178 let mut notes = NotesSlidePart::new(1);
179 notes.set_notes_text("Updated notes");
180 assert_eq!(notes.notes_text(), "Updated notes");
181 }
182
183 #[test]
184 fn test_notes_slide_to_xml() {
185 let notes = NotesSlidePart::with_text(1, "Test notes");
186 let xml = notes.to_xml().unwrap();
187 assert!(xml.contains("p:notes"));
188 assert!(xml.contains("Test notes"));
189 }
190
191 #[test]
192 fn test_notes_slide_multiline() {
193 let notes = NotesSlidePart::with_text(1, "Line 1\nLine 2\nLine 3");
194 let xml = notes.to_xml().unwrap();
195 assert!(xml.contains("Line 1"));
196 assert!(xml.contains("Line 2"));
197 assert!(xml.contains("Line 3"));
198 }
199
200 #[test]
201 fn test_notes_slide_rel_target() {
202 let notes = NotesSlidePart::new(3);
203 assert_eq!(notes.rel_target(), "../notesSlides/notesSlide3.xml");
204 }
205}