diff --git a/CHANGELOG.md b/CHANGELOG.md index 943e680..5999c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## @0.4.17 (26. Apr, 2024) - Floating images cause docx generation to fail with error `should end: LastElementNameNotAvailable` diff --git a/docx-core/src/documents/elements/instr_tc.rs b/docx-core/src/documents/elements/instr_tc.rs index a124ee2..11405fe 100644 --- a/docx-core/src/documents/elements/instr_tc.rs +++ b/docx-core/src/documents/elements/instr_tc.rs @@ -48,7 +48,7 @@ impl BuildXML for InstrTC { let mut b = XMLBuilder::from(stream); let raw = b.inner_mut()?; - write!(raw, "TC {}", self.text)?; + write!(raw, "TC \"{}\"", self.text)?; if let Some(ref t) = self.item_type_identifier { write!(raw, " \\f {}", t)?; diff --git a/docx-core/src/documents/elements/instr_toc.rs b/docx-core/src/documents/elements/instr_toc.rs index 2bcaa48..9fbb015 100644 --- a/docx-core/src/documents/elements/instr_toc.rs +++ b/docx-core/src/documents/elements/instr_toc.rs @@ -57,7 +57,7 @@ pub struct InstrToC { pub seq_field_identifier_for_prefix: Option, // \f #[serde(skip_serializing_if = "Option::is_none")] - pub tc_field_identifier: Option, + pub tc_field_identifier: Option>, // \h pub hyperlink: bool, // \w @@ -89,8 +89,8 @@ impl InstrToC { self } - pub fn tc_field_identifier(mut self, t: impl Into) -> Self { - self.tc_field_identifier = Some(t.into()); + pub fn tc_field_identifier(mut self, t: Option) -> Self { + self.tc_field_identifier = Some(t); self } @@ -192,7 +192,11 @@ impl BuildXML for InstrToC { // \f if let Some(ref t) = self.tc_field_identifier { - write!(raw, " \\f "{}"", t)?; + if let Some(ref t) = t { + write!(raw, " \\f "{}"", t)?; + } else { + write!(raw, " \\f")?; + } } // \l @@ -279,7 +283,7 @@ impl std::str::FromStr for InstrToC { type Err = (); fn from_str(instr: &str) -> Result { - let mut s = instr.split(' '); + let mut s = instr.split(' ').peekable(); let mut toc = InstrToC::new(); loop { if let Some(i) = s.next() { @@ -309,9 +313,19 @@ impl std::str::FromStr for InstrToC { } } "\\f" => { - if let Some(r) = s.next() { - let r = r.replace(""", "").replace('\"', ""); - toc = toc.tc_field_identifier(r); + if let Some(n) = s.peek() { + if !n.starts_with("\\") { + if let Some(r) = s.next() { + let r = r.replace(""", "").replace('\"', ""); + if r.is_empty() { + toc = toc.tc_field_identifier(None); + } else { + toc = toc.tc_field_identifier(Some(r)); + } + } + } else { + toc = toc.tc_field_identifier(None); + } } } "\\h" => toc = toc.hyperlink(), @@ -457,4 +471,18 @@ mod tests { .hyperlink() ); } + + #[test] + fn with_instr_text2() { + let s = r#"TOC \f \h \z \u"#; + let i = InstrToC::with_instr_text(s); + assert_eq!( + i, + InstrToC::new() + .tc_field_identifier(None) + .use_applied_paragraph_line_level() + .hide_tab_and_page_numbers_in_webview() + .hyperlink() + ); + } } diff --git a/docx-core/src/documents/elements/run.rs b/docx-core/src/documents/elements/run.rs index 7d198b5..dd8b8d1 100644 --- a/docx-core/src/documents/elements/run.rs +++ b/docx-core/src/documents/elements/run.rs @@ -194,6 +194,13 @@ impl Run { self } + pub fn add_tc(mut self, tc: InstrTC) -> Run { + self = self.add_field_char(crate::types::FieldCharType::Begin, false); + self = self.add_instr_text(InstrText::TC(tc)); + self = self.add_field_char(crate::types::FieldCharType::End, false); + self + } + pub fn add_instr_text(mut self, i: InstrText) -> Run { self.children.push(RunChild::InstrText(Box::new(i))); self diff --git a/docx-core/src/documents/elements/table_of_contents.rs b/docx-core/src/documents/elements/table_of_contents.rs index c403f24..c7f1832 100644 --- a/docx-core/src/documents/elements/table_of_contents.rs +++ b/docx-core/src/documents/elements/table_of_contents.rs @@ -83,6 +83,11 @@ impl TableOfContents { self } + pub fn tc_field_identifier(mut self, f: Option) -> Self { + self.instr = self.instr.tc_field_identifier(f); + self + } + pub fn add_style_with_level(mut self, s: StyleWithLevel) -> Self { self.instr = self.instr.add_style_with_level(s); self diff --git a/docx-wasm/js/index.ts b/docx-wasm/js/index.ts index 869461b..4b071f9 100644 --- a/docx-wasm/js/index.ts +++ b/docx-wasm/js/index.ts @@ -654,6 +654,7 @@ export * from "./paragraph-property"; export * from "./insert"; export * from "./delete"; export * from "./border"; +export * from "./tc"; export * from "./table"; export * from "./table-cell"; export * from "./table-cell-border"; diff --git a/docx-wasm/js/json/bindings/InstrToC.ts b/docx-wasm/js/json/bindings/InstrToC.ts index 6f227f3..e06e4b3 100644 --- a/docx-wasm/js/json/bindings/InstrToC.ts +++ b/docx-wasm/js/json/bindings/InstrToC.ts @@ -1,3 +1,3 @@ import type { StyleWithLevel } from "./StyleWithLevel"; -export interface InstrToC { headingStylesRange?: [number, number], tcFieldLevelRange?: [number, number], omitPageNumbersLevelRange?: [number, number], entryBookmarkName?: string, stylesWithLevels: Array, entryAndPageNumberSeparator?: string, sequenceAndPageNumbersSeparator?: string, captionLabel: string | null, captionLabelIncludingNumbers?: string, seqFieldIdentifierForPrefix?: string, tcFieldIdentifier?: string, hyperlink: boolean, preserveTab: boolean, preserveNewLine: boolean, useAppliedParagraphLineLevel: boolean, hideTabAndPageNumbersInWebview: boolean, } \ No newline at end of file +export interface InstrToC { headingStylesRange?: [number, number], tcFieldLevelRange?: [number, number], omitPageNumbersLevelRange?: [number, number], entryBookmarkName?: string, stylesWithLevels: Array, entryAndPageNumberSeparator?: string, sequenceAndPageNumbersSeparator?: string, captionLabel: string | null, captionLabelIncludingNumbers?: string, seqFieldIdentifierForPrefix?: string, tcFieldIdentifier?: string | null, hyperlink: boolean, preserveTab: boolean, preserveNewLine: boolean, useAppliedParagraphLineLevel: boolean, hideTabAndPageNumbersInWebview: boolean, } \ No newline at end of file diff --git a/docx-wasm/js/run.ts b/docx-wasm/js/run.ts index 76e833a..2a60e05 100644 --- a/docx-wasm/js/run.ts +++ b/docx-wasm/js/run.ts @@ -14,8 +14,16 @@ import { setRunProperty, VertAlignType, } from "./run-property"; +import { Tc } from "./tc"; -export type RunChild = Text | DeleteText | Tab | Break | Image | PositionalTab; +export type RunChild = + | Text + | DeleteText + | Tab + | Break + | Image + | PositionalTab + | Tc; export class Run { children: RunChild[] = []; @@ -51,6 +59,11 @@ export class Run { return this; } + addTc(tc: Tc) { + this.children.push(tc); + return this; + } + style(style: string) { this.property ??= createDefaultRunProperty(); this.property.style(style); @@ -178,6 +191,8 @@ export class Run { pic = pic.rotate(child.rot); } run = run.add_image(pic); + } else if (child instanceof Tc) { + run = run.add_tc(child._text, child._omitPageNumber, child._level); } }); diff --git a/docx-wasm/js/table-of-contents.ts b/docx-wasm/js/table-of-contents.ts index de77cb8..b35924e 100644 --- a/docx-wasm/js/table-of-contents.ts +++ b/docx-wasm/js/table-of-contents.ts @@ -23,6 +23,7 @@ export class TableOfContents { _afterContents: (Paragraph | Table)[] = []; _delete: { author: string; date: string } | null = null; _paragraphProperty: ParagraphProperty | null = null; + _tcFieldIdentifier?: string | null; constructor(instrText?: string) { this._instrText = instrText; @@ -53,6 +54,11 @@ export class TableOfContents { return this; }; + tcFieldIdentifier = (f?: string) => { + this._tcFieldIdentifier = f ?? null; + return this; + }; + addStyleWithLevel = (styleId: string, level: number) => { this._styleWithLevels.push({ styleId, level }); return this; @@ -142,6 +148,10 @@ export class TableOfContents { toc = toc.delete(this._delete.author, this._delete.date); } + if (this._tcFieldIdentifier !== undefined) { + toc = toc.tc_field_identifier(this._tcFieldIdentifier ?? undefined); + } + for (const sl of this._styleWithLevels) { toc = toc.add_style_with_level(sl.styleId, sl.level); } diff --git a/docx-wasm/js/tc.ts b/docx-wasm/js/tc.ts new file mode 100644 index 0000000..be8436e --- /dev/null +++ b/docx-wasm/js/tc.ts @@ -0,0 +1,19 @@ +export class Tc { + _text: string; + _level?: number | undefined; + _omitPageNumber: boolean; + + constructor(t: string) { + this._text = t; + } + + level(l: number) { + this._level = l; + return this; + } + + omitPageNumber() { + this._omitPageNumber = true; + return this; + } +} diff --git a/docx-wasm/package.json b/docx-wasm/package.json index af5fb03..b3ee9d8 100644 --- a/docx-wasm/package.json +++ b/docx-wasm/package.json @@ -1,6 +1,6 @@ { "name": "docx-wasm", - "version": "0.4.18-rc27", + "version": "0.4.18-rc30", "main": "dist/node/index.js", "browser": "dist/web/index.js", "author": "bokuweb ", diff --git a/docx-wasm/src/run.rs b/docx-wasm/src/run.rs index 2aadfb4..08b86e8 100644 --- a/docx-wasm/src/run.rs +++ b/docx-wasm/src/run.rs @@ -51,6 +51,16 @@ impl Run { self } + pub fn add_tc(mut self, text: &str, omits_page_number: bool, level: Option) -> Run { + self.0 = self.0.add_tc(docx_rs::InstrTC { + text: text.into(), + omits_page_number, + level, + item_type_identifier: None, + }); + self + } + pub fn style(mut self, style: &str) -> Run { self.0.run_property = self.0.run_property.style(style); self diff --git a/docx-wasm/src/table_of_contents.rs b/docx-wasm/src/table_of_contents.rs index 0359490..af9da7f 100644 --- a/docx-wasm/src/table_of_contents.rs +++ b/docx-wasm/src/table_of_contents.rs @@ -29,6 +29,11 @@ impl TableOfContents { self } + pub fn tc_field_identifier(mut self, f: Option) -> Self { + self.0.instr = self.0.instr.tc_field_identifier(f); + self + } + pub fn add_style_with_level(mut self, style: &str, level: usize) -> Self { self.0.instr = self .0 diff --git a/docx-wasm/test/__snapshots__/index.test.js.snap b/docx-wasm/test/__snapshots__/index.test.js.snap index 2b9451f..167a32c 100644 --- a/docx-wasm/test/__snapshots__/index.test.js.snap +++ b/docx-wasm/test/__snapshots__/index.test.js.snap @@ -171033,10 +171033,18 @@ Object { } `; +exports[`writer should write ToC with TC 1`] = `""`; + +exports[`writer should write ToC with TC 2`] = `"TOC \\\\fHello!!TC \\"Hello!!TC\\" \\\\l 1WorldTC \\"World!!TC\\" \\\\l 1"`; + exports[`writer should write ToC with instrText 1`] = `""`; exports[`writer should write ToC with instrText 2`] = `"Before contentsTOC \\\\uAfter contentsHello!!World"`; +exports[`writer should write ToC with instrText TC 1`] = `""`; + +exports[`writer should write ToC with instrText TC 2`] = `"TOC \\\\f \\\\h \\\\u \\\\zHello!!TC \\"Hello!!TC\\" \\\\l 1WorldTC \\"World!!TC\\" \\\\l 1"`; + exports[`writer should write ToC with items 1`] = `""`; exports[`writer should write ToC with items 2`] = `"TOCHello!!PAGEREF _Toc00000000 \\\\h2WorldPAGEREF _Toc00000001 \\\\h3Hello!!World"`; diff --git a/docx-wasm/test/index.test.js b/docx-wasm/test/index.test.js index 34d7b77..954d41e 100644 --- a/docx-wasm/test/index.test.js +++ b/docx-wasm/test/index.test.js @@ -1159,4 +1159,61 @@ describe("writer", () => { } } }); + + test("should write ToC with TC", () => { + const p1 = new w.Paragraph() + .addRun(new w.Run().addText("Hello!!")) + .addRun(new w.Run().addTc(new w.Tc("Hello!!TC").level(1))) + .pageBreakBefore(true); + const p2 = new w.Paragraph() + .addRun(new w.Run().addText("World")) + .addRun(new w.Run().addTc(new w.Tc("World!!TC").level(1))) + .pageBreakBefore(true); + const buffer = new w.Docx() + .addTableOfContents( + new w.TableOfContents() + .alias("Table of contents") + .dirty() + .tcFieldIdentifier() + .paragraphProperty(new w.ParagraphProperty().style("11")) + ) + .addParagraph(p1) + .addParagraph(p2) + .build(); + writeFileSync("../output/js/toc_with_tc.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 ToC with instrText TC", () => { + const p1 = new w.Paragraph() + .addRun(new w.Run().addText("Hello!!")) + .addRun(new w.Run().addTc(new w.Tc("Hello!!TC").level(1))) + .pageBreakBefore(true); + const p2 = new w.Paragraph() + .addRun(new w.Run().addText("World")) + .addRun(new w.Run().addTc(new w.Tc("World!!TC").level(1))) + .pageBreakBefore(true); + const buffer = new w.Docx() + .addTableOfContents( + new w.TableOfContents("TOC \\f \\h \\z \\u") + .alias("Table of contents") + .dirty() + .paragraphProperty(new w.ParagraphProperty().style("11")) + ) + .addParagraph(p1) + .addParagraph(p2) + .build(); + writeFileSync("../output/js/toc_with_instrtext_tc.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(); + } + } + }); });