use quick_xml::{
Reader, XmlVersion,
escape::resolve_predefined_entity,
events::{BytesStart, Event},
};
use std::io::{Cursor, Read};
use zip::ZipArchive;
const MAX_ENTRY_BYTES: u64 = 64 * 1024 * 1024;
pub(super) fn attr(e: &BytesStart<'_>, want: &str) -> Option<String> {
e.attributes()
.flatten()
.find(|a| a.key.local_name().as_ref() == want)
.and_then(|a| {
a.normalized_value(XmlVersion::Implicit1_0)
.ok()
.map(|v| v.into_owned())
})
}
pub(super) fn open(bytes: &[u8]) -> Result<ZipArchive<Cursor<&[u8]>>, String> {
ZipArchive::new(Cursor::new(bytes)).map_err(|e| format!("not a valid zip container: {e}"))
}
pub(super) fn read_entry(
zip: &mut ZipArchive<Cursor<&[u8]>>,
name: &str,
) -> Result<Option<Vec<u8>>, String> {
let file = match zip.by_name(name) {
Ok(f) => f,
Err(zip::result::ZipError::FileNotFound) => return Ok(None),
Err(e) => return Err(format!("cannot read {name}: {e}")),
};
if file.is_dir() {
return Ok(None);
}
if file.size() > MAX_ENTRY_BYTES {
return Err(format!(
"{name} is too large to parse ({} bytes; limit {MAX_ENTRY_BYTES})",
file.size()
));
}
let mut buf = Vec::with_capacity(file.size().min(1 << 20) as usize);
file.take(MAX_ENTRY_BYTES)
.read_to_end(&mut buf)
.map_err(|e| format!("cannot decompress {name}: {e}"))?;
Ok(Some(buf))
}
pub(super) fn read_xml(
zip: &mut ZipArchive<Cursor<&[u8]>>,
name: &str,
) -> Result<Option<String>, String> {
Ok(read_entry(zip, name)?.map(|bytes| String::from_utf8_lossy(&bytes).into_owned()))
}
pub(super) fn push_text(event: &Event<'_>, out: &mut String) {
match event {
Event::Text(t) => out.push_str(t),
Event::CData(c) => out.push_str(c),
Event::GeneralRef(r) => {
if let Ok(Some(ch)) = r.resolve_char_ref() {
out.push(ch);
} else if let Some(repl) = resolve_predefined_entity(r.as_ref()) {
out.push_str(repl);
}
}
_ => {}
}
}
pub(super) fn walk<F>(xml: &str, mut f: F) -> Result<(), String>
where
F: FnMut(&Event<'_>) -> Result<(), String>,
{
let mut reader = Reader::from_str(xml);
loop {
let event = reader
.read_event()
.map_err(|e| format!("malformed XML: {e}"))?;
if matches!(event, Event::Eof) {
return Ok(());
}
f(&event)?;
}
}