Skip to main content

ppt_rs/generator/slide_xml/
mod.rs

1//! Slide XML generation for different layouts
2//!
3//! This module handles generating XML for various slide layouts:
4//! - Blank slides
5//! - Title only slides
6//! - Centered title slides
7//! - Title and content slides
8//! - Two column slides
9//! - Title and big content slides
10
11mod common;
12mod layouts;
13mod content;
14
15use super::slide_content::{SlideContent, SlideLayout};
16
17pub use common::create_slide_rels_xml;
18
19/// Create simple slide XML
20pub fn create_slide_xml(slide_num: usize, title: &str) -> String {
21    let slide_title = if slide_num == 1 {
22        title.to_string()
23    } else {
24        format!("Slide {slide_num}")
25    };
26    
27    format!(
28        r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
29<p:sld 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">
30<p:cSld>
31<p:spTree>
32<p:nvGrpSpPr>
33<p:cNvPr id="1" name=""/>
34<p:cNvGrpSpPr/>
35<p:nvPr/>
36</p:nvGrpSpPr>
37<p:grpSpPr>
38<a:xfrm>
39<a:off x="0" y="0"/>
40<a:ext cx="0" cy="0"/>
41<a:chOff x="0" y="0"/>
42<a:chExt cx="0" cy="0"/>
43</a:xfrm>
44</p:grpSpPr>
45<p:sp>
46<p:nvSpPr>
47<p:cNvPr id="2" name="Title 1"/>
48<p:cNvSpPr>
49<a:spLocks noGrp="1"/>
50</p:cNvSpPr>
51<p:nvPr>
52<p:ph type="ctrTitle"/>
53</p:nvPr>
54</p:nvSpPr>
55<p:spPr/>
56<p:txBody>
57<a:bodyPr/>
58<a:lstStyle/>
59<a:p>
60<a:r>
61<a:rPr lang="en-US"/>
62<a:t>{slide_title}</a:t>
63</a:r>
64<a:endParaRPr lang="en-US"/>
65</a:p>
66</p:txBody>
67</p:sp>
68</p:spTree>
69</p:cSld>
70<p:clrMapOvr>
71<a:masterClrMapping/>
72</p:clrMapOvr>
73</p:sld>"#
74    )
75}
76
77/// Create slide XML with content based on layout
78pub fn create_slide_xml_with_content(
79    _slide_num: usize,
80    content: &SlideContent,
81    chart_rids: &[String],
82    ink_rel_id: Option<&str>,
83) -> String {
84    let mut xml = match content.layout {
85        SlideLayout::Blank => layouts::create_blank_slide(content, chart_rids, ink_rel_id),
86        SlideLayout::TitleOnly => layouts::create_title_only_slide(content, chart_rids, ink_rel_id),
87        SlideLayout::CenteredTitle => layouts::create_centered_title_slide(content, chart_rids, ink_rel_id),
88        SlideLayout::TitleAndBigContent => layouts::create_title_and_big_content_slide(content, chart_rids, ink_rel_id),
89        SlideLayout::TwoColumn => layouts::create_two_column_slide(content, chart_rids, ink_rel_id),
90        SlideLayout::SectionHeader => layouts::create_section_header_slide(content, chart_rids, ink_rel_id),
91        SlideLayout::TitleAndContent => layouts::create_title_and_content_slide(content, chart_rids, ink_rel_id),
92    };
93
94    // Inject transition if present
95    let transition_xml = content.transition.to_xml();
96    if !transition_xml.is_empty()
97        && let Some(pos) = xml.rfind("</p:sld>") {
98        xml.insert_str(pos, &transition_xml);
99    }
100
101    xml
102}
103
104#[cfg(test)]
105mod tests {
106    use crate::generator::slide::formatting::parse_inline_formatting;
107
108    #[test]
109    fn test_parse_inline_formatting_plain() {
110        let segments = parse_inline_formatting("Hello world");
111        assert_eq!(segments.len(), 1);
112        assert_eq!(segments[0].text, "Hello world");
113        assert!(!segments[0].bold);
114        assert!(!segments[0].italic);
115    }
116
117    #[test]
118    fn test_parse_inline_formatting_bold() {
119        let segments = parse_inline_formatting("This is **bold** text");
120        assert_eq!(segments.len(), 3);
121        assert_eq!(segments[0].text, "This is ");
122        assert!(!segments[0].bold);
123        assert_eq!(segments[1].text, "bold");
124        assert!(segments[1].bold);
125        assert_eq!(segments[2].text, " text");
126        assert!(!segments[2].bold);
127    }
128
129    #[test]
130    fn test_parse_inline_formatting_italic() {
131        let segments = parse_inline_formatting("This is *italic* text");
132        assert_eq!(segments.len(), 3);
133        assert_eq!(segments[1].text, "italic");
134        assert!(segments[1].italic);
135    }
136
137    #[test]
138    fn test_parse_inline_formatting_code() {
139        let segments = parse_inline_formatting("Use `code` here");
140        assert_eq!(segments.len(), 3);
141        assert_eq!(segments[1].text, "code");
142        assert!(segments[1].code);
143    }
144
145    #[test]
146    fn test_parse_inline_formatting_mixed() {
147        let segments = parse_inline_formatting("**bold** and *italic*");
148        assert!(segments.iter().any(|s| s.bold && s.text == "bold"));
149        assert!(segments.iter().any(|s| s.italic && s.text == "italic"));
150    }
151}