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 { 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.document = d;
self self
} }
pub fn styles(mut self, s: Styles) -> Self {
self.styles = s;
self
}
pub fn add_paragraph(mut self, p: Paragraph) -> Docx { pub fn add_paragraph(mut self, p: Paragraph) -> Docx {
if p.has_numbering { if p.has_numbering {
// If this document has numbering, set numberings.xml to document_rels. // 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> { fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
let mut parser = EventReader::new(reader); let mut parser = EventReader::new(reader);
let mut doc = Self::default(); let mut doc = Self::default();
loop { loop {
let e = parser.next(); let e = parser.next();
match e { 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, XMLReadError,
#[error("Failed to find document.")] #[error("Failed to find document.")]
DocumentNotFoundError, DocumentNotFoundError,
#[error("Failed to find document rels.")]
DocumentRelsNotFoundError,
#[error("Failed to find styles.")]
DocumentStylesNotFoundError,
#[error("Unknown error")] #[error("Unknown error")]
Unknown, Unknown,
} }

View File

@ -1,6 +1,7 @@
mod attributes; mod attributes;
mod delete; mod delete;
mod document; mod document;
mod document_rels;
mod errors; mod errors;
mod from_xml; mod from_xml;
mod insert; mod insert;
@ -21,12 +22,15 @@ use zip;
use crate::documents::*; use crate::documents::*;
pub use attributes::*; pub use attributes::*;
pub use document_rels::*;
pub use errors::ReaderError; pub use errors::ReaderError;
pub use from_xml::*; pub use from_xml::*;
pub use xml_element::*; pub use xml_element::*;
const DOC_RELATIONSHIP_TYPE: &str = const DOC_RELATIONSHIP_TYPE: &str =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"; "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> { pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
let cur = Cursor::new(buf); let cur = Cursor::new(buf);
@ -46,6 +50,15 @@ pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
.ok_or(ReaderError::DocumentNotFoundError)?; .ok_or(ReaderError::DocumentNotFoundError)?;
let document_xml = archive.by_name(&main_rel.2)?; let document_xml = archive.by_name(&main_rel.2)?;
let document = Document::from_xml(document_xml)?; 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) Ok(docx)
} }

View File

@ -37,28 +37,39 @@ impl FromXML for Styles {
} }
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
//
// use super::*; use super::*;
// #[cfg(test)] use crate::types::*;
// use pretty_assertions::assert_eq; #[cfg(test)]
// use pretty_assertions::assert_eq;
// #[test]
// fn test_from_xml() { #[test]
// let xml = r#"<?xml version="1.0" encoding="UTF-8"?> fn test_from_xml() {
// <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> let xml =
// <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" /> r#"<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
// </Relationships>"#; <w:style w:type="character" w:styleId="FootnoteTextChar">
// let c = Rels::from_xml(xml.as_bytes()).unwrap(); <w:name w:val="Footnote Text Char"></w:name>
// let mut rels = Vec::new(); <w:rPr>
// rels.push(( <w:sz w:val="20"></w:sz>
// "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" <w:szCs w:val="20"></w:szCs>
// .to_owned(), </w:rPr>
// "rId1".to_owned(), <w:uiPriority w:val="99"></w:uiPriority>
// "docProps/core.xml".to_owned(), <w:unhideWhenUsed></w:unhideWhenUsed>
// )); <w:basedOn w:val="DefaultParagraphFont"></w:basedOn>
// assert_eq!(Rels { rels }, c); <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, VertAlign,
Spacing, Spacing,
Styles, Styles,
Relationship,
Relationships,
Unsupported, Unsupported,
} }
@ -129,6 +131,8 @@ impl FromStr for XMLElement {
"vertAlign" => Ok(XMLElement::VertAlign), "vertAlign" => Ok(XMLElement::VertAlign),
"spacing" => Ok(XMLElement::Spacing), "spacing" => Ok(XMLElement::Spacing),
"styles" => Ok(XMLElement::Styles), "styles" => Ok(XMLElement::Styles),
"Relationships" => Ok(XMLElement::Relationships),
"Relationship" => Ok(XMLElement::Relationship),
_ => Ok(XMLElement::Unsupported), _ => Ok(XMLElement::Unsupported),
} }
} }

View File

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