use std::{
io,
path::{
Component,
Path,
PathBuf,
},
string::FromUtf8Error,
};
use quick_xml::events::attributes::Attribute;
#[macro_export]
macro_rules! xml_read_loop {
($reader:ident $(,$pat:pat => $result:expr)+ $(,)?) => {
let mut buf = Vec::new();
loop {
let ev = match $reader.read_event_into(&mut buf) {
Ok(v) => v,
Err(e) => panic!("Error at position {}: {e:?}", $reader.buffer_position()),
};
match ev {
$($pat => $result,)+
_ => (),
}
buf.clear();
}
};
}
pub(crate) use crate::xml_read_loop;
#[macro_export]
macro_rules! set_string_from_xml {
($self:ident, $e:ident, $attr:ident, $xml_attr:expr) => {{
if let Some(v) = get_attribute($e, $xml_attr.as_bytes()) {
$self.$attr.set_value_string(v);
}
}};
}
pub(crate) use crate::set_string_from_xml;
pub(crate) fn normalize_path(path: &str) -> PathBuf {
let path = Path::new(path);
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
#[inline]
pub(crate) fn join_paths(base_path: &str, target: &str) -> String {
match target.split_once('/') {
Some(("", target)) => normalize_path_to_str(target),
_ => normalize_path_to_str(&format!("{base_path}/{target}")),
}
}
#[inline]
pub(crate) fn normalize_path_to_str(path: &str) -> String {
let ret = normalize_path(path);
ret.to_str().unwrap_or("").replace('\\', "/")
}
pub(crate) fn zip_by_name<'a, R: io::Read + io::Seek>(
arv: &'a mut zip::ZipArchive<R>,
name: &str,
) -> Result<zip::read::ZipFile<'a, R>, zip::result::ZipError> {
if arv.by_name(name).is_ok() {
return arv.by_name(name);
}
let backslash_name = name.replace('/', "\\");
if backslash_name != name && arv.by_name(&backslash_name).is_ok() {
return arv.by_name(&backslash_name);
}
let normalized = name.replace('\\', "/").to_ascii_lowercase();
for i in 0..arv.len() {
let entry_name = match arv.by_index(i) {
Ok(f) => f.name().to_owned(),
Err(_) => continue,
};
let entry_normalized = entry_name.replace('\\', "/").to_ascii_lowercase();
if entry_normalized == normalized {
return arv.by_name(&entry_name);
}
}
Err(zip::result::ZipError::FileNotFound)
}
#[inline]
pub(crate) fn get_attribute(e: &quick_xml::events::BytesStart<'_>, key: &[u8]) -> Option<String> {
let result = e
.attributes()
.with_checks(false)
.find_map(|attr| match attr {
Ok(ref attr) if attr.key.into_inner() == key => {
Some(get_attribute_value(attr).unwrap())
}
_ => None,
});
if result.is_some() {
return result;
}
let local_name = {
let pos = key.iter().position(|&b| b == b':')?;
&key[pos + 1..]
};
e.attributes()
.with_checks(false)
.find_map(|attr| match attr {
Ok(ref attr) => {
let attr_key = attr.key.into_inner();
let attr_local = match attr_key.iter().position(|&b| b == b':') {
Some(pos) => &attr_key[pos + 1..],
None => attr_key,
};
if attr_local == local_name {
Some(get_attribute_value(attr).unwrap())
} else {
None
}
}
_ => None,
})
}
#[inline]
pub(crate) fn get_attribute_value(attr: &Attribute) -> Result<String, FromUtf8Error> {
String::from_utf8(attr.value.to_vec())
}