use super::fib::Fib;
use super::paragraph_props::{index_by_paragraph, ParagraphProp};
use super::piece_table::{self, Piece, ReconstructedText};
use super::stylesheet::StyleSheet;
use super::text_extractor::{self, DocParagraph, ParagraphType};
use super::{cfb_reader, fib, stylesheet};
pub(super) struct Story {
pub text: ReconstructedText,
pub props: Vec<Option<ParagraphProp>>,
}
pub(super) struct ParsedDoc {
pub word_doc: Vec<u8>,
pub table: Vec<u8>,
pub fib: Fib,
pub stylesheet: StyleSheet,
pub props: Vec<ParagraphProp>,
}
impl ParsedDoc {
pub fn open(bytes: &[u8]) -> Result<Self, String> {
let mut cfb = cfb_reader::DocCfb::open(bytes)?;
let word_doc = cfb.word_document_stream()?;
let fib = fib::parse_fib(&word_doc)?;
let table = cfb.table_stream(fib.f_which_tbl_stm)?;
let stylesheet = stylesheet::parse_stylesheet(&table, fib.fc_stshf, fib.lcb_stshf)?;
let props = super::paragraph_props::parse_paragraph_props(
&word_doc,
&table,
fib.fc_plcf_papx,
fib.lcb_plcf_papx,
)?;
Ok(ParsedDoc {
word_doc,
table,
fib,
stylesheet,
props,
})
}
fn story(&self, cp_from: u32, cp_to: i32) -> Result<(Story, Vec<Piece>), String> {
let pieces = piece_table::parse_pieces_range(
&self.table,
self.fib.fc_clx,
self.fib.lcb_clx,
cp_from,
cp_to,
)?;
let text = piece_table::reconstruct_from_pieces(&self.word_doc, &pieces);
let props = index_by_paragraph(&self.props, &pieces, &text);
Ok((Story { text, props }, pieces))
}
pub fn main_story(&self) -> Result<(Story, Vec<Piece>), String> {
self.story(0, self.fib.ccp_text)
}
pub fn main_paragraphs_indexed(&self) -> Result<Vec<(usize, DocParagraph)>, String> {
let (story, _) = self.main_story()?;
Ok(text_extractor::extract_paragraphs_indexed(
&story.text,
&story.props,
&self.stylesheet,
))
}
pub fn all_paragraphs(&self) -> Result<Vec<DocParagraph>, String> {
let mut out: Vec<DocParagraph> = self
.main_paragraphs_indexed()?
.into_iter()
.map(|(_, p)| p)
.collect();
self.append_side_stories(&mut out);
Ok(out)
}
fn append_side_stories(&self, paragraphs: &mut Vec<DocParagraph>) {
let f = &self.fib;
let mut cp = f.ccp_text.max(0) as u32;
let stories: [(&str, i32); 6] = [
("Footnotes", f.ccp_ftn),
("Headers and footers", f.ccp_hdd),
("Macros", f.ccp_mcr),
("Comments", f.ccp_atn),
("Endnotes", f.ccp_edn),
("Text boxes", f.ccp_txbx),
];
for (label, len) in stories {
let len = len.max(0) as u32;
if len == 0 {
continue;
}
let (from, to) = (cp, cp + len);
cp = to;
let Ok((story, _)) = self.story(from, to as i32) else {
continue;
};
let extracted = text_extractor::extract_paragraphs(
&story.text,
&story.props,
&self.stylesheet,
);
let has_content = extracted.iter().any(|p| !p.content.trim().is_empty());
if !has_content {
continue;
}
paragraphs.push(DocParagraph::plain(
format!("[{label}]"),
ParagraphType::Heading(1),
None,
));
paragraphs.extend(
extracted
.into_iter()
.filter(|p| !p.content.trim().is_empty()),
);
}
}
}