use quick_xml::events::Event;
use quick_xml::Reader;
use crate::docx::error::Result;
use crate::docx::model::{ScriptTag, Theme, ThemeColorScheme, ThemeFontScheme, ThemeScriptFont};
use crate::docx::xml;
pub fn parse_theme(data: &[u8]) -> Result<Theme> {
let mut reader = Reader::from_reader(data);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut theme = Theme::default();
loop {
match xml::next_event(&mut reader, &mut buf)? {
Event::Start(ref e) if xml::local_name(e.name().as_ref()) == b"theme" => break,
Event::Eof => return Ok(theme),
_ => {}
}
}
loop {
match xml::next_event(&mut reader, &mut buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"themeElements" => {
parse_theme_elements(&mut reader, &mut buf, &mut theme)?;
}
b"objectDefaults" | b"extraClrSchemeLst" | b"extLst" => {
xml::skip_to_end(&mut reader, &mut buf, local)?;
}
_ => {
xml::warn_unsupported_element("theme", local);
xml::skip_to_end(&mut reader, &mut buf, local)?;
}
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"objectDefaults" | b"extraClrSchemeLst" => {}
_ => xml::warn_unsupported_element("theme", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"theme" => break,
Event::Eof => return Err(xml::unexpected_eof(b"theme")),
_ => {}
}
}
Ok(theme)
}
fn parse_theme_elements(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
theme: &mut Theme,
) -> Result<()> {
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"clrScheme" => {
theme.color_scheme = parse_color_scheme(reader, buf)?;
}
b"fontScheme" => {
parse_font_scheme(reader, buf, theme)?;
}
b"fmtScheme" => {
xml::skip_to_end(reader, buf, b"fmtScheme")?;
}
_ => {
xml::warn_unsupported_element("themeElements", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"themeElements" => break,
Event::Eof => return Err(xml::unexpected_eof(b"themeElements")),
_ => {}
}
}
Ok(())
}
fn parse_color_scheme(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<ThemeColorScheme> {
let mut scheme = ThemeColorScheme::default();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"dk1" | b"lt1" | b"dk2" | b"lt2" | b"accent1" | b"accent2" | b"accent3"
| b"accent4" | b"accent5" | b"accent6" | b"hlink" | b"folHlink" => {
if let Some(rgb) = parse_theme_color(reader, buf, local)? {
set_theme_color(&mut scheme, local, rgb);
}
}
_ => {
xml::warn_unsupported_element("clrScheme", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"clrScheme" => break,
Event::Eof => return Err(xml::unexpected_eof(b"clrScheme")),
_ => {}
}
}
Ok(scheme)
}
fn parse_theme_color(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
end_tag: &[u8],
) -> Result<Option<u32>> {
let mut rgb = None;
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"srgbClr" => {
if let Some(val) = xml::optional_attr(e, b"val")? {
rgb = xml::parse_hex_color(&val);
}
xml::skip_to_end(reader, buf, b"srgbClr")?;
}
b"sysClr" => {
if let Some(val) = xml::optional_attr(e, b"lastClr")? {
rgb = xml::parse_hex_color(&val);
}
xml::skip_to_end(reader, buf, b"sysClr")?;
}
_ => {
xml::warn_unsupported_element("themeColor", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"srgbClr" => {
if let Some(val) = xml::optional_attr(e, b"val")? {
rgb = xml::parse_hex_color(&val);
}
}
b"sysClr" => {
if let Some(val) = xml::optional_attr(e, b"lastClr")? {
rgb = xml::parse_hex_color(&val);
}
}
_ => xml::warn_unsupported_element("themeColor", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == end_tag => break,
Event::Eof => return Err(xml::unexpected_eof(end_tag)),
_ => {}
}
}
Ok(rgb)
}
fn set_theme_color(scheme: &mut ThemeColorScheme, name: &[u8], rgb: u32) {
match name {
b"dk1" => scheme.dark1 = rgb,
b"lt1" => scheme.light1 = rgb,
b"dk2" => scheme.dark2 = rgb,
b"lt2" => scheme.light2 = rgb,
b"accent1" => scheme.accent1 = rgb,
b"accent2" => scheme.accent2 = rgb,
b"accent3" => scheme.accent3 = rgb,
b"accent4" => scheme.accent4 = rgb,
b"accent5" => scheme.accent5 = rgb,
b"accent6" => scheme.accent6 = rgb,
b"hlink" => scheme.hyperlink = rgb,
b"folHlink" => scheme.followed_hyperlink = rgb,
_ => {}
}
}
fn parse_font_scheme(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
theme: &mut Theme,
) -> Result<()> {
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"majorFont" => {
theme.major_font = parse_font_collection(reader, buf, b"majorFont")?;
}
b"minorFont" => {
theme.minor_font = parse_font_collection(reader, buf, b"minorFont")?;
}
_ => {
xml::warn_unsupported_element("fontScheme", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"fontScheme" => break,
Event::Eof => return Err(xml::unexpected_eof(b"fontScheme")),
_ => {}
}
}
Ok(())
}
fn parse_font_collection(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
end_tag: &[u8],
) -> Result<ThemeFontScheme> {
let mut scheme = ThemeFontScheme::default();
loop {
match xml::next_event(reader, buf)? {
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"latin" => {
scheme.latin = xml::optional_attr(e, b"typeface")?.ok_or_else(|| {
crate::docx::error::ParseError::MissingAttribute {
element: "a:latin".into(),
attr: "typeface".into(),
}
})?;
}
b"ea" => {
scheme.east_asian =
xml::optional_attr(e, b"typeface")?.ok_or_else(|| {
crate::docx::error::ParseError::MissingAttribute {
element: "a:ea".into(),
attr: "typeface".into(),
}
})?;
}
b"cs" => {
scheme.complex_script =
xml::optional_attr(e, b"typeface")?.ok_or_else(|| {
crate::docx::error::ParseError::MissingAttribute {
element: "a:cs".into(),
attr: "typeface".into(),
}
})?;
}
b"font" => {
let script_str = xml::optional_attr(e, b"script")?.ok_or_else(|| {
crate::docx::error::ParseError::MissingAttribute {
element: "a:font".into(),
attr: "script".into(),
}
})?;
let script = parse_script_tag(&script_str);
let typeface = xml::optional_attr(e, b"typeface")?.ok_or_else(|| {
crate::docx::error::ParseError::MissingAttribute {
element: "a:font".into(),
attr: "typeface".into(),
}
})?;
scheme
.script_fonts
.push(ThemeScriptFont { script, typeface });
}
_ => xml::warn_unsupported_element("fontCollection", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == end_tag => break,
Event::Eof => return Err(xml::unexpected_eof(end_tag)),
_ => {}
}
}
Ok(scheme)
}
fn parse_script_tag(s: &str) -> ScriptTag {
match s {
"Arab" => ScriptTag::Arab,
"Armn" => ScriptTag::Armn,
"Beng" => ScriptTag::Beng,
"Bopo" => ScriptTag::Bopo,
"Bugi" => ScriptTag::Bugi,
"Cans" => ScriptTag::Cans,
"Cher" => ScriptTag::Cher,
"Deva" => ScriptTag::Deva,
"Ethi" => ScriptTag::Ethi,
"Geor" => ScriptTag::Geor,
"Gujr" => ScriptTag::Gujr,
"Guru" => ScriptTag::Guru,
"Hang" => ScriptTag::Hang,
"Hans" => ScriptTag::Hans,
"Hant" => ScriptTag::Hant,
"Hebr" => ScriptTag::Hebr,
"Java" => ScriptTag::Java,
"Jpan" => ScriptTag::Jpan,
"Khmr" => ScriptTag::Khmr,
"Knda" => ScriptTag::Knda,
"Laoo" => ScriptTag::Laoo,
"Lisu" => ScriptTag::Lisu,
"Mlym" => ScriptTag::Mlym,
"Mong" => ScriptTag::Mong,
"Mymr" => ScriptTag::Mymr,
"Nkoo" => ScriptTag::Nkoo,
"Olck" => ScriptTag::Olck,
"Orya" => ScriptTag::Orya,
"Osma" => ScriptTag::Osma,
"Phag" => ScriptTag::Phag,
"Sinh" => ScriptTag::Sinh,
"Sora" => ScriptTag::Sora,
"Syre" => ScriptTag::Syre,
"Syrj" => ScriptTag::Syrj,
"Syrn" => ScriptTag::Syrn,
"Syrc" => ScriptTag::Syrc,
"Tale" => ScriptTag::Tale,
"Talu" => ScriptTag::Talu,
"Taml" => ScriptTag::Taml,
"Telu" => ScriptTag::Telu,
"Tfng" => ScriptTag::Tfng,
"Thaa" => ScriptTag::Thaa,
"Thai" => ScriptTag::Thai,
"Tibt" => ScriptTag::Tibt,
"Uigh" => ScriptTag::Uigh,
"Viet" => ScriptTag::Viet,
"Yiii" => ScriptTag::Yiii,
other => {
log::warn!("theme: unrecognized script tag {:?}", other);
let bytes = other.as_bytes();
let mut packed = 0u32;
for (i, &b) in bytes.iter().take(4).enumerate() {
packed |= (b as u32) << (i * 8);
}
ScriptTag::Other(packed)
}
}
}