From 025aee280f9b44ea8f049e101eea737fc46dc286 Mon Sep 17 00:00:00 2001 From: bokuweb Date: Thu, 25 Nov 2021 01:49:27 +0900 Subject: [PATCH] Support default footer (#366) * feat: Support default footer writer with rust * feat: Support footer for default section with js * fix: improve footer interface --- docx-core/examples/footer.rs | 14 + docx-core/src/documents/content_types.rs | 14 +- docx-core/src/documents/document.rs | 5 + docx-core/src/documents/document_rels.rs | 10 + .../documents/elements/footer_reference.rs | 28 + docx-core/src/documents/elements/mod.rs | 2 + .../documents/elements/section_property.rs | 20 +- docx-core/src/documents/footer.rs | 107 + docx-core/src/documents/footer_id.rs | 24 + docx-core/src/documents/mod.rs | 53 +- docx-core/src/documents/xml_docx.rs | 1 + docx-core/src/xml_builder/drawing.rs | 1 - docx-core/src/xml_builder/elements.rs | 1 + docx-core/src/xml_builder/footer.rs | 49 + docx-core/src/xml_builder/mod.rs | 1 + docx-core/src/zipper/mod.rs | 5 + .../snapshots/lib__reader__line_spacing.snap | 2 +- .../snapshots/lib__reader__read_bom.snap | 2 +- .../snapshots/lib__reader__read_bookmark.snap | 2 +- .../snapshots/lib__reader__read_comment.snap | 2 +- .../lib__reader__read_decoration.snap | 2 +- .../lib__reader__read_extended_comments.snap | 2 +- .../snapshots/lib__reader__read_from_doc.snap | 2 +- .../snapshots/lib__reader__read_hello.snap | 2 +- ..._reader__read_highlight_and_underline.snap | 2 +- .../snapshots/lib__reader__read_history.snap | 2 +- .../lib__reader__read_indent_word_online.snap | 2 +- .../lib__reader__read_insert_table.snap | 2 +- .../lib__reader__read_lvl_override.snap | 2 +- .../lib__reader__read_numbering.snap | 2 +- .../lib__reader__read_tab_and_break.snap | 2 +- .../lib__reader__read_table_docx.snap | 2 +- ...eader__read_table_merged_libre_office.snap | 2 +- .../snapshots/lib__reader__read_textbox.snap | 2 +- .../tests/snapshots/reader__line_spacing.snap | 2 +- .../tests/snapshots/reader__read_bom.snap | 2 +- .../snapshots/reader__read_bookmark.snap | 2 +- .../tests/snapshots/reader__read_comment.snap | 2 +- .../snapshots/reader__read_decoration.snap | 2 +- .../reader__read_extended_comments.snap | 2 +- .../snapshots/reader__read_from_doc.snap | 2 +- .../tests/snapshots/reader__read_hello.snap | 2 +- .../reader__read_highlight_and_underline.snap | 2 +- .../tests/snapshots/reader__read_history.snap | 2 +- .../reader__read_indent_word_online.snap | 2 +- .../snapshots/reader__read_insert_table.snap | 2 +- .../snapshots/reader__read_lvl_override.snap | 2 +- .../snapshots/reader__read_numbering.snap | 2 +- .../snapshots/reader__read_tab_and_break.snap | 2 +- .../snapshots/reader__read_table_docx.snap | 2 +- ...eader__read_table_merged_libre_office.snap | 2 +- .../tests/snapshots/reader__read_textbox.snap | 2 +- docx-wasm/js/footer.ts | 14 + docx-wasm/js/index.ts | 1720 +++++++++-------- docx-wasm/js/section-property.ts | 7 + docx-wasm/src/doc.rs | 5 + docx-wasm/src/footer.rs | 30 + docx-wasm/src/lib.rs | 2 + .../test/__snapshots__/index.test.js.snap | 68 + docx-wasm/test/index.test.js | 14 + 60 files changed, 1377 insertions(+), 890 deletions(-) create mode 100644 docx-core/examples/footer.rs create mode 100644 docx-core/src/documents/elements/footer_reference.rs create mode 100644 docx-core/src/documents/footer.rs create mode 100644 docx-core/src/documents/footer_id.rs create mode 100644 docx-core/src/xml_builder/footer.rs create mode 100644 docx-wasm/js/footer.ts create mode 100644 docx-wasm/src/footer.rs diff --git a/docx-core/examples/footer.rs b/docx-core/examples/footer.rs new file mode 100644 index 0000000..48ee0df --- /dev/null +++ b/docx-core/examples/footer.rs @@ -0,0 +1,14 @@ +use docx_rs::*; + +pub fn main() -> Result<(), DocxError> { + let path = std::path::Path::new("./output/footer.docx"); + let file = std::fs::File::create(&path).unwrap(); + let footer = + Footer::new().add_paragraph(Paragraph::new().add_run(Run::new().add_text("Hello"))); + Docx::new() + .footer(footer) + .add_paragraph(Paragraph::new().add_run(Run::new().add_text("World"))) + .build() + .pack(file)?; + Ok(()) +} diff --git a/docx-core/src/documents/content_types.rs b/docx-core/src/documents/content_types.rs index 6de3cb6..f65c907 100644 --- a/docx-core/src/documents/content_types.rs +++ b/docx-core/src/documents/content_types.rs @@ -12,6 +12,7 @@ pub struct ContentTypes { types: BTreeMap, web_extension_count: usize, custom_xml_count: usize, + footer_count: usize, } impl ContentTypes { @@ -114,6 +115,15 @@ impl ContentTypes { self.custom_xml_count += 1; self } + + pub fn add_footer(mut self) -> Self { + self.types.insert( + format!("/word/footer{}.xml", self.footer_count), + "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml".to_owned(), + ); + self.footer_count += 1; + self + } } impl Default for ContentTypes { @@ -122,6 +132,7 @@ impl Default for ContentTypes { types: BTreeMap::new(), web_extension_count: 1, custom_xml_count: 1, + footer_count: 1, } } } @@ -200,7 +211,8 @@ mod tests { ContentTypes { types, web_extension_count: 1, - custom_xml_count: 1 + custom_xml_count: 1, + footer_count: 1, }, c ); diff --git a/docx-core/src/documents/document.rs b/docx-core/src/documents/document.rs index 87240d2..71c438b 100644 --- a/docx-core/src/documents/document.rs +++ b/docx-core/src/documents/document.rs @@ -149,6 +149,11 @@ impl Document { self.section_property = property; self } + + pub fn footer_reference(mut self, r: FooterReference) -> Self { + self.section_property = self.section_property.footer_reference(r); + self + } } impl BuildXML for DocumentChild { diff --git a/docx-core/src/documents/document_rels.rs b/docx-core/src/documents/document_rels.rs index 8afe9f0..892a903 100644 --- a/docx-core/src/documents/document_rels.rs +++ b/docx-core/src/documents/document_rels.rs @@ -11,6 +11,7 @@ pub struct DocumentRels { pub has_numberings: bool, pub image_ids: Vec, pub custom_xml_count: usize, + pub footer_count: usize, } impl DocumentRels { @@ -31,6 +32,7 @@ impl Default for DocumentRels { has_numberings: false, image_ids: vec![], custom_xml_count: 0, + footer_count: 0, } } } @@ -83,6 +85,14 @@ impl BuildXML for DocumentRels { ) } + for i in 0..self.footer_count { + b = b.relationship( + &create_footer_rid(i + 1), + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer", + &format!("footer{}.xml", i + 1), + ) + } + for i in 0..self.custom_xml_count { b = b.relationship( &format!("rId{}", i + 8), diff --git a/docx-core/src/documents/elements/footer_reference.rs b/docx-core/src/documents/elements/footer_reference.rs new file mode 100644 index 0000000..1dfe973 --- /dev/null +++ b/docx-core/src/documents/elements/footer_reference.rs @@ -0,0 +1,28 @@ +use crate::documents::BuildXML; +use crate::xml_builder::*; + +use serde::Serialize; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FooterReference { + footer_type: String, + id: String, +} + +impl FooterReference { + pub fn new(t: impl Into, id: impl Into) -> FooterReference { + FooterReference { + footer_type: t.into(), + id: id.into(), + } + } +} + +impl BuildXML for FooterReference { + fn build(&self) -> Vec { + XMLBuilder::new() + .footer_reference(&self.footer_type, &self.id) + .build() + } +} diff --git a/docx-core/src/documents/elements/mod.rs b/docx-core/src/documents/elements/mod.rs index 23ff965..224ba1c 100644 --- a/docx-core/src/documents/elements/mod.rs +++ b/docx-core/src/documents/elements/mod.rs @@ -25,6 +25,7 @@ mod drawing; mod font; mod grid_span; mod header_reference; +mod footer_reference; mod highlight; mod indent; mod indent_level; @@ -121,6 +122,7 @@ pub use drawing::*; pub use font::*; pub use grid_span::*; pub use header_reference::*; +pub use footer_reference::*; pub use highlight::*; pub use indent::*; pub use indent_level::*; diff --git a/docx-core/src/documents/elements/section_property.rs b/docx-core/src/documents/elements/section_property.rs index ef087f8..8e473fc 100644 --- a/docx-core/src/documents/elements/section_property.rs +++ b/docx-core/src/documents/elements/section_property.rs @@ -13,6 +13,7 @@ pub struct SectionProperty { columns: usize, doc_grid: DocGrid, header_reference: HeaderReference, + footer_reference: Option, section_type: Option, } @@ -40,6 +41,11 @@ impl SectionProperty { self.doc_grid = doc_grid; self } + + pub fn footer_reference(mut self, r: FooterReference) -> Self { + self.footer_reference = Some(r); + self + } } impl Default for SectionProperty { @@ -50,6 +56,7 @@ impl Default for SectionProperty { columns: 425, doc_grid: DocGrid::default(), header_reference: HeaderReference::default(), + footer_reference: None, section_type: None, } } @@ -64,7 +71,8 @@ impl BuildXML for SectionProperty { .add_child(&self.page_margin) .add_child(&self.header_reference) .columns(&format!("{}", &self.columns)) - .add_child(&self.doc_grid); + .add_child(&self.doc_grid) + .add_optional_child(&self.footer_reference); if let Some(t) = self.section_type { b = b.type_tag(&t.to_string()); @@ -90,4 +98,14 @@ mod tests { r#""# ); } + + #[test] + fn test_section_property_with_footer() { + let c = SectionProperty::new().footer_reference(FooterReference::new("default", "rId6")); + let b = c.build(); + assert_eq!( + str::from_utf8(&b).unwrap(), + r#""# + ); + } } diff --git a/docx-core/src/documents/footer.rs b/docx-core/src/documents/footer.rs new file mode 100644 index 0000000..2297123 --- /dev/null +++ b/docx-core/src/documents/footer.rs @@ -0,0 +1,107 @@ +use serde::ser::{SerializeStruct, Serializer}; +use serde::Serialize; + +use super::*; +use crate::documents::BuildXML; +use crate::xml_builder::*; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Footer { + pub has_numbering: bool, + pub children: Vec, +} + +impl Footer { + pub fn new() -> Footer { + Default::default() + } + + pub fn add_paragraph(mut self, p: Paragraph) -> Self { + if p.has_numbering { + self.has_numbering = true + } + self.children.push(FooterChild::Paragraph(p)); + self + } + + pub fn add_table(mut self, t: Table) -> Self { + if t.has_numbering { + self.has_numbering = true + } + self.children.push(FooterChild::Table(t)); + self + } +} + +impl Default for Footer { + fn default() -> Self { + Self { + children: vec![], + has_numbering: false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FooterChild { + Paragraph(Paragraph), + Table(Table), +} + +impl Serialize for FooterChild { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + FooterChild::Paragraph(ref p) => { + let mut t = serializer.serialize_struct("Paragraph", 2)?; + t.serialize_field("type", "paragraph")?; + t.serialize_field("data", p)?; + t.end() + } + FooterChild::Table(ref c) => { + let mut t = serializer.serialize_struct("Table", 2)?; + t.serialize_field("type", "table")?; + t.serialize_field("data", c)?; + t.end() + } + } + } +} + +impl BuildXML for Footer { + fn build(&self) -> Vec { + let mut b = XMLBuilder::new(); + b = b.declaration(Some(true)).open_footer(); + + for c in &self.children { + match c { + FooterChild::Paragraph(p) => b = b.add_child(p), + FooterChild::Table(t) => b = b.add_child(t), + } + } + b.close().build() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[cfg(test)] + use pretty_assertions::assert_eq; + use std::str; + + #[test] + fn test_settings() { + let c = Footer::new(); + let b = c.build(); + assert_eq!( + str::from_utf8(&b).unwrap(), + r#" +"# + ); + } +} diff --git a/docx-core/src/documents/footer_id.rs b/docx-core/src/documents/footer_id.rs new file mode 100644 index 0000000..0e47610 --- /dev/null +++ b/docx-core/src/documents/footer_id.rs @@ -0,0 +1,24 @@ +/* +#[cfg(not(test))] +use std::sync::atomic::AtomicUsize; +#[cfg(not(test))] +static FOOTER_ID: AtomicUsize = AtomicUsize::new(1); + +#[cfg(not(test))] +pub fn generate_pic_id() -> usize { + use std::sync::atomic::Ordering; + + let id = FOOTER_ID.load(Ordering::Relaxed); + FOOTER_ID.store(id.wrapping_add(1), Ordering::Relaxed); + id +} + +#[cfg(test)] +pub fn generate_footer_id() -> usize { + 123 +} +*/ + +pub fn create_footer_rid(id: usize) -> String { + format!("rIdFooter{}", id) +} diff --git a/docx-core/src/documents/mod.rs b/docx-core/src/documents/mod.rs index 90783a3..45dd8bb 100644 --- a/docx-core/src/documents/mod.rs +++ b/docx-core/src/documents/mod.rs @@ -12,6 +12,8 @@ mod document; mod document_rels; mod elements; mod font_table; +mod footer; +mod footer_id; mod header; mod header_id; mod history_id; @@ -43,6 +45,8 @@ pub use document::*; pub use document_rels::*; pub use elements::*; pub use font_table::*; +pub use footer::*; +pub use footer_id::*; pub use header::*; pub use numberings::*; pub use rels::*; @@ -71,6 +75,7 @@ pub struct Docx { pub font_table: FontTable, pub media: Vec<(usize, Vec)>, pub header: Header, + pub footer: Option