use log::warn;
use quick_xml::escape::unescape;
use std::io::{Cursor, Read};
use zip::ZipArchive;
pub(crate) fn has_namespace(name: &[u8], namespace: &[u8]) -> bool {
name.windows(namespace.len()).any(|w| w == namespace)
}
pub(crate) fn open_office_archive<'a>(
content: &'a [u8],
file_path: &str,
) -> Result<ZipArchive<Cursor<&'a [u8]>>, zip::result::ZipError> {
let cursor = Cursor::new(content);
ZipArchive::new(cursor).map_err(|e| {
warn!("Failed to open Office file as ZIP archive {file_path}: {e:?}");
e
})
}
const XML_ENTITIES: &[(&str, &str)] = &[
("&", "&"),
("<", "<"),
(">", ">"),
(""", "\""),
("'", "'"),
];
pub(crate) fn decode_xml_entities_fallback(text: &str) -> String {
let mut result = text.to_string();
for (entity, replacement) in XML_ENTITIES {
result = result.replace(entity, replacement);
}
result
}
pub(crate) fn decode_xml_entities(text: &str) -> String {
match unescape(text) {
Ok(decoded) => decoded.into_owned(),
Err(_) => decode_xml_entities_fallback(text),
}
}
pub(crate) fn read_xml_from_archive<F>(
archive: &mut ZipArchive<Cursor<&[u8]>>,
file_path: &str,
file_type: &str,
stats_file_path: &str,
mut extractor: F,
) where
F: FnMut(&str),
{
match archive.by_name(file_path) {
Ok(mut file) => {
let mut xml_content = String::new();
if file.read_to_string(&mut xml_content).is_ok() {
extractor(&xml_content);
} else {
warn!("Failed to read {file_path} from {file_type} {stats_file_path}");
}
}
Err(e) => {
warn!("{file_path} not found in {file_type} {stats_file_path}: {e:?}");
}
}
}