diff --git a/docx-core/examples/toc_simple.rs b/docx-core/examples/toc_simple.rs index ff480c0..25b0c13 100644 --- a/docx-core/examples/toc_simple.rs +++ b/docx-core/examples/toc_simple.rs @@ -4,7 +4,7 @@ pub fn main() -> Result<(), DocxError> { let path = std::path::Path::new("./output/toc_simple.docx"); let file = std::fs::File::create(&path).unwrap(); let p1 = Paragraph::new() - .add_run(Run::new().add_text("Hello")) + .add_run(Run::new().add_text("!!Hello")) .style("Heading1") .page_break_before(true); let style1 = Style::new("Heading1", StyleType::Paragraph).name("Heading 1"); diff --git a/docx-core/src/documents/elements/delete_instr_text.rs b/docx-core/src/documents/elements/delete_instr_text.rs new file mode 100644 index 0000000..c12dfb1 --- /dev/null +++ b/docx-core/src/documents/elements/delete_instr_text.rs @@ -0,0 +1,92 @@ +use serde::ser::{SerializeStruct, Serializer}; +use serde::Serialize; + +use crate::documents::*; +use crate::xml_builder::*; + +#[derive(Debug, Clone, PartialEq)] +pub enum DeleteInstrText { + TOC(InstrToC), + TC(InstrTC), + PAGEREF(InstrPAGEREF), + HYPERLINK(InstrHyperlink), + Unsupported(String), +} + +impl BuildXML for Box { + fn build(&self) -> Vec { + let instr = match self.as_ref() { + DeleteInstrText::TOC(toc) => toc.build(), + DeleteInstrText::TC(tc) => tc.build(), + DeleteInstrText::PAGEREF(page_ref) => page_ref.build(), + DeleteInstrText::HYPERLINK(_link) => todo!(), + DeleteInstrText::Unsupported(s) => s.as_bytes().to_vec(), + }; + XMLBuilder::new() + .open_delete_instr_text() + .add_bytes(&instr) + .close() + .build() + } +} + +impl Serialize for DeleteInstrText { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + DeleteInstrText::TOC(ref s) => { + let mut t = serializer.serialize_struct("TOC", 2)?; + t.serialize_field("type", "toc")?; + t.serialize_field("data", s)?; + t.end() + } + DeleteInstrText::TC(ref s) => { + let mut t = serializer.serialize_struct("TC", 2)?; + t.serialize_field("type", "tc")?; + t.serialize_field("data", s)?; + t.end() + } + DeleteInstrText::PAGEREF(ref s) => { + let mut t = serializer.serialize_struct("PAGEREF", 2)?; + t.serialize_field("type", "pageref")?; + t.serialize_field("data", s)?; + t.end() + } + DeleteInstrText::HYPERLINK(ref s) => { + let mut t = serializer.serialize_struct("HYPERLINK", 2)?; + t.serialize_field("type", "hyperlink")?; + t.serialize_field("data", s)?; + t.end() + } + DeleteInstrText::Unsupported(ref s) => { + let mut t = serializer.serialize_struct("Unsupported", 2)?; + t.serialize_field("type", "unsupported")?; + t.serialize_field("data", s)?; + t.end() + } + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[cfg(test)] + use pretty_assertions::assert_eq; + use std::str; + + #[test] + fn test_delete_toc_instr() { + let b = Box::new(DeleteInstrText::TOC( + InstrToC::new().heading_styles_range(1, 3), + )) + .build(); + assert_eq!( + str::from_utf8(&b).unwrap(), + r#"TOC \o "1-3""# + ); + } +} diff --git a/docx-core/src/documents/elements/mod.rs b/docx-core/src/documents/elements/mod.rs index 4df6b93..6d29595 100644 --- a/docx-core/src/documents/elements/mod.rs +++ b/docx-core/src/documents/elements/mod.rs @@ -16,6 +16,7 @@ mod comment_range_start; mod data_binding; mod default_tab_stop; mod delete; +mod delete_instr_text; mod delete_text; mod div; mod doc_defaults; @@ -133,6 +134,7 @@ pub use comment_range_start::*; pub use data_binding::*; pub use default_tab_stop::*; pub use delete::*; +pub use delete_instr_text::*; pub use delete_text::*; pub use div::*; pub use doc_defaults::*; diff --git a/docx-core/src/documents/elements/run.rs b/docx-core/src/documents/elements/run.rs index 6f6dade..db15a48 100644 --- a/docx-core/src/documents/elements/run.rs +++ b/docx-core/src/documents/elements/run.rs @@ -35,6 +35,7 @@ pub enum RunChild { CommentEnd(CommentRangeEnd), FieldChar(FieldChar), InstrText(Box), + DeleteInstrText(Box), // For reader InstrTextString(String), } @@ -104,6 +105,12 @@ impl Serialize for RunChild { t.serialize_field("data", i)?; t.end() } + RunChild::DeleteInstrText(ref i) => { + let mut t = serializer.serialize_struct("DeleteInstrText", 2)?; + t.serialize_field("type", "deleteInstrText")?; + t.serialize_field("data", i)?; + t.end() + } RunChild::InstrTextString(ref i) => { let mut t = serializer.serialize_struct("InstrTextString", 2)?; t.serialize_field("type", "instrTextString")?; @@ -163,6 +170,11 @@ impl Run { self } + pub fn add_delete_instr_text(mut self, i: DeleteInstrText) -> Run { + self.children.push(RunChild::DeleteInstrText(Box::new(i))); + self + } + pub fn add_tab(mut self) -> Run { self.children.push(RunChild::Tab(Tab::new())); self @@ -279,7 +291,8 @@ impl BuildXML for Run { RunChild::CommentEnd(c) => b = b.add_child(c), RunChild::FieldChar(c) => b = b.add_child(c), RunChild::InstrText(c) => b = b.add_child(c), - _ => {} + RunChild::DeleteInstrText(c) => b = b.add_child(c), + RunChild::InstrTextString(_) => unreachable!(), } } b.close().build() diff --git a/docx-core/src/documents/elements/table_of_contents.rs b/docx-core/src/documents/elements/table_of_contents.rs index 91567ec..ebe1a53 100644 --- a/docx-core/src/documents/elements/table_of_contents.rs +++ b/docx-core/src/documents/elements/table_of_contents.rs @@ -33,12 +33,19 @@ impl Serialize for TocContent { } } +#[derive(Serialize, Debug, Clone, PartialEq, Default)] +pub struct TableOfContentsReviewData { + pub author: String, + pub date: String, +} + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TOCTOC_topic_ID0ELZO1.html // This struct is only used by writers #[derive(Serialize, Debug, Clone, PartialEq, Default)] pub struct TableOfContents { pub instr: InstrToC, pub items: Vec, + // don't use pub auto: bool, pub dirty: bool, pub alias: Option, @@ -49,6 +56,8 @@ pub struct TableOfContents { // it is inserted in after toc. #[serde(skip_serializing_if = "Vec::is_empty")] pub after_contents: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub delete: Option, } impl TableOfContents { @@ -84,6 +93,14 @@ impl TableOfContents { self } + pub fn delete(mut self, author: impl Into, date: impl Into) -> Self { + self.delete = Some(TableOfContentsReviewData { + author: author.into(), + date: date.into(), + }); + self + } + // pub fn tc_field_level_range(mut self, start: usize, end: usize) -> Self { // self.instr = self.instr.tc_field_level_range(start, end); // self @@ -133,13 +150,6 @@ impl BuildXML for TableOfContents { p = p.alias(alias); } if self.items.is_empty() { - let p1 = Paragraph::new().add_run( - Run::new() - .add_field_char(FieldCharType::Begin, true) - .add_instr_text(InstrText::TOC(self.instr.clone())) - .add_field_char(FieldCharType::Separate, false), - ); - let mut b = XMLBuilder::new() .open_structured_tag() .add_child(&p) @@ -156,6 +166,23 @@ impl BuildXML for TableOfContents { } } + let p1 = if let Some(ref del) = self.delete { + Paragraph::new().add_delete( + Delete::new().author(&del.author).date(&del.date).add_run( + Run::new() + .add_field_char(FieldCharType::Begin, true) + .add_delete_instr_text(DeleteInstrText::TOC(self.instr.clone())) + .add_field_char(FieldCharType::Separate, false), + ), + ) + } else { + Paragraph::new().add_run( + Run::new() + .add_field_char(FieldCharType::Begin, true) + .add_instr_text(InstrText::TOC(self.instr.clone())) + .add_field_char(FieldCharType::Separate, false), + ) + }; b = b.add_child(&p1); let p2 = Paragraph::new().add_run(Run::new().add_field_char(FieldCharType::End, false)); diff --git a/docx-core/src/xml_builder/elements.rs b/docx-core/src/xml_builder/elements.rs index fcc87b7..dfcccc4 100644 --- a/docx-core/src/xml_builder/elements.rs +++ b/docx-core/src/xml_builder/elements.rs @@ -159,6 +159,7 @@ impl XMLBuilder { closed!(field_character, "w:fldChar", "w:fldCharType", "w:dirty"); open!(open_instr_text, "w:instrText"); + open!(open_delete_instr_text, "w:delInstrText"); closed!(text_direction, "w:textDirection", "w:val"); diff --git a/docx-wasm/js/table-of-contents.ts b/docx-wasm/js/table-of-contents.ts index 51ef4a9..689dd8a 100644 --- a/docx-wasm/js/table-of-contents.ts +++ b/docx-wasm/js/table-of-contents.ts @@ -16,6 +16,7 @@ export class TableOfContents { _pageRefPlaceholder = ""; _beforeContents: (Paragraph | Table)[] = []; _afterContents: (Paragraph | Table)[] = []; + _delete: { author: string; date: string } | null = null; constructor(instrText?: string) { this._instrText = instrText; @@ -76,6 +77,11 @@ export class TableOfContents { return this; }; + delete = (author: string, date: string) => { + this._delete = { author, date }; + return this; + }; + addItem = (item: TableOfContentsItem) => { this._items.push(item); return this; @@ -112,6 +118,10 @@ export class TableOfContents { toc = toc.page_ref_placeholder(this._pageRefPlaceholder); } + if (this._delete) { + toc = toc.delete(this._delete.author, this._delete.date); + } + for (const sl of this._styleWithLevels) { toc = toc.add_style_with_level(sl.styleId, sl.level); } diff --git a/docx-wasm/src/table_of_contents.rs b/docx-wasm/src/table_of_contents.rs index d19e3b8..c7d472b 100644 --- a/docx-wasm/src/table_of_contents.rs +++ b/docx-wasm/src/table_of_contents.rs @@ -67,6 +67,11 @@ impl TableOfContents { self } + pub fn delete(mut self, author: &str, date: &str) -> Self { + self.0 = self.0.delete(author, date); + self + } + pub fn add_before_paragraph(mut self, p: Paragraph) -> Self { self.0 = self.0.add_before_paragraph(p.take()); self diff --git a/docx-wasm/test/__snapshots__/index.test.js.snap b/docx-wasm/test/__snapshots__/index.test.js.snap index d407252..bd58970 100644 --- a/docx-wasm/test/__snapshots__/index.test.js.snap +++ b/docx-wasm/test/__snapshots__/index.test.js.snap @@ -152157,6 +152157,27 @@ exports[`writer should write default font 3`] = ` " `; +exports[`writer should write deleted ToC 1`] = ` +" + + + + + +" +`; + +exports[`writer should write deleted ToC 2`] = ` +" + + +TOC \\\\uAfter contents + +Hello!! +World +" +`; + exports[`writer should write dirty and disable auto items ToC 1`] = ` " @@ -152486,7 +152507,7 @@ exports[`writer should write jpeg image with del 1`] = ` exports[`writer should write jpeg image with del 2`] = ` " - + @@ -152539,7 +152560,7 @@ exports[`writer should write jpeg image with ins 1`] = ` exports[`writer should write jpeg image with ins 2`] = ` " - + @@ -152801,7 +152822,7 @@ exports[`writer should write paragraph delete 1`] = ` exports[`writer should write paragraph delete 2`] = ` " - Hello world!!Foo + Hello world!!Foo " `; diff --git a/docx-wasm/test/index.test.js b/docx-wasm/test/index.test.js index 7556a28..b1cf3f8 100644 --- a/docx-wasm/test/index.test.js +++ b/docx-wasm/test/index.test.js @@ -795,6 +795,42 @@ describe("writer", () => { } }); + test("should write deleted ToC", () => { + const after = new w.Paragraph().addRun( + new w.Run().addText("After contents") + ); + const p1 = new w.Paragraph() + .addRun(new w.Run().addText("Hello!!")) + .pageBreakBefore(true) + .style("Heading1"); + const style1 = new w.Style("Heading1", "paragraph").name("Heading 1"); + const p2 = new w.Paragraph() + .addRun(new w.Run().addText("World")) + .pageBreakBefore(true) + .style("Heading2"); + const style2 = new w.Style("Heading2", "paragraph").name("Heading 2"); + const buffer = new w.Docx() + .addTableOfContents( + new w.TableOfContents(`TOC \o "1-3" \h \z \\u`) + .alias("Table of contents") + .delete("bokuweb", "2021-12-23T18:16:00Z") + .addAfterParagraph(after) + .dirty() + ) + .addParagraph(p1) + .addParagraph(p2) + .addStyle(style1) + .addStyle(style2) + .build(); + writeFileSync("../output/js/deleted_toc.docx", buffer); + const z = new Zip(Buffer.from(buffer)); + for (const e of z.getEntries()) { + if (e.entryName.match(/document.xml/)) { + expect(z.readAsText(e)).toMatchSnapshot(); + } + } + }); + test("should write paragraph delete", () => { const p1 = new w.Paragraph() .addRun(new w.Run().addText("Hello world!!"))