use super::{Paragraph, StyleRegistry, Table};
use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize)]
pub struct Document {
pub metadata: Metadata,
pub sections: Vec<Section>,
pub styles: StyleRegistry,
pub resources: HashMap<String, Resource>,
}
impl Document {
pub fn new() -> Self {
Self::default()
}
pub fn paragraph_count(&self) -> usize {
self.sections
.iter()
.map(|s| {
s.content
.iter()
.filter(|b| matches!(b, Block::Paragraph(_)))
.count()
})
.sum()
}
pub fn paragraphs(&self) -> impl Iterator<Item = &Paragraph> {
self.sections.iter().flat_map(|s| {
s.content.iter().filter_map(|b| match b {
Block::Paragraph(p) => Some(p),
_ => None,
})
})
}
pub fn plain_text(&self) -> String {
let mut result = Vec::new();
for section in &self.sections {
for block in §ion.content {
match block {
Block::Paragraph(p) => result.push(p.plain_text()),
Block::Table(t) => {
for row in &t.rows {
for cell in &row.cells {
result.push(cell.plain_text());
}
}
}
}
}
}
result.join("\n")
}
pub fn raw_content(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Metadata {
pub title: Option<String>,
pub author: Option<String>,
pub subject: Option<String>,
pub keywords: Vec<String>,
pub created: Option<String>,
pub modified: Option<String>,
pub creator_app: Option<String>,
pub format_version: Option<String>,
#[serde(default)]
pub is_distribution: bool,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Section {
pub index: usize,
pub content: Vec<Block>,
pub header: Option<Vec<Paragraph>>,
pub footer: Option<Vec<Paragraph>>,
}
impl Section {
pub fn new(index: usize) -> Self {
Self {
index,
content: Vec::new(),
header: None,
footer: None,
}
}
pub fn push_paragraph(&mut self, paragraph: Paragraph) {
self.content.push(Block::Paragraph(paragraph));
}
pub fn push_table(&mut self, table: Table) {
self.content.push(Block::Table(table));
}
}
#[derive(Debug, Clone, Serialize)]
pub enum Block {
Paragraph(Paragraph),
Table(Table),
}
#[derive(Debug, Clone, Serialize)]
pub struct Resource {
pub resource_type: ResourceType,
pub filename: Option<String>,
pub mime_type: Option<String>,
#[serde(skip)]
pub data: Vec<u8>,
pub size: usize,
}
impl Resource {
pub fn new(resource_type: ResourceType, data: Vec<u8>) -> Self {
let size = data.len();
Self {
resource_type,
filename: None,
mime_type: None,
data,
size,
}
}
pub fn image(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
let size = data.len();
Self {
resource_type: ResourceType::Image,
filename: None,
mime_type: Some(mime_type.into()),
data,
size,
}
}
pub fn extension(&self) -> &str {
match self.mime_type.as_deref() {
Some("image/png") => "png",
Some("image/jpeg") | Some("image/jpg") => "jpg",
Some("image/gif") => "gif",
Some("image/bmp") => "bmp",
Some("image/webp") => "webp",
Some("image/svg+xml") => "svg",
_ => match self.resource_type {
ResourceType::Image => "bin",
ResourceType::OleObject => "ole",
ResourceType::Other => "bin",
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ResourceType {
Image,
OleObject,
Other,
}