use log::warn;
use quick_xml::Reader;
use quick_xml::events::Event;
use crate::config::RuntimeConfig;
use crate::parsers::ParseResult;
use crate::results::PptxMetadata;
use crate::utils::zip_read::read_zip_entry_to_string_limited;
use super::constants::{OFFICE_CORE_XML, PPTX_CORE_PROPERTIES};
use super::utils::{has_namespace, open_office_archive};
pub fn extract_pptx_metadata(
content: &[u8],
stats: &ParseResult,
config: &RuntimeConfig,
) -> PptxMetadata {
let mut metadata = PptxMetadata {
file_size: Some(stats.byte_count),
..Default::default()
};
let Ok(mut archive) = open_office_archive(content, &stats.file_path) else {
return metadata;
};
if let Ok(f) = archive.by_name(OFFICE_CORE_XML)
&& let Ok(xml) = read_zip_entry_to_string_limited(
f,
config.max_zip_entry_uncompressed_bytes,
OFFICE_CORE_XML,
&stats.file_path,
)
{
extract_core_properties(&xml, &mut metadata);
}
if let Ok(f) = archive.by_name("ppt/presentation.xml")
&& let Ok(xml) = read_zip_entry_to_string_limited(
f,
config.max_zip_entry_uncompressed_bytes,
"ppt/presentation.xml",
&stats.file_path,
)
{
metadata.slide_count = Some(count_sld_id_elements(&xml));
}
metadata
}
fn extract_core_properties(xml: &str, m: &mut PptxMetadata) {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut text = String::new();
let mut in_element = false;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref _e)) => {
in_element = true;
text.clear();
}
Ok(Event::Text(e)) => {
if in_element && let Ok(s) = std::str::from_utf8(e.as_ref()) {
text.push_str(s);
}
}
Ok(Event::End(ref e)) => {
let name = e.name().as_ref().to_vec();
let v = text.trim();
for prop in PPTX_CORE_PROPERTIES {
if name.ends_with(prop.element) && has_namespace(&name, prop.namespace) {
if !v.is_empty() {
(prop.setter)(m, v.to_string());
}
break;
}
}
in_element = false;
text.clear();
}
Ok(Event::Eof) => break,
Err(e) => {
warn!("PPTX core.xml parse error: {e}");
break;
}
_ => {}
}
buf.clear();
}
}
fn count_sld_id_elements(xml: &str) -> usize {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut count = 0usize;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e) | Event::Empty(ref e)) => {
if e.name().as_ref().ends_with(b"sldId") {
count += 1;
}
}
Ok(Event::Eof) | Err(_) => break,
_ => {}
}
buf.clear();
}
count
}
crate::no_template_mining!(extract_pptx_templates, "PPTX: slides; no template mining.");