parent
aa0ccfa6c6
commit
d8f5e19e7e
|
@ -114,20 +114,20 @@ impl ContentTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_header(mut self) -> Self {
|
pub fn add_header(mut self) -> Self {
|
||||||
|
self.header_count += 1;
|
||||||
self.types.insert(
|
self.types.insert(
|
||||||
format!("/word/header{}.xml", self.header_count),
|
format!("/word/header{}.xml", self.header_count),
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml".to_owned(),
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml".to_owned(),
|
||||||
);
|
);
|
||||||
self.header_count += 1;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_footer(mut self) -> Self {
|
pub fn add_footer(mut self) -> Self {
|
||||||
|
self.footer_count += 1;
|
||||||
self.types.insert(
|
self.types.insert(
|
||||||
format!("/word/footer{}.xml", self.footer_count),
|
format!("/word/footer{}.xml", self.footer_count),
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml".to_owned(),
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml".to_owned(),
|
||||||
);
|
);
|
||||||
self.footer_count += 1;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,8 +138,8 @@ impl Default for ContentTypes {
|
||||||
types: BTreeMap::new(),
|
types: BTreeMap::new(),
|
||||||
web_extension_count: 1,
|
web_extension_count: 1,
|
||||||
custom_xml_count: 1,
|
custom_xml_count: 1,
|
||||||
header_count: 1,
|
header_count: 0,
|
||||||
footer_count: 1,
|
footer_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,8 +219,8 @@ mod tests {
|
||||||
types,
|
types,
|
||||||
web_extension_count: 1,
|
web_extension_count: 1,
|
||||||
custom_xml_count: 1,
|
custom_xml_count: 1,
|
||||||
header_count: 1,
|
header_count: 0,
|
||||||
footer_count: 1,
|
footer_count: 0,
|
||||||
},
|
},
|
||||||
c
|
c
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,8 +6,8 @@ use serde::Serialize;
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FooterReference {
|
pub struct FooterReference {
|
||||||
footer_type: String,
|
pub footer_type: String,
|
||||||
id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FooterReference {
|
impl FooterReference {
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
use std::io::Read;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::reader::*;
|
||||||
|
use xml::reader::{EventReader, XmlEvent};
|
||||||
|
|
||||||
|
use super::{Paragraph, Table};
|
||||||
|
|
||||||
|
impl FromXML for Footer {
|
||||||
|
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
|
||||||
|
let mut parser = EventReader::new(reader);
|
||||||
|
let mut footer = Self::default();
|
||||||
|
loop {
|
||||||
|
let e = parser.next();
|
||||||
|
match e {
|
||||||
|
Ok(XmlEvent::StartElement {
|
||||||
|
attributes, name, ..
|
||||||
|
}) => {
|
||||||
|
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||||
|
match e {
|
||||||
|
XMLElement::Paragraph => {
|
||||||
|
let p = Paragraph::read(&mut parser, &attributes)?;
|
||||||
|
footer = footer.add_paragraph(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
XMLElement::Table => {
|
||||||
|
let t = Table::read(&mut parser, &attributes)?;
|
||||||
|
footer = footer.add_table(t);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(XmlEvent::EndDocument) => break,
|
||||||
|
Err(_) => return Err(ReaderError::XMLReadError),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(footer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_footer_from_xml() {
|
||||||
|
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:ftr xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||||
|
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
||||||
|
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||||
|
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
|
||||||
|
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
|
||||||
|
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14">
|
||||||
|
<w:p w14:paraId="12345678">
|
||||||
|
<w:pPr>
|
||||||
|
<w:rPr />
|
||||||
|
</w:pPr>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr />
|
||||||
|
<w:t xml:space="preserve">Hello Footer</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
</w:ftr>"#;
|
||||||
|
let h = Footer::from_xml(xml.as_bytes()).unwrap();
|
||||||
|
let expected =
|
||||||
|
Footer::new().add_paragraph(Paragraph::new().add_run(Run::new().add_text("Hello Footer")));
|
||||||
|
assert_eq!(h, expected)
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ mod document;
|
||||||
mod document_rels;
|
mod document_rels;
|
||||||
mod drawing;
|
mod drawing;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod footer;
|
||||||
mod from_xml;
|
mod from_xml;
|
||||||
mod header;
|
mod header;
|
||||||
mod ignore;
|
mod ignore;
|
||||||
|
@ -81,6 +82,8 @@ const WEB_SETTINGS_TYPE: &str =
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings";
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings";
|
||||||
const HEADER_TYPE: &str =
|
const HEADER_TYPE: &str =
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
||||||
|
const FOOTER_TYPE: &str =
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
|
||||||
// 2011
|
// 2011
|
||||||
const COMMENTS_EXTENDED_TYPE: &str =
|
const COMMENTS_EXTENDED_TYPE: &str =
|
||||||
"http://schemas.microsoft.com/office/2011/relationships/commentsExtended";
|
"http://schemas.microsoft.com/office/2011/relationships/commentsExtended";
|
||||||
|
@ -97,18 +100,36 @@ fn read_headers(
|
||||||
let data = read_zip(archive, path.to_str().expect("should have header path."));
|
let data = read_zip(archive, path.to_str().expect("should have header path."));
|
||||||
if let Ok(d) = data {
|
if let Ok(d) = data {
|
||||||
if let Ok(h) = Header::from_xml(&d[..]) {
|
if let Ok(h) = Header::from_xml(&d[..]) {
|
||||||
Some((rid, h))
|
return Some((rid, h));
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
None
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_footers(
|
||||||
|
rels: &ReadDocumentRels,
|
||||||
|
archive: &mut ZipArchive<Cursor<&[u8]>>,
|
||||||
|
) -> HashMap<RId, Footer> {
|
||||||
|
let footer_paths = rels.find_target_path(FOOTER_TYPE);
|
||||||
|
let footers: HashMap<RId, Footer> = footer_paths
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(rid, path)| {
|
||||||
|
let data = read_zip(archive, path.to_str().expect("should have footer path."));
|
||||||
|
if let Ok(d) = data {
|
||||||
|
if let Ok(h) = Footer::from_xml(&d[..]) {
|
||||||
|
return Some((rid, h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
footers
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
||||||
let mut docx = Docx::new();
|
let mut docx = Docx::new();
|
||||||
let cur = Cursor::new(buf);
|
let cur = Cursor::new(buf);
|
||||||
|
@ -150,6 +171,7 @@ pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
||||||
let rels = read_document_rels(&mut archive, &document_path)?;
|
let rels = read_document_rels(&mut archive, &document_path)?;
|
||||||
|
|
||||||
let headers = read_headers(&rels, &mut archive);
|
let headers = read_headers(&rels, &mut archive);
|
||||||
|
let footers = read_footers(&rels, &mut archive);
|
||||||
|
|
||||||
// Read commentsExtended
|
// Read commentsExtended
|
||||||
let comments_extended_path = rels.find_target_path(COMMENTS_EXTENDED_TYPE);
|
let comments_extended_path = rels.find_target_path(COMMENTS_EXTENDED_TYPE);
|
||||||
|
@ -235,6 +257,9 @@ pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
||||||
if let Some(h) = docx.document.section_property.header_reference.clone() {
|
if let Some(h) = docx.document.section_property.header_reference.clone() {
|
||||||
if let Some(header) = headers.get(&h.id) {
|
if let Some(header) = headers.get(&h.id) {
|
||||||
docx.document = docx.document.header(header.clone(), &h.id);
|
docx.document = docx.document.header(header.clone(), &h.id);
|
||||||
|
let count = docx.document_rels.header_count + 1;
|
||||||
|
docx.document_rels.header_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_header();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ref h) = docx
|
if let Some(ref h) = docx
|
||||||
|
@ -245,11 +270,48 @@ pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
||||||
{
|
{
|
||||||
if let Some(header) = headers.get(&h.id) {
|
if let Some(header) = headers.get(&h.id) {
|
||||||
docx.document = docx.document.first_header(header.clone(), &h.id);
|
docx.document = docx.document.first_header(header.clone(), &h.id);
|
||||||
|
let count = docx.document_rels.header_count + 1;
|
||||||
|
docx.document_rels.header_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_header();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ref h) = docx.document.section_property.even_header_reference.clone() {
|
if let Some(ref h) = docx.document.section_property.even_header_reference.clone() {
|
||||||
if let Some(header) = headers.get(&h.id) {
|
if let Some(header) = headers.get(&h.id) {
|
||||||
docx.document = docx.document.even_header(header.clone(), &h.id);
|
docx.document = docx.document.even_header(header.clone(), &h.id);
|
||||||
|
let count = docx.document_rels.header_count + 1;
|
||||||
|
docx.document_rels.header_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_header();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign footers
|
||||||
|
if let Some(f) = docx.document.section_property.footer_reference.clone() {
|
||||||
|
if let Some(footer) = footers.get(&f.id) {
|
||||||
|
docx.document = docx.document.footer(footer.clone(), &f.id);
|
||||||
|
let count = docx.document_rels.footer_count + 1;
|
||||||
|
docx.document_rels.footer_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_footer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref f) = docx
|
||||||
|
.document
|
||||||
|
.section_property
|
||||||
|
.first_footer_reference
|
||||||
|
.clone()
|
||||||
|
{
|
||||||
|
if let Some(footer) = footers.get(&f.id) {
|
||||||
|
docx.document = docx.document.first_footer(footer.clone(), &f.id);
|
||||||
|
let count = docx.document_rels.footer_count + 1;
|
||||||
|
docx.document_rels.footer_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_footer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref f) = docx.document.section_property.even_footer_reference.clone() {
|
||||||
|
if let Some(footer) = footers.get(&f.id) {
|
||||||
|
docx.document = docx.document.even_footer(footer.clone(), &f.id);
|
||||||
|
let count = docx.document_rels.footer_count + 1;
|
||||||
|
docx.document_rels.footer_count = count;
|
||||||
|
docx.content_type = docx.content_type.add_footer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,9 @@ fn read_page_margin(
|
||||||
Ok(margin)
|
Ok(margin)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_header_reference(attributes: &[OwnedAttribute]) -> Result<(String, String), ReaderError> {
|
fn read_header_or_footer_reference(
|
||||||
|
attributes: &[OwnedAttribute],
|
||||||
|
) -> Result<(String, String), ReaderError> {
|
||||||
let mut rid = "".to_owned();
|
let mut rid = "".to_owned();
|
||||||
let mut header_type = "default".to_owned();
|
let mut header_type = "default".to_owned();
|
||||||
for a in attributes {
|
for a in attributes {
|
||||||
|
@ -103,7 +105,9 @@ impl ElementReader for SectionProperty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XMLElement::HeaderReference => {
|
XMLElement::HeaderReference => {
|
||||||
if let Ok((rid, header_type)) = read_header_reference(&attributes) {
|
if let Ok((rid, header_type)) =
|
||||||
|
read_header_or_footer_reference(&attributes)
|
||||||
|
{
|
||||||
match header_type.as_str() {
|
match header_type.as_str() {
|
||||||
"default" => {
|
"default" => {
|
||||||
sp.header_reference =
|
sp.header_reference =
|
||||||
|
@ -121,6 +125,27 @@ impl ElementReader for SectionProperty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
XMLElement::FooterReference => {
|
||||||
|
if let Ok((rid, footer_type)) =
|
||||||
|
read_header_or_footer_reference(&attributes)
|
||||||
|
{
|
||||||
|
match footer_type.as_str() {
|
||||||
|
"default" => {
|
||||||
|
sp.footer_reference =
|
||||||
|
Some(FooterReference::new(footer_type, rid));
|
||||||
|
}
|
||||||
|
"first" => {
|
||||||
|
sp.first_footer_reference =
|
||||||
|
Some(FooterReference::new(footer_type, rid));
|
||||||
|
}
|
||||||
|
"even" => {
|
||||||
|
sp.even_footer_reference =
|
||||||
|
Some(FooterReference::new(footer_type, rid));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
XMLElement::TitlePg => sp = sp.title_pg(),
|
XMLElement::TitlePg => sp = sp.title_pg(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub enum XMLElement {
|
||||||
PageMargin,
|
PageMargin,
|
||||||
WebSettings,
|
WebSettings,
|
||||||
HeaderReference,
|
HeaderReference,
|
||||||
|
FooterReference,
|
||||||
TitlePg,
|
TitlePg,
|
||||||
EvenAndOddHeaders,
|
EvenAndOddHeaders,
|
||||||
Unsupported,
|
Unsupported,
|
||||||
|
@ -339,6 +340,7 @@ impl FromStr for XMLElement {
|
||||||
"pageBreakBefore" => Ok(XMLElement::PageBreakBefore),
|
"pageBreakBefore" => Ok(XMLElement::PageBreakBefore),
|
||||||
"windowControl" => Ok(XMLElement::WindowControl),
|
"windowControl" => Ok(XMLElement::WindowControl),
|
||||||
"headerReference" => Ok(XMLElement::HeaderReference),
|
"headerReference" => Ok(XMLElement::HeaderReference),
|
||||||
|
"footerReference" => Ok(XMLElement::FooterReference),
|
||||||
"titlePg" => Ok(XMLElement::TitlePg),
|
"titlePg" => Ok(XMLElement::TitlePg),
|
||||||
"evenAndOddHeaders" => Ok(XMLElement::EvenAndOddHeaders),
|
"evenAndOddHeaders" => Ok(XMLElement::EvenAndOddHeaders),
|
||||||
_ => Ok(XMLElement::Unsupported),
|
_ => Ok(XMLElement::Unsupported),
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -80,6 +80,12 @@ describe("reader", () => {
|
||||||
const json = w.readDocx(buffer);
|
const json = w.readDocx(buffer);
|
||||||
expect(json).toMatchSnapshot();
|
expect(json).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should read footer docx", () => {
|
||||||
|
const buffer = readFileSync("../fixtures/footer/footer.docx");
|
||||||
|
const json = w.readDocx(buffer);
|
||||||
|
expect(json).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("writer", () => {
|
describe("writer", () => {
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue