use super::Document;
use crate::media_type;
use crate::style::PageLayout;
use crate::xml::{Element, Ns};
use crate::{Error, Family};
pub struct TextDocument {
pub document: Document,
}
impl TextDocument {
pub fn read(bytes: &[u8]) -> Result<Self, Error> {
let document = Document::read(bytes, media_type::TEXT_ANY)?;
Ok(Self { document })
}
pub fn body(&self) -> Option<&Element> {
self.document.body_of("text")
}
pub fn body_mut(&mut self) -> Option<&mut Element> {
self.document
.content
.child_mut(&Ns::Office, "body")?
.child_mut(&Ns::Office, "text")
}
pub fn body_path(&self) -> Option<Vec<usize>> {
self.document.body_path("text")
}
pub fn page_layout(&self) -> PageLayout {
let master = self
.document
.styles_part
.as_ref()
.and_then(|styles| styles.child(&Ns::Office, "master-styles"))
.and_then(|masters| masters.child(&Ns::Style, "master-page"))
.and_then(|master| master.attr(&Ns::Style, "name"));
master
.and_then(|name| self.document.styles.page_layout_of(name))
.cloned()
.unwrap_or_default()
}
pub fn text_width(&self) -> f32 {
let layout = self.page_layout();
let left = layout
.margin
.left
.map_or(0.0, super::super::value::Length::points);
let right = layout
.margin
.right
.map_or(0.0, super::super::value::Length::points);
(layout.width.points() - left - right).max(1.0)
}
pub fn outline(&self) -> Vec<Heading> {
let mut headings = Vec::new();
if let Some(body) = self.body() {
collect_headings(body, &mut headings);
}
headings
}
pub fn paragraph_style(&self, element: &Element) -> std::rc::Rc<crate::Properties> {
let name = element.attr(&Ns::Text, "style-name").unwrap_or("Standard");
self.document.styles.resolve(&Family::Paragraph, name)
}
}
pub struct Heading {
pub level: u8,
pub text: String,
}
fn collect_headings(parent: &Element, into: &mut Vec<Heading>) {
for element in parent.elements() {
if element.is(&Ns::Text, "h") {
let level = element
.attr_usize(&Ns::Text, "outline-level")
.unwrap_or(1)
.clamp(1, 10);
#[allow(clippy::cast_possible_truncation)]
into.push(Heading {
level: level as u8,
text: element.plain_text(),
});
} else if element.is(&Ns::Text, "section") {
collect_headings(element, into);
}
}
}