Read styles (#37)

* feat: style reader

* feat: Add styles reader

* fix: lint
main
bokuweb 2020-02-12 16:03:11 +09:00 committed by GitHub
parent 6ec9be7fcd
commit 513faa67c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 175 additions and 28 deletions

View File

@ -79,10 +79,29 @@ impl Docx {
}
pub fn document(mut self, d: Document) -> Docx {
for child in &self.document.children {
match child {
DocumentChild::Paragraph(paragraph) => {
if paragraph.has_numbering {
self.document_rels.has_numberings = true;
}
}
DocumentChild::Table(table) => {
if table.has_numbering {
self.document_rels.has_numberings = true;
}
}
}
}
self.document = d;
self
}
pub fn styles(mut self, s: Styles) -> Self {
self.styles = s;
self
}
pub fn add_paragraph(mut self, p: Paragraph) -> Docx {
if p.has_numbering {
// If this document has numbering, set numberings.xml to document_rels.

View File

@ -10,7 +10,6 @@ impl FromXML for Document {
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
let mut parser = EventReader::new(reader);
let mut doc = Self::default();
loop {
let e = parser.next();
match e {

View File

@ -0,0 +1,92 @@
use std::collections::HashMap;
use std::io::{Cursor, Read};
use std::path::*;
use std::str::FromStr;
use xml::reader::{EventReader, XmlEvent};
use super::errors::*;
use super::*;
#[derive(Debug, Clone, PartialEq)]
pub struct ReadDocumentRels {
rels: HashMap<String, PathBuf>,
}
impl ReadDocumentRels {
pub fn find_target_path(&self, target: &str) -> Option<PathBuf> {
self.rels.get(target).cloned()
}
}
pub fn read_document_rels(
archive: &mut zip::read::ZipArchive<Cursor<&[u8]>>,
main_path: impl AsRef<Path>,
) -> Result<ReadDocumentRels, ReaderError> {
let dir = &main_path
.as_ref()
.parent()
.ok_or(ReaderError::DocumentRelsNotFoundError)?;
let p = find_rels_filename(&main_path)?;
let p = p.to_str().ok_or(ReaderError::DocumentRelsNotFoundError)?;
let rels_xml = archive.by_name(&p)?;
let rels = read_rels_xml(rels_xml, dir)?;
Ok(rels)
}
fn read_rels_xml<R: Read>(
reader: R,
dir: impl AsRef<Path>,
) -> Result<ReadDocumentRels, ReaderError> {
let mut parser = EventReader::new(reader);
let mut rels = ReadDocumentRels {
rels: HashMap::new(),
};
loop {
let e = parser.next();
match e {
Ok(XmlEvent::StartElement {
attributes, name, ..
}) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if let XMLElement::Relationship = e {
let mut rel_type = "".to_owned();
let mut target = PathBuf::default();
for a in attributes {
let local_name = &a.name.local_name;
if local_name == "Type" {
rel_type = a.value.to_owned();
} else if local_name == "Target" {
target = Path::new(dir.as_ref()).join(a.value);
}
}
rels.rels.insert(rel_type, target);
continue;
}
}
Ok(XmlEvent::EndElement { name, .. }) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if let XMLElement::Relationships = e {
break;
}
}
Err(_) => return Err(ReaderError::XMLReadError),
_ => {}
}
}
Ok(rels)
}
fn find_rels_filename(main_path: impl AsRef<Path>) -> Result<PathBuf, ReaderError> {
let path = main_path.as_ref();
let dir = path
.parent()
.ok_or(ReaderError::DocumentRelsNotFoundError)?;
let base = path
.file_stem()
.ok_or(ReaderError::DocumentRelsNotFoundError)?;
Ok(Path::new(dir)
.join("_rels")
.join(base)
.with_extension("xml.rels"))
}

View File

@ -12,6 +12,10 @@ pub enum ReaderError {
XMLReadError,
#[error("Failed to find document.")]
DocumentNotFoundError,
#[error("Failed to find document rels.")]
DocumentRelsNotFoundError,
#[error("Failed to find styles.")]
DocumentStylesNotFoundError,
#[error("Unknown error")]
Unknown,
}

View File

@ -1,6 +1,7 @@
mod attributes;
mod delete;
mod document;
mod document_rels;
mod errors;
mod from_xml;
mod insert;
@ -21,12 +22,15 @@ use zip;
use crate::documents::*;
pub use attributes::*;
pub use document_rels::*;
pub use errors::ReaderError;
pub use from_xml::*;
pub use xml_element::*;
const DOC_RELATIONSHIP_TYPE: &str =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
const STYLE_RELATIONSHIP_TYPE: &str =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
let cur = Cursor::new(buf);
@ -46,6 +50,15 @@ pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
.ok_or(ReaderError::DocumentNotFoundError)?;
let document_xml = archive.by_name(&main_rel.2)?;
let document = Document::from_xml(document_xml)?;
let docx = Docx::new().document(document);
// Read document relationships
let rels = read_document_rels(&mut archive, &main_rel.2)?;
let style_path = rels
.find_target_path(STYLE_RELATIONSHIP_TYPE)
.ok_or(ReaderError::DocumentStylesNotFoundError)?;
let styles_xml = archive.by_name(style_path.to_str().expect("should have styles"))?;
let styles = Styles::from_xml(styles_xml)?;
let docx = Docx::new().document(document).styles(styles);
Ok(docx)
}

View File

@ -37,28 +37,39 @@ impl FromXML for Styles {
}
}
// #[cfg(test)]
// mod tests {
//
// use super::*;
// #[cfg(test)]
// use pretty_assertions::assert_eq;
//
// #[test]
// fn test_from_xml() {
// let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
// <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
// <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" />
// </Relationships>"#;
// let c = Rels::from_xml(xml.as_bytes()).unwrap();
// let mut rels = Vec::new();
// rels.push((
// "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
// .to_owned(),
// "rId1".to_owned(),
// "docProps/core.xml".to_owned(),
// ));
// assert_eq!(Rels { rels }, c);
// }
// }
//
#[cfg(test)]
mod tests {
use super::*;
use crate::types::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
#[test]
fn test_from_xml() {
let xml =
r#"<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="character" w:styleId="FootnoteTextChar">
<w:name w:val="Footnote Text Char"></w:name>
<w:rPr>
<w:sz w:val="20"></w:sz>
<w:szCs w:val="20"></w:szCs>
</w:rPr>
<w:uiPriority w:val="99"></w:uiPriority>
<w:unhideWhenUsed></w:unhideWhenUsed>
<w:basedOn w:val="DefaultParagraphFont"></w:basedOn>
<w:link w:val="FootnoteText"></w:link>
<w:uiPriority w:val="99"></w:uiPriority>
<w:semiHidden></w:semiHidden>
</w:style>
</w:styles>"#;
let s = Styles::from_xml(xml.as_bytes()).unwrap();
let mut styles = Styles::new();
styles = styles.add_style(
Style::new("FootnoteTextChar", StyleType::Character)
.name("Footnote Text Char")
.size(20),
);
assert_eq!(s, styles);
}
}

View File

@ -65,6 +65,8 @@ pub enum XMLElement {
VertAlign,
Spacing,
Styles,
Relationship,
Relationships,
Unsupported,
}
@ -129,6 +131,8 @@ impl FromStr for XMLElement {
"vertAlign" => Ok(XMLElement::VertAlign),
"spacing" => Ok(XMLElement::Spacing),
"styles" => Ok(XMLElement::Styles),
"Relationships" => Ok(XMLElement::Relationships),
"Relationship" => Ok(XMLElement::Relationship),
_ => Ok(XMLElement::Unsupported),
}
}

View File

@ -11,6 +11,8 @@ use std::str::FromStr;
pub enum StyleType {
Paragraph,
Character,
Numbering,
Unsupported,
}
impl fmt::Display for StyleType {
@ -18,6 +20,8 @@ impl fmt::Display for StyleType {
match *self {
StyleType::Paragraph => write!(f, "paragraph"),
StyleType::Character => write!(f, "character"),
StyleType::Numbering => write!(f, "numbering"),
StyleType::Unsupported => write!(f, "unsupported"),
}
}
}
@ -28,7 +32,8 @@ impl FromStr for StyleType {
match s {
"paragraph" => Ok(StyleType::Paragraph),
"character" => Ok(StyleType::Character),
_ => Err(errors::TypeError::FromStrError),
"numbering" => Ok(StyleType::Numbering),
_ => Ok(StyleType::Unsupported),
}
}
}