mod export;
mod fonts;
mod layouts;
mod media;
mod notes;
mod print;
mod signature;
mod slide_props;
mod slides;
mod vba;
#[cfg(test)]
mod tests;
use std::io::{Read, Write};
use std::path::Path;
use crate::core_properties::CoreProperties;
use crate::error::{PptxError, PptxResult};
use crate::opc::constants::{content_type as CT, relationship_type as RT};
use crate::opc::pack_uri::PackURI;
use crate::opc::package::OpcPackage;
use crate::opc::part::Part;
#[must_use]
pub struct Presentation {
package: OpcPackage,
}
impl Presentation {
pub fn new() -> PptxResult<Self> {
Ok(Self {
package: OpcPackage::new()?,
})
}
pub fn open(path: impl AsRef<Path>) -> PptxResult<Self> {
Ok(Self {
package: OpcPackage::open(path)?,
})
}
pub fn from_bytes(data: &[u8]) -> PptxResult<Self> {
Ok(Self {
package: OpcPackage::from_bytes(data)?,
})
}
pub fn from_reader(mut reader: impl Read) -> PptxResult<Self> {
const MAX_INPUT_SIZE: u64 = 500 * 1024 * 1024; let mut buf = Vec::new();
reader.by_ref().take(MAX_INPUT_SIZE).read_to_end(&mut buf)?;
let mut extra = [0u8; 1];
if reader.read(&mut extra)? > 0 {
return Err(PptxError::ResourceLimit {
message: format!("input exceeds maximum size of {} bytes", MAX_INPUT_SIZE),
});
}
Self::from_bytes(&buf)
}
pub fn save(&self, path: impl AsRef<Path>) -> PptxResult<()> {
self.package.save(path)
}
pub fn to_bytes(&self) -> PptxResult<Vec<u8>> {
self.package.to_bytes()
}
pub fn write_to(&self, mut writer: impl Write) -> PptxResult<()> {
let bytes = self.to_bytes()?;
writer.write_all(&bytes)?;
Ok(())
}
}
impl Presentation {
pub fn core_properties(&self) -> PptxResult<CoreProperties> {
let core_rel = self.package.pkg_rels.by_reltype(RT::CORE_PROPERTIES);
core_rel.map_or_else(
|_| Ok(CoreProperties::new()),
|rel| {
let partname = rel.target_partname(self.package.pkg_rels.base_uri())?;
self.package.part(&partname).map_or_else(
|| Ok(CoreProperties::new()),
|part| CoreProperties::from_xml(&part.blob),
)
},
)
}
pub fn set_core_properties(&mut self, props: &CoreProperties) -> PptxResult<()> {
let partname = PackURI::new("/docProps/core.xml")?;
let xml = props.to_xml()?;
let part = Part::new(partname, CT::OPC_CORE_PROPERTIES, xml);
self.package.put_part(part);
self.package
.pkg_rels
.or_add(RT::CORE_PROPERTIES, "docProps/core.xml", false);
Ok(())
}
#[must_use]
pub const fn package(&self) -> &OpcPackage {
&self.package
}
pub fn package_mut(&mut self) -> &mut OpcPackage {
&mut self.package
}
pub(crate) fn presentation_part(&self) -> PptxResult<&Part> {
self.package.part_by_reltype(RT::OFFICE_DOCUMENT)
}
pub(crate) fn presentation_partname(&self) -> PptxResult<PackURI> {
let rel = self.package.pkg_rels.by_reltype(RT::OFFICE_DOCUMENT)?;
rel.target_partname(self.package.pkg_rels.base_uri())
}
}
pub(super) fn remove_xml_element(xml: &str, element_name: &str) -> String {
let open_tag = format!("<{element_name}");
let close_tag = format!("</{element_name}>");
if let Some(start) = xml.find(&open_tag) {
if let Some(end) = xml[start..].find(&close_tag) {
let end_pos = start + end + close_tag.len();
let mut result = String::with_capacity(xml.len() - (end_pos - start));
result.push_str(&xml[..start]);
result.push_str(&xml[end_pos..]);
return result;
}
if let Some(end) = xml[start..].find("/>") {
let end_pos = start + end + 2;
let mut result = String::with_capacity(xml.len() - (end_pos - start));
result.push_str(&xml[..start]);
result.push_str(&xml[end_pos..]);
return result;
}
}
xml.to_string()
}