1use oxml_core::OxmlError;
2use oxml_core::raw_xml::{capture_element, capture_empty_element};
3use oxml_core::xml::{local_name, matches_local_name};
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
5use quick_xml::{Reader, Writer, XmlVersion};
6use std::fmt;
7
8use crate::color::{ColorChoice, ColorError, ThemeColorSlot};
9use crate::effect::{
10 CT_EffectList, EffectError, raw_contains_placeholder_color, raw_is_effect_dag,
11};
12use crate::fill::{Fill, FillError};
13use crate::line::{CT_LineProperties, LineError};
14use crate::namespace::A_NS;
15use crate::order::OrderedRawChildren;
16
17#[cfg(test)]
18mod tests {
19 use std::fs;
20 use std::path::{Path, PathBuf};
21 use std::process::Command;
22
23 use oxml_opc::OpcPackage;
24
25 use super::{CT_OfficeStyleSheet, OFFICE_DEFAULT_XML};
26
27 const POWERPOINT_VERSION: &str = "16.104";
28 const POWERPOINT_BUILD: &str = "16.104.25121423";
29 const POWERPOINT_APP_BUILD: &str = "1214";
30
31 #[test]
32 fn powerpoint_office_theme_round_trips_structurally() {
33 let theme = CT_OfficeStyleSheet::from_xml(OFFICE_DEFAULT_XML.as_bytes()).unwrap();
34 let reparsed = CT_OfficeStyleSheet::from_xml(&theme.to_xml().unwrap()).unwrap();
35
36 assert_eq!(reparsed, theme);
37 }
38
39 #[test]
40 #[ignore = "requires RDOCX_POWERPOINT_ORACLE_SHELL and pinned Microsoft PowerPoint"]
41 fn office_default_theme_is_accepted_by_powerpoint() {
42 assert_powerpoint_build();
43 let shell = PathBuf::from(
44 std::env::var_os("RDOCX_POWERPOINT_ORACLE_SHELL")
45 .expect("RDOCX_POWERPOINT_ORACLE_SHELL must name a PowerPoint-authored PPTX"),
46 );
47 let output = std::env::temp_dir().join(format!(
48 "rdocx-f065-office-default-{}.pptx",
49 std::process::id()
50 ));
51 let mut package = OpcPackage::open(&shell).unwrap();
52 package.set_part(
53 "/ppt/theme/theme1.xml",
54 CT_OfficeStyleSheet::office_default().to_xml().unwrap(),
55 );
56 package.save(&output).unwrap();
57 open_without_repair(&output);
58 fs::remove_file(output).unwrap();
59 }
60
61 #[test]
62 fn theme_reads_any_prefix_and_writes_fixed_a_prefix_in_schema_order() {
63 let xml = minimal_theme()
64 .replace("name=\"Test\" custom", "name=\"Test & Co\" custom")
65 .replace("typeface=\"Major\"", "typeface=\"Major & Display\"");
66 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
67 assert_eq!(theme.name.as_deref(), Some("Test & Co"));
68 assert_eq!(
69 theme.theme_elements.font_scheme.major_font.latin.typeface,
70 "Major & Display"
71 );
72 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
73 let reparsed = CT_OfficeStyleSheet::from_xml(written.as_bytes()).unwrap();
74
75 assert_eq!(reparsed, theme);
76 assert!(
77 written.starts_with(
78 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><a:theme"
79 )
80 );
81 assert!(written.contains("<a:themeElements><a:clrScheme"));
82 assert!(written.find("<a:clrScheme").unwrap() < written.find("<a:fontScheme").unwrap());
83 assert!(written.find("<a:fontScheme").unwrap() < written.find("<a:fmtScheme").unwrap());
84 assert!(!written.contains("<z:themeElements"));
85 }
86
87 #[test]
88 fn format_scheme_preserves_all_four_style_lists_in_order() {
89 let theme = CT_OfficeStyleSheet::office_default();
90 let matrix = &theme.theme_elements.format_scheme;
91
92 assert_eq!(matrix.fill_styles.len(), 3);
93 assert_eq!(matrix.line_styles.len(), 3);
94 assert_eq!(matrix.effect_styles.len(), 3);
95 assert_eq!(matrix.background_fill_styles.len(), 3);
96 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
97 assert!(written.find("<a:fillStyleLst").unwrap() < written.find("<a:lnStyleLst").unwrap());
98 assert!(
99 written.find("<a:lnStyleLst").unwrap() < written.find("<a:effectStyleLst").unwrap()
100 );
101 assert!(
102 written.find("<a:effectStyleLst").unwrap() < written.find("<a:bgFillStyleLst").unwrap()
103 );
104 }
105
106 #[test]
107 fn format_scheme_without_name_round_trips_without_inventing_attribute() {
108 let xml = minimal_theme().replace(
109 "<z:fmtScheme name=\"Test\">",
110 "<z:fmtScheme custom=\"preserved\">",
111 );
112 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
113 assert_eq!(theme.theme_elements.format_scheme.name, None);
114
115 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
116 let format_scheme = written
117 .split_once("<a:fmtScheme")
118 .unwrap()
119 .1
120 .split_once('>')
121 .unwrap()
122 .0;
123 assert!(!format_scheme.contains("name="));
124 assert!(format_scheme.contains("custom=\"preserved\""));
125 assert_eq!(
126 CT_OfficeStyleSheet::from_xml(written.as_bytes()).unwrap(),
127 theme
128 );
129 }
130
131 #[test]
132 fn unknown_theme_children_round_trip_byte_for_byte_in_place() {
133 let xml = minimal_theme()
134 .replace(
135 "<z:clrScheme",
136 "<x:before id=\"1\"><x:child>one & two</x:child><!--note--></x:before><z:clrScheme",
137 )
138 .replace("</z:clrScheme>", "</z:clrScheme><x:between x:id=\"2\"/>")
139 .replace("</z:fmtScheme>", "</z:fmtScheme><x:after>kept</x:after>");
140 let written = CT_OfficeStyleSheet::from_xml(xml.as_bytes())
141 .unwrap()
142 .to_xml()
143 .unwrap();
144
145 assert!(contains(
146 &written,
147 br#"<x:before id="1"><x:child>one & two</x:child><!--note--></x:before>"#
148 ));
149 assert!(contains(&written, br#"<x:between x:id="2"/>"#));
150 assert!(contains(&written, br#"<x:after>kept</x:after>"#));
151 assert!(contains(&written, br#"custom="root""#));
152 }
153
154 #[test]
155 fn qualified_extension_attributes_do_not_replace_unqualified_theme_attributes() {
156 let xml = minimal_theme()
157 .replace(
158 "name=\"Test\" custom=\"root\"",
159 "name=\"Test\" x:name=\"extension\" custom=\"root\"",
160 )
161 .replace(
162 "<z:themeElements>",
163 "<x:extension x:name=\"child\"/><z:themeElements>",
164 );
165 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
166 let written = theme.to_xml().unwrap();
167 let reparsed = CT_OfficeStyleSheet::from_xml(&written).unwrap();
168
169 assert_eq!(reparsed, theme);
170 assert!(contains(&written, br#"xmlns:x="urn:test""#));
171 assert!(contains(&written, br#"x:name="extension""#));
172 assert!(contains(&written, br#"<x:extension x:name="child"/>"#));
173
174 let missing_unqualified = minimal_theme().replace(
175 "<z:clrScheme name=\"Test\">",
176 "<z:clrScheme x:name=\"extension\">",
177 );
178 assert!(CT_OfficeStyleSheet::from_xml(missing_unqualified.as_bytes()).is_err());
179 }
180
181 #[test]
182 fn office_default_contains_the_standard_colour_font_and_style_contract() {
183 let theme = CT_OfficeStyleSheet::office_default();
184 let elements = &theme.theme_elements;
185
186 assert_eq!(elements.color_scheme.iter().count(), 12);
187 assert_eq!(
188 elements.font_scheme.major_font.latin.typeface,
189 "Aptos Display"
190 );
191 assert_eq!(elements.font_scheme.minor_font.latin.typeface, "Aptos");
192 assert!(elements.font_scheme.major_font.supplemental_fonts.len() > 40);
193 assert!(elements.font_scheme.minor_font.supplemental_fonts.len() > 40);
194 assert_eq!(elements.format_scheme.fill_styles.len(), 3);
195 assert_eq!(elements.format_scheme.line_styles.len(), 3);
196 assert_eq!(elements.format_scheme.effect_styles.len(), 3);
197 assert_eq!(elements.format_scheme.background_fill_styles.len(), 3);
198 }
199
200 #[test]
201 fn malformed_theme_returns_an_error_without_panicking() {
202 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme/>").is_err());
203 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements/></a:theme>").is_err());
204 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements>").is_err());
205 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements><a:clrScheme name=\"x\"><a:dk1><a:srgbClr val=\"not-rgb\"/></a:dk1></a:clrScheme></a:themeElements></a:theme>").is_err());
206 let duplicate = minimal_theme().replacen(
207 "</z:clrScheme>",
208 "<z:dk1><z:srgbClr val=\"000000\"/></z:dk1></z:clrScheme>",
209 1,
210 );
211 assert!(CT_OfficeStyleSheet::from_xml(duplicate.as_bytes()).is_err());
212 }
213
214 fn minimal_theme() -> String {
215 r#"<z:theme xmlns:z="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:test" name="Test" custom="root"><z:themeElements><z:clrScheme name="Test"><z:dk1><z:srgbClr val="000000"/></z:dk1><z:lt1><z:srgbClr val="FFFFFF"/></z:lt1><z:dk2><z:srgbClr val="111111"/></z:dk2><z:lt2><z:srgbClr val="EEEEEE"/></z:lt2><z:accent1><z:srgbClr val="100001"/></z:accent1><z:accent2><z:srgbClr val="200002"/></z:accent2><z:accent3><z:srgbClr val="300003"/></z:accent3><z:accent4><z:srgbClr val="400004"/></z:accent4><z:accent5><z:srgbClr val="500005"/></z:accent5><z:accent6><z:srgbClr val="600006"/></z:accent6><z:hlink><z:srgbClr val="0000FF"/></z:hlink><z:folHlink><z:srgbClr val="800080"/></z:folHlink></z:clrScheme><z:fontScheme name="Test"><z:majorFont><z:latin typeface="Major"/><z:ea typeface=""/><z:cs typeface=""/></z:majorFont><z:minorFont><z:latin typeface="Minor"/><z:ea typeface=""/><z:cs typeface=""/></z:minorFont></z:fontScheme><z:fmtScheme name="Test"><z:fillStyleLst><z:noFill/></z:fillStyleLst><z:lnStyleLst><z:ln/></z:lnStyleLst><z:effectStyleLst><z:effectStyle><z:effectLst/></z:effectStyle></z:effectStyleLst><z:bgFillStyleLst><z:noFill/></z:bgFillStyleLst></z:fmtScheme></z:themeElements></z:theme>"#.to_owned()
216 }
217
218 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
219 haystack.windows(needle.len()).any(|part| part == needle)
220 }
221
222 fn assert_powerpoint_build() {
223 let app = "/Applications/Microsoft PowerPoint.app/Contents/Info.plist";
224 let version = Command::new("/usr/libexec/PlistBuddy")
225 .args(["-c", "Print :CFBundleShortVersionString", app])
226 .output()
227 .unwrap();
228 let build = Command::new("/usr/libexec/PlistBuddy")
229 .args(["-c", "Print :CFBundleVersion", app])
230 .output()
231 .unwrap();
232 assert!(version.status.success());
233 assert!(build.status.success());
234 assert_eq!(
235 String::from_utf8(version.stdout).unwrap().trim(),
236 POWERPOINT_VERSION
237 );
238 assert_eq!(
239 String::from_utf8(build.stdout).unwrap().trim(),
240 POWERPOINT_BUILD
241 );
242 }
243
244 fn open_without_repair(path: &Path) {
245 let path = path.to_string_lossy();
246 let name = Path::new(path.as_ref())
247 .file_name()
248 .unwrap()
249 .to_string_lossy();
250 let script = format!(
251 "with timeout of 120 seconds\ntell application \"Microsoft PowerPoint\"\nset previousStartUpDialog to start up dialog\ntry\nif (Version as text) is not \"{POWERPOINT_VERSION}\" then error \"PowerPoint version mismatch\"\nif (build as text) is not \"{POWERPOINT_APP_BUILD}\" then error \"PowerPoint build mismatch\"\nset start up dialog to false\nset deckPath to \"{path}\"\nopen my POSIX file deckPath\nset checkedDeck to presentation \"{name}\"\nif (full name of checkedDeck) is not deckPath then error \"exact path mismatch\"\nclose checkedDeck saving no\nset start up dialog to previousStartUpDialog\non error errorMessage number errorNumber\ntry\nclose checkedDeck saving no\nend try\nset start up dialog to previousStartUpDialog\nerror errorMessage number errorNumber\nend try\nend tell\nend timeout\n"
252 );
253 let result = Command::new("osascript")
254 .args(["-e", &script])
255 .output()
256 .unwrap();
257 assert!(
258 result.status.success(),
259 "PowerPoint no-repair open failed: {}",
260 String::from_utf8_lossy(&result.stderr)
261 );
262 }
263}
264
265#[derive(Debug)]
266pub enum ThemeError {
267 Xml(OxmlError),
268 Color(ColorError),
269 Fill(FillError),
270 Line(LineError),
271 Effect(EffectError),
272 UnexpectedElement(String),
273 MissingElement(String),
274 DuplicateElement(String),
275 MissingAttribute { element: String, attribute: String },
276 ChildOutOfOrder { parent: String, child: String },
277}
278
279impl fmt::Display for ThemeError {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 match self {
282 Self::Xml(e) => e.fmt(f),
283 Self::Color(e) => e.fmt(f),
284 Self::Fill(e) => e.fmt(f),
285 Self::Line(e) => e.fmt(f),
286 Self::Effect(e) => e.fmt(f),
287 Self::UnexpectedElement(e) => write!(f, "unexpected DrawingML theme element: {e}"),
288 Self::MissingElement(e) => write!(f, "DrawingML theme requires {e}"),
289 Self::DuplicateElement(e) => write!(f, "DrawingML theme contains duplicate {e}"),
290 Self::MissingAttribute { element, attribute } => {
291 write!(f, "DrawingML {element} requires @{attribute}")
292 }
293 Self::ChildOutOfOrder { parent, child } => {
294 write!(f, "DrawingML {child} is out of schema order in {parent}")
295 }
296 }
297 }
298}
299
300impl std::error::Error for ThemeError {
301 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
302 match self {
303 Self::Xml(e) => Some(e),
304 Self::Color(e) => Some(e),
305 Self::Fill(e) => Some(e),
306 Self::Line(e) => Some(e),
307 Self::Effect(e) => Some(e),
308 _ => None,
309 }
310 }
311}
312
313impl From<OxmlError> for ThemeError {
314 fn from(error: OxmlError) -> Self {
315 Self::Xml(error)
316 }
317}
318
319impl From<ColorError> for ThemeError {
320 fn from(error: ColorError) -> Self {
321 Self::Color(error)
322 }
323}
324
325impl From<FillError> for ThemeError {
326 fn from(error: FillError) -> Self {
327 Self::Fill(error)
328 }
329}
330
331impl From<LineError> for ThemeError {
332 fn from(error: LineError) -> Self {
333 Self::Line(error)
334 }
335}
336
337impl From<EffectError> for ThemeError {
338 fn from(error: EffectError) -> Self {
339 Self::Effect(error)
340 }
341}
342
343pub type Result<T> = std::result::Result<T, ThemeError>;
344
345type RawAttributes = Vec<(String, String)>;
346
347#[allow(non_camel_case_types)]
348#[derive(Clone, Debug, Eq, PartialEq)]
349pub struct CT_OfficeStyleSheet {
350 pub name: Option<String>,
351 pub theme_elements: CT_ThemeElements,
352 raw_attributes: RawAttributes,
353 raw_children: OrderedRawChildren,
354}
355
356#[allow(non_camel_case_types)]
357#[derive(Clone, Debug, Eq, PartialEq)]
358pub struct CT_ThemeElements {
359 pub color_scheme: CT_ColorScheme,
360 pub font_scheme: CT_FontScheme,
361 pub format_scheme: CT_StyleMatrix,
362 raw_attributes: RawAttributes,
363 raw_children: OrderedRawChildren,
364}
365
366#[allow(non_camel_case_types)]
367#[derive(Clone, Debug, Eq, PartialEq)]
368pub struct CT_ColorScheme {
369 pub name: String,
370 pub dark1: ColorChoice,
371 pub light1: ColorChoice,
372 pub dark2: ColorChoice,
373 pub light2: ColorChoice,
374 pub accent1: ColorChoice,
375 pub accent2: ColorChoice,
376 pub accent3: ColorChoice,
377 pub accent4: ColorChoice,
378 pub accent5: ColorChoice,
379 pub accent6: ColorChoice,
380 pub hyperlink: ColorChoice,
381 pub followed_hyperlink: ColorChoice,
382 raw_attributes: RawAttributes,
383 raw_children: OrderedRawChildren,
384 slot_attributes: [RawAttributes; 12],
385 slot_children: [OrderedRawChildren; 12],
386}
387
388impl CT_ColorScheme {
389 pub const SLOTS: [ThemeColorSlot; 12] = [
390 ThemeColorSlot::Dark1,
391 ThemeColorSlot::Light1,
392 ThemeColorSlot::Dark2,
393 ThemeColorSlot::Light2,
394 ThemeColorSlot::Accent1,
395 ThemeColorSlot::Accent2,
396 ThemeColorSlot::Accent3,
397 ThemeColorSlot::Accent4,
398 ThemeColorSlot::Accent5,
399 ThemeColorSlot::Accent6,
400 ThemeColorSlot::Hyperlink,
401 ThemeColorSlot::FollowedHyperlink,
402 ];
403
404 pub fn color(&self, slot: ThemeColorSlot) -> &ColorChoice {
405 match slot {
406 ThemeColorSlot::Dark1 => &self.dark1,
407 ThemeColorSlot::Light1 => &self.light1,
408 ThemeColorSlot::Dark2 => &self.dark2,
409 ThemeColorSlot::Light2 => &self.light2,
410 ThemeColorSlot::Accent1 => &self.accent1,
411 ThemeColorSlot::Accent2 => &self.accent2,
412 ThemeColorSlot::Accent3 => &self.accent3,
413 ThemeColorSlot::Accent4 => &self.accent4,
414 ThemeColorSlot::Accent5 => &self.accent5,
415 ThemeColorSlot::Accent6 => &self.accent6,
416 ThemeColorSlot::Hyperlink => &self.hyperlink,
417 ThemeColorSlot::FollowedHyperlink => &self.followed_hyperlink,
418 }
419 }
420
421 pub fn iter(&self) -> impl Iterator<Item = (ThemeColorSlot, &ColorChoice)> {
422 Self::SLOTS.into_iter().map(|slot| (slot, self.color(slot)))
423 }
424}
425
426#[allow(non_camel_case_types)]
427#[derive(Clone, Debug, Eq, PartialEq)]
428pub struct CT_FontScheme {
429 pub name: String,
430 pub major_font: CT_FontCollection,
431 pub minor_font: CT_FontCollection,
432 raw_attributes: RawAttributes,
433 raw_children: OrderedRawChildren,
434}
435
436#[allow(non_camel_case_types)]
437#[derive(Clone, Debug, Eq, PartialEq)]
438pub struct CT_FontCollection {
439 pub latin: ThemeTypeface,
440 pub east_asian: ThemeTypeface,
441 pub complex_script: ThemeTypeface,
442 pub supplemental_fonts: Vec<SupplementalFont>,
443 raw_attributes: RawAttributes,
444 raw_children: OrderedRawChildren,
445}
446
447#[derive(Clone, Debug, Eq, PartialEq)]
448pub struct ThemeTypeface {
449 pub typeface: String,
450 raw_attributes: RawAttributes,
451 raw_children: OrderedRawChildren,
452}
453
454#[derive(Clone, Debug, Eq, PartialEq)]
455pub struct SupplementalFont {
456 pub script: String,
457 pub typeface: String,
458 raw_attributes: RawAttributes,
459 raw_children: OrderedRawChildren,
460}
461
462#[derive(Clone, Debug, Default, Eq, PartialEq)]
463struct StyleListRaw {
464 attributes: RawAttributes,
465 children: OrderedRawChildren,
466}
467
468#[allow(non_camel_case_types)]
469#[derive(Clone, Debug, Eq, PartialEq)]
470pub struct CT_StyleMatrix {
471 pub name: Option<String>,
472 pub fill_styles: Vec<Fill>,
473 pub line_styles: Vec<CT_LineProperties>,
474 pub effect_styles: Vec<CT_EffectStyle>,
475 pub background_fill_styles: Vec<Fill>,
476 raw_attributes: RawAttributes,
477 raw_children: OrderedRawChildren,
478 fill_list_raw: StyleListRaw,
479 line_list_raw: StyleListRaw,
480 effect_list_raw: StyleListRaw,
481 background_fill_list_raw: StyleListRaw,
482}
483
484#[allow(non_camel_case_types)]
485#[derive(Clone, Debug, Eq, PartialEq)]
486pub struct CT_EffectStyle {
487 pub effect_list: Option<CT_EffectList>,
488 raw_attributes: RawAttributes,
489 raw_children: OrderedRawChildren,
490}
491
492impl CT_OfficeStyleSheet {
493 pub fn from_xml(xml: &[u8]) -> Result<Self> {
494 let mut reader = Reader::from_reader(xml);
495 let mut buf = Vec::new();
496 loop {
497 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
498 Event::Start(e) if matches_local_name(e.name().as_ref(), b"theme") => {
499 return Self::read(&mut reader, &e);
500 }
501 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"theme") => {
502 return Err(missing("themeElements"));
503 }
504 Event::Start(e) | Event::Empty(e) => return Err(unexpected(&e)),
505 Event::Eof => return Err(missing("theme")),
506 _ => {}
507 }
508 buf.clear();
509 }
510 }
511
512 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
513 let mut elements = None;
514 let mut raw_children = OrderedRawChildren::default();
515 let mut boundary = 0;
516 let mut buf = Vec::new();
517 loop {
518 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
519 Event::Start(e) if matches_local_name(e.name().as_ref(), b"themeElements") => {
520 reject_duplicate(&elements, "themeElements")?;
521 elements = Some(CT_ThemeElements::read(reader, &e)?);
522 boundary = 1;
523 }
524 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"themeElements") => {
525 return Err(missing("themeElements children"));
526 }
527 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
528 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
529 Event::End(e) if matches_local_name(e.name().as_ref(), b"theme") => break,
530 Event::Eof => return Err(missing_end("theme")),
531 _ => {}
532 }
533 buf.clear();
534 }
535 Ok(Self {
536 name: decoded_attr(start, b"name")?,
537 theme_elements: elements.ok_or_else(|| missing("themeElements"))?,
538 raw_attributes: raw_attributes(start, &[b"name"], true)?,
539 raw_children,
540 })
541 }
542
543 pub fn to_xml(&self) -> Result<Vec<u8>> {
544 let mut writer = Writer::new(Vec::new());
545 writer
546 .write_event(Event::Decl(BytesDecl::new(
547 "1.0",
548 Some("UTF-8"),
549 Some("yes"),
550 )))
551 .map_err(OxmlError::from)?;
552 let mut start = BytesStart::new("a:theme");
553 start.push_attribute(("xmlns:a", A_NS));
554 if let Some(name) = self.name.as_deref() {
555 start.push_attribute(("name", name));
556 }
557 push_attributes(&mut start, &self.raw_attributes);
558 write_start(&mut writer, start)?;
559 emit_raw(&mut writer, self.raw_children.at(0))?;
560 self.theme_elements.write(&mut writer)?;
561 emit_raw(&mut writer, self.raw_children.at(1))?;
562 write_end(&mut writer, "a:theme")?;
563 Ok(writer.into_inner())
564 }
565
566 pub fn office_default() -> Self {
567 Self::from_xml(OFFICE_DEFAULT_XML.as_bytes())
568 .unwrap_or_else(|error| panic!("built-in Office theme is invalid: {error}"))
569 }
570}
571
572impl CT_ThemeElements {
573 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
574 let mut colors = None;
575 let mut fonts = None;
576 let mut formats = None;
577 let mut raw_children = OrderedRawChildren::default();
578 let mut boundary = 0;
579 let mut buf = Vec::new();
580 loop {
581 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
582 Event::Start(e) if matches_local_name(e.name().as_ref(), b"clrScheme") => {
583 require_order("themeElements", "clrScheme", boundary, 0)?;
584 reject_duplicate(&colors, "clrScheme")?;
585 colors = Some(CT_ColorScheme::read(reader, &e)?);
586 boundary = 1;
587 }
588 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fontScheme") => {
589 require_order("themeElements", "fontScheme", boundary, 1)?;
590 reject_duplicate(&fonts, "fontScheme")?;
591 fonts = Some(CT_FontScheme::read(reader, &e)?);
592 boundary = 2;
593 }
594 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fmtScheme") => {
595 require_order("themeElements", "fmtScheme", boundary, 2)?;
596 reject_duplicate(&formats, "fmtScheme")?;
597 formats = Some(CT_StyleMatrix::read(reader, &e)?);
598 boundary = 3;
599 }
600 Event::Empty(e) if is_theme_elements_child(e.name().as_ref()) => {
601 return Err(missing(format!("{} children", element_name(&e))));
602 }
603 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
604 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
605 Event::End(e) if matches_local_name(e.name().as_ref(), b"themeElements") => break,
606 Event::Eof => return Err(missing_end("themeElements")),
607 _ => {}
608 }
609 buf.clear();
610 }
611 Ok(Self {
612 color_scheme: colors.ok_or_else(|| missing("clrScheme"))?,
613 font_scheme: fonts.ok_or_else(|| missing("fontScheme"))?,
614 format_scheme: formats.ok_or_else(|| missing("fmtScheme"))?,
615 raw_attributes: raw_attributes(start, &[], false)?,
616 raw_children,
617 })
618 }
619
620 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
621 let mut start = BytesStart::new("a:themeElements");
622 push_attributes(&mut start, &self.raw_attributes);
623 write_start(writer, start)?;
624 emit_raw(writer, self.raw_children.at(0))?;
625 self.color_scheme.write(writer)?;
626 emit_raw(writer, self.raw_children.at(1))?;
627 self.font_scheme.write(writer)?;
628 emit_raw(writer, self.raw_children.at(2))?;
629 self.format_scheme.write(writer)?;
630 emit_raw(writer, self.raw_children.at(3))?;
631 write_end(writer, "a:themeElements")
632 }
633}
634
635#[derive(Default)]
636struct ParsedColors {
637 values: [Option<ColorChoice>; 12],
638 attributes: [RawAttributes; 12],
639 children: [OrderedRawChildren; 12],
640}
641
642struct ParsedColorSlot {
643 value: ColorChoice,
644 attributes: RawAttributes,
645 children: OrderedRawChildren,
646}
647
648impl CT_ColorScheme {
649 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
650 let mut parsed = ParsedColors::default();
651 let mut raw_children = OrderedRawChildren::default();
652 let mut boundary = 0;
653 let mut buf = Vec::new();
654 loop {
655 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
656 Event::Start(e) if color_slot_index(e.name().as_ref()).is_some() => {
657 let index = color_slot_index(e.name().as_ref()).unwrap();
658 let name = element_name(&e);
659 require_order("clrScheme", &name, boundary, index)?;
660 if parsed.values[index].is_some() {
661 return Err(duplicate(name));
662 }
663 let slot = read_color_slot(reader, &e)?;
664 parsed.values[index] = Some(slot.value);
665 parsed.attributes[index] = slot.attributes;
666 parsed.children[index] = slot.children;
667 boundary = index + 1;
668 }
669 Event::Empty(e) if color_slot_index(e.name().as_ref()).is_some() => {
670 return Err(missing(format!("{} colour", element_name(&e))));
671 }
672 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
673 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
674 Event::End(e) if matches_local_name(e.name().as_ref(), b"clrScheme") => break,
675 Event::Eof => return Err(missing_end("clrScheme")),
676 _ => {}
677 }
678 buf.clear();
679 }
680 let [
681 dark1,
682 light1,
683 dark2,
684 light2,
685 accent1,
686 accent2,
687 accent3,
688 accent4,
689 accent5,
690 accent6,
691 hyperlink,
692 followed_hyperlink,
693 ] = parsed.values;
694 Ok(Self {
695 name: required_attr(start, b"name")?,
696 dark1: dark1.ok_or_else(|| missing("dk1"))?,
697 light1: light1.ok_or_else(|| missing("lt1"))?,
698 dark2: dark2.ok_or_else(|| missing("dk2"))?,
699 light2: light2.ok_or_else(|| missing("lt2"))?,
700 accent1: accent1.ok_or_else(|| missing("accent1"))?,
701 accent2: accent2.ok_or_else(|| missing("accent2"))?,
702 accent3: accent3.ok_or_else(|| missing("accent3"))?,
703 accent4: accent4.ok_or_else(|| missing("accent4"))?,
704 accent5: accent5.ok_or_else(|| missing("accent5"))?,
705 accent6: accent6.ok_or_else(|| missing("accent6"))?,
706 hyperlink: hyperlink.ok_or_else(|| missing("hlink"))?,
707 followed_hyperlink: followed_hyperlink.ok_or_else(|| missing("folHlink"))?,
708 raw_attributes: raw_attributes(start, &[b"name"], false)?,
709 raw_children,
710 slot_attributes: parsed.attributes,
711 slot_children: parsed.children,
712 })
713 }
714
715 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
716 let mut start = BytesStart::new("a:clrScheme");
717 start.push_attribute(("name", self.name.as_str()));
718 push_attributes(&mut start, &self.raw_attributes);
719 write_start(writer, start)?;
720 for (index, (slot, color)) in self.iter().enumerate() {
721 emit_raw(writer, self.raw_children.at(index))?;
722 let tag = format!("a:{}", slot.as_str());
723 let mut slot_start = BytesStart::new(tag.as_str());
724 push_attributes(&mut slot_start, &self.slot_attributes[index]);
725 write_start(writer, slot_start)?;
726 emit_raw(writer, self.slot_children[index].at(0))?;
727 color.to_xml(writer)?;
728 emit_raw(writer, self.slot_children[index].at(1))?;
729 write_end(writer, &tag)?;
730 }
731 emit_raw(writer, self.raw_children.at(12))?;
732 write_end(writer, "a:clrScheme")
733 }
734}
735
736fn read_color_slot(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<ParsedColorSlot> {
737 let end = local_name(start.name().as_ref()).to_vec();
738 let mut value = None;
739 let mut children = OrderedRawChildren::default();
740 let mut boundary = 0;
741 let mut buf = Vec::new();
742 loop {
743 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
744 Event::Start(e) if is_color(e.name().as_ref()) => {
745 reject_duplicate(&value, "theme colour choice")?;
746 value = Some(ColorChoice::from_xml(reader, &e)?);
747 boundary = 1;
748 }
749 Event::Empty(e) if is_color(e.name().as_ref()) => {
750 reject_duplicate(&value, "theme colour choice")?;
751 value = Some(ColorChoice::from_empty_xml(&e)?);
752 boundary = 1;
753 }
754 Event::Start(e) => children.push(boundary, capture_element(reader, &e)?),
755 Event::Empty(e) => children.push(boundary, capture_empty_element(&e)?),
756 Event::End(e) if local_name(e.name().as_ref()) == end => break,
757 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
758 _ => {}
759 }
760 buf.clear();
761 }
762 Ok(ParsedColorSlot {
763 value: value.ok_or_else(|| missing("theme colour choice"))?,
764 attributes: raw_attributes(start, &[], false)?,
765 children,
766 })
767}
768
769impl CT_FontScheme {
770 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
771 let mut major = None;
772 let mut minor = None;
773 let mut raw_children = OrderedRawChildren::default();
774 let mut boundary = 0;
775 let mut buf = Vec::new();
776 loop {
777 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
778 Event::Start(e) if matches_local_name(e.name().as_ref(), b"majorFont") => {
779 require_order("fontScheme", "majorFont", boundary, 0)?;
780 reject_duplicate(&major, "majorFont")?;
781 major = Some(CT_FontCollection::read(reader, &e)?);
782 boundary = 1;
783 }
784 Event::Start(e) if matches_local_name(e.name().as_ref(), b"minorFont") => {
785 require_order("fontScheme", "minorFont", boundary, 1)?;
786 reject_duplicate(&minor, "minorFont")?;
787 minor = Some(CT_FontCollection::read(reader, &e)?);
788 boundary = 2;
789 }
790 Event::Empty(e)
791 if matches_local_name(e.name().as_ref(), b"majorFont")
792 || matches_local_name(e.name().as_ref(), b"minorFont") =>
793 {
794 return Err(missing(format!("{} children", element_name(&e))));
795 }
796 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
797 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
798 Event::End(e) if matches_local_name(e.name().as_ref(), b"fontScheme") => break,
799 Event::Eof => return Err(missing_end("fontScheme")),
800 _ => {}
801 }
802 buf.clear();
803 }
804 Ok(Self {
805 name: required_attr(start, b"name")?,
806 major_font: major.ok_or_else(|| missing("majorFont"))?,
807 minor_font: minor.ok_or_else(|| missing("minorFont"))?,
808 raw_attributes: raw_attributes(start, &[b"name"], false)?,
809 raw_children,
810 })
811 }
812
813 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
814 let mut start = BytesStart::new("a:fontScheme");
815 start.push_attribute(("name", self.name.as_str()));
816 push_attributes(&mut start, &self.raw_attributes);
817 write_start(writer, start)?;
818 emit_raw(writer, self.raw_children.at(0))?;
819 self.major_font.write(writer, "a:majorFont")?;
820 emit_raw(writer, self.raw_children.at(1))?;
821 self.minor_font.write(writer, "a:minorFont")?;
822 emit_raw(writer, self.raw_children.at(2))?;
823 write_end(writer, "a:fontScheme")
824 }
825}
826
827impl CT_FontCollection {
828 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
829 let end = local_name(start.name().as_ref()).to_vec();
830 let mut latin = None;
831 let mut east_asian = None;
832 let mut complex_script = None;
833 let mut supplemental_fonts = Vec::new();
834 let mut raw_children = OrderedRawChildren::default();
835 let mut boundary = 0;
836 let mut buf = Vec::new();
837 loop {
838 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
839 Event::Start(e) if matches_local_name(e.name().as_ref(), b"latin") => {
840 require_order("font collection", "latin", boundary, 0)?;
841 reject_duplicate(&latin, "latin")?;
842 latin = Some(read_typeface(reader, &e, false)?);
843 boundary = 1;
844 }
845 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"latin") => {
846 require_order("font collection", "latin", boundary, 0)?;
847 reject_duplicate(&latin, "latin")?;
848 latin = Some(read_typeface(reader, &e, true)?);
849 boundary = 1;
850 }
851 Event::Start(e) if matches_local_name(e.name().as_ref(), b"ea") => {
852 require_order("font collection", "ea", boundary, 1)?;
853 reject_duplicate(&east_asian, "ea")?;
854 east_asian = Some(read_typeface(reader, &e, false)?);
855 boundary = 2;
856 }
857 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"ea") => {
858 require_order("font collection", "ea", boundary, 1)?;
859 reject_duplicate(&east_asian, "ea")?;
860 east_asian = Some(read_typeface(reader, &e, true)?);
861 boundary = 2;
862 }
863 Event::Start(e) if matches_local_name(e.name().as_ref(), b"cs") => {
864 require_order("font collection", "cs", boundary, 2)?;
865 reject_duplicate(&complex_script, "cs")?;
866 complex_script = Some(read_typeface(reader, &e, false)?);
867 boundary = 3;
868 }
869 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"cs") => {
870 require_order("font collection", "cs", boundary, 2)?;
871 reject_duplicate(&complex_script, "cs")?;
872 complex_script = Some(read_typeface(reader, &e, true)?);
873 boundary = 3;
874 }
875 Event::Start(e) if matches_local_name(e.name().as_ref(), b"font") => {
876 if boundary < 3 {
877 return Err(out_of_order("font collection", "font"));
878 }
879 supplemental_fonts.push(read_supplemental_font(reader, &e, false)?);
880 boundary = 3 + supplemental_fonts.len();
881 }
882 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"font") => {
883 if boundary < 3 {
884 return Err(out_of_order("font collection", "font"));
885 }
886 supplemental_fonts.push(read_supplemental_font(reader, &e, true)?);
887 boundary = 3 + supplemental_fonts.len();
888 }
889 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
890 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
891 Event::End(e) if local_name(e.name().as_ref()) == end => break,
892 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
893 _ => {}
894 }
895 buf.clear();
896 }
897 Ok(Self {
898 latin: latin.ok_or_else(|| missing("latin"))?,
899 east_asian: east_asian.ok_or_else(|| missing("ea"))?,
900 complex_script: complex_script.ok_or_else(|| missing("cs"))?,
901 supplemental_fonts,
902 raw_attributes: raw_attributes(start, &[], false)?,
903 raw_children,
904 })
905 }
906
907 fn write(&self, writer: &mut Writer<Vec<u8>>, tag: &str) -> Result<()> {
908 let mut start = BytesStart::new(tag);
909 push_attributes(&mut start, &self.raw_attributes);
910 write_start(writer, start)?;
911 emit_raw(writer, self.raw_children.at(0))?;
912 self.latin.write(writer, "a:latin")?;
913 emit_raw(writer, self.raw_children.at(1))?;
914 self.east_asian.write(writer, "a:ea")?;
915 emit_raw(writer, self.raw_children.at(2))?;
916 self.complex_script.write(writer, "a:cs")?;
917 for (index, font) in self.supplemental_fonts.iter().enumerate() {
918 emit_raw(writer, self.raw_children.at(3 + index))?;
919 font.write(writer)?;
920 }
921 emit_raw(
922 writer,
923 self.raw_children.at(3 + self.supplemental_fonts.len()),
924 )?;
925 write_end(writer, tag)
926 }
927}
928
929fn read_typeface(
930 reader: &mut Reader<&[u8]>,
931 start: &BytesStart<'_>,
932 empty: bool,
933) -> Result<ThemeTypeface> {
934 Ok(ThemeTypeface {
935 typeface: required_attr(start, b"typeface")?,
936 raw_attributes: raw_attributes(start, &[b"typeface"], false)?,
937 raw_children: read_leaf_children(reader, start, empty)?,
938 })
939}
940
941impl ThemeTypeface {
942 fn write(&self, writer: &mut Writer<Vec<u8>>, tag: &str) -> Result<()> {
943 let mut start = BytesStart::new(tag);
944 start.push_attribute(("typeface", self.typeface.as_str()));
945 push_attributes(&mut start, &self.raw_attributes);
946 write_leaf(writer, start, tag, &self.raw_children)
947 }
948}
949
950fn read_supplemental_font(
951 reader: &mut Reader<&[u8]>,
952 start: &BytesStart<'_>,
953 empty: bool,
954) -> Result<SupplementalFont> {
955 Ok(SupplementalFont {
956 script: required_attr(start, b"script")?,
957 typeface: required_attr(start, b"typeface")?,
958 raw_attributes: raw_attributes(start, &[b"script", b"typeface"], false)?,
959 raw_children: read_leaf_children(reader, start, empty)?,
960 })
961}
962
963impl SupplementalFont {
964 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
965 let mut start = BytesStart::new("a:font");
966 start.push_attribute(("script", self.script.as_str()));
967 start.push_attribute(("typeface", self.typeface.as_str()));
968 push_attributes(&mut start, &self.raw_attributes);
969 write_leaf(writer, start, "a:font", &self.raw_children)
970 }
971}
972
973impl CT_StyleMatrix {
974 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
975 let mut fills = None;
976 let mut lines = None;
977 let mut effects = None;
978 let mut background_fills = None;
979 let mut fill_list_raw = StyleListRaw::default();
980 let mut line_list_raw = StyleListRaw::default();
981 let mut effect_list_raw = StyleListRaw::default();
982 let mut background_fill_list_raw = StyleListRaw::default();
983 let mut raw_children = OrderedRawChildren::default();
984 let mut boundary = 0;
985 let mut buf = Vec::new();
986 loop {
987 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
988 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fillStyleLst") => {
989 require_order("fmtScheme", "fillStyleLst", boundary, 0)?;
990 reject_duplicate(&fills, "fillStyleLst")?;
991 let (values, raw) = read_fill_list(reader, &e, b"fillStyleLst")?;
992 fills = Some(values);
993 fill_list_raw = raw;
994 boundary = 1;
995 }
996 Event::Start(e) if matches_local_name(e.name().as_ref(), b"lnStyleLst") => {
997 require_order("fmtScheme", "lnStyleLst", boundary, 1)?;
998 reject_duplicate(&lines, "lnStyleLst")?;
999 let (values, raw) = read_line_list(reader, &e)?;
1000 lines = Some(values);
1001 line_list_raw = raw;
1002 boundary = 2;
1003 }
1004 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectStyleLst") => {
1005 require_order("fmtScheme", "effectStyleLst", boundary, 2)?;
1006 reject_duplicate(&effects, "effectStyleLst")?;
1007 let (values, raw) = read_effect_style_list(reader, &e)?;
1008 effects = Some(values);
1009 effect_list_raw = raw;
1010 boundary = 3;
1011 }
1012 Event::Start(e) if matches_local_name(e.name().as_ref(), b"bgFillStyleLst") => {
1013 require_order("fmtScheme", "bgFillStyleLst", boundary, 3)?;
1014 reject_duplicate(&background_fills, "bgFillStyleLst")?;
1015 let (values, raw) = read_fill_list(reader, &e, b"bgFillStyleLst")?;
1016 background_fills = Some(values);
1017 background_fill_list_raw = raw;
1018 boundary = 4;
1019 }
1020 Event::Empty(e) if is_style_list(e.name().as_ref()) => {
1021 return Err(missing(format!("{} entries", element_name(&e))));
1022 }
1023 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
1024 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
1025 Event::End(e) if matches_local_name(e.name().as_ref(), b"fmtScheme") => break,
1026 Event::Eof => return Err(missing_end("fmtScheme")),
1027 _ => {}
1028 }
1029 buf.clear();
1030 }
1031 Ok(Self {
1032 name: decoded_attr(start, b"name")?,
1033 fill_styles: fills.ok_or_else(|| missing("fillStyleLst"))?,
1034 line_styles: lines.ok_or_else(|| missing("lnStyleLst"))?,
1035 effect_styles: effects.ok_or_else(|| missing("effectStyleLst"))?,
1036 background_fill_styles: background_fills.ok_or_else(|| missing("bgFillStyleLst"))?,
1037 raw_attributes: raw_attributes(start, &[b"name"], false)?,
1038 raw_children,
1039 fill_list_raw,
1040 line_list_raw,
1041 effect_list_raw,
1042 background_fill_list_raw,
1043 })
1044 }
1045
1046 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
1047 let mut start = BytesStart::new("a:fmtScheme");
1048 if let Some(name) = self.name.as_deref() {
1049 start.push_attribute(("name", name));
1050 }
1051 push_attributes(&mut start, &self.raw_attributes);
1052 write_start(writer, start)?;
1053 emit_raw(writer, self.raw_children.at(0))?;
1054 write_fill_list(
1055 writer,
1056 "a:fillStyleLst",
1057 &self.fill_styles,
1058 &self.fill_list_raw,
1059 )?;
1060 emit_raw(writer, self.raw_children.at(1))?;
1061 write_line_list(writer, &self.line_styles, &self.line_list_raw)?;
1062 emit_raw(writer, self.raw_children.at(2))?;
1063 write_effect_style_list(writer, &self.effect_styles, &self.effect_list_raw)?;
1064 emit_raw(writer, self.raw_children.at(3))?;
1065 write_fill_list(
1066 writer,
1067 "a:bgFillStyleLst",
1068 &self.background_fill_styles,
1069 &self.background_fill_list_raw,
1070 )?;
1071 emit_raw(writer, self.raw_children.at(4))?;
1072 write_end(writer, "a:fmtScheme")
1073 }
1074}
1075
1076fn read_fill_list(
1077 reader: &mut Reader<&[u8]>,
1078 start: &BytesStart<'_>,
1079 end: &[u8],
1080) -> Result<(Vec<Fill>, StyleListRaw)> {
1081 let mut values = Vec::new();
1082 let mut raw = StyleListRaw {
1083 attributes: raw_attributes(start, &[], false)?,
1084 ..StyleListRaw::default()
1085 };
1086 let mut buf = Vec::new();
1087 loop {
1088 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1089 Event::Start(e) if is_fill(e.name().as_ref()) => {
1090 let xml = capture_element(reader, &e)?;
1091 values.push(Fill::from_xml(&xml)?);
1092 }
1093 Event::Empty(e) if is_fill(e.name().as_ref()) => {
1094 let xml = capture_empty_element(&e)?;
1095 values.push(Fill::from_xml(&xml)?);
1096 }
1097 Event::Start(e) => raw
1098 .children
1099 .push(values.len(), capture_element(reader, &e)?),
1100 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1101 Event::End(e) if local_name(e.name().as_ref()) == end => break,
1102 Event::Eof => return Err(missing_end(String::from_utf8_lossy(end))),
1103 _ => {}
1104 }
1105 buf.clear();
1106 }
1107 Ok((values, raw))
1108}
1109
1110fn read_line_list(
1111 reader: &mut Reader<&[u8]>,
1112 start: &BytesStart<'_>,
1113) -> Result<(Vec<CT_LineProperties>, StyleListRaw)> {
1114 let mut values = Vec::new();
1115 let mut raw = StyleListRaw {
1116 attributes: raw_attributes(start, &[], false)?,
1117 ..StyleListRaw::default()
1118 };
1119 let mut buf = Vec::new();
1120 loop {
1121 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1122 Event::Start(e) if matches_local_name(e.name().as_ref(), b"ln") => {
1123 let xml = capture_element(reader, &e)?;
1124 values.push(CT_LineProperties::from_xml(&xml)?);
1125 }
1126 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"ln") => {
1127 let xml = capture_empty_element(&e)?;
1128 values.push(CT_LineProperties::from_xml(&xml)?);
1129 }
1130 Event::Start(e) => raw
1131 .children
1132 .push(values.len(), capture_element(reader, &e)?),
1133 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1134 Event::End(e) if matches_local_name(e.name().as_ref(), b"lnStyleLst") => break,
1135 Event::Eof => return Err(missing_end("lnStyleLst")),
1136 _ => {}
1137 }
1138 buf.clear();
1139 }
1140 Ok((values, raw))
1141}
1142
1143fn read_effect_style_list(
1144 reader: &mut Reader<&[u8]>,
1145 start: &BytesStart<'_>,
1146) -> Result<(Vec<CT_EffectStyle>, StyleListRaw)> {
1147 let mut values = Vec::new();
1148 let mut raw = StyleListRaw {
1149 attributes: raw_attributes(start, &[], false)?,
1150 ..StyleListRaw::default()
1151 };
1152 let mut buf = Vec::new();
1153 loop {
1154 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1155 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => {
1156 values.push(CT_EffectStyle::read(reader, &e)?);
1157 }
1158 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => {
1159 values.push(CT_EffectStyle {
1160 effect_list: None,
1161 raw_attributes: raw_attributes(&e, &[], false)?,
1162 raw_children: OrderedRawChildren::default(),
1163 });
1164 }
1165 Event::Start(e) => raw
1166 .children
1167 .push(values.len(), capture_element(reader, &e)?),
1168 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1169 Event::End(e) if matches_local_name(e.name().as_ref(), b"effectStyleLst") => break,
1170 Event::Eof => return Err(missing_end("effectStyleLst")),
1171 _ => {}
1172 }
1173 buf.clear();
1174 }
1175 Ok((values, raw))
1176}
1177
1178impl CT_EffectStyle {
1179 pub fn has_unmodelled_effect(&self) -> bool {
1181 (0..=1).any(|boundary| self.raw_children.at(boundary).any(raw_is_effect_dag))
1182 }
1183
1184 pub fn has_unmodelled_effect_placeholder_color(&self) -> bool {
1186 (0..=1).any(|boundary| {
1187 self.raw_children
1188 .at(boundary)
1189 .any(|xml| raw_is_effect_dag(xml) && raw_contains_placeholder_color(xml))
1190 })
1191 }
1192
1193 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1194 let mut effect_list = None;
1195 let mut raw_children = OrderedRawChildren::default();
1196 let mut boundary = 0;
1197 let mut buf = Vec::new();
1198 loop {
1199 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1200 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectLst") => {
1201 reject_duplicate(&effect_list, "effectLst")?;
1202 effect_list = Some(CT_EffectList::from_xml(&capture_element(reader, &e)?)?);
1203 boundary = 1;
1204 }
1205 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"effectLst") => {
1206 reject_duplicate(&effect_list, "effectLst")?;
1207 effect_list = Some(CT_EffectList::from_xml(&capture_empty_element(&e)?)?);
1208 boundary = 1;
1209 }
1210 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
1211 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
1212 Event::End(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => break,
1213 Event::Eof => return Err(missing_end("effectStyle")),
1214 _ => {}
1215 }
1216 buf.clear();
1217 }
1218 Ok(Self {
1219 effect_list,
1220 raw_attributes: raw_attributes(start, &[], false)?,
1221 raw_children,
1222 })
1223 }
1224
1225 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
1226 let mut start = BytesStart::new("a:effectStyle");
1227 push_attributes(&mut start, &self.raw_attributes);
1228 write_start(writer, start)?;
1229 emit_raw(writer, self.raw_children.at(0))?;
1230 if let Some(effects) = &self.effect_list {
1231 effects.write_xml(writer)?;
1232 }
1233 emit_raw(writer, self.raw_children.at(1))?;
1234 write_end(writer, "a:effectStyle")
1235 }
1236}
1237
1238fn write_fill_list(
1239 writer: &mut Writer<Vec<u8>>,
1240 tag: &str,
1241 values: &[Fill],
1242 raw: &StyleListRaw,
1243) -> Result<()> {
1244 let mut start = BytesStart::new(tag);
1245 push_attributes(&mut start, &raw.attributes);
1246 write_start(writer, start)?;
1247 for (index, value) in values.iter().enumerate() {
1248 emit_raw(writer, raw.children.at(index))?;
1249 value.write_xml(writer)?;
1250 }
1251 emit_raw(writer, raw.children.at(values.len()))?;
1252 write_end(writer, tag)
1253}
1254
1255fn write_line_list(
1256 writer: &mut Writer<Vec<u8>>,
1257 values: &[CT_LineProperties],
1258 raw: &StyleListRaw,
1259) -> Result<()> {
1260 let mut start = BytesStart::new("a:lnStyleLst");
1261 push_attributes(&mut start, &raw.attributes);
1262 write_start(writer, start)?;
1263 for (index, value) in values.iter().enumerate() {
1264 emit_raw(writer, raw.children.at(index))?;
1265 value.write_xml(writer)?;
1266 }
1267 emit_raw(writer, raw.children.at(values.len()))?;
1268 write_end(writer, "a:lnStyleLst")
1269}
1270
1271fn write_effect_style_list(
1272 writer: &mut Writer<Vec<u8>>,
1273 values: &[CT_EffectStyle],
1274 raw: &StyleListRaw,
1275) -> Result<()> {
1276 let mut start = BytesStart::new("a:effectStyleLst");
1277 push_attributes(&mut start, &raw.attributes);
1278 write_start(writer, start)?;
1279 for (index, value) in values.iter().enumerate() {
1280 emit_raw(writer, raw.children.at(index))?;
1281 value.write(writer)?;
1282 }
1283 emit_raw(writer, raw.children.at(values.len()))?;
1284 write_end(writer, "a:effectStyleLst")
1285}
1286
1287fn read_leaf_children(
1288 reader: &mut Reader<&[u8]>,
1289 start: &BytesStart<'_>,
1290 empty: bool,
1291) -> Result<OrderedRawChildren> {
1292 if empty {
1293 return Ok(OrderedRawChildren::default());
1294 }
1295 let end = local_name(start.name().as_ref()).to_vec();
1296 let mut raw = OrderedRawChildren::default();
1297 let mut buf = Vec::new();
1298 loop {
1299 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1300 Event::Start(e) => raw.push(0, capture_element(reader, &e)?),
1301 Event::Empty(e) => raw.push(0, capture_empty_element(&e)?),
1302 Event::End(e) if local_name(e.name().as_ref()) == end => break,
1303 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
1304 _ => {}
1305 }
1306 buf.clear();
1307 }
1308 Ok(raw)
1309}
1310
1311fn write_leaf(
1312 writer: &mut Writer<Vec<u8>>,
1313 start: BytesStart<'_>,
1314 tag: &str,
1315 raw: &OrderedRawChildren,
1316) -> Result<()> {
1317 if raw.is_empty() {
1318 writer
1319 .write_event(Event::Empty(start))
1320 .map_err(OxmlError::from)?;
1321 return Ok(());
1322 }
1323 write_start(writer, start)?;
1324 emit_raw(writer, raw.at(0))?;
1325 write_end(writer, tag)
1326}
1327
1328fn raw_attributes(
1329 start: &BytesStart<'_>,
1330 modelled: &[&[u8]],
1331 is_root: bool,
1332) -> Result<RawAttributes> {
1333 let mut raw = Vec::new();
1334 for attribute in start.attributes() {
1335 let attribute = attribute.map_err(OxmlError::from)?;
1336 let key = attribute.key.as_ref();
1337 if modelled.contains(&key) {
1338 continue;
1339 }
1340 if is_root && key == b"xmlns:a" {
1341 continue;
1342 }
1343 let name = std::str::from_utf8(key)
1344 .map_err(OxmlError::from)?
1345 .to_owned();
1346 let value = attribute
1347 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
1348 .map_err(OxmlError::from)?
1349 .into_owned();
1350 raw.push((name, value));
1351 }
1352 Ok(raw)
1353}
1354
1355fn required_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<String> {
1356 decoded_attr(start, name)?.ok_or_else(|| ThemeError::MissingAttribute {
1357 element: element_name(start),
1358 attribute: String::from_utf8_lossy(name).into_owned(),
1359 })
1360}
1361
1362fn decoded_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>> {
1363 for attribute in start.attributes() {
1364 let attribute = attribute.map_err(OxmlError::from)?;
1365 if attribute.key.as_ref() == name {
1366 return Ok(Some(
1367 attribute
1368 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
1369 .map_err(OxmlError::from)?
1370 .into_owned(),
1371 ));
1372 }
1373 }
1374 Ok(None)
1375}
1376
1377fn push_attributes(start: &mut BytesStart<'_>, attributes: &RawAttributes) {
1378 for (name, value) in attributes {
1379 start.push_attribute((name.as_str(), value.as_str()));
1380 }
1381}
1382
1383fn reject_duplicate<T>(value: &Option<T>, name: impl Into<String>) -> Result<()> {
1384 if value.is_some() {
1385 Err(duplicate(name))
1386 } else {
1387 Ok(())
1388 }
1389}
1390
1391fn require_order(parent: &str, child: &str, boundary: usize, index: usize) -> Result<()> {
1392 if boundary > index {
1393 Err(out_of_order(parent, child))
1394 } else {
1395 Ok(())
1396 }
1397}
1398
1399fn color_slot_index(name: &[u8]) -> Option<usize> {
1400 match local_name(name) {
1401 b"dk1" => Some(0),
1402 b"lt1" => Some(1),
1403 b"dk2" => Some(2),
1404 b"lt2" => Some(3),
1405 b"accent1" => Some(4),
1406 b"accent2" => Some(5),
1407 b"accent3" => Some(6),
1408 b"accent4" => Some(7),
1409 b"accent5" => Some(8),
1410 b"accent6" => Some(9),
1411 b"hlink" => Some(10),
1412 b"folHlink" => Some(11),
1413 _ => None,
1414 }
1415}
1416
1417fn is_color(name: &[u8]) -> bool {
1418 matches!(
1419 local_name(name),
1420 b"srgbClr" | b"schemeClr" | b"sysClr" | b"prstClr"
1421 )
1422}
1423
1424fn is_fill(name: &[u8]) -> bool {
1425 matches!(
1426 local_name(name),
1427 b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
1428 )
1429}
1430
1431fn is_theme_elements_child(name: &[u8]) -> bool {
1432 matches!(
1433 local_name(name),
1434 b"clrScheme" | b"fontScheme" | b"fmtScheme"
1435 )
1436}
1437
1438fn is_style_list(name: &[u8]) -> bool {
1439 matches!(
1440 local_name(name),
1441 b"fillStyleLst" | b"lnStyleLst" | b"effectStyleLst" | b"bgFillStyleLst"
1442 )
1443}
1444
1445fn write_start(writer: &mut Writer<Vec<u8>>, start: BytesStart<'_>) -> Result<()> {
1446 writer
1447 .write_event(Event::Start(start))
1448 .map_err(OxmlError::from)?;
1449 Ok(())
1450}
1451
1452fn write_end(writer: &mut Writer<Vec<u8>>, name: &str) -> Result<()> {
1453 writer
1454 .write_event(Event::End(BytesEnd::new(name)))
1455 .map_err(OxmlError::from)?;
1456 Ok(())
1457}
1458
1459fn emit_raw<'a>(
1460 writer: &mut Writer<Vec<u8>>,
1461 values: impl Iterator<Item = &'a [u8]>,
1462) -> Result<()> {
1463 for value in values {
1464 writer.get_mut().extend_from_slice(value);
1465 }
1466 Ok(())
1467}
1468
1469fn element_name(start: &BytesStart<'_>) -> String {
1470 String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned()
1471}
1472
1473fn unexpected(start: &BytesStart<'_>) -> ThemeError {
1474 ThemeError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
1475}
1476
1477fn missing(name: impl Into<String>) -> ThemeError {
1478 ThemeError::MissingElement(name.into())
1479}
1480
1481fn missing_end(name: impl fmt::Display) -> ThemeError {
1482 ThemeError::Xml(OxmlError::MissingElement(format!(
1483 "closing DrawingML {name}"
1484 )))
1485}
1486
1487fn duplicate(name: impl Into<String>) -> ThemeError {
1488 ThemeError::DuplicateElement(name.into())
1489}
1490
1491fn out_of_order(parent: impl Into<String>, child: impl Into<String>) -> ThemeError {
1492 ThemeError::ChildOutOfOrder {
1493 parent: parent.into(),
1494 child: child.into(),
1495 }
1496}
1497
1498pub const OFFICE_DEFAULT_XML: &str = r###"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="0E2841"/></a:dk2><a:lt2><a:srgbClr val="E8E8E8"/></a:lt2><a:accent1><a:srgbClr val="156082"/></a:accent1><a:accent2><a:srgbClr val="E97132"/></a:accent2><a:accent3><a:srgbClr val="196B24"/></a:accent3><a:accent4><a:srgbClr val="0F9ED5"/></a:accent4><a:accent5><a:srgbClr val="A02B93"/></a:accent5><a:accent6><a:srgbClr val="4EA72E"/></a:accent6><a:hlink><a:srgbClr val="467886"/></a:hlink><a:folHlink><a:srgbClr val="96607D"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Aptos Display" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont><a:latin typeface="Aptos" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults><a:lnDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style></a:lnDef></a:objectDefaults><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{2E142A2C-CD16-42D6-873A-C26D2A0506FA}" vid="{1BDDFF52-6CD6-40A5-AB3C-68EB2F1E4D0A}"/></a:ext></a:extLst></a:theme>"###;