Skip to main content

ppt_rs/generator/
notes_xml.rs

1//! Notes XML generation for speaker notes
2
3use crate::core::escape_xml;
4
5/// Generate a proper GUID for field IDs
6/// Format: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
7fn generate_field_guid(slide_num: usize) -> String {
8    // Generate a deterministic but valid-looking GUID based on slide number
9    format!("{{B0E4A5D7-2C3F-4A8B-9E1D-{:012X}}}", slide_num)
10}
11
12/// Generate notes slide XML for speaker notes
13pub fn create_notes_xml(slide_num: usize, notes_text: &str) -> String {
14    let escaped_notes = escape_xml(notes_text);
15    let field_guid = generate_field_guid(slide_num);
16    
17    format!(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
18<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">
19<p:cSld>
20<p:spTree>
21<p:nvGrpSpPr>
22<p:cNvPr id="1" name=""/>
23<p:cNvGrpSpPr/>
24<p:nvPr/>
25</p:nvGrpSpPr>
26<p:grpSpPr>
27<a:xfrm>
28<a:off x="0" y="0"/>
29<a:ext cx="0" cy="0"/>
30<a:chOff x="0" y="0"/>
31<a:chExt cx="0" cy="0"/>
32</a:xfrm>
33</p:grpSpPr>
34<p:sp>
35<p:nvSpPr>
36<p:cNvPr id="2" name="Slide Image Placeholder 1"/>
37<p:cNvSpPr>
38<a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/>
39</p:cNvSpPr>
40<p:nvPr>
41<p:ph type="sldImg"/>
42</p:nvPr>
43</p:nvSpPr>
44<p:spPr/>
45</p:sp>
46<p:sp>
47<p:nvSpPr>
48<p:cNvPr id="3" name="Notes Placeholder 2"/>
49<p:cNvSpPr>
50<a:spLocks noGrp="1"/>
51</p:cNvSpPr>
52<p:nvPr>
53<p:ph type="body" idx="1"/>
54</p:nvPr>
55</p:nvSpPr>
56<p:spPr/>
57<p:txBody>
58<a:bodyPr/>
59<a:lstStyle/>
60<a:p>
61<a:r>
62<a:rPr lang="en-US" dirty="0"/>
63<a:t>{escaped_notes}</a:t>
64</a:r>
65</a:p>
66</p:txBody>
67</p:sp>
68<p:sp>
69<p:nvSpPr>
70<p:cNvPr id="4" name="Slide Number Placeholder 3"/>
71<p:cNvSpPr>
72<a:spLocks noGrp="1"/>
73</p:cNvSpPr>
74<p:nvPr>
75<p:ph type="sldNum" sz="quarter" idx="10"/>
76</p:nvPr>
77</p:nvSpPr>
78<p:spPr/>
79<p:txBody>
80<a:bodyPr/>
81<a:lstStyle/>
82<a:p>
83<a:fld id="{field_guid}" type="slidenum">
84<a:rPr lang="en-US"/>
85<a:t>{slide_num}</a:t>
86</a:fld>
87<a:endParaRPr lang="en-US"/>
88</a:p>
89</p:txBody>
90</p:sp>
91</p:spTree>
92</p:cSld>
93<p:clrMapOvr>
94<a:masterClrMapping/>
95</p:clrMapOvr>
96</p:notes>"#)
97}
98
99/// Generate notes slide relationship XML
100pub fn create_notes_rels_xml(slide_num: usize) -> String {
101    format!(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
102<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
103<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="../slides/slide{slide_num}.xml"/>
104<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" Target="../notesMasters/notesMaster1.xml"/>
105</Relationships>"#)
106}
107
108/// Generate notes master XML
109pub fn create_notes_master_xml() -> String {
110    r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
111<p:notesMaster 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">
112<p:cSld>
113<p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg>
114<p:spTree>
115<p:nvGrpSpPr>
116<p:cNvPr id="1" name=""/>
117<p:cNvGrpSpPr/>
118<p:nvPr/>
119</p:nvGrpSpPr>
120<p:grpSpPr>
121<a:xfrm>
122<a:off x="0" y="0"/>
123<a:ext cx="0" cy="0"/>
124<a:chOff x="0" y="0"/>
125<a:chExt cx="0" cy="0"/>
126</a:xfrm>
127</p:grpSpPr>
128<p:sp>
129<p:nvSpPr>
130<p:cNvPr id="2" name="Header Placeholder 1"/>
131<p:cNvSpPr>
132<a:spLocks noGrp="1"/>
133</p:cNvSpPr>
134<p:nvPr>
135<p:ph type="hdr" sz="quarter"/>
136</p:nvPr>
137</p:nvSpPr>
138<p:spPr>
139<a:xfrm>
140<a:off x="0" y="0"/>
141<a:ext cx="2971800" cy="458788"/>
142</a:xfrm>
143<a:prstGeom prst="rect">
144<a:avLst/>
145</a:prstGeom>
146</p:spPr>
147<p:txBody>
148<a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/>
149<a:lstStyle>
150<a:lvl1pPr algn="l">
151<a:defRPr sz="1200"/>
152</a:lvl1pPr>
153</a:lstStyle>
154<a:p>
155<a:endParaRPr lang="en-US"/>
156</a:p>
157</p:txBody>
158</p:sp>
159<p:sp>
160<p:nvSpPr>
161<p:cNvPr id="3" name="Slide Image Placeholder 2"/>
162<p:cNvSpPr>
163<a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/>
164</p:cNvSpPr>
165<p:nvPr>
166<p:ph type="sldImg" idx="2"/>
167</p:nvPr>
168</p:nvSpPr>
169<p:spPr>
170<a:xfrm>
171<a:off x="685800" y="1143000"/>
172<a:ext cx="5486400" cy="3086100"/>
173</a:xfrm>
174<a:prstGeom prst="rect">
175<a:avLst/>
176</a:prstGeom>
177<a:noFill/>
178<a:ln w="12700">
179<a:solidFill>
180<a:prstClr val="black"/>
181</a:solidFill>
182</a:ln>
183</p:spPr>
184<p:txBody>
185<a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0" anchor="ctr"/>
186<a:lstStyle/>
187<a:p>
188<a:endParaRPr lang="en-US"/>
189</a:p>
190</p:txBody>
191</p:sp>
192<p:sp>
193<p:nvSpPr>
194<p:cNvPr id="4" name="Notes Placeholder 3"/>
195<p:cNvSpPr>
196<a:spLocks noGrp="1"/>
197</p:cNvSpPr>
198<p:nvPr>
199<p:ph type="body" sz="quarter" idx="3"/>
200</p:nvPr>
201</p:nvSpPr>
202<p:spPr>
203<a:xfrm>
204<a:off x="685800" y="4400550"/>
205<a:ext cx="5486400" cy="3600450"/>
206</a:xfrm>
207<a:prstGeom prst="rect">
208<a:avLst/>
209</a:prstGeom>
210</p:spPr>
211<p:txBody>
212<a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/>
213<a:lstStyle/>
214<a:p>
215<a:pPr lvl="0"/>
216<a:r>
217<a:rPr lang="en-US"/>
218<a:t>Click to edit Master text styles</a:t>
219</a:r>
220</a:p>
221</p:txBody>
222</p:sp>
223</p:spTree>
224</p:cSld>
225<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"/>
226<p:notesStyle>
227<a:lvl1pPr marL="0" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1">
228<a:defRPr sz="1200" kern="1200">
229<a:solidFill>
230<a:schemeClr val="tx1"/>
231</a:solidFill>
232<a:latin typeface="+mn-lt"/>
233<a:ea typeface="+mn-ea"/>
234<a:cs typeface="+mn-cs"/>
235</a:defRPr>
236</a:lvl1pPr>
237</p:notesStyle>
238</p:notesMaster>"#.to_string()
239}
240
241/// Generate notes master relationship XML
242pub fn create_notes_master_rels_xml() -> String {
243    r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
244<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
245<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme2.xml"/>
246</Relationships>"#.to_string()
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_create_notes_xml() {
255        let xml = create_notes_xml(1, "These are my speaker notes");
256        assert!(xml.contains("p:notes"));
257        assert!(xml.contains("These are my speaker notes"));
258        assert!(xml.contains("Notes Placeholder"));
259    }
260
261    #[test]
262    fn test_create_notes_xml_escapes_special_chars() {
263        let xml = create_notes_xml(1, "Notes with <special> & \"chars\"");
264        assert!(xml.contains("&lt;special&gt;"));
265        assert!(xml.contains("&amp;"));
266        assert!(xml.contains("&quot;chars&quot;"));
267    }
268
269    #[test]
270    fn test_create_notes_rels_xml() {
271        let xml = create_notes_rels_xml(3);
272        assert!(xml.contains("slide3.xml"));
273        assert!(xml.contains("notesMaster1.xml"));
274    }
275
276    #[test]
277    fn test_create_notes_master_xml() {
278        let xml = create_notes_master_xml();
279        assert!(xml.contains("p:notesMaster"));
280        assert!(xml.contains("<p:bg>"));
281        assert!(xml.contains("Notes Placeholder"));
282    }
283
284    #[test]
285    fn test_create_notes_master_rels_xml() {
286        let xml = create_notes_master_rels_xml();
287        assert!(xml.contains("theme2.xml"));
288    }
289}