use std::borrow::Cow;
use std::fmt;
use super::content_types::ContentTypes;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PartName(String);
impl PartName {
pub fn from_unchecked(s: impl Into<String>) -> Self {
let s = s.into();
PartName(if s.starts_with('/') {
s
} else {
format!("/{}", s)
})
}
pub fn new(s: &str) -> Result<Self, PartNameError> {
if !s.starts_with('/') {
return Err(PartNameError::NotAbsolute);
}
for seg in s.split('/').skip(1) {
if seg.is_empty() {
return Err(PartNameError::EmptySegment);
}
if seg == "." || seg == ".." {
return Err(PartNameError::RelSegment);
}
}
Ok(PartName(s.to_string()))
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn into_string(self) -> String {
self.0
}
#[inline]
pub fn to_zip_path(&self) -> &str {
&self.0[1..]
}
pub fn ext(&self) -> String {
match self.0.rfind('.') {
Some(i) => self.0[i..].to_ascii_lowercase(),
None => String::new(),
}
}
pub fn sibling(&self, filename: &str) -> PartName {
let p = self.0.rfind('/').unwrap_or(0);
PartName(format!("{}/{}", &self.0[..p], filename))
}
pub fn parent(&self) -> Option<PartName> {
let p = self.0.rfind('/')?;
if p == 0 {
return None;
}
Some(PartName(self.0[..p].to_string()))
}
}
impl fmt::Debug for PartName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PartName({})", self.0)
}
}
impl fmt::Display for PartName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::str::FromStr for PartName {
type Err = PartNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
PartName::new(s)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PartNameError {
#[error("part name must start with '/'")]
NotAbsolute,
#[error("part name contains empty segment")]
EmptySegment,
#[error("part name contains relative segment '.' or '..'")]
RelSegment,
}
pub fn new_part_name(s: &str) -> PartName {
PartName::from_unchecked(s)
}
#[derive(Debug, Clone)]
pub struct Part {
pub partname: PartName,
pub content_type: String,
pub blob: Vec<u8>,
}
impl Part {
pub fn new<N, C, B>(partname: N, content_type: C, blob: B) -> Self
where
N: Into<PartName>,
C: Into<String>,
B: Into<Vec<u8>>,
{
Part {
partname: partname.into(),
content_type: content_type.into(),
blob: blob.into(),
}
}
pub fn blob_text(&self) -> Option<Cow<'_, str>> {
match std::str::from_utf8(&self.blob) {
Ok(s) => Some(Cow::Borrowed(s)),
Err(_) => None,
}
}
pub fn len(&self) -> usize {
self.blob.len()
}
pub fn is_empty(&self) -> bool {
self.blob.is_empty()
}
pub fn contribute_to(&self, ct: &mut ContentTypes) {
let ext = self.partname.ext();
if ext == ".rels" {
return;
}
ct.add_override(self.partname.as_str(), &self.content_type);
}
}
impl From<Part> for std::path::PathBuf {
fn from(p: Part) -> std::path::PathBuf {
std::path::PathBuf::from(p.partname.to_zip_path())
}
}