From d5b6225a9042227820f35267bec9700358f161e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 21 Aug 2018 07:19:46 -0300 Subject: [PATCH 01/32] starting table of contents --- src/file/table-of-contents/index.ts | 1 + src/file/table-of-contents/properties.ts | 3 +++ src/file/table-of-contents/table-of-contents.ts | 3 +++ 3 files changed, 7 insertions(+) create mode 100644 src/file/table-of-contents/index.ts create mode 100644 src/file/table-of-contents/properties.ts create mode 100644 src/file/table-of-contents/table-of-contents.ts diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts new file mode 100644 index 0000000000..13700e9bfb --- /dev/null +++ b/src/file/table-of-contents/index.ts @@ -0,0 +1 @@ +export * from "./table-of-contents"; diff --git a/src/file/table-of-contents/properties.ts b/src/file/table-of-contents/properties.ts new file mode 100644 index 0000000000..fe7e749daf --- /dev/null +++ b/src/file/table-of-contents/properties.ts @@ -0,0 +1,3 @@ +import { XmlComponent } from "file/xml-components"; + +export class TableOfContentsProperties extends XmlComponent {} diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts new file mode 100644 index 0000000000..a4da01dd85 --- /dev/null +++ b/src/file/table-of-contents/table-of-contents.ts @@ -0,0 +1,3 @@ +import { XmlComponent } from "file/xml-components"; + +export class TableOfContents extends XmlComponent {} From a6a8012b3967c52b5195fa024423a78e158ae0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Wed, 22 Aug 2018 10:30:19 -0300 Subject: [PATCH 02/32] wrote some inital code --- .gitignore | 3 +++ src/file/table-of-contents/properties.ts | 6 +++--- src/file/table-of-contents/table-of-contents.spec.ts | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/file/table-of-contents/table-of-contents.spec.ts diff --git a/.gitignore b/.gitignore index 4f8777b466..1510fe9647 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ package-lock.json # Documents My Document.docx + +# Temporary folder +tmp \ No newline at end of file diff --git a/src/file/table-of-contents/properties.ts b/src/file/table-of-contents/properties.ts index fe7e749daf..4b452e42b1 100644 --- a/src/file/table-of-contents/properties.ts +++ b/src/file/table-of-contents/properties.ts @@ -1,3 +1,3 @@ -import { XmlComponent } from "file/xml-components"; - -export class TableOfContentsProperties extends XmlComponent {} +export class TableOfContentsProperties { + public headingRange = "1-6"; +} diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts new file mode 100644 index 0000000000..d576fa0706 --- /dev/null +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -0,0 +1,7 @@ +import { expect } from "chai"; + +describe("Table of Contents", () => { + it("should be true", () => { + expect(1).to.be.equal(1); + }); +}); From 8d83219bb6d77f546aa228b708a65a09c2e5bbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Wed, 29 Aug 2018 12:06:01 -0300 Subject: [PATCH 03/32] wrote some initial code --- docs/usage/table-of-contents.md | 1 + .../table-of-contents.spec.ts | 23 +++++++++++++++++-- .../table-of-contents/table-of-contents.ts | 11 ++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 docs/usage/table-of-contents.md diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md new file mode 100644 index 0000000000..c39c88a790 --- /dev/null +++ b/docs/usage/table-of-contents.md @@ -0,0 +1 @@ +# Table of Contents \ No newline at end of file diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index d576fa0706..9e22af0ca9 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -1,7 +1,26 @@ import { expect } from "chai"; +import { Formatter } from "../../export/formatter"; +import { TableOfContents } from "./"; + +const DEFAULT_TOC = { + "w:std": { + "w:sdtPr": {}, + "w:sdtEndPr": {}, + "w:sdtContent": { + "w:p": { + "w:pPr": {}, + }, + }, + }, +}; + describe("Table of Contents", () => { - it("should be true", () => { - expect(1).to.be.equal(1); + describe("#constructor", () => { + it("should construct a TOC with default options", () => { + const toc = new TableOfContents(); + const tree = new Formatter().format(toc); + expect(tree).to.be.deep.equal(DEFAULT_TOC); + }); }); }); diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index a4da01dd85..a04c94042a 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,3 +1,12 @@ import { XmlComponent } from "file/xml-components"; -export class TableOfContents extends XmlComponent {} +import { TableOfContentsProperties } from "./properties"; + +export class TableOfContents extends XmlComponent { + private readonly properties: TableOfContentsProperties; + + constructor(properties?: TableOfContentsProperties) { + super("w:std"); + this.properties = properties || new TableOfContentsProperties(); + } +} From d1044d262e5265f0258247317358bcdbcf8b47cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Fri, 31 Aug 2018 10:22:40 -0300 Subject: [PATCH 04/32] table of contents extending paragraph --- .../table-of-contents/table-of-contents.spec.ts | 12 ++++-------- src/file/table-of-contents/table-of-contents.ts | 15 +++++++-------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 9e22af0ca9..b437656c48 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -4,15 +4,11 @@ import { Formatter } from "../../export/formatter"; import { TableOfContents } from "./"; const DEFAULT_TOC = { - "w:std": { - "w:sdtPr": {}, - "w:sdtEndPr": {}, - "w:sdtContent": { - "w:p": { - "w:pPr": {}, - }, + "w:p": [ + { + "w:pPr": [], }, - }, + ], }; describe("Table of Contents", () => { diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index a04c94042a..d7cee3cc75 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,12 +1,11 @@ -import { XmlComponent } from "file/xml-components"; +// import { TableOfContentsProperties } from "./properties"; +import { Paragraph } from "../paragraph"; -import { TableOfContentsProperties } from "./properties"; +export class TableOfContents extends Paragraph { + // private readonly tocProperties: TableOfContentsProperties; -export class TableOfContents extends XmlComponent { - private readonly properties: TableOfContentsProperties; - - constructor(properties?: TableOfContentsProperties) { - super("w:std"); - this.properties = properties || new TableOfContentsProperties(); + constructor(/*tocProperties?: TableOfContentsProperties*/) { + super(); + // this.tocProperties = tocProperties || new TableOfContentsProperties(); } } From c55f82c425fefc90816b6a2c9a04a909ec91e083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Mon, 3 Sep 2018 10:48:50 -0300 Subject: [PATCH 05/32] test improvment --- src/file/table-of-contents/table-of-contents.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index b437656c48..8e6016de00 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -8,6 +8,13 @@ const DEFAULT_TOC = { { "w:pPr": [], }, + { + "w:r": [ + { + "w:fldChar": [], + }, + ], + }, ], }; From a367951d0730efc039c99194882854f8e14f99eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Mon, 3 Sep 2018 10:54:53 -0300 Subject: [PATCH 06/32] moved Begin, Separate and End from page-number.ts to field.ts --- src/file/paragraph/run/field.ts | 26 ++++++++++++++++++++++++++ src/file/paragraph/run/page-number.ts | 25 ------------------------- src/file/paragraph/run/run.ts | 3 ++- 3 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 src/file/paragraph/run/field.ts diff --git a/src/file/paragraph/run/field.ts b/src/file/paragraph/run/field.ts new file mode 100644 index 0000000000..8465418cf7 --- /dev/null +++ b/src/file/paragraph/run/field.ts @@ -0,0 +1,26 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> { + protected xmlKeys = { type: "w:fldCharType" }; +} + +export class Begin extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "begin" })); + } +} + +export class Separate extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "separate" })); + } +} + +export class End extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "end" })); + } +} diff --git a/src/file/paragraph/run/page-number.ts b/src/file/paragraph/run/page-number.ts index 4048c4f79a..ab3592fd18 100644 --- a/src/file/paragraph/run/page-number.ts +++ b/src/file/paragraph/run/page-number.ts @@ -1,20 +1,9 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> { - protected xmlKeys = { type: "w:fldCharType" }; -} - class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { protected xmlKeys = { space: "xml:space" }; } -export class Begin extends XmlComponent { - constructor() { - super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "begin" })); - } -} - export class Page extends XmlComponent { constructor() { super("w:instrText"); @@ -22,17 +11,3 @@ export class Page extends XmlComponent { this.root.push("PAGE"); } } - -export class Separate extends XmlComponent { - constructor() { - super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "separate" })); - } -} - -export class End extends XmlComponent { - constructor() { - super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "end" })); - } -} diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index 70b344842f..b7722e1e44 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -1,6 +1,7 @@ // http://officeopenxml.com/WPtext.php import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; +import { Begin, End, Separate } from "./field"; import { Bold, BoldComplexScript, @@ -13,7 +14,7 @@ import { SizeComplexScript, Strike, } from "./formatting"; -import { Begin, End, Page, Separate } from "./page-number"; +import { Page } from "./page-number"; import { RunProperties } from "./properties"; import { RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; From b1711ae293d89619912cbbeeb0502b413217d5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Mon, 3 Sep 2018 11:20:36 -0300 Subject: [PATCH 07/32] first simple version of TOC working --- src/file/table-of-contents/instruction.ts | 13 +++++++ .../table-of-contents.spec.ts | 35 +++++++++++++++++-- .../table-of-contents/table-of-contents.ts | 8 ++++- 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/file/table-of-contents/instruction.ts diff --git a/src/file/table-of-contents/instruction.ts b/src/file/table-of-contents/instruction.ts new file mode 100644 index 0000000000..eb67cbd49b --- /dev/null +++ b/src/file/table-of-contents/instruction.ts @@ -0,0 +1,13 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { + protected xmlKeys = { space: "xml:space" }; +} + +export class TableOfContentsInstruction extends XmlComponent { + constructor() { + super("w:instrText"); + this.root.push(new TextAttributes({ space: "preserve" })); + this.root.push("TOC"); + } +} diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 8e6016de00..98560fd00a 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -9,9 +9,39 @@ const DEFAULT_TOC = { "w:pPr": [], }, { - "w:r": [ + "w:fldChar": [ { - "w:fldChar": [], + _attr: { + "w:fldCharType": "begin", + }, + }, + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "TOC", + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, }, ], }, @@ -23,6 +53,7 @@ describe("Table of Contents", () => { it("should construct a TOC with default options", () => { const toc = new TableOfContents(); const tree = new Formatter().format(toc); + // console.log(JSON.stringify(tree, null, 2)); expect(tree).to.be.deep.equal(DEFAULT_TOC); }); }); diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index d7cee3cc75..3f0f206e59 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,5 +1,7 @@ // import { TableOfContentsProperties } from "./properties"; -import { Paragraph } from "../paragraph"; +import { Paragraph } from "file/paragraph"; +import { Begin, End, Separate } from "file/paragraph/run/field"; +import { TableOfContentsInstruction } from "./instruction"; export class TableOfContents extends Paragraph { // private readonly tocProperties: TableOfContentsProperties; @@ -7,5 +9,9 @@ export class TableOfContents extends Paragraph { constructor(/*tocProperties?: TableOfContentsProperties*/) { super(); // this.tocProperties = tocProperties || new TableOfContentsProperties(); + this.root.push(new Begin()); + this.root.push(new Separate()); + this.root.push(new TableOfContentsInstruction()); + this.root.push(new End()); } } From 12c1f82efec7d7db9e90ebe4b9d0b84a472749f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 4 Sep 2018 11:03:17 -0300 Subject: [PATCH 08/32] demo27 for TableOfContents --- demo/demo27.ts | 26 ++++++++++++++++++++++++++ src/file/index.ts | 1 + 2 files changed, 27 insertions(+) create mode 100644 demo/demo27.ts diff --git a/demo/demo27.ts b/demo/demo27.ts new file mode 100644 index 0000000000..34798a377f --- /dev/null +++ b/demo/demo27.ts @@ -0,0 +1,26 @@ +// Creates two paragraphs, one with a border and one without +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph, TableOfContents } from "../build"; + +const doc = new Document(); + +// Creates an table of contents with default properties +const toc = new TableOfContents(); + +// The class TableOfContents extends Paragraph and can be added as such. +doc.addParagraph(toc) + +doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); + +doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); +doc.addParagraph(new Paragraph("Header #2.1").heading2()); +doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/src/file/index.ts b/src/file/index.ts index d1e04ffcf7..8705f40011 100644 --- a/src/file/index.ts +++ b/src/file/index.ts @@ -6,4 +6,5 @@ export * from "./media"; export * from "./drawing"; export * from "./document"; export * from "./styles"; +export * from "./table-of-contents"; export * from "./xml-components"; From e9a007d446dd8cee36bd3d71d239261801b81d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 4 Sep 2018 12:05:41 -0300 Subject: [PATCH 09/32] create default properties for table-of-contents instruction --- demo/demo27.ts | 2 + src/file/table-of-contents/instruction.ts | 48 ++++++++++++++++++- .../table-of-contents.spec.ts | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/demo/demo27.ts b/demo/demo27.ts index 34798a377f..23cf91165f 100644 --- a/demo/demo27.ts +++ b/demo/demo27.ts @@ -5,6 +5,8 @@ import { Document, Packer, Paragraph, TableOfContents } from "../build"; const doc = new Document(); +// WordprocessingML docs for TableOfContents can be found here: +// http://officeopenxml.com/WPtableOfContents.php // Creates an table of contents with default properties const toc = new TableOfContents(); diff --git a/src/file/table-of-contents/instruction.ts b/src/file/table-of-contents/instruction.ts index eb67cbd49b..fae0339313 100644 --- a/src/file/table-of-contents/instruction.ts +++ b/src/file/table-of-contents/instruction.ts @@ -4,10 +4,54 @@ class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserv protected xmlKeys = { space: "xml:space" }; } +/** + * Options according to this docs: + * http://officeopenxml.com/WPtableOfContents.php + */ + +export class StyleLevel { + public styleName: string; + public level: number; +} +export class TableOfContentsInstructionProperties { + // \b option + public entriesFromSession: string; + // \h option + public hiperlink: true; + // \n option + public entryLevelsRange = "1-6"; + // \o option + public headerRange: string; + // \t option + public styles: StyleLevel[]; + // \z option +} + export class TableOfContentsInstruction extends XmlComponent { - constructor() { + private readonly properties: TableOfContentsInstructionProperties; + + constructor(properties?: TableOfContentsInstructionProperties) { super("w:instrText"); + this.properties = properties || new TableOfContentsInstructionProperties(); + this.root.push(new TextAttributes({ space: "preserve" })); - this.root.push("TOC"); + let instruction = "TOC"; + if (this.properties.entriesFromSession) { + instruction = `${instruction} \b "${this.properties.entriesFromSession}"`; + } + if (this.properties.hiperlink) { + instruction = `${instruction} \h`; + } + if (this.properties.entryLevelsRange) { + instruction = `${instruction} \n "${this.properties.entryLevelsRange}"`; + } + if (this.properties.headerRange) { + instruction = `${instruction} \o "${this.properties.headerRange}"`; + } + if (this.properties.styles && this.properties.styles.length) { + const styles = this.properties.styles.map((sl) => `${sl.styleName}, ${sl.level}`).join(", "); + instruction = `${instruction} \t "${styles}"`; + } + this.root.push(instruction); } } diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 98560fd00a..864f1c36cd 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -33,7 +33,7 @@ const DEFAULT_TOC = { "xml:space": "preserve", }, }, - "TOC", + 'TOC \n "1-6"', ], }, { From 7926f6c189bbac8d2038c4d2416a8387f0974314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 4 Sep 2018 17:29:24 -0300 Subject: [PATCH 10/32] escape bars --- src/file/table-of-contents/instruction.ts | 10 +++++----- src/file/table-of-contents/table-of-contents.spec.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/file/table-of-contents/instruction.ts b/src/file/table-of-contents/instruction.ts index fae0339313..7fe253e783 100644 --- a/src/file/table-of-contents/instruction.ts +++ b/src/file/table-of-contents/instruction.ts @@ -37,20 +37,20 @@ export class TableOfContentsInstruction extends XmlComponent { this.root.push(new TextAttributes({ space: "preserve" })); let instruction = "TOC"; if (this.properties.entriesFromSession) { - instruction = `${instruction} \b "${this.properties.entriesFromSession}"`; + instruction = `${instruction} \\b "${this.properties.entriesFromSession}"`; } if (this.properties.hiperlink) { - instruction = `${instruction} \h`; + instruction = `${instruction} \\h`; } if (this.properties.entryLevelsRange) { - instruction = `${instruction} \n "${this.properties.entryLevelsRange}"`; + instruction = `${instruction} \\n "${this.properties.entryLevelsRange}"`; } if (this.properties.headerRange) { - instruction = `${instruction} \o "${this.properties.headerRange}"`; + instruction = `${instruction} \\o "${this.properties.headerRange}"`; } if (this.properties.styles && this.properties.styles.length) { const styles = this.properties.styles.map((sl) => `${sl.styleName}, ${sl.level}`).join(", "); - instruction = `${instruction} \t "${styles}"`; + instruction = `${instruction} \\t "${styles}"`; } this.root.push(instruction); } diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 864f1c36cd..3ff1eab243 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -33,7 +33,7 @@ const DEFAULT_TOC = { "xml:space": "preserve", }, }, - 'TOC \n "1-6"', + 'TOC \\n "1-6"', ], }, { From aedfca377f34b2d491ae0e628a448772b3e71f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 4 Sep 2018 18:22:08 -0300 Subject: [PATCH 11/32] elements need to be inside runs --- .../table-of-contents.spec.ts | 64 +++++++++++-------- .../table-of-contents/table-of-contents.ts | 14 ++-- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 3ff1eab243..b725c8de92 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -9,39 +9,53 @@ const DEFAULT_TOC = { "w:pPr": [], }, { - "w:fldChar": [ + "w:r": [ { - _attr: { - "w:fldCharType": "begin", - }, + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "begin", + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + 'TOC \\n "1-6"', + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], }, ], }, { - "w:fldChar": [ + "w:r": [ { - _attr: { - "w:fldCharType": "separate", - }, + "w:rPr": [], }, - ], - }, - { - "w:instrText": [ { - _attr: { - "xml:space": "preserve", - }, - }, - 'TOC \\n "1-6"', - ], - }, - { - "w:fldChar": [ - { - _attr: { - "w:fldCharType": "end", - }, + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, + }, + ], }, ], }, diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index 3f0f206e59..7c0acec7d6 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,5 +1,6 @@ // import { TableOfContentsProperties } from "./properties"; import { Paragraph } from "file/paragraph"; +import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; import { TableOfContentsInstruction } from "./instruction"; @@ -9,9 +10,14 @@ export class TableOfContents extends Paragraph { constructor(/*tocProperties?: TableOfContentsProperties*/) { super(); // this.tocProperties = tocProperties || new TableOfContentsProperties(); - this.root.push(new Begin()); - this.root.push(new Separate()); - this.root.push(new TableOfContentsInstruction()); - this.root.push(new End()); + const firstRun = new Run(); + firstRun.addChildElement(new Begin()); + firstRun.addChildElement(new TableOfContentsInstruction()); + firstRun.addChildElement(new Separate()); + this.root.push(firstRun); + + const secondRun = new Run(); + secondRun.addChildElement(new End()); + this.root.push(secondRun); } } From 146d0ad9e5149e6783ac1c0e69bacecefb30ea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Mon, 10 Sep 2018 10:01:26 -0300 Subject: [PATCH 12/32] Moved addTableOfContents to File and creating a settings.xml and applying updateFields=true when there is a table of contents --- demo/demo27.ts | 12 +-- src/export/packer/next-compiler.spec.ts | 5 +- src/export/packer/next-compiler.ts | 5 ++ src/file/document/document.ts | 6 ++ src/file/file.ts | 13 ++++ src/file/settings/index.ts | 2 + src/file/settings/settings.spec.ts | 78 +++++++++++++++++++ src/file/settings/settings.ts | 77 ++++++++++++++++++ src/file/settings/update-fields.spec.ts | 46 +++++++++++ src/file/settings/update-fields.ts | 22 ++++++ src/file/table-of-contents/instruction.ts | 4 +- .../table-of-contents.spec.ts | 2 +- .../table-of-contents/table-of-contents.ts | 10 ++- 13 files changed, 268 insertions(+), 14 deletions(-) create mode 100644 src/file/settings/index.ts create mode 100644 src/file/settings/settings.spec.ts create mode 100644 src/file/settings/settings.ts create mode 100644 src/file/settings/update-fields.spec.ts create mode 100644 src/file/settings/update-fields.ts diff --git a/demo/demo27.ts b/demo/demo27.ts index 23cf91165f..c4fe6c44b3 100644 --- a/demo/demo27.ts +++ b/demo/demo27.ts @@ -1,25 +1,25 @@ // Creates two paragraphs, one with a border and one without // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, TableOfContents } from "../build"; +import { File, Packer, Paragraph, TableOfContents } from "../build"; -const doc = new Document(); +const doc = new File(); // WordprocessingML docs for TableOfContents can be found here: // http://officeopenxml.com/WPtableOfContents.php // Creates an table of contents with default properties const toc = new TableOfContents(); -// The class TableOfContents extends Paragraph and can be added as such. -doc.addParagraph(toc) +// A TableOfContents must be added via File class. +doc.addTableOfContents(toc) doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); -doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); +doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); doc.addParagraph(new Paragraph("Header #2.1").heading2()); -doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); +doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); const packer = new Packer(); diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index fc02d4a814..6398fa8b46 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -19,7 +19,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(17); + expect(fileNames).has.length(18); expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("docProps/core.xml"); @@ -31,6 +31,7 @@ describe("Compiler", () => { expect(fileNames).to.include("word/footnotes.xml"); expect(fileNames).to.include("word/_rels/footer1.xml.rels"); expect(fileNames).to.include("word/_rels/document.xml.rels"); + expect(fileNames).to.include("word/settings.xml"); expect(fileNames).to.include("[Content_Types].xml"); expect(fileNames).to.include("_rels/.rels"); }); @@ -47,7 +48,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(25); + expect(fileNames).has.length(26); expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/_rels/header1.xml.rels"); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index f06c09e196..6ee989d33e 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -23,6 +23,7 @@ interface IXmlifyedFileMapping { ContentTypes: IXmlifyedFile; AppProperties: IXmlifyedFile; FootNotes: IXmlifyedFile; + Settings: IXmlifyedFile; } export class Compiler { @@ -120,6 +121,10 @@ export class Compiler { data: xml(this.formatter.format(file.FootNotes)), path: "word/footnotes.xml", }, + Settings: { + data: xml(this.formatter.format(file.Settings)), + path: "word/settings.xml", + }, }; } } diff --git a/src/file/document/document.ts b/src/file/document/document.ts index 72e48852d7..bae187ca2d 100644 --- a/src/file/document/document.ts +++ b/src/file/document/document.ts @@ -2,6 +2,7 @@ import { XmlComponent } from "file/xml-components"; import { Paragraph } from "../paragraph"; import { Table } from "../table"; +import { TableOfContents } from "../table-of-contents"; import { Body } from "./body"; import { SectionPropertiesOptions } from "./body/section-properties"; import { DocumentAttributes } from "./document-attributes"; @@ -41,6 +42,11 @@ export class Document extends XmlComponent { return this; } + public addTableOfContents(toc: TableOfContents): Document { + this.body.push(toc); + return this; + } + public createParagraph(text?: string): Paragraph { const para = new Paragraph(text); this.addParagraph(para); diff --git a/src/file/file.ts b/src/file/file.ts index 472baf28b5..baff385317 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -10,10 +10,12 @@ import { Image, Media } from "./media"; import { Numbering } from "./numbering"; import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; +import { Settings } from "./settings"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; import { Table } from "./table"; +import { TableOfContents } from "./table-of-contents"; export class File { private readonly document: Document; @@ -26,6 +28,7 @@ export class File { private readonly headerWrapper: HeaderWrapper[] = []; private readonly footerWrapper: FooterWrapper[] = []; private readonly footNotes: FootNotes; + private readonly settings: Settings; private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; @@ -105,6 +108,12 @@ export class File { sectionPropertiesOptions.footerId = footer.Footer.ReferenceId; } this.document = new Document(sectionPropertiesOptions); + this.settings = new Settings(); + } + + public addTableOfContents(toc: TableOfContents): void { + this.document.addTableOfContents(toc); + this.settings.addUpdateFields(); } public addParagraph(paragraph: Paragraph): void { @@ -277,4 +286,8 @@ export class File { public get FootNotes(): FootNotes { return this.footNotes; } + + public get Settings(): Settings { + return this.settings; + } } diff --git a/src/file/settings/index.ts b/src/file/settings/index.ts new file mode 100644 index 0000000000..d750485a16 --- /dev/null +++ b/src/file/settings/index.ts @@ -0,0 +1,2 @@ +export * from "./settings"; +export * from "./update-fields"; diff --git a/src/file/settings/settings.spec.ts b/src/file/settings/settings.spec.ts new file mode 100644 index 0000000000..9b29e04d69 --- /dev/null +++ b/src/file/settings/settings.spec.ts @@ -0,0 +1,78 @@ +import { expect } from "chai"; + +import { Formatter } from "../../export/formatter"; +import { Settings } from "./"; + +describe("Settings", () => { + describe("#constructor", () => { + it("should create a empty Settings with correct rootKey", () => { + const settings = new Settings(); + const tree = new Formatter().format(settings); + let keys = Object.keys(tree); + + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:settings"); + + expect(tree["w:settings"]).is.an.instanceof(Array); + expect(tree["w:settings"]).has.length(1); + + keys = Object.keys(tree["w:settings"][0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + }); + }); + + describe("#addUpdateFields", () => { + const assertSettingsWithUpdateFields = (settings: Settings) => { + const tree = new Formatter().format(settings); + let keys = Object.keys(tree); + + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:settings"); + + const rootArray = tree["w:settings"]; + expect(rootArray).is.an.instanceof(Array); + expect(rootArray).has.length(2); + + keys = Object.keys(rootArray[0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + + keys = Object.keys(rootArray[1]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:updateFields"); + + const updateFieldsArray = rootArray[1]["w:updateFields"]; + keys = Object.keys(updateFieldsArray[0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + + const updateFieldsAttr = updateFieldsArray[0]._attr; + expect(updateFieldsAttr["w:val"]).to.be.equal(true); + }; + + it("should add a UpdateFields with value true", () => { + const settings = new Settings(); + settings.addUpdateFields(); + + assertSettingsWithUpdateFields(settings); + }); + + it("should add a UpdateFields with value true only once", () => { + const settings = new Settings(); + settings.addUpdateFields(); + + assertSettingsWithUpdateFields(settings); + + settings.addUpdateFields(); + + assertSettingsWithUpdateFields(settings); + }); + }); +}); diff --git a/src/file/settings/settings.ts b/src/file/settings/settings.ts new file mode 100644 index 0000000000..15e473efc9 --- /dev/null +++ b/src/file/settings/settings.ts @@ -0,0 +1,77 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { UpdateFields } from "./update-fields"; + +export interface ISettingsAttributesProperties { + wpc?: string; + mc?: string; + o?: string; + r?: string; + m?: string; + v?: string; + wp14?: string; + wp?: string; + w10?: string; + w?: string; + w14?: string; + w15?: string; + wpg?: string; + wpi?: string; + wne?: string; + wps?: string; + Ignorable?: string; +} + +export class SettingsAttributes extends XmlAttributeComponent { + protected xmlKeys = { + wpc: "xmlns:wpc", + mc: "xmlns:mc", + o: "xmlns:o", + r: "xmlns:r", + m: "xmlns:m", + v: "xmlns:v", + wp14: "xmlns:wp14", + wp: "xmlns:wp", + w10: "xmlns:w10", + w: "xmlns:w", + w14: "xmlns:w14", + w15: "xmlns:w15", + wpg: "xmlns:wpg", + wpi: "xmlns:wpi", + wne: "xmlns:wne", + wps: "xmlns:wps", + Ignorable: "mc:Ignorable", + }; +} + +export class Settings extends XmlComponent { + constructor() { + super("w:settings"); + this.root.push( + new SettingsAttributes({ + wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + mc: "http://schemas.openxmlformats.org/markup-compatibility/2006", + o: "urn:schemas-microsoft-com:office:office", + r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + m: "http://schemas.openxmlformats.org/officeDocument/2006/math", + v: "urn:schemas-microsoft-com:vml", + wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + w10: "urn:schemas-microsoft-com:office:word", + w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + w14: "http://schemas.microsoft.com/office/word/2010/wordml", + w15: "http://schemas.microsoft.com/office/word/2012/wordml", + wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + wne: "http://schemas.microsoft.com/office/word/2006/wordml", + wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + Ignorable: "w14 w15 wp14", + }), + ); + } + + public addUpdateFields(): void { + if (!this.root.find((child) => child instanceof UpdateFields)) { + this.addChildElement(new UpdateFields()); + } + } +} diff --git a/src/file/settings/update-fields.spec.ts b/src/file/settings/update-fields.spec.ts new file mode 100644 index 0000000000..0e0ed1ed4b --- /dev/null +++ b/src/file/settings/update-fields.spec.ts @@ -0,0 +1,46 @@ +import { expect } from "chai"; + +import { Formatter } from "../../export/formatter"; +import { UpdateFields } from "./"; + +const UF_TRUE = { + "w:updateFields": [ + { + _attr: { + "w:val": true, + }, + }, + ], +}; + +const UF_FALSE = { + "w:updateFields": [ + { + _attr: { + "w:val": false, + }, + }, + ], +}; + +describe("Update Fields", () => { + describe("#constructor", () => { + it("should construct a Update Fields with TRUE value by default", () => { + const uf = new UpdateFields(); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_TRUE); + }); + + it("should construct a Update Fields with TRUE value", () => { + const uf = new UpdateFields(true); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_TRUE); + }); + + it("should construct a Update Fields with FALSE value", () => { + const uf = new UpdateFields(false); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_FALSE); + }); + }); +}); diff --git a/src/file/settings/update-fields.ts b/src/file/settings/update-fields.ts new file mode 100644 index 0000000000..fdb3208f58 --- /dev/null +++ b/src/file/settings/update-fields.ts @@ -0,0 +1,22 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +export interface IUpdateFieldsAttributesProperties { + enabled: boolean; +} + +export class UpdateFieldsAttributes extends XmlAttributeComponent { + protected xmlKeys = { + enabled: "w:val", + }; +} + +export class UpdateFields extends XmlComponent { + constructor(enabled: boolean = true) { + super("w:updateFields"); + this.root.push( + new UpdateFieldsAttributes({ + enabled, + }), + ); + } +} diff --git a/src/file/table-of-contents/instruction.ts b/src/file/table-of-contents/instruction.ts index 7fe253e783..1ea9481141 100644 --- a/src/file/table-of-contents/instruction.ts +++ b/src/file/table-of-contents/instruction.ts @@ -19,9 +19,9 @@ export class TableOfContentsInstructionProperties { // \h option public hiperlink: true; // \n option - public entryLevelsRange = "1-6"; + public entryLevelsRange: string; // \o option - public headerRange: string; + public headerRange = "1-6"; // \t option public styles: StyleLevel[]; // \z option diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index b725c8de92..98c6a36936 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -29,7 +29,7 @@ const DEFAULT_TOC = { "xml:space": "preserve", }, }, - 'TOC \\n "1-6"', + 'TOC \\o "1-6"', ], }, { diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index 7c0acec7d6..d6e17e8cf6 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,14 +1,18 @@ // import { TableOfContentsProperties } from "./properties"; -import { Paragraph } from "file/paragraph"; +import { ParagraphProperties } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; +import { XmlComponent } from "file/xml-components"; import { TableOfContentsInstruction } from "./instruction"; -export class TableOfContents extends Paragraph { +export class TableOfContents extends XmlComponent { // private readonly tocProperties: TableOfContentsProperties; + private readonly properties: ParagraphProperties; constructor(/*tocProperties?: TableOfContentsProperties*/) { - super(); + super("w:p"); + this.properties = new ParagraphProperties(); + this.root.push(this.properties); // this.tocProperties = tocProperties || new TableOfContentsProperties(); const firstRun = new Run(); firstRun.addChildElement(new Begin()); From 0eb36be0539ec9b06c8455d3494a080f815cb97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Mon, 17 Sep 2018 22:09:05 -0300 Subject: [PATCH 13/32] there is no need for the settings.xml file anymore --- src/export/packer/next-compiler.spec.ts | 5 +- src/export/packer/next-compiler.ts | 5 -- src/file/file.ts | 8 +-- src/file/settings/index.ts | 2 - src/file/settings/settings.spec.ts | 78 ------------------------- src/file/settings/settings.ts | 77 ------------------------ src/file/settings/update-fields.spec.ts | 46 --------------- src/file/settings/update-fields.ts | 22 ------- 8 files changed, 4 insertions(+), 239 deletions(-) delete mode 100644 src/file/settings/index.ts delete mode 100644 src/file/settings/settings.spec.ts delete mode 100644 src/file/settings/settings.ts delete mode 100644 src/file/settings/update-fields.spec.ts delete mode 100644 src/file/settings/update-fields.ts diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index 6398fa8b46..fc02d4a814 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -19,7 +19,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(18); + expect(fileNames).has.length(17); expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("docProps/core.xml"); @@ -31,7 +31,6 @@ describe("Compiler", () => { expect(fileNames).to.include("word/footnotes.xml"); expect(fileNames).to.include("word/_rels/footer1.xml.rels"); expect(fileNames).to.include("word/_rels/document.xml.rels"); - expect(fileNames).to.include("word/settings.xml"); expect(fileNames).to.include("[Content_Types].xml"); expect(fileNames).to.include("_rels/.rels"); }); @@ -48,7 +47,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(26); + expect(fileNames).has.length(25); expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/_rels/header1.xml.rels"); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 6ee989d33e..f06c09e196 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -23,7 +23,6 @@ interface IXmlifyedFileMapping { ContentTypes: IXmlifyedFile; AppProperties: IXmlifyedFile; FootNotes: IXmlifyedFile; - Settings: IXmlifyedFile; } export class Compiler { @@ -121,10 +120,6 @@ export class Compiler { data: xml(this.formatter.format(file.FootNotes)), path: "word/footnotes.xml", }, - Settings: { - data: xml(this.formatter.format(file.Settings)), - path: "word/settings.xml", - }, }; } } diff --git a/src/file/file.ts b/src/file/file.ts index baff385317..69a2f53a2c 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -10,7 +10,6 @@ import { Image, Media } from "./media"; import { Numbering } from "./numbering"; import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; -import { Settings } from "./settings"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; @@ -28,7 +27,6 @@ export class File { private readonly headerWrapper: HeaderWrapper[] = []; private readonly footerWrapper: FooterWrapper[] = []; private readonly footNotes: FootNotes; - private readonly settings: Settings; private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; @@ -108,12 +106,10 @@ export class File { sectionPropertiesOptions.footerId = footer.Footer.ReferenceId; } this.document = new Document(sectionPropertiesOptions); - this.settings = new Settings(); } public addTableOfContents(toc: TableOfContents): void { this.document.addTableOfContents(toc); - this.settings.addUpdateFields(); } public addParagraph(paragraph: Paragraph): void { @@ -287,7 +283,7 @@ export class File { return this.footNotes; } - public get Settings(): Settings { - return this.settings; + public generateTablesOfContents(): void { + } } diff --git a/src/file/settings/index.ts b/src/file/settings/index.ts deleted file mode 100644 index d750485a16..0000000000 --- a/src/file/settings/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./settings"; -export * from "./update-fields"; diff --git a/src/file/settings/settings.spec.ts b/src/file/settings/settings.spec.ts deleted file mode 100644 index 9b29e04d69..0000000000 --- a/src/file/settings/settings.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "../../export/formatter"; -import { Settings } from "./"; - -describe("Settings", () => { - describe("#constructor", () => { - it("should create a empty Settings with correct rootKey", () => { - const settings = new Settings(); - const tree = new Formatter().format(settings); - let keys = Object.keys(tree); - - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:settings"); - - expect(tree["w:settings"]).is.an.instanceof(Array); - expect(tree["w:settings"]).has.length(1); - - keys = Object.keys(tree["w:settings"][0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - }); - }); - - describe("#addUpdateFields", () => { - const assertSettingsWithUpdateFields = (settings: Settings) => { - const tree = new Formatter().format(settings); - let keys = Object.keys(tree); - - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:settings"); - - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(2); - - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - - keys = Object.keys(rootArray[1]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:updateFields"); - - const updateFieldsArray = rootArray[1]["w:updateFields"]; - keys = Object.keys(updateFieldsArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - - const updateFieldsAttr = updateFieldsArray[0]._attr; - expect(updateFieldsAttr["w:val"]).to.be.equal(true); - }; - - it("should add a UpdateFields with value true", () => { - const settings = new Settings(); - settings.addUpdateFields(); - - assertSettingsWithUpdateFields(settings); - }); - - it("should add a UpdateFields with value true only once", () => { - const settings = new Settings(); - settings.addUpdateFields(); - - assertSettingsWithUpdateFields(settings); - - settings.addUpdateFields(); - - assertSettingsWithUpdateFields(settings); - }); - }); -}); diff --git a/src/file/settings/settings.ts b/src/file/settings/settings.ts deleted file mode 100644 index 15e473efc9..0000000000 --- a/src/file/settings/settings.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -import { UpdateFields } from "./update-fields"; - -export interface ISettingsAttributesProperties { - wpc?: string; - mc?: string; - o?: string; - r?: string; - m?: string; - v?: string; - wp14?: string; - wp?: string; - w10?: string; - w?: string; - w14?: string; - w15?: string; - wpg?: string; - wpi?: string; - wne?: string; - wps?: string; - Ignorable?: string; -} - -export class SettingsAttributes extends XmlAttributeComponent { - protected xmlKeys = { - wpc: "xmlns:wpc", - mc: "xmlns:mc", - o: "xmlns:o", - r: "xmlns:r", - m: "xmlns:m", - v: "xmlns:v", - wp14: "xmlns:wp14", - wp: "xmlns:wp", - w10: "xmlns:w10", - w: "xmlns:w", - w14: "xmlns:w14", - w15: "xmlns:w15", - wpg: "xmlns:wpg", - wpi: "xmlns:wpi", - wne: "xmlns:wne", - wps: "xmlns:wps", - Ignorable: "mc:Ignorable", - }; -} - -export class Settings extends XmlComponent { - constructor() { - super("w:settings"); - this.root.push( - new SettingsAttributes({ - wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", - mc: "http://schemas.openxmlformats.org/markup-compatibility/2006", - o: "urn:schemas-microsoft-com:office:office", - r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships", - m: "http://schemas.openxmlformats.org/officeDocument/2006/math", - v: "urn:schemas-microsoft-com:vml", - wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", - wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", - w10: "urn:schemas-microsoft-com:office:word", - w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main", - w14: "http://schemas.microsoft.com/office/word/2010/wordml", - w15: "http://schemas.microsoft.com/office/word/2012/wordml", - wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", - wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", - wne: "http://schemas.microsoft.com/office/word/2006/wordml", - wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", - Ignorable: "w14 w15 wp14", - }), - ); - } - - public addUpdateFields(): void { - if (!this.root.find((child) => child instanceof UpdateFields)) { - this.addChildElement(new UpdateFields()); - } - } -} diff --git a/src/file/settings/update-fields.spec.ts b/src/file/settings/update-fields.spec.ts deleted file mode 100644 index 0e0ed1ed4b..0000000000 --- a/src/file/settings/update-fields.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "../../export/formatter"; -import { UpdateFields } from "./"; - -const UF_TRUE = { - "w:updateFields": [ - { - _attr: { - "w:val": true, - }, - }, - ], -}; - -const UF_FALSE = { - "w:updateFields": [ - { - _attr: { - "w:val": false, - }, - }, - ], -}; - -describe("Update Fields", () => { - describe("#constructor", () => { - it("should construct a Update Fields with TRUE value by default", () => { - const uf = new UpdateFields(); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_TRUE); - }); - - it("should construct a Update Fields with TRUE value", () => { - const uf = new UpdateFields(true); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_TRUE); - }); - - it("should construct a Update Fields with FALSE value", () => { - const uf = new UpdateFields(false); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_FALSE); - }); - }); -}); diff --git a/src/file/settings/update-fields.ts b/src/file/settings/update-fields.ts deleted file mode 100644 index fdb3208f58..0000000000 --- a/src/file/settings/update-fields.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export interface IUpdateFieldsAttributesProperties { - enabled: boolean; -} - -export class UpdateFieldsAttributes extends XmlAttributeComponent { - protected xmlKeys = { - enabled: "w:val", - }; -} - -export class UpdateFields extends XmlComponent { - constructor(enabled: boolean = true) { - super("w:updateFields"); - this.root.push( - new UpdateFieldsAttributes({ - enabled, - }), - ); - } -} From 8e911698a50f6e379cf866b415e7e50582d42851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 18 Sep 2018 05:24:19 -0300 Subject: [PATCH 14/32] generating the content for a table of contents --- package.json | 1 + src/export/packer/next-compiler.ts | 2 + src/file/document/body/body.ts | 10 ++- src/file/document/document.ts | 8 +++ src/file/file.ts | 69 ++++++++++++++++++- src/file/paragraph/formatting/style.ts | 7 +- src/file/paragraph/formatting/tab-stop.ts | 26 +++---- src/file/paragraph/paragraph.ts | 23 ++++--- src/file/paragraph/properties.ts | 5 ++ src/file/table-of-contents/index.ts | 1 + .../page-reference-instruction.ts | 13 ++++ ...on.ts => table-of-contents-instruction.ts} | 4 ++ .../table-of-contents/table-of-contents.ts | 37 +++++++--- 13 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 src/file/table-of-contents/page-reference-instruction.ts rename src/file/table-of-contents/{instruction.ts => table-of-contents-instruction.ts} (95%) diff --git a/package.json b/package.json index 330ad72bac..84a941106b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "fast-xml-parser": "^3.3.6", "image-size": "^0.6.2", "jszip": "^3.1.5", + "lodash": "^4.17.11", "xml": "^1.0.1" }, "author": "Dolan Miu", diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index f06c09e196..e89786c78f 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -33,6 +33,8 @@ export class Compiler { } public async compile(file: File): Promise { + file.generateTablesOfContents(); + const zip = new JSZip(); const xmlifiedFileMapping = this.xmlifyFile(file); diff --git a/src/file/document/body/body.ts b/src/file/document/body/body.ts index e274fa359f..941a913bf9 100644 --- a/src/file/document/body/body.ts +++ b/src/file/document/body/body.ts @@ -1,5 +1,5 @@ import { IXmlableObject, XmlComponent } from "file/xml-components"; -import { Paragraph, ParagraphProperties } from "../.."; +import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties"; export class Body extends XmlComponent { @@ -53,6 +53,14 @@ export class Body extends XmlComponent { return this.defaultSection; } + public getTablesOfContents(): TableOfContents[] { + return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[]; + } + + public getParagraphs(): Paragraph[] { + return this.root.filter((child) => child instanceof Paragraph) as Paragraph[]; + } + private createSectionParagraph(section: SectionProperties): Paragraph { const paragraph = new Paragraph(); const properties = new ParagraphProperties(); diff --git a/src/file/document/document.ts b/src/file/document/document.ts index bae187ca2d..d9dae1020f 100644 --- a/src/file/document/document.ts +++ b/src/file/document/document.ts @@ -66,4 +66,12 @@ export class Document extends XmlComponent { public get Body(): Body { return this.body; } + + public getTablesOfContents(): TableOfContents[] { + return this.body.getTablesOfContents(); + } + + public getParagraphs(): Paragraph[] { + return this.body.getParagraphs(); + } } diff --git a/src/file/file.ts b/src/file/file.ts index 69a2f53a2c..429ec5d9be 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -1,3 +1,4 @@ +import { cloneDeep } from "lodash"; import { AppProperties } from "./app-properties/app-properties"; import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; @@ -8,13 +9,15 @@ import { FootNotes } from "./footnotes"; import { HeaderWrapper } from "./header-wrapper"; import { Image, Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; +import { Bookmark, Hyperlink, Paragraph, Run } from "./paragraph"; +import { Begin, End, Separate } from "./paragraph/run/field"; +import { Tab } from "./paragraph/run/tab"; import { Relationships } from "./relationships"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; import { Table } from "./table"; -import { TableOfContents } from "./table-of-contents"; +import { PageReferenceInstruction, TableOfContents } from "./table-of-contents"; export class File { private readonly document: Document; @@ -32,6 +35,7 @@ export class File { private readonly appProperties: AppProperties; private currentRelationshipId: number = 1; + private currentTocBookmarkId: number = 1; constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) { if (!options) { @@ -284,6 +288,65 @@ export class File { } public generateTablesOfContents(): void { - + this.document.getTablesOfContents().forEach((child) => this.generateContent(child)); + } + + private generateContent(toc: TableOfContents): void { + // console.log("TOC", JSON.stringify(toc)); + if (toc.getHeaderRange()) { + this.generateContentForHeaderRange(toc); + } + } + + private generateContentForHeaderRange(toc: TableOfContents): void { + const headerRange = toc.getHeaderRange(); + const hyphenIndex = headerRange.indexOf("-"); + if (hyphenIndex !== -1) { + const rangeBegin = parseInt(headerRange.substring(0, hyphenIndex), 2); + const rangeEnd = parseInt(headerRange.substring(hyphenIndex + 1), 2); + const styles = new Array(); + for (let i = rangeBegin; i <= rangeEnd; i++) { + styles.push(`Heading${i}`); + } + // console.log("Find Headers for range ", rangeBegin, " - ", rangeEnd, styles.join(",")); + this.document + .getParagraphs() + .filter((paragraph) => this.paragraphContainAnyStyle(paragraph, styles)) + .forEach((paragraph) => this.generateContentForParagraph(paragraph, toc)); + } else { + throw new Error(`Invalid headerRange: '${headerRange}'`); + } + } + + private paragraphContainAnyStyle(paragraph: Paragraph, styles: string[]): boolean { + return paragraph.getStyles().some((style) => styles.indexOf(style.styleId) !== -1); + } + + private generateContentForParagraph(paragraph: Paragraph, toc: TableOfContents): void { + const bookmarkId = `_TOC_${this.currentTocBookmarkId}`; + // console.log("Generating content for paragraph: ", bookmarkId); + + const generatedParagraph = cloneDeep(paragraph); + generatedParagraph.rightTabStop(9016, "dot"); + + const tabRun = new Run(); + tabRun.addChildElement(new Tab()); + generatedParagraph.addChildElement(tabRun); + + const beginRun = new Run(); + beginRun.addChildElement(new Begin()); + beginRun.addChildElement(new PageReferenceInstruction(bookmarkId)); + beginRun.addChildElement(new Separate()); + generatedParagraph.addRun(beginRun); + + const endRun = new Run(); + endRun.addChildElement(new End()); + generatedParagraph.addRun(endRun); + + toc.addGeneratedContent(generatedParagraph); + + paragraph.addBookmark(this.createBookmark(bookmarkId, "")); + // console.log("Paragraph after content generation", JSON.stringify(paragraph, null, 2)); + this.currentTocBookmarkId++; } } diff --git a/src/file/paragraph/formatting/style.ts b/src/file/paragraph/formatting/style.ts index 8a4ed4f9ad..30d57eb997 100644 --- a/src/file/paragraph/formatting/style.ts +++ b/src/file/paragraph/formatting/style.ts @@ -1,11 +1,14 @@ import { Attributes, XmlComponent } from "file/xml-components"; export class Style extends XmlComponent { - constructor(type: string) { + public readonly styleId: string; + + constructor(styleId: string) { super("w:pStyle"); + this.styleId = styleId; this.root.push( new Attributes({ - val: type, + val: styleId, }), ); } diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts index 86f61fb747..3159a12177 100644 --- a/src/file/paragraph/formatting/tab-stop.ts +++ b/src/file/paragraph/formatting/tab-stop.ts @@ -2,25 +2,27 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class TabStop extends XmlComponent { - constructor(tab: Tab) { + constructor(tab: TabStopItem) { super("w:tabs"); this.root.push(tab); } } export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start"; +export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore"; -export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number }> { - protected xmlKeys = { val: "w:val", pos: "w:pos" }; +export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader: LeaderType }> { + protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" }; } -export class Tab extends XmlComponent { - constructor(value: TabValue, position: string | number) { +export class TabStopItem extends XmlComponent { + constructor(value: TabValue, position: string | number, leader?: LeaderType) { super("w:tab"); this.root.push( new TabAttributes({ val: value, pos: position, + leader: leader || "none", }), ); } @@ -28,24 +30,24 @@ export class Tab extends XmlComponent { export class MaxRightTabStop extends TabStop { constructor() { - super(new Tab("right", 9026)); + super(new TabStopItem("right", 9026)); } } export class LeftTabStop extends TabStop { - constructor(position: number) { - super(new Tab("left", position)); + constructor(position: number, leader?: LeaderType) { + super(new TabStopItem("left", position, leader)); } } export class RightTabStop extends TabStop { - constructor(position: number) { - super(new Tab("right", position)); + constructor(position: number, leader?: LeaderType) { + super(new TabStopItem("right", position, leader)); } } export class CenterTabStop extends TabStop { - constructor(position: number) { - super(new Tab("center", position)); + constructor(position: number, leader?: LeaderType) { + super(new TabStopItem("center", position, leader)); } } diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 42b34e0c0d..c08d168fe8 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -12,7 +12,7 @@ import { KeepLines, KeepNext } from "./formatting/keep"; import { PageBreak, PageBreakBefore } from "./formatting/page-break"; import { ISpacingProperties, Spacing } from "./formatting/spacing"; import { Style } from "./formatting/style"; -import { CenterTabStop, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop"; +import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { Bookmark, Hyperlink } from "./links"; import { ParagraphProperties } from "./properties"; @@ -160,18 +160,18 @@ export class Paragraph extends XmlComponent { return this; } - public leftTabStop(position: number): Paragraph { - this.properties.push(new LeftTabStop(position)); + public leftTabStop(position: number, leader?: LeaderType): Paragraph { + this.properties.push(new LeftTabStop(position, leader)); return this; } - public rightTabStop(position: number): Paragraph { - this.properties.push(new RightTabStop(position)); + public rightTabStop(position: number, leader?: LeaderType): Paragraph { + this.properties.push(new RightTabStop(position, leader)); return this; } - public centerTabStop(position: number): Paragraph { - this.properties.push(new CenterTabStop(position)); + public centerTabStop(position: number, leader?: LeaderType): Paragraph { + this.properties.push(new CenterTabStop(position, leader)); return this; } @@ -232,7 +232,12 @@ export class Paragraph extends XmlComponent { return this; } - public get Properties(): ParagraphProperties { - return this.properties; + public getStyles(): Style[] { + return this.properties.getStyles(); + } + + public addTabStop(run: Run): Paragraph { + this.root.splice(1, 0, run); + return this; } } diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index 5f0c651246..fa0dd971ff 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -1,6 +1,7 @@ // http://officeopenxml.com/WPparagraphProperties.php import { XmlComponent } from "file/xml-components"; import { Border } from "./formatting/border"; +import { Style } from "./formatting/style"; export class ParagraphProperties extends XmlComponent { public paragraphBorder: Border; @@ -17,4 +18,8 @@ export class ParagraphProperties extends XmlComponent { public push(item: XmlComponent): void { this.root.push(item); } + + public getStyles(): Style[] { + return this.root.filter((child) => child instanceof Style) as Style[]; + } } diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts index 13700e9bfb..0f593a9652 100644 --- a/src/file/table-of-contents/index.ts +++ b/src/file/table-of-contents/index.ts @@ -1 +1,2 @@ export * from "./table-of-contents"; +export * from "./page-reference-instruction"; diff --git a/src/file/table-of-contents/page-reference-instruction.ts b/src/file/table-of-contents/page-reference-instruction.ts new file mode 100644 index 0000000000..e630e164b4 --- /dev/null +++ b/src/file/table-of-contents/page-reference-instruction.ts @@ -0,0 +1,13 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { + protected xmlKeys = { space: "xml:space" }; +} + +export class PageReferenceInstruction extends XmlComponent { + constructor(bookmarkId: string) { + super("w:instrText"); + this.root.push(new TextAttributes({ space: "preserve" })); + this.root.push(`PAGEREF ${bookmarkId} \h`); + } +} diff --git a/src/file/table-of-contents/instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts similarity index 95% rename from src/file/table-of-contents/instruction.ts rename to src/file/table-of-contents/table-of-contents-instruction.ts index 1ea9481141..aaf0df948d 100644 --- a/src/file/table-of-contents/instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -54,4 +54,8 @@ export class TableOfContentsInstruction extends XmlComponent { } this.root.push(instruction); } + + public getHeaderRange(): string { + return this.properties.headerRange; + } } diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index d6e17e8cf6..e9984f073e 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,27 +1,42 @@ // import { TableOfContentsProperties } from "./properties"; -import { ParagraphProperties } from "file/paragraph"; +import { Paragraph, ParagraphProperties } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; import { XmlComponent } from "file/xml-components"; -import { TableOfContentsInstruction } from "./instruction"; +import { TableOfContentsInstruction } from "./table-of-contents-instruction"; export class TableOfContents extends XmlComponent { // private readonly tocProperties: TableOfContentsProperties; private readonly properties: ParagraphProperties; + private readonly instruction: TableOfContentsInstruction; + constructor(/*tocProperties?: TableOfContentsProperties*/) { - super("w:p"); + super("w:sdt"); this.properties = new ParagraphProperties(); + this.instruction = new TableOfContentsInstruction(); this.root.push(this.properties); // this.tocProperties = tocProperties || new TableOfContentsProperties(); - const firstRun = new Run(); - firstRun.addChildElement(new Begin()); - firstRun.addChildElement(new TableOfContentsInstruction()); - firstRun.addChildElement(new Separate()); - this.root.push(firstRun); + const beginParagraph = new Paragraph(); + const beginRun = new Run(); + beginRun.addChildElement(new Begin()); + beginRun.addChildElement(this.instruction); + beginRun.addChildElement(new Separate()); + beginParagraph.addRun(beginRun); + this.root.push(beginParagraph); - const secondRun = new Run(); - secondRun.addChildElement(new End()); - this.root.push(secondRun); + const endParagraph = new Paragraph(); + const endRun = new Run(); + endRun.addChildElement(new End()); + endParagraph.addRun(endRun); + this.root.push(endParagraph); + } + + public getHeaderRange(): string { + return this.instruction.getHeaderRange(); + } + + public addGeneratedContent(paragraph: Paragraph): void { + this.root.splice(this.root.length - 1, 0, paragraph); } } From baf0f17bd6d7548711612fc0c6c796fb05831821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 18 Sep 2018 13:53:30 -0300 Subject: [PATCH 15/32] added a method for clear the page breaks of a paragraph --- src/file/file.ts | 8 +++++--- src/file/paragraph/paragraph.ts | 6 ++++++ src/file/paragraph/properties.ts | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/file/file.ts b/src/file/file.ts index 429ec5d9be..484f69bd2d 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -288,6 +288,7 @@ export class File { } public generateTablesOfContents(): void { + // console.log("generateTablesOfContents"); this.document.getTablesOfContents().forEach((child) => this.generateContent(child)); } @@ -301,9 +302,10 @@ export class File { private generateContentForHeaderRange(toc: TableOfContents): void { const headerRange = toc.getHeaderRange(); const hyphenIndex = headerRange.indexOf("-"); + // console.log("Hyphen Index: ", hyphenIndex); if (hyphenIndex !== -1) { - const rangeBegin = parseInt(headerRange.substring(0, hyphenIndex), 2); - const rangeEnd = parseInt(headerRange.substring(hyphenIndex + 1), 2); + const rangeBegin = parseInt(headerRange.substring(0, hyphenIndex), 10); + const rangeEnd = parseInt(headerRange.substring(hyphenIndex + 1), 10); const styles = new Array(); for (let i = rangeBegin; i <= rangeEnd; i++) { styles.push(`Heading${i}`); @@ -327,7 +329,7 @@ export class File { // console.log("Generating content for paragraph: ", bookmarkId); const generatedParagraph = cloneDeep(paragraph); - generatedParagraph.rightTabStop(9016, "dot"); + generatedParagraph.clearPageBreaks().rightTabStop(9016, "dot"); const tabRun = new Run(); tabRun.addChildElement(new Tab()); diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index c08d168fe8..2de547bb7a 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -240,4 +240,10 @@ export class Paragraph extends XmlComponent { this.root.splice(1, 0, run); return this; } + + public clearPageBreaks(): Paragraph { + this.root = this.root.filter((child) => !(child instanceof PageBreak)); + this.properties.clearPageBreaks(); + return this; + } } diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index fa0dd971ff..53ae637835 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -1,6 +1,7 @@ // http://officeopenxml.com/WPparagraphProperties.php import { XmlComponent } from "file/xml-components"; import { Border } from "./formatting/border"; +import { PageBreakBefore } from "./formatting/page-break"; import { Style } from "./formatting/style"; export class ParagraphProperties extends XmlComponent { @@ -22,4 +23,9 @@ export class ParagraphProperties extends XmlComponent { public getStyles(): Style[] { return this.root.filter((child) => child instanceof Style) as Style[]; } + + public clearPageBreaks(): ParagraphProperties { + this.root = this.root.filter((child) => !(child instanceof PageBreakBefore)); + return this; + } } From 7cd8864fb91b8eef1e14fe23d1943f68724c9e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Wed, 19 Sep 2018 11:01:07 -0300 Subject: [PATCH 16/32] added stdPr and stdContent to table of contents --- demo/demo27.ts | 2 +- src/file/file.ts | 4 +++- src/file/table-of-contents/alias.ts | 12 ++++++++++++ src/file/table-of-contents/std-content.ts | 12 ++++++++++++ src/file/table-of-contents/std-properties.ts | 9 +++++++++ .../table-of-contents/table-of-contents.ts | 18 ++++++++++++------ 6 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 src/file/table-of-contents/alias.ts create mode 100644 src/file/table-of-contents/std-content.ts create mode 100644 src/file/table-of-contents/std-properties.ts diff --git a/demo/demo27.ts b/demo/demo27.ts index c4fe6c44b3..bbba325052 100644 --- a/demo/demo27.ts +++ b/demo/demo27.ts @@ -24,5 +24,5 @@ doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); const packer = new Packer(); packer.toBuffer(doc).then((buffer) => { - fs.writeFileSync("My Document.docx", buffer); + fs.writeFileSync("tmp/My Document.docx", buffer); }); diff --git a/src/file/file.ts b/src/file/file.ts index 484f69bd2d..9e45e885ac 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -9,7 +9,7 @@ import { FootNotes } from "./footnotes"; import { HeaderWrapper } from "./header-wrapper"; import { Image, Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, Paragraph, Run } from "./paragraph"; +import { Bookmark, Hyperlink, Paragraph, Run, TextRun } from "./paragraph"; import { Begin, End, Separate } from "./paragraph/run/field"; import { Tab } from "./paragraph/run/tab"; import { Relationships } from "./relationships"; @@ -341,6 +341,8 @@ export class File { beginRun.addChildElement(new Separate()); generatedParagraph.addRun(beginRun); + generatedParagraph.addRun(new TextRun("?")); + const endRun = new Run(); endRun.addChildElement(new End()); generatedParagraph.addRun(endRun); diff --git a/src/file/table-of-contents/alias.ts b/src/file/table-of-contents/alias.ts new file mode 100644 index 0000000000..d3a3235391 --- /dev/null +++ b/src/file/table-of-contents/alias.ts @@ -0,0 +1,12 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class AliasAttributes extends XmlAttributeComponent<{ alias: string }> { + protected xmlKeys = { alias: "w:val" }; +} + +export class Alias extends XmlComponent { + constructor(alias: string) { + super("w:alias"); + this.root.push(new AliasAttributes({ alias })); + } +} diff --git a/src/file/table-of-contents/std-content.ts b/src/file/table-of-contents/std-content.ts new file mode 100644 index 0000000000..cbab4c8440 --- /dev/null +++ b/src/file/table-of-contents/std-content.ts @@ -0,0 +1,12 @@ +import { Paragraph } from "file/paragraph"; +import { XmlComponent } from "file/xml-components"; + +export class StdContent extends XmlComponent { + constructor() { + super("w:stdContent"); + } + + public addGeneratedContent(paragraph: Paragraph): void { + this.root.splice(this.root.length - 1, 0, paragraph); + } +} diff --git a/src/file/table-of-contents/std-properties.ts b/src/file/table-of-contents/std-properties.ts new file mode 100644 index 0000000000..628e92840c --- /dev/null +++ b/src/file/table-of-contents/std-properties.ts @@ -0,0 +1,9 @@ +import { XmlComponent } from "file/xml-components"; +import { Alias } from "./alias"; + +export class StdProperties extends XmlComponent { + constructor(alias: string) { + super("w:stdPr"); + this.root.push(new Alias(alias)); + } +} diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index e9984f073e..7a31373143 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,21 +1,27 @@ // import { TableOfContentsProperties } from "./properties"; -import { Paragraph, ParagraphProperties } from "file/paragraph"; +import { Paragraph } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; import { XmlComponent } from "file/xml-components"; +import { StdContent } from "./std-content"; +import { StdProperties } from "./std-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; export class TableOfContents extends XmlComponent { // private readonly tocProperties: TableOfContentsProperties; - private readonly properties: ParagraphProperties; + private readonly properties: StdProperties; + + private readonly content: StdContent; private readonly instruction: TableOfContentsInstruction; constructor(/*tocProperties?: TableOfContentsProperties*/) { super("w:sdt"); - this.properties = new ParagraphProperties(); + this.properties = new StdProperties("Table of Contents"); + this.content = new StdContent(); this.instruction = new TableOfContentsInstruction(); this.root.push(this.properties); + this.root.push(this.content); // this.tocProperties = tocProperties || new TableOfContentsProperties(); const beginParagraph = new Paragraph(); const beginRun = new Run(); @@ -23,13 +29,13 @@ export class TableOfContents extends XmlComponent { beginRun.addChildElement(this.instruction); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); - this.root.push(beginParagraph); + this.content.addChildElement(beginParagraph); const endParagraph = new Paragraph(); const endRun = new Run(); endRun.addChildElement(new End()); endParagraph.addRun(endRun); - this.root.push(endParagraph); + this.content.addChildElement(endParagraph); } public getHeaderRange(): string { @@ -37,6 +43,6 @@ export class TableOfContents extends XmlComponent { } public addGeneratedContent(paragraph: Paragraph): void { - this.root.splice(this.root.length - 1, 0, paragraph); + this.content.addGeneratedContent(paragraph); } } From 0684738ec290bffb1d277c750ae0abf58f4e9162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 07:09:17 -0300 Subject: [PATCH 17/32] removed lodash --- package.json | 1 - src/file/file.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 84a941106b..330ad72bac 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "fast-xml-parser": "^3.3.6", "image-size": "^0.6.2", "jszip": "^3.1.5", - "lodash": "^4.17.11", "xml": "^1.0.1" }, "author": "Dolan Miu", diff --git a/src/file/file.ts b/src/file/file.ts index 9e45e885ac..18bae5ce71 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -1,4 +1,3 @@ -import { cloneDeep } from "lodash"; import { AppProperties } from "./app-properties/app-properties"; import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; @@ -328,7 +327,9 @@ export class File { const bookmarkId = `_TOC_${this.currentTocBookmarkId}`; // console.log("Generating content for paragraph: ", bookmarkId); - const generatedParagraph = cloneDeep(paragraph); + // deep clone the original paragraph + const generatedParagraph = Object.assign(Object.create(Object.getPrototypeOf(paragraph)), paragraph); + generatedParagraph.clearPageBreaks().rightTabStop(9016, "dot"); const tabRun = new Run(); From c07b5cf709d43058b8f27f3174ddb9af3a1e4ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 10:11:59 -0300 Subject: [PATCH 18/32] added settings.xml back --- src/export/packer/next-compiler.spec.ts | 5 +- src/export/packer/next-compiler.ts | 5 ++ src/file/file.ts | 13 ++++- src/file/settings/index.ts | 2 + src/file/settings/settings.spec.ts | 60 ++++++++++++++++++++ src/file/settings/settings.ts | 73 +++++++++++++++++++++++++ src/file/settings/update-fields.spec.ts | 40 ++++++++++++++ src/file/settings/update-fields.ts | 19 +++++++ 8 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 src/file/settings/index.ts create mode 100644 src/file/settings/settings.spec.ts create mode 100644 src/file/settings/settings.ts create mode 100644 src/file/settings/update-fields.spec.ts create mode 100644 src/file/settings/update-fields.ts diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index fc02d4a814..774b71c26f 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -19,7 +19,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(17); + expect(fileNames).has.length(18); expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("docProps/core.xml"); @@ -29,6 +29,7 @@ describe("Compiler", () => { expect(fileNames).to.include("word/_rels/header1.xml.rels"); expect(fileNames).to.include("word/footer1.xml"); expect(fileNames).to.include("word/footnotes.xml"); + expect(fileNames).to.include("word/settings.xml"); expect(fileNames).to.include("word/_rels/footer1.xml.rels"); expect(fileNames).to.include("word/_rels/document.xml.rels"); expect(fileNames).to.include("[Content_Types].xml"); @@ -47,7 +48,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(25); + expect(fileNames).has.length(26); expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/_rels/header1.xml.rels"); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index e89786c78f..af00b2bc92 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -23,6 +23,7 @@ interface IXmlifyedFileMapping { ContentTypes: IXmlifyedFile; AppProperties: IXmlifyedFile; FootNotes: IXmlifyedFile; + Settings: IXmlifyedFile; } export class Compiler { @@ -122,6 +123,10 @@ export class Compiler { data: xml(this.formatter.format(file.FootNotes)), path: "word/footnotes.xml", }, + Settings: { + data: xml(this.formatter.format(file.Settings)), + path: "word/settings.xml", + }, }; } } diff --git a/src/file/file.ts b/src/file/file.ts index 18bae5ce71..45af9e385a 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -12,6 +12,7 @@ import { Bookmark, Hyperlink, Paragraph, Run, TextRun } from "./paragraph"; import { Begin, End, Separate } from "./paragraph/run/field"; import { Tab } from "./paragraph/run/tab"; import { Relationships } from "./relationships"; +import { Settings } from "./settings"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; @@ -29,6 +30,7 @@ export class File { private readonly headerWrapper: HeaderWrapper[] = []; private readonly footerWrapper: FooterWrapper[] = []; private readonly footNotes: FootNotes; + private readonly settings: Settings; private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; @@ -109,6 +111,7 @@ export class File { sectionPropertiesOptions.footerId = footer.Footer.ReferenceId; } this.document = new Document(sectionPropertiesOptions); + this.settings = new Settings(); } public addTableOfContents(toc: TableOfContents): void { @@ -286,9 +289,17 @@ export class File { return this.footNotes; } + public get Settings(): Settings { + return this.settings; + } + public generateTablesOfContents(): void { // console.log("generateTablesOfContents"); - this.document.getTablesOfContents().forEach((child) => this.generateContent(child)); + const TOCs = this.document.getTablesOfContents(); + if (TOCs && TOCs.length) { + this.settings.addUpdateFields(); + TOCs.forEach((child) => this.generateContent(child)); + } } private generateContent(toc: TableOfContents): void { diff --git a/src/file/settings/index.ts b/src/file/settings/index.ts new file mode 100644 index 0000000000..d750485a16 --- /dev/null +++ b/src/file/settings/index.ts @@ -0,0 +1,2 @@ +export * from "./settings"; +export * from "./update-fields"; diff --git a/src/file/settings/settings.spec.ts b/src/file/settings/settings.spec.ts new file mode 100644 index 0000000000..9ba0016319 --- /dev/null +++ b/src/file/settings/settings.spec.ts @@ -0,0 +1,60 @@ +import { expect } from "chai"; +import { Formatter } from "../../export/formatter"; +import { Settings } from "./"; +describe("Settings", () => { + describe("#constructor", () => { + it("should create a empty Settings with correct rootKey", () => { + const settings = new Settings(); + const tree = new Formatter().format(settings); + let keys = Object.keys(tree); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:settings"); + expect(tree["w:settings"]).is.an.instanceof(Array); + expect(tree["w:settings"]).has.length(1); + keys = Object.keys(tree["w:settings"][0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + }); + }); + describe("#addUpdateFields", () => { + const assertSettingsWithUpdateFields = (settings: Settings) => { + const tree = new Formatter().format(settings); + let keys = Object.keys(tree); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:settings"); + const rootArray = tree["w:settings"]; + expect(rootArray).is.an.instanceof(Array); + expect(rootArray).has.length(2); + keys = Object.keys(rootArray[0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + keys = Object.keys(rootArray[1]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("w:updateFields"); + const updateFieldsArray = rootArray[1]["w:updateFields"]; + keys = Object.keys(updateFieldsArray[0]); + expect(keys).is.an.instanceof(Array); + expect(keys).has.length(1); + expect(keys[0]).to.be.equal("_attr"); + const updateFieldsAttr = updateFieldsArray[0]._attr; + expect(updateFieldsAttr["w:val"]).to.be.equal(true); + }; + it("should add a UpdateFields with value true", () => { + const settings = new Settings(); + settings.addUpdateFields(); + assertSettingsWithUpdateFields(settings); + }); + it("should add a UpdateFields with value true only once", () => { + const settings = new Settings(); + settings.addUpdateFields(); + assertSettingsWithUpdateFields(settings); + settings.addUpdateFields(); + assertSettingsWithUpdateFields(settings); + }); + }); +}); diff --git a/src/file/settings/settings.ts b/src/file/settings/settings.ts new file mode 100644 index 0000000000..d955de0177 --- /dev/null +++ b/src/file/settings/settings.ts @@ -0,0 +1,73 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { UpdateFields } from "./update-fields"; +export interface ISettingsAttributesProperties { + wpc?: string; + mc?: string; + o?: string; + r?: string; + m?: string; + v?: string; + wp14?: string; + wp?: string; + w10?: string; + w?: string; + w14?: string; + w15?: string; + wpg?: string; + wpi?: string; + wne?: string; + wps?: string; + Ignorable?: string; +} +export class SettingsAttributes extends XmlAttributeComponent { + protected xmlKeys = { + wpc: "xmlns:wpc", + mc: "xmlns:mc", + o: "xmlns:o", + r: "xmlns:r", + m: "xmlns:m", + v: "xmlns:v", + wp14: "xmlns:wp14", + wp: "xmlns:wp", + w10: "xmlns:w10", + w: "xmlns:w", + w14: "xmlns:w14", + w15: "xmlns:w15", + wpg: "xmlns:wpg", + wpi: "xmlns:wpi", + wne: "xmlns:wne", + wps: "xmlns:wps", + Ignorable: "mc:Ignorable", + }; +} +export class Settings extends XmlComponent { + constructor() { + super("w:settings"); + this.root.push( + new SettingsAttributes({ + wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + mc: "http://schemas.openxmlformats.org/markup-compatibility/2006", + o: "urn:schemas-microsoft-com:office:office", + r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + m: "http://schemas.openxmlformats.org/officeDocument/2006/math", + v: "urn:schemas-microsoft-com:vml", + wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + w10: "urn:schemas-microsoft-com:office:word", + w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + w14: "http://schemas.microsoft.com/office/word/2010/wordml", + w15: "http://schemas.microsoft.com/office/word/2012/wordml", + wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + wne: "http://schemas.microsoft.com/office/word/2006/wordml", + wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + Ignorable: "w14 w15 wp14", + }), + ); + } + public addUpdateFields(): void { + if (!this.root.find((child) => child instanceof UpdateFields)) { + this.addChildElement(new UpdateFields()); + } + } +} diff --git a/src/file/settings/update-fields.spec.ts b/src/file/settings/update-fields.spec.ts new file mode 100644 index 0000000000..70fad8685c --- /dev/null +++ b/src/file/settings/update-fields.spec.ts @@ -0,0 +1,40 @@ +import { expect } from "chai"; +import { Formatter } from "../../export/formatter"; +import { UpdateFields } from "./"; +const UF_TRUE = { + "w:updateFields": [ + { + _attr: { + "w:val": true, + }, + }, + ], +}; +const UF_FALSE = { + "w:updateFields": [ + { + _attr: { + "w:val": false, + }, + }, + ], +}; +describe("Update Fields", () => { + describe("#constructor", () => { + it("should construct a Update Fields with TRUE value by default", () => { + const uf = new UpdateFields(); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_TRUE); + }); + it("should construct a Update Fields with TRUE value", () => { + const uf = new UpdateFields(true); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_TRUE); + }); + it("should construct a Update Fields with FALSE value", () => { + const uf = new UpdateFields(false); + const tree = new Formatter().format(uf); + expect(tree).to.be.deep.equal(UF_FALSE); + }); + }); +}); diff --git a/src/file/settings/update-fields.ts b/src/file/settings/update-fields.ts new file mode 100644 index 0000000000..782aa13264 --- /dev/null +++ b/src/file/settings/update-fields.ts @@ -0,0 +1,19 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +export interface IUpdateFieldsAttributesProperties { + enabled: boolean; +} +export class UpdateFieldsAttributes extends XmlAttributeComponent { + protected xmlKeys = { + enabled: "w:val", + }; +} +export class UpdateFields extends XmlComponent { + constructor(enabled: boolean = true) { + super("w:updateFields"); + this.root.push( + new UpdateFieldsAttributes({ + enabled, + }), + ); + } +} From 12e2ae9e91afbb5f1e4368abe6c52431278a852d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 10:30:16 -0300 Subject: [PATCH 19/32] making leader a option field and test improvments --- .../paragraph/formatting/tab-stop.spec.ts | 25 +++++++++++++++++-- src/file/paragraph/formatting/tab-stop.ts | 4 +-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/file/paragraph/formatting/tab-stop.spec.ts b/src/file/paragraph/formatting/tab-stop.spec.ts index bb9fa80d96..465b4deb36 100644 --- a/src/file/paragraph/formatting/tab-stop.spec.ts +++ b/src/file/paragraph/formatting/tab-stop.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import { Utility } from "../../../tests/utility"; -import { LeftTabStop, MaxRightTabStop } from "./tab-stop"; +import { LeftTabStop, RightTabStop, MaxRightTabStop } from "./tab-stop"; describe("LeftTabStop", () => { let tabStop: LeftTabStop; @@ -28,7 +28,28 @@ describe("LeftTabStop", () => { }); describe("RightTabStop", () => { - // TODO + let tabStop: RightTabStop; + + beforeEach(() => { + tabStop = new RightTabStop(100, "dot"); + }); + + describe("#constructor()", () => { + it("should create a Tab Stop with correct attributes", () => { + const newJson = Utility.jsonify(tabStop); + const attributes = { + val: "right", + pos: 100, + leader: "dot", + }; + assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes)); + }); + + it("should create a Tab Stop with w:tab", () => { + const newJson = Utility.jsonify(tabStop); + assert.equal(newJson.root[0].rootKey, "w:tab"); + }); + }); }); describe("MaxRightTabStop", () => { diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts index 3159a12177..bcaeee8371 100644 --- a/src/file/paragraph/formatting/tab-stop.ts +++ b/src/file/paragraph/formatting/tab-stop.ts @@ -11,7 +11,7 @@ export class TabStop extends XmlComponent { export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start"; export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore"; -export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader: LeaderType }> { +export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader?: LeaderType }> { protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" }; } @@ -22,7 +22,7 @@ export class TabStopItem extends XmlComponent { new TabAttributes({ val: value, pos: position, - leader: leader || "none", + leader, }), ); } From 4805efad2ef190345e8ea54c016b96101e6c1f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 10:31:49 -0300 Subject: [PATCH 20/32] organized imports --- src/file/paragraph/formatting/tab-stop.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file/paragraph/formatting/tab-stop.spec.ts b/src/file/paragraph/formatting/tab-stop.spec.ts index 465b4deb36..5b324623f5 100644 --- a/src/file/paragraph/formatting/tab-stop.spec.ts +++ b/src/file/paragraph/formatting/tab-stop.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import { Utility } from "../../../tests/utility"; -import { LeftTabStop, RightTabStop, MaxRightTabStop } from "./tab-stop"; +import { LeftTabStop, MaxRightTabStop, RightTabStop } from "./tab-stop"; describe("LeftTabStop", () => { let tabStop: LeftTabStop; From 1cff104baed952b67c91384484dc51528d593b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 10:47:10 -0300 Subject: [PATCH 21/32] correct sdt objects of table of contents --- .../{std-content.ts => sdt-content.ts} | 4 +- .../{std-properties.ts => sdt-properties.ts} | 4 +- .../table-of-contents.spec.ts | 98 ++++++++++++------- .../table-of-contents/table-of-contents.ts | 12 +-- 4 files changed, 73 insertions(+), 45 deletions(-) rename src/file/table-of-contents/{std-content.ts => sdt-content.ts} (76%) rename src/file/table-of-contents/{std-properties.ts => sdt-properties.ts} (68%) diff --git a/src/file/table-of-contents/std-content.ts b/src/file/table-of-contents/sdt-content.ts similarity index 76% rename from src/file/table-of-contents/std-content.ts rename to src/file/table-of-contents/sdt-content.ts index cbab4c8440..a059886b42 100644 --- a/src/file/table-of-contents/std-content.ts +++ b/src/file/table-of-contents/sdt-content.ts @@ -1,9 +1,9 @@ import { Paragraph } from "file/paragraph"; import { XmlComponent } from "file/xml-components"; -export class StdContent extends XmlComponent { +export class SdtContent extends XmlComponent { constructor() { - super("w:stdContent"); + super("w:sdtContent"); } public addGeneratedContent(paragraph: Paragraph): void { diff --git a/src/file/table-of-contents/std-properties.ts b/src/file/table-of-contents/sdt-properties.ts similarity index 68% rename from src/file/table-of-contents/std-properties.ts rename to src/file/table-of-contents/sdt-properties.ts index 628e92840c..bb8ef77a6b 100644 --- a/src/file/table-of-contents/std-properties.ts +++ b/src/file/table-of-contents/sdt-properties.ts @@ -1,9 +1,9 @@ import { XmlComponent } from "file/xml-components"; import { Alias } from "./alias"; -export class StdProperties extends XmlComponent { +export class SdtProperties extends XmlComponent { constructor(alias: string) { - super("w:stdPr"); + super("w:sdtPr"); this.root.push(new Alias(alias)); } } diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 98c6a36936..d29b3ffc75 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -4,39 +4,14 @@ import { Formatter } from "../../export/formatter"; import { TableOfContents } from "./"; const DEFAULT_TOC = { - "w:p": [ + "w:sdt": [ { - "w:pPr": [], - }, - { - "w:r": [ + "w:sdtPr": [ { - "w:rPr": [], - }, - { - "w:fldChar": [ + "w:alias": [ { _attr: { - "w:fldCharType": "begin", - }, - }, - ], - }, - { - "w:instrText": [ - { - _attr: { - "xml:space": "preserve", - }, - }, - 'TOC \\o "1-6"', - ], - }, - { - "w:fldChar": [ - { - _attr: { - "w:fldCharType": "separate", + "w:val": "Table of Contents", }, }, ], @@ -44,16 +19,69 @@ const DEFAULT_TOC = { ], }, { - "w:r": [ + "w:sdtContent": [ { - "w:rPr": [], + "w:p": [ + { + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "begin", + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + 'TOC \\o "1-6"', + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], + }, + ], + }, + ], }, { - "w:fldChar": [ + "w:p": [ { - _attr: { - "w:fldCharType": "end", - }, + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, + }, + ], + }, + ], }, ], }, diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index 7a31373143..f6f70558f9 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -3,22 +3,22 @@ import { Paragraph } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; import { XmlComponent } from "file/xml-components"; -import { StdContent } from "./std-content"; -import { StdProperties } from "./std-properties"; +import { SdtContent } from "./sdt-content"; +import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; export class TableOfContents extends XmlComponent { // private readonly tocProperties: TableOfContentsProperties; - private readonly properties: StdProperties; + private readonly properties: SdtProperties; - private readonly content: StdContent; + private readonly content: SdtContent; private readonly instruction: TableOfContentsInstruction; constructor(/*tocProperties?: TableOfContentsProperties*/) { super("w:sdt"); - this.properties = new StdProperties("Table of Contents"); - this.content = new StdContent(); + this.properties = new SdtProperties("Table of Contents"); + this.content = new SdtContent(); this.instruction = new TableOfContentsInstruction(); this.root.push(this.properties); this.root.push(this.content); From bf1f702e5a2c369cb4a67b621084e95ae0f644bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Thu, 20 Sep 2018 15:13:18 -0300 Subject: [PATCH 22/32] created a clone method for xml-components --- src/file/file.ts | 2 +- src/file/paragraph/paragraph.spec.ts | 19 +++++++++++++++++++ src/file/xml-components/xml-component.ts | 7 ++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/file/file.ts b/src/file/file.ts index 18bae5ce71..b4bd0fb9e4 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -328,7 +328,7 @@ export class File { // console.log("Generating content for paragraph: ", bookmarkId); // deep clone the original paragraph - const generatedParagraph = Object.assign(Object.create(Object.getPrototypeOf(paragraph)), paragraph); + const generatedParagraph = paragraph.clone() as Paragraph; generatedParagraph.clearPageBreaks().rightTabStop(9016, "dot"); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 2413769b1e..5bf4ee5f15 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -393,4 +393,23 @@ describe("Paragraph", () => { }); }); }); + + describe("#clone", () => { + it("changes in a cloned paragraph must not affect the original paragraph", () => { + paragraph.pageBreakBefore(); + + const clonedParagraph = paragraph.clone() as file.Paragraph; + clonedParagraph.clearPageBreaks(); + + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], + }); + + const clonedTree = new Formatter().format(clonedParagraph); + expect(clonedTree).to.deep.equal({ + "w:p": [{ "w:pPr": [] }], + }); + }); + }); }); diff --git a/src/file/xml-components/xml-component.ts b/src/file/xml-components/xml-component.ts index 0ed604e9ee..68b50da59d 100644 --- a/src/file/xml-components/xml-component.ts +++ b/src/file/xml-components/xml-component.ts @@ -30,7 +30,6 @@ export abstract class XmlComponent extends BaseXmlComponent { }; } - // TODO: Unused method public addChildElement(child: XmlComponent | string): XmlComponent { this.root.push(child); @@ -40,4 +39,10 @@ export abstract class XmlComponent extends BaseXmlComponent { public delete(): void { this.deleted = true; } + + public clone(): XmlComponent { + const newXmlComponent = Object.assign(Object.create(Object.getPrototypeOf(this)), this); + newXmlComponent.root = newXmlComponent.root.map((child) => (child instanceof XmlComponent ? child.clone() : child)); + return newXmlComponent as XmlComponent; + } } From 8b463b3bb69f56d4d8b9ef554f1e19870fa579aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Fri, 21 Sep 2018 10:26:28 -0300 Subject: [PATCH 23/32] updated clone deep dependency and make fields dirty to be updated when word is opened --- package.json | 1 + src/file/file.ts | 4 +- src/file/paragraph/formatting/tab-stop.ts | 4 +- src/file/paragraph/paragraph.spec.ts | 38 ++++++++++++++----- src/file/paragraph/paragraph.ts | 14 ++++++- src/file/paragraph/run/field.ts | 16 ++++---- .../table-of-contents.spec.ts | 1 + .../table-of-contents/table-of-contents.ts | 2 +- src/file/xml-components/xml-component.ts | 6 --- 9 files changed, 56 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index e63dd166bf..e10bd3b7ba 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "fast-xml-parser": "^3.3.6", "image-size": "^0.6.2", "jszip": "^3.1.5", + "lodash.clonedeep": "^4.5.0", "xml": "^1.0.1" }, "author": "Dolan Miu", diff --git a/src/file/file.ts b/src/file/file.ts index 8d1b07f827..63d5fa1656 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -345,14 +345,14 @@ export class File { // deep clone the original paragraph const generatedParagraph = paragraph.clone() as Paragraph; - generatedParagraph.clearPageBreaks().rightTabStop(9016, "dot"); + generatedParagraph.clearPageBreaks().maxRightTabStop("dot"); const tabRun = new Run(); tabRun.addChildElement(new Tab()); generatedParagraph.addChildElement(tabRun); const beginRun = new Run(); - beginRun.addChildElement(new Begin()); + beginRun.addChildElement(new Begin(true)); beginRun.addChildElement(new PageReferenceInstruction(bookmarkId)); beginRun.addChildElement(new Separate()); generatedParagraph.addRun(beginRun); diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts index bcaeee8371..5dc4f68e4f 100644 --- a/src/file/paragraph/formatting/tab-stop.ts +++ b/src/file/paragraph/formatting/tab-stop.ts @@ -29,8 +29,8 @@ export class TabStopItem extends XmlComponent { } export class MaxRightTabStop extends TabStop { - constructor() { - super(new TabStopItem("right", 9026)); + constructor(leader?: LeaderType) { + super(new TabStopItem("right", 9026, leader)); } } diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 5bf4ee5f15..2ab8836f96 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -398,18 +398,38 @@ describe("Paragraph", () => { it("changes in a cloned paragraph must not affect the original paragraph", () => { paragraph.pageBreakBefore(); - const clonedParagraph = paragraph.clone() as file.Paragraph; - clonedParagraph.clearPageBreaks(); - const tree = new Formatter().format(paragraph); - expect(tree).to.deep.equal({ - "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], - }); + expect(tree).to.deep.equal({ "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }] }, "Paragraph with a page break before"); + + const clonedParagraph = paragraph.clone(); + expect(clonedParagraph).to.be.instanceof(file.Paragraph); + expect(clonedParagraph.paragraphProperties).to.be.instanceof(file.ParagraphProperties); const clonedTree = new Formatter().format(clonedParagraph); - expect(clonedTree).to.deep.equal({ - "w:p": [{ "w:pPr": [] }], - }); + expect(clonedTree).to.deep.equal( + { + "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], + }, + "Cloned Paragraph with page break before", + ); + + clonedParagraph.clearPageBreaks(); + + const clonedTreeAfter = new Formatter().format(clonedParagraph); + expect(clonedTreeAfter).to.deep.equal( + { + "w:p": [{ "w:pPr": [] }], + }, + "Cloned Paragraph after clearPageBreaks must have no properties", + ); + + const treeAfter = new Formatter().format(paragraph); + expect(treeAfter).to.deep.equal( + { + "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], + }, + "Paragraph after clearPageBreaks in Cloned Paragraph must keep the properties.", + ); }); }); }); diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 2de547bb7a..16f6ef0cd1 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,4 +1,6 @@ // http://officeopenxml.com/WPparagraph.php +import * as cloneDeep from "lodash.clonedeep"; + import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { Image } from "file/media"; import { Num } from "file/numbering/num"; @@ -30,6 +32,10 @@ export class Paragraph extends XmlComponent { } } + public get paragraphProperties(): ParagraphProperties { + return this.properties; + } + public get Borders(): Border { return this.properties.paragraphBorder; } @@ -155,8 +161,8 @@ export class Paragraph extends XmlComponent { return this; } - public maxRightTabStop(): Paragraph { - this.properties.push(new MaxRightTabStop()); + public maxRightTabStop(leader?: LeaderType): Paragraph { + this.properties.push(new MaxRightTabStop(leader)); return this; } @@ -246,4 +252,8 @@ export class Paragraph extends XmlComponent { this.properties.clearPageBreaks(); return this; } + + public clone(): Paragraph { + return cloneDeep(this, false); + } } diff --git a/src/file/paragraph/run/field.ts b/src/file/paragraph/run/field.ts index 8465418cf7..0e72c1d624 100644 --- a/src/file/paragraph/run/field.ts +++ b/src/file/paragraph/run/field.ts @@ -1,26 +1,26 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> { - protected xmlKeys = { type: "w:fldCharType" }; +class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate"; dirty?: boolean }> { + protected xmlKeys = { type: "w:fldCharType", dirty: "w:dirty" }; } export class Begin extends XmlComponent { - constructor() { + constructor(dirty?: boolean) { super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "begin" })); + this.root.push(new FidCharAttrs({ type: "begin", dirty })); } } export class Separate extends XmlComponent { - constructor() { + constructor(dirty?: boolean) { super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "separate" })); + this.root.push(new FidCharAttrs({ type: "separate", dirty })); } } export class End extends XmlComponent { - constructor() { + constructor(dirty?: boolean) { super("w:fldChar"); - this.root.push(new FidCharAttrs({ type: "end" })); + this.root.push(new FidCharAttrs({ type: "end", dirty })); } } diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index d29b3ffc75..9ae3c53da5 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -35,6 +35,7 @@ const DEFAULT_TOC = { { _attr: { "w:fldCharType": "begin", + "w:dirty": true, }, }, ], diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index f6f70558f9..ded1be62cc 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -25,7 +25,7 @@ export class TableOfContents extends XmlComponent { // this.tocProperties = tocProperties || new TableOfContentsProperties(); const beginParagraph = new Paragraph(); const beginRun = new Run(); - beginRun.addChildElement(new Begin()); + beginRun.addChildElement(new Begin(true)); beginRun.addChildElement(this.instruction); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); diff --git a/src/file/xml-components/xml-component.ts b/src/file/xml-components/xml-component.ts index 68b50da59d..fae843db29 100644 --- a/src/file/xml-components/xml-component.ts +++ b/src/file/xml-components/xml-component.ts @@ -39,10 +39,4 @@ export abstract class XmlComponent extends BaseXmlComponent { public delete(): void { this.deleted = true; } - - public clone(): XmlComponent { - const newXmlComponent = Object.assign(Object.create(Object.getPrototypeOf(this)), this); - newXmlComponent.root = newXmlComponent.root.map((child) => (child instanceof XmlComponent ? child.clone() : child)); - return newXmlComponent as XmlComponent; - } } From 4ca81df401fe95acbaa2d9309b9360bb1283d414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Fri, 21 Sep 2018 11:16:14 -0300 Subject: [PATCH 24/32] TOC content generation aborted --- package.json | 1 - src/export/packer/next-compiler.ts | 3 +- src/file/file.ts | 78 +------------------ src/file/paragraph/paragraph.spec.ts | 39 ---------- src/file/paragraph/paragraph.ts | 6 -- src/file/table-of-contents/index.ts | 1 - .../page-reference-instruction.ts | 13 ---- src/file/table-of-contents/sdt-content.ts | 5 -- .../table-of-contents/table-of-contents.ts | 33 +++----- 9 files changed, 14 insertions(+), 165 deletions(-) delete mode 100644 src/file/table-of-contents/page-reference-instruction.ts diff --git a/package.json b/package.json index e10bd3b7ba..e63dd166bf 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "fast-xml-parser": "^3.3.6", "image-size": "^0.6.2", "jszip": "^3.1.5", - "lodash.clonedeep": "^4.5.0", "xml": "^1.0.1" }, "author": "Dolan Miu", diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index af00b2bc92..1ee02bf589 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -34,8 +34,6 @@ export class Compiler { } public async compile(file: File): Promise { - file.generateTablesOfContents(); - const zip = new JSZip(); const xmlifiedFileMapping = this.xmlifyFile(file); @@ -65,6 +63,7 @@ export class Compiler { } private xmlifyFile(file: File): IXmlifyedFileMapping { + file.verifyUpdateFields(); return { Document: { data: xml(this.formatter.format(file.Document), true), diff --git a/src/file/file.ts b/src/file/file.ts index 63d5fa1656..afa268a5a7 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -8,16 +8,14 @@ import { FootNotes } from "./footnotes"; import { HeaderWrapper } from "./header-wrapper"; import { Image, Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, Paragraph, Run, TextRun } from "./paragraph"; -import { Begin, End, Separate } from "./paragraph/run/field"; -import { Tab } from "./paragraph/run/tab"; +import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Settings } from "./settings"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; import { Table } from "./table"; -import { PageReferenceInstruction, TableOfContents } from "./table-of-contents"; +import { TableOfContents } from "./table-of-contents"; export class File { private readonly document: Document; @@ -36,7 +34,6 @@ export class File { private readonly appProperties: AppProperties; private currentRelationshipId: number = 1; - private currentTocBookmarkId: number = 1; constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) { if (!options) { @@ -297,76 +294,9 @@ export class File { return this.settings; } - public generateTablesOfContents(): void { - // console.log("generateTablesOfContents"); - const TOCs = this.document.getTablesOfContents(); - if (TOCs && TOCs.length) { + public verifyUpdateFields(): void { + if (this.document.getTablesOfContents().length) { this.settings.addUpdateFields(); - TOCs.forEach((child) => this.generateContent(child)); } } - - private generateContent(toc: TableOfContents): void { - // console.log("TOC", JSON.stringify(toc)); - if (toc.getHeaderRange()) { - this.generateContentForHeaderRange(toc); - } - } - - private generateContentForHeaderRange(toc: TableOfContents): void { - const headerRange = toc.getHeaderRange(); - const hyphenIndex = headerRange.indexOf("-"); - // console.log("Hyphen Index: ", hyphenIndex); - if (hyphenIndex !== -1) { - const rangeBegin = parseInt(headerRange.substring(0, hyphenIndex), 10); - const rangeEnd = parseInt(headerRange.substring(hyphenIndex + 1), 10); - const styles = new Array(); - for (let i = rangeBegin; i <= rangeEnd; i++) { - styles.push(`Heading${i}`); - } - // console.log("Find Headers for range ", rangeBegin, " - ", rangeEnd, styles.join(",")); - this.document - .getParagraphs() - .filter((paragraph) => this.paragraphContainAnyStyle(paragraph, styles)) - .forEach((paragraph) => this.generateContentForParagraph(paragraph, toc)); - } else { - throw new Error(`Invalid headerRange: '${headerRange}'`); - } - } - - private paragraphContainAnyStyle(paragraph: Paragraph, styles: string[]): boolean { - return paragraph.getStyles().some((style) => styles.indexOf(style.styleId) !== -1); - } - - private generateContentForParagraph(paragraph: Paragraph, toc: TableOfContents): void { - const bookmarkId = `_TOC_${this.currentTocBookmarkId}`; - // console.log("Generating content for paragraph: ", bookmarkId); - - // deep clone the original paragraph - const generatedParagraph = paragraph.clone() as Paragraph; - - generatedParagraph.clearPageBreaks().maxRightTabStop("dot"); - - const tabRun = new Run(); - tabRun.addChildElement(new Tab()); - generatedParagraph.addChildElement(tabRun); - - const beginRun = new Run(); - beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(new PageReferenceInstruction(bookmarkId)); - beginRun.addChildElement(new Separate()); - generatedParagraph.addRun(beginRun); - - generatedParagraph.addRun(new TextRun("?")); - - const endRun = new Run(); - endRun.addChildElement(new End()); - generatedParagraph.addRun(endRun); - - toc.addGeneratedContent(generatedParagraph); - - paragraph.addBookmark(this.createBookmark(bookmarkId, "")); - // console.log("Paragraph after content generation", JSON.stringify(paragraph, null, 2)); - this.currentTocBookmarkId++; - } } diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 2ab8836f96..2413769b1e 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -393,43 +393,4 @@ describe("Paragraph", () => { }); }); }); - - describe("#clone", () => { - it("changes in a cloned paragraph must not affect the original paragraph", () => { - paragraph.pageBreakBefore(); - - const tree = new Formatter().format(paragraph); - expect(tree).to.deep.equal({ "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }] }, "Paragraph with a page break before"); - - const clonedParagraph = paragraph.clone(); - expect(clonedParagraph).to.be.instanceof(file.Paragraph); - expect(clonedParagraph.paragraphProperties).to.be.instanceof(file.ParagraphProperties); - - const clonedTree = new Formatter().format(clonedParagraph); - expect(clonedTree).to.deep.equal( - { - "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], - }, - "Cloned Paragraph with page break before", - ); - - clonedParagraph.clearPageBreaks(); - - const clonedTreeAfter = new Formatter().format(clonedParagraph); - expect(clonedTreeAfter).to.deep.equal( - { - "w:p": [{ "w:pPr": [] }], - }, - "Cloned Paragraph after clearPageBreaks must have no properties", - ); - - const treeAfter = new Formatter().format(paragraph); - expect(treeAfter).to.deep.equal( - { - "w:p": [{ "w:pPr": [{ "w:pageBreakBefore": [] }] }], - }, - "Paragraph after clearPageBreaks in Cloned Paragraph must keep the properties.", - ); - }); - }); }); diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 16f6ef0cd1..a047ef1fa6 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,6 +1,4 @@ // http://officeopenxml.com/WPparagraph.php -import * as cloneDeep from "lodash.clonedeep"; - import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { Image } from "file/media"; import { Num } from "file/numbering/num"; @@ -252,8 +250,4 @@ export class Paragraph extends XmlComponent { this.properties.clearPageBreaks(); return this; } - - public clone(): Paragraph { - return cloneDeep(this, false); - } } diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts index 0f593a9652..13700e9bfb 100644 --- a/src/file/table-of-contents/index.ts +++ b/src/file/table-of-contents/index.ts @@ -1,2 +1 @@ export * from "./table-of-contents"; -export * from "./page-reference-instruction"; diff --git a/src/file/table-of-contents/page-reference-instruction.ts b/src/file/table-of-contents/page-reference-instruction.ts deleted file mode 100644 index e630e164b4..0000000000 --- a/src/file/table-of-contents/page-reference-instruction.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { - protected xmlKeys = { space: "xml:space" }; -} - -export class PageReferenceInstruction extends XmlComponent { - constructor(bookmarkId: string) { - super("w:instrText"); - this.root.push(new TextAttributes({ space: "preserve" })); - this.root.push(`PAGEREF ${bookmarkId} \h`); - } -} diff --git a/src/file/table-of-contents/sdt-content.ts b/src/file/table-of-contents/sdt-content.ts index a059886b42..e668da6052 100644 --- a/src/file/table-of-contents/sdt-content.ts +++ b/src/file/table-of-contents/sdt-content.ts @@ -1,12 +1,7 @@ -import { Paragraph } from "file/paragraph"; import { XmlComponent } from "file/xml-components"; export class SdtContent extends XmlComponent { constructor() { super("w:sdtContent"); } - - public addGeneratedContent(paragraph: Paragraph): void { - this.root.splice(this.root.length - 1, 0, paragraph); - } } diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index ded1be62cc..d4a93b57e1 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -8,41 +8,26 @@ import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; export class TableOfContents extends XmlComponent { - // private readonly tocProperties: TableOfContentsProperties; - private readonly properties: SdtProperties; - - private readonly content: SdtContent; - - private readonly instruction: TableOfContentsInstruction; - - constructor(/*tocProperties?: TableOfContentsProperties*/) { + constructor() { super("w:sdt"); - this.properties = new SdtProperties("Table of Contents"); - this.content = new SdtContent(); - this.instruction = new TableOfContentsInstruction(); - this.root.push(this.properties); - this.root.push(this.content); - // this.tocProperties = tocProperties || new TableOfContentsProperties(); + this.root.push(new SdtProperties("Table of Contents")); + + const content = new SdtContent(); + const beginParagraph = new Paragraph(); const beginRun = new Run(); beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(this.instruction); + beginRun.addChildElement(new TableOfContentsInstruction()); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); - this.content.addChildElement(beginParagraph); + content.addChildElement(beginParagraph); const endParagraph = new Paragraph(); const endRun = new Run(); endRun.addChildElement(new End()); endParagraph.addRun(endRun); - this.content.addChildElement(endParagraph); - } + content.addChildElement(endParagraph); - public getHeaderRange(): string { - return this.instruction.getHeaderRange(); - } - - public addGeneratedContent(paragraph: Paragraph): void { - this.content.addGeneratedContent(paragraph); + this.root.push(content); } } From 4de6b51e76d4d80f37b60873c0cc362acbf02fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 00:10:27 -0300 Subject: [PATCH 25/32] removed getStyles and clearPageBreak that became useless --- src/file/paragraph/paragraph.ts | 10 ---------- src/file/paragraph/properties.ts | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index a047ef1fa6..b7930970b5 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -236,18 +236,8 @@ export class Paragraph extends XmlComponent { return this; } - public getStyles(): Style[] { - return this.properties.getStyles(); - } - public addTabStop(run: Run): Paragraph { this.root.splice(1, 0, run); return this; } - - public clearPageBreaks(): Paragraph { - this.root = this.root.filter((child) => !(child instanceof PageBreak)); - this.properties.clearPageBreaks(); - return this; - } } diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index 53ae637835..5f0c651246 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -1,8 +1,6 @@ // http://officeopenxml.com/WPparagraphProperties.php import { XmlComponent } from "file/xml-components"; import { Border } from "./formatting/border"; -import { PageBreakBefore } from "./formatting/page-break"; -import { Style } from "./formatting/style"; export class ParagraphProperties extends XmlComponent { public paragraphBorder: Border; @@ -19,13 +17,4 @@ export class ParagraphProperties extends XmlComponent { public push(item: XmlComponent): void { this.root.push(item); } - - public getStyles(): Style[] { - return this.root.filter((child) => child instanceof Style) as Style[]; - } - - public clearPageBreaks(): ParagraphProperties { - this.root = this.root.filter((child) => !(child instanceof PageBreakBefore)); - return this; - } } From 808c5b00a080a46fd1101ad63ffa8d63e2be68e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 01:18:47 -0300 Subject: [PATCH 26/32] table of contents with all the options --- src/file/table-of-contents/index.ts | 1 + src/file/table-of-contents/properties.ts | 3 - .../table-of-contents-instruction.ts | 83 ++++++----- .../table-of-contents-properties.ts | 122 +++++++++++++++ .../table-of-contents.spec.ts | 140 ++++++++++++++++-- .../table-of-contents/table-of-contents.ts | 7 +- 6 files changed, 300 insertions(+), 56 deletions(-) delete mode 100644 src/file/table-of-contents/properties.ts create mode 100644 src/file/table-of-contents/table-of-contents-properties.ts diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts index 13700e9bfb..f36b16b738 100644 --- a/src/file/table-of-contents/index.ts +++ b/src/file/table-of-contents/index.ts @@ -1 +1,2 @@ export * from "./table-of-contents"; +export * from "./table-of-contents-properties"; diff --git a/src/file/table-of-contents/properties.ts b/src/file/table-of-contents/properties.ts deleted file mode 100644 index 4b452e42b1..0000000000 --- a/src/file/table-of-contents/properties.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class TableOfContentsProperties { - public headingRange = "1-6"; -} diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts index aaf0df948d..ec8a6f5a94 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -1,61 +1,68 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { TableOfContentsProperties } from "./table-of-contents-properties"; class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { protected xmlKeys = { space: "xml:space" }; } -/** - * Options according to this docs: - * http://officeopenxml.com/WPtableOfContents.php - */ - -export class StyleLevel { - public styleName: string; - public level: number; -} -export class TableOfContentsInstructionProperties { - // \b option - public entriesFromSession: string; - // \h option - public hiperlink: true; - // \n option - public entryLevelsRange: string; - // \o option - public headerRange = "1-6"; - // \t option - public styles: StyleLevel[]; - // \z option -} - export class TableOfContentsInstruction extends XmlComponent { - private readonly properties: TableOfContentsInstructionProperties; + private readonly properties: TableOfContentsProperties; - constructor(properties?: TableOfContentsInstructionProperties) { + constructor(properties?: TableOfContentsProperties) { super("w:instrText"); - this.properties = properties || new TableOfContentsInstructionProperties(); + this.properties = properties || new TableOfContentsProperties(); this.root.push(new TextAttributes({ space: "preserve" })); let instruction = "TOC"; - if (this.properties.entriesFromSession) { - instruction = `${instruction} \\b "${this.properties.entriesFromSession}"`; + if (this.properties.captionLabel) { + instruction = `${instruction} \\a "${this.properties.captionLabel}"`; + } + if (this.properties.entriesFromBookmark) { + instruction = `${instruction} \\b "${this.properties.entriesFromBookmark}"`; + } + if (this.properties.captionLabelIncludingNumbers) { + instruction = `${instruction} \\c "${this.properties.captionLabelIncludingNumbers}"`; + } + if (this.properties.sequenceAndPageNumbersSeparator) { + instruction = `${instruction} \\d "${this.properties.sequenceAndPageNumbersSeparator}"`; + } + if (this.properties.tcFieldIdentifier) { + instruction = `${instruction} \\f "${this.properties.tcFieldIdentifier}"`; } if (this.properties.hiperlink) { instruction = `${instruction} \\h`; } - if (this.properties.entryLevelsRange) { - instruction = `${instruction} \\n "${this.properties.entryLevelsRange}"`; + if (this.properties.tcFieldLevelRange) { + instruction = `${instruction} \\l "${this.properties.tcFieldLevelRange}`; } - if (this.properties.headerRange) { - instruction = `${instruction} \\o "${this.properties.headerRange}"`; + if (this.properties.pageNumbersEntryLevelsRange) { + instruction = `${instruction} \\n "${this.properties.pageNumbersEntryLevelsRange}`; } - if (this.properties.styles && this.properties.styles.length) { - const styles = this.properties.styles.map((sl) => `${sl.styleName}, ${sl.level}`).join(", "); + if (this.properties.headingStyleRange) { + instruction = `${instruction} \\o "${this.properties.headingStyleRange}`; + } + if (this.properties.entryAndPageNumberSeparator) { + instruction = `${instruction} \\p "${this.properties.entryAndPageNumberSeparator}`; + } + if (this.properties.seqFieldIdentifierForPrefix) { + instruction = `${instruction} \\s "${this.properties.seqFieldIdentifierForPrefix}`; + } + if (this.properties.stylesWithLevels && this.properties.stylesWithLevels.length) { + const styles = this.properties.stylesWithLevels.map((sl) => `${sl.styleName},${sl.level}`).join(","); instruction = `${instruction} \\t "${styles}"`; } + if (this.properties.useAppliedParagraphOutlineLevel) { + instruction = `${instruction} \\u`; + } + if (this.properties.preserveTabInEntries) { + instruction = `${instruction} \\w`; + } + if (this.properties.preserveNewLineInEntries) { + instruction = `${instruction} \\x`; + } + if (this.properties.hideTabAndPageNumbersInWebView) { + instruction = `${instruction} \\z`; + } this.root.push(instruction); } - - public getHeaderRange(): string { - return this.properties.headerRange; - } } diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts new file mode 100644 index 0000000000..2f50589429 --- /dev/null +++ b/src/file/table-of-contents/table-of-contents-properties.ts @@ -0,0 +1,122 @@ +export class StyleLevel { + public styleName: string; + public level: number; + + constructor(styleName: string, level: number) { + this.styleName = styleName; + this.level = level; + } +} + +/** + * Options according to this docs: + * https://www.ecma-international.org/publications/standards/Ecma-376.htm + * Part 1 - Page 1251 + * + * Short Guide: + * http://officeopenxml.com/WPtableOfContents.php + */ +export class TableOfContentsProperties { + /** + * \a option - Includes captioned items, but omits caption labels and numbers. + * The identifier designated by text in this switch's field-argument corresponds to the caption label. + * Use captionLabelIncludingNumbers (\c) to build a table of captions with labels and numbers. + */ + public captionLabel: string; + + /** + * \b option - Includes entries only from the portion of the document marked by + * the bookmark named by text in this switch's field-argument. + */ + public entriesFromBookmark: string; + + /** + * \c option - Includes figures, tables, charts, and other items that are numbered + * by a SEQ field (§17.16.5.56). The sequence identifier designated by text in this switch's + * field-argument, which corresponds to the caption label, shall match the identifier in the + * corresponding SEQ field. + */ + public captionLabelIncludingNumbers: string; + + /** + * \d option - When used with \s, the text in this switch's field-argument defines + * the separator between sequence and page numbers. The default separator is a hyphen (-). + */ + public sequenceAndPageNumbersSeparator: string; + + /** + * \f option - Includes only those TC fields whose identifier exactly matches the + * text in this switch's field-argument (which is typically a letter). + */ + public tcFieldIdentifier: string; + + /** + * \h option - Makes the table of contents entries hyperlinks. + */ + public hiperlink: boolean; + + /** + * \l option - Includes TC fields that assign entries to one of the levels specified + * by text in this switch's field-argument as a range having the form startLevel-endLevel, + * where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. + * TC fields that assign entries to lower levels are skipped. + */ + public tcFieldLevelRange: string; + + /** + * \n option - Without field-argument, omits page numbers from the table of contents. + * Page numbers are omitted from all levels unless a range of entry levels is specified by + * text in this switch's field-argument. A range is specified as for \l. + */ + public pageNumbersEntryLevelsRange: string; + + /** + * \o option - Uses paragraphs formatted with all or the specified range of builtin + * heading styles. Headings in a style range are specified by text in this switch's + * field-argument using the notation specified as for \l, where each integer corresponds + * to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). + * If no heading range is specified, all heading levels used in the document are listed. + */ + public headingStyleRange: string; + + /** + * \p option - Text in this switch's field-argument specifies a sequence of characters + * that separate an entry and its page number. The default is a tab with leader dots. + */ + public entryAndPageNumberSeparator: string; + + /** + * \s option - For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. + * The prefix depends on the type of entry. text in this switch's field-argument shall match the + * identifier in the SEQ field. + */ + public seqFieldIdentifierForPrefix: string; + + /** + * \t field-argument Uses paragraphs formatted with styles other than the built-in heading styles. + * Text in this switch's field-argument specifies those styles as a set of comma-separated doublets, + * with each doublet being a comma-separated set of style name and table of content level. + * \t can be combined with \o. + */ + public stylesWithLevels: StyleLevel[]; + + /** + * \u Uses the applied paragraph outline level. + */ + public useAppliedParagraphOutlineLevel = false; + + /** + * \w Preserves tab entries within table entries. + */ + public preserveTabInEntries = false; + + /** + * \x Preserves newline characters within table entries. + */ + public preserveNewLineInEntries = false; + + /** + * \z Hides tab leader and page numbers in web page view (§17.18.102). + */ + public hideTabAndPageNumbersInWebView = false; +} diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 9ae3c53da5..b306cceea2 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -1,7 +1,46 @@ import { expect } from "chai"; import { Formatter } from "../../export/formatter"; -import { TableOfContents } from "./"; +import { StyleLevel, TableOfContents, TableOfContentsProperties } from "./"; + +describe("Table of Contents", () => { + describe("#constructor", () => { + it("should construct a TOC without options", () => { + const toc = new TableOfContents(); + const tree = new Formatter().format(toc); + expect(tree).to.be.deep.equal(DEFAULT_TOC); + }); + + it("should construct a TOC with all the options and alias", () => { + const props = new TableOfContentsProperties(); + + props.captionLabel = "A"; + props.entriesFromBookmark = "B"; + props.captionLabelIncludingNumbers = "C"; + props.sequenceAndPageNumbersSeparator = "D"; + props.tcFieldIdentifier = "F"; + props.hiperlink = true; + props.tcFieldLevelRange = "L"; + props.pageNumbersEntryLevelsRange = "N"; + props.headingStyleRange = "O"; + props.entryAndPageNumberSeparator = "P"; + props.seqFieldIdentifierForPrefix = "S"; + + const styles = new Array(); + styles.push(new StyleLevel("SL", 1)); + styles.push(new StyleLevel("SL", 2)); + props.stylesWithLevels = styles; + props.useAppliedParagraphOutlineLevel = true; + props.preserveTabInEntries = true; + props.preserveNewLineInEntries = true; + props.hideTabAndPageNumbersInWebView = true; + + const toc = new TableOfContents("Summary", props); + const tree = new Formatter().format(toc); + expect(tree).to.be.deep.equal(COMPLETE_TOC); + }); + }); +}); const DEFAULT_TOC = { "w:sdt": [ @@ -47,7 +86,7 @@ const DEFAULT_TOC = { "xml:space": "preserve", }, }, - 'TOC \\o "1-6"', + "TOC", ], }, { @@ -91,13 +130,90 @@ const DEFAULT_TOC = { ], }; -describe("Table of Contents", () => { - describe("#constructor", () => { - it("should construct a TOC with default options", () => { - const toc = new TableOfContents(); - const tree = new Formatter().format(toc); - // console.log(JSON.stringify(tree, null, 2)); - expect(tree).to.be.deep.equal(DEFAULT_TOC); - }); - }); -}); +const COMPLETE_TOC = { + "w:sdt": [ + { + "w:sdtPr": [ + { + "w:alias": [ + { + _attr: { + "w:val": "Summary", + }, + }, + ], + }, + ], + }, + { + "w:sdtContent": [ + { + "w:p": [ + { + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "begin", + "w:dirty": true, + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + 'TOC \\a "A" \\b "B" \\c "C" \\d "D" \\f "F" \\h \\l "L \\n "N \\o "O \\p "P \\s "S \\t "SL,1,SL,2" \\u \\w \\x \\z', + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], + }, + ], + }, + ], + }, + { + "w:p": [ + { + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index d4a93b57e1..c659ec2ad2 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -6,18 +6,19 @@ import { XmlComponent } from "file/xml-components"; import { SdtContent } from "./sdt-content"; import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; +import { TableOfContentsProperties } from "./table-of-contents-properties"; export class TableOfContents extends XmlComponent { - constructor() { + constructor(alias: string = "Table of Contents", properties?: TableOfContentsProperties) { super("w:sdt"); - this.root.push(new SdtProperties("Table of Contents")); + this.root.push(new SdtProperties(alias)); const content = new SdtContent(); const beginParagraph = new Paragraph(); const beginRun = new Run(); beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(new TableOfContentsInstruction()); + beginRun.addChildElement(new TableOfContentsInstruction(properties)); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); content.addChildElement(beginParagraph); From 8ee6fd3e670d832ab0cb9eca9979bdadb154d2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 01:33:44 -0300 Subject: [PATCH 27/32] demo updated --- demo/demo28.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/demo/demo28.ts b/demo/demo28.ts index 3fdd14da46..80b6c7fcff 100644 --- a/demo/demo28.ts +++ b/demo/demo28.ts @@ -1,14 +1,29 @@ // Creates two paragraphs, one with a border and one without // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { File, Packer, Paragraph, TableOfContents } from "../build"; +import { File, Packer, Paragraph, StyleLevel, TableOfContents, TableOfContentsProperties } from "../build"; const doc = new File(); +// The first argument is an ID you use to apply the style to paragraphs +// The second argument is a human-friendly name to show in the UI +doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") + .basedOn("Heading1") + .next("Heading1") + .color("990000") + .italics() + // WordprocessingML docs for TableOfContents can be found here: // http://officeopenxml.com/WPtableOfContents.php -// Creates an table of contents with default properties -const toc = new TableOfContents(); + + +// Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, +// making the entries be hiperlinks for the paragraph +const props = new TableOfContentsProperties(); +props.hiperlink = true; +props.headingStyleRange = "1-5"; +props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] +const toc = new TableOfContents("Summary", props); // A TableOfContents must be added via File class. doc.addTableOfContents(toc); @@ -21,6 +36,8 @@ doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); doc.addParagraph(new Paragraph("Header #2.1").heading2()); doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); +doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore()); + const packer = new Packer(); packer.toBuffer(doc).then((buffer) => { From b12d6ef484e9cd74b0c05b0b347153db8bba01bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 02:35:08 -0300 Subject: [PATCH 28/32] initial documentation --- docs/usage/table-of-contents.md | 63 ++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index c39c88a790..7a027ad44c 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -1 +1,62 @@ -# Table of Contents \ No newline at end of file +# Table of Contents + +You can generate table of contents with docx.js. + +>Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically. +>This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?". +>You have say yes to Word generate the content of all table of contents. + +The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251). + +A short guide can be found [here](http://officeopenxml.com/WPtableOfContents.php). + +## Table of Contents Properties + +Here is the list of all properties that you can use to generate your tables of contents. + +| Option | Type | TOC Field Switch | Description | +| --- | --- | --- | --- | +|captionLabel|string|\a field-argument|Includes captioned items, but omits caption labels and numbers. The identifier designated by ```text``` in this switch's field-argument corresponds to the caption label. Use \c to build a table of captions with labels and numbers.| +|entriesFromBookmark|string|\b field-argument|Includes entries only from the portion of the document marked by the bookmark named by ```text``` in this switch's field-argument.| +|captionLabelIncludingNumbers|string|\c field-argument|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by ```text``` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.| +|sequenceAndPageNumbersSeparator|string|\d field-argument|When used with \s, the ```text``` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).| +|tcFieldIdentifier|string|\f field-argument|Includes only those TC fields whose identifier exactly matches the ```text``` in this switch's field-argument (which is typically a letter).| +|hiperlink|boolean|\h|Makes the table of contents entries hyperlinks.| +|tcFieldLevelRange|string|\l field-argument|Includes TC fields that assign entries to one of the levels specified by ```text``` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.| +|pageNumbersEntryLevelsRange|string|\n field-argument|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by ```text``` in this switch's field-argument. A range is specified as for \l.| +|headingStyleRange|string|\o field-argument|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by ```text``` in this switch's field-argument using the notation specified as for \l, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.| +|entryAndPageNumberSeparator|string|\p field-argument|```text``` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.| +|seqFieldIdentifierForPrefix|string|\s field-argument|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. ```text``` in this switch's field-argument shall match the identifier in the SEQ field.| +|stylesWithLevels|StyleLevel[]|\t field-argument| Uses paragraphs formatted with styles other than the built-in heading styles. ```text``` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. \t can be combined with \o.| +|useAppliedParagraphOutlineLevel|boolean|\u|Uses the applied paragraph outline level.| +|preserveTabInEntries|boolean|\w|Preserves tab entries within table entries.| +|preserveNewLineInEntries|boolean|\x|Preserves newline characters within table entries.| +|hideTabAndPageNumbersInWebView|boolean|\z|Hides tab leader and page numbers in web page view (§17.18.102).| + +## Examples + +```js +// Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, +// making the entries be hiperlinks for the paragraph +const props = new TableOfContentsProperties(); +props.hiperlink = true; +props.headingStyleRange = "1-5"; +props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] +const toc = new TableOfContents("Summary", props); + +// A TableOfContents must be added via File class. +doc.addTableOfContents(toc); + +doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); + +doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); +doc.addParagraph(new Paragraph("Header #2.1").heading2()); +doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); + +doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore()); + +``` + +Check `demo28.js` to see the complete example. \ No newline at end of file From 72e3d229dc37c704083d525c85bce4ee035edf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 02:36:00 -0300 Subject: [PATCH 29/32] unnecessary comment removed --- demo/demo28.ts | 1 - docs/usage/table-of-contents.md | 1 - 2 files changed, 2 deletions(-) diff --git a/demo/demo28.ts b/demo/demo28.ts index 80b6c7fcff..85a22b0328 100644 --- a/demo/demo28.ts +++ b/demo/demo28.ts @@ -25,7 +25,6 @@ props.headingStyleRange = "1-5"; props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] const toc = new TableOfContents("Summary", props); -// A TableOfContents must be added via File class. doc.addTableOfContents(toc); doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index 7a027ad44c..c664924f96 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -44,7 +44,6 @@ props.headingStyleRange = "1-5"; props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] const toc = new TableOfContents("Summary", props); -// A TableOfContents must be added via File class. doc.addTableOfContents(toc); doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); From 00efedaa09eafc4ff7702d062b25e0b5c7cd1aae Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 25 Sep 2018 19:49:44 +0100 Subject: [PATCH 30/32] Fix spelling and linting and improve readme --- demo/demo28.ts | 11 ++-- docs/usage/table-of-contents.md | 53 ++++++++++--------- .../table-of-contents-instruction.ts | 2 +- .../table-of-contents-properties.ts | 2 +- .../table-of-contents.spec.ts | 2 +- .../table-of-contents/table-of-contents.ts | 1 - 6 files changed, 36 insertions(+), 35 deletions(-) diff --git a/demo/demo28.ts b/demo/demo28.ts index 85a22b0328..1f7f56e2fa 100644 --- a/demo/demo28.ts +++ b/demo/demo28.ts @@ -11,18 +11,17 @@ doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") .basedOn("Heading1") .next("Heading1") .color("990000") - .italics() + .italics(); // WordprocessingML docs for TableOfContents can be found here: // http://officeopenxml.com/WPtableOfContents.php - // Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, -// making the entries be hiperlinks for the paragraph +// making the entries be hyperlinks for the paragraph const props = new TableOfContentsProperties(); -props.hiperlink = true; +props.hyperlink = true; props.headingStyleRange = "1-5"; -props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] +props.stylesWithLevels = [new StyleLevel("MySpectacularStyle", 1)]; const toc = new TableOfContents("Summary", props); doc.addTableOfContents(toc); @@ -40,5 +39,5 @@ doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularSt const packer = new Packer(); packer.toBuffer(doc).then((buffer) => { - fs.writeFileSync("tmp/My Document.docx", buffer); + fs.writeFileSync("My Document.docx", buffer); }); diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index c664924f96..6899eae5a7 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -1,6 +1,6 @@ # Table of Contents -You can generate table of contents with docx.js. +You can generate table of contents with `docx`. >Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically. >This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?". @@ -12,34 +12,34 @@ A short guide can be found [here](http://officeopenxml.com/WPtableOfContents.php ## Table of Contents Properties -Here is the list of all properties that you can use to generate your tables of contents. +Here is the list of all properties that you can use to generate your tables of contents: | Option | Type | TOC Field Switch | Description | | --- | --- | --- | --- | -|captionLabel|string|\a field-argument|Includes captioned items, but omits caption labels and numbers. The identifier designated by ```text``` in this switch's field-argument corresponds to the caption label. Use \c to build a table of captions with labels and numbers.| -|entriesFromBookmark|string|\b field-argument|Includes entries only from the portion of the document marked by the bookmark named by ```text``` in this switch's field-argument.| -|captionLabelIncludingNumbers|string|\c field-argument|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by ```text``` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.| -|sequenceAndPageNumbersSeparator|string|\d field-argument|When used with \s, the ```text``` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).| -|tcFieldIdentifier|string|\f field-argument|Includes only those TC fields whose identifier exactly matches the ```text``` in this switch's field-argument (which is typically a letter).| -|hiperlink|boolean|\h|Makes the table of contents entries hyperlinks.| -|tcFieldLevelRange|string|\l field-argument|Includes TC fields that assign entries to one of the levels specified by ```text``` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.| -|pageNumbersEntryLevelsRange|string|\n field-argument|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by ```text``` in this switch's field-argument. A range is specified as for \l.| -|headingStyleRange|string|\o field-argument|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by ```text``` in this switch's field-argument using the notation specified as for \l, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.| -|entryAndPageNumberSeparator|string|\p field-argument|```text``` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.| -|seqFieldIdentifierForPrefix|string|\s field-argument|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. ```text``` in this switch's field-argument shall match the identifier in the SEQ field.| -|stylesWithLevels|StyleLevel[]|\t field-argument| Uses paragraphs formatted with styles other than the built-in heading styles. ```text``` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. \t can be combined with \o.| -|useAppliedParagraphOutlineLevel|boolean|\u|Uses the applied paragraph outline level.| -|preserveTabInEntries|boolean|\w|Preserves tab entries within table entries.| -|preserveNewLineInEntries|boolean|\x|Preserves newline characters within table entries.| -|hideTabAndPageNumbersInWebView|boolean|\z|Hides tab leader and page numbers in web page view (§17.18.102).| +|captionLabel|string|`\a`|Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use ``\c`` to build a table of captions with labels and numbers.| +|entriesFromBookmark|string|`\b`|Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument.| +|captionLabelIncludingNumbers|string|`\c`|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.| +|sequenceAndPageNumbersSeparator|string|`\d`|When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).| +|tcFieldIdentifier|string|`\f`|Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter).| +|hyperlink|boolean|`\h`|Makes the table of contents entries hyperlinks.| +|tcFieldLevelRange|string|`\l`|Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.| +|pageNumbersEntryLevelsRange|string|`\n`|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`.| +|headingStyleRange|string|`\o`|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.| +|entryAndPageNumberSeparator|string|`\p`|`text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.| +|seqFieldIdentifierForPrefix|string|`\s`|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field.| +|stylesWithLevels|StyleLevel[]|`\t`| Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`.| +|useAppliedParagraphOutlineLevel|boolean|`\u`|Uses the applied paragraph outline level.| +|preserveTabInEntries|boolean|`\w`|Preserves tab entries within table entries.| +|preserveNewLineInEntries|boolean|`\x`|Preserves newline characters within table entries.| +|hideTabAndPageNumbersInWebView|boolean|`\z`|Hides tab leader and page numbers in web page view (§17.18.102).| ## Examples ```js // Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, -// making the entries be hiperlinks for the paragraph +// making the entries be hyperlinks for the paragraph const props = new TableOfContentsProperties(); -props.hiperlink = true; +props.hyperlink = true; props.headingStyleRange = "1-5"; props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] const toc = new TableOfContents("Summary", props); @@ -47,15 +47,18 @@ const toc = new TableOfContents("Summary", props); doc.addTableOfContents(toc); doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); -doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); +doc.addParagraph(new Paragraph("I'm a little text, very nicely written.'")); doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); -doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); +doc.addParagraph(new Paragraph("I'm another text very nicely written.'")); doc.addParagraph(new Paragraph("Header #2.1").heading2()); -doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); +doc.addParagraph(new Paragraph("I'm another text very nicely written.'")); doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore()); - ``` -Check `demo28.js` to see the complete example. \ No newline at end of file +### Complete example + +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo28.ts ":include") + +_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo28.ts_ diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts index ec8a6f5a94..b4221b022b 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -29,7 +29,7 @@ export class TableOfContentsInstruction extends XmlComponent { if (this.properties.tcFieldIdentifier) { instruction = `${instruction} \\f "${this.properties.tcFieldIdentifier}"`; } - if (this.properties.hiperlink) { + if (this.properties.hyperlink) { instruction = `${instruction} \\h`; } if (this.properties.tcFieldLevelRange) { diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts index 2f50589429..8b8c26ec98 100644 --- a/src/file/table-of-contents/table-of-contents-properties.ts +++ b/src/file/table-of-contents/table-of-contents-properties.ts @@ -53,7 +53,7 @@ export class TableOfContentsProperties { /** * \h option - Makes the table of contents entries hyperlinks. */ - public hiperlink: boolean; + public hyperlink: boolean; /** * \l option - Includes TC fields that assign entries to one of the levels specified diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index b306cceea2..81fcab8616 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -19,7 +19,7 @@ describe("Table of Contents", () => { props.captionLabelIncludingNumbers = "C"; props.sequenceAndPageNumbersSeparator = "D"; props.tcFieldIdentifier = "F"; - props.hiperlink = true; + props.hyperlink = true; props.tcFieldLevelRange = "L"; props.pageNumbersEntryLevelsRange = "N"; props.headingStyleRange = "O"; diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index c659ec2ad2..d84aaa3432 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,4 +1,3 @@ -// import { TableOfContentsProperties } from "./properties"; import { Paragraph } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; From 3a42f2a2f09c94fee30805a1fd067a1c45e0f8d5 Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 25 Sep 2018 20:05:35 +0100 Subject: [PATCH 31/32] Make API simplier with interfaces --- demo/demo28.ts | 12 +++---- docs/usage/table-of-contents.md | 26 ++++++++++---- .../table-of-contents-instruction.ts | 10 +++--- .../table-of-contents-properties.ts | 34 +++++++++---------- .../table-of-contents.spec.ts | 4 +-- .../table-of-contents/table-of-contents.ts | 4 +-- 6 files changed, 52 insertions(+), 38 deletions(-) diff --git a/demo/demo28.ts b/demo/demo28.ts index 1f7f56e2fa..ce545b986c 100644 --- a/demo/demo28.ts +++ b/demo/demo28.ts @@ -1,7 +1,7 @@ // Creates two paragraphs, one with a border and one without // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { File, Packer, Paragraph, StyleLevel, TableOfContents, TableOfContentsProperties } from "../build"; +import { File, Packer, Paragraph, StyleLevel, TableOfContents } from "../build"; const doc = new File(); @@ -18,11 +18,11 @@ doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") // Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, // making the entries be hyperlinks for the paragraph -const props = new TableOfContentsProperties(); -props.hyperlink = true; -props.headingStyleRange = "1-5"; -props.stylesWithLevels = [new StyleLevel("MySpectacularStyle", 1)]; -const toc = new TableOfContents("Summary", props); +const toc = new TableOfContents("Summary", { + hyperlink: true, + headingStyleRange: "1-5", + stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] +}); doc.addTableOfContents(toc); diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index 6899eae5a7..2f43da4c2f 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -1,6 +1,6 @@ # Table of Contents -You can generate table of contents with `docx`. +You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php). >Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically. >This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?". @@ -8,7 +8,19 @@ You can generate table of contents with `docx`. The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251). -A short guide can be found [here](http://officeopenxml.com/WPtableOfContents.php). +## How to + +All you need to do is create a `TableOfContents` object and assign it to the document. + +```js +const toc = new TableOfContents("Summary", { + hyperlink: true, + headingStyleRange: "1-5", + stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] +}); + +doc.addTableOfContents(toc); +``` ## Table of Contents Properties @@ -38,11 +50,11 @@ Here is the list of all properties that you can use to generate your tables of c ```js // Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, // making the entries be hyperlinks for the paragraph -const props = new TableOfContentsProperties(); -props.hyperlink = true; -props.headingStyleRange = "1-5"; -props.stylesWithLevels = [new StyleLevel("MySpectacularStyle",1)] -const toc = new TableOfContents("Summary", props); +const toc = new TableOfContents("Summary", { + hyperlink: true, + headingStyleRange: "1-5", + stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] +}); doc.addTableOfContents(toc); diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts index b4221b022b..c73a439861 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -1,19 +1,21 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -import { TableOfContentsProperties } from "./table-of-contents-properties"; +import { ITableOfContentsProperties } from "./table-of-contents-properties"; class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { protected xmlKeys = { space: "xml:space" }; } export class TableOfContentsInstruction extends XmlComponent { - private readonly properties: TableOfContentsProperties; + private readonly properties: ITableOfContentsProperties; - constructor(properties?: TableOfContentsProperties) { + constructor(properties: ITableOfContentsProperties = {}) { super("w:instrText"); - this.properties = properties || new TableOfContentsProperties(); + + this.properties = properties; this.root.push(new TextAttributes({ space: "preserve" })); let instruction = "TOC"; + if (this.properties.captionLabel) { instruction = `${instruction} \\a "${this.properties.captionLabel}"`; } diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts index 8b8c26ec98..21b1c5ec40 100644 --- a/src/file/table-of-contents/table-of-contents-properties.ts +++ b/src/file/table-of-contents/table-of-contents-properties.ts @@ -16,19 +16,19 @@ export class StyleLevel { * Short Guide: * http://officeopenxml.com/WPtableOfContents.php */ -export class TableOfContentsProperties { +export interface ITableOfContentsProperties { /** * \a option - Includes captioned items, but omits caption labels and numbers. * The identifier designated by text in this switch's field-argument corresponds to the caption label. * Use captionLabelIncludingNumbers (\c) to build a table of captions with labels and numbers. */ - public captionLabel: string; + captionLabel?: string; /** * \b option - Includes entries only from the portion of the document marked by * the bookmark named by text in this switch's field-argument. */ - public entriesFromBookmark: string; + entriesFromBookmark?: string; /** * \c option - Includes figures, tables, charts, and other items that are numbered @@ -36,24 +36,24 @@ export class TableOfContentsProperties { * field-argument, which corresponds to the caption label, shall match the identifier in the * corresponding SEQ field. */ - public captionLabelIncludingNumbers: string; + captionLabelIncludingNumbers?: string; /** * \d option - When used with \s, the text in this switch's field-argument defines * the separator between sequence and page numbers. The default separator is a hyphen (-). */ - public sequenceAndPageNumbersSeparator: string; + sequenceAndPageNumbersSeparator?: string; /** * \f option - Includes only those TC fields whose identifier exactly matches the * text in this switch's field-argument (which is typically a letter). */ - public tcFieldIdentifier: string; + tcFieldIdentifier?: string; /** * \h option - Makes the table of contents entries hyperlinks. */ - public hyperlink: boolean; + hyperlink?: boolean; /** * \l option - Includes TC fields that assign entries to one of the levels specified @@ -61,14 +61,14 @@ export class TableOfContentsProperties { * where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. * TC fields that assign entries to lower levels are skipped. */ - public tcFieldLevelRange: string; + tcFieldLevelRange?: string; /** * \n option - Without field-argument, omits page numbers from the table of contents. * Page numbers are omitted from all levels unless a range of entry levels is specified by * text in this switch's field-argument. A range is specified as for \l. */ - public pageNumbersEntryLevelsRange: string; + pageNumbersEntryLevelsRange?: string; /** * \o option - Uses paragraphs formatted with all or the specified range of builtin @@ -77,20 +77,20 @@ export class TableOfContentsProperties { * to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). * If no heading range is specified, all heading levels used in the document are listed. */ - public headingStyleRange: string; + headingStyleRange?: string; /** * \p option - Text in this switch's field-argument specifies a sequence of characters * that separate an entry and its page number. The default is a tab with leader dots. */ - public entryAndPageNumberSeparator: string; + entryAndPageNumberSeparator?: string; /** * \s option - For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. * The prefix depends on the type of entry. text in this switch's field-argument shall match the * identifier in the SEQ field. */ - public seqFieldIdentifierForPrefix: string; + seqFieldIdentifierForPrefix?: string; /** * \t field-argument Uses paragraphs formatted with styles other than the built-in heading styles. @@ -98,25 +98,25 @@ export class TableOfContentsProperties { * with each doublet being a comma-separated set of style name and table of content level. * \t can be combined with \o. */ - public stylesWithLevels: StyleLevel[]; + stylesWithLevels?: StyleLevel[]; /** * \u Uses the applied paragraph outline level. */ - public useAppliedParagraphOutlineLevel = false; + useAppliedParagraphOutlineLevel?: boolean; /** * \w Preserves tab entries within table entries. */ - public preserveTabInEntries = false; + preserveTabInEntries?: boolean; /** * \x Preserves newline characters within table entries. */ - public preserveNewLineInEntries = false; + preserveNewLineInEntries?: boolean; /** * \z Hides tab leader and page numbers in web page view (§17.18.102). */ - public hideTabAndPageNumbersInWebView = false; + hideTabAndPageNumbersInWebView?: boolean; } diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 81fcab8616..0d5554fe2b 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "../../export/formatter"; -import { StyleLevel, TableOfContents, TableOfContentsProperties } from "./"; +import { ITableOfContentsProperties, StyleLevel, TableOfContents } from "./"; describe("Table of Contents", () => { describe("#constructor", () => { @@ -12,7 +12,7 @@ describe("Table of Contents", () => { }); it("should construct a TOC with all the options and alias", () => { - const props = new TableOfContentsProperties(); + const props: ITableOfContentsProperties = {}; props.captionLabel = "A"; props.entriesFromBookmark = "B"; diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index d84aaa3432..a7fa85f85f 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -5,10 +5,10 @@ import { XmlComponent } from "file/xml-components"; import { SdtContent } from "./sdt-content"; import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; -import { TableOfContentsProperties } from "./table-of-contents-properties"; +import { ITableOfContentsProperties } from "./table-of-contents-properties"; export class TableOfContents extends XmlComponent { - constructor(alias: string = "Table of Contents", properties?: TableOfContentsProperties) { + constructor(alias: string = "Table of Contents", properties?: ITableOfContentsProperties) { super("w:sdt"); this.root.push(new SdtProperties(alias)); From c140d2c37c66a38dcb48f59e50969bcdb670d5fb Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 25 Sep 2018 21:09:30 +0100 Subject: [PATCH 32/32] ITableOfContentsProperties to ITableOfContentsOptions --- demo/demo28.ts | 2 +- .../table-of-contents-instruction.ts | 15 ++++++++++----- .../table-of-contents-properties.ts | 2 +- .../table-of-contents/table-of-contents.spec.ts | 4 ++-- src/file/table-of-contents/table-of-contents.ts | 4 ++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/demo/demo28.ts b/demo/demo28.ts index ce545b986c..d13acf5b46 100644 --- a/demo/demo28.ts +++ b/demo/demo28.ts @@ -21,7 +21,7 @@ doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") const toc = new TableOfContents("Summary", { hyperlink: true, headingStyleRange: "1-5", - stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] + stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)], }); doc.addTableOfContents(toc); diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts index c73a439861..1183ebd3de 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -1,19 +1,24 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -import { ITableOfContentsProperties } from "./table-of-contents-properties"; +import { ITableOfContentsOptions } from "./table-of-contents-properties"; -class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { +enum SpaceType { + DEFAULT = "default", + PRESERVE = "preserve", +} + +class TextAttributes extends XmlAttributeComponent<{ space: SpaceType }> { protected xmlKeys = { space: "xml:space" }; } export class TableOfContentsInstruction extends XmlComponent { - private readonly properties: ITableOfContentsProperties; + private readonly properties: ITableOfContentsOptions; - constructor(properties: ITableOfContentsProperties = {}) { + constructor(properties: ITableOfContentsOptions = {}) { super("w:instrText"); this.properties = properties; - this.root.push(new TextAttributes({ space: "preserve" })); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); let instruction = "TOC"; if (this.properties.captionLabel) { diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts index 21b1c5ec40..bc312429af 100644 --- a/src/file/table-of-contents/table-of-contents-properties.ts +++ b/src/file/table-of-contents/table-of-contents-properties.ts @@ -16,7 +16,7 @@ export class StyleLevel { * Short Guide: * http://officeopenxml.com/WPtableOfContents.php */ -export interface ITableOfContentsProperties { +export interface ITableOfContentsOptions { /** * \a option - Includes captioned items, but omits caption labels and numbers. * The identifier designated by text in this switch's field-argument corresponds to the caption label. diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 0d5554fe2b..7d7d4a55b6 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "../../export/formatter"; -import { ITableOfContentsProperties, StyleLevel, TableOfContents } from "./"; +import { ITableOfContentsOptions, StyleLevel, TableOfContents } from "./"; describe("Table of Contents", () => { describe("#constructor", () => { @@ -12,7 +12,7 @@ describe("Table of Contents", () => { }); it("should construct a TOC with all the options and alias", () => { - const props: ITableOfContentsProperties = {}; + const props: ITableOfContentsOptions = {}; props.captionLabel = "A"; props.entriesFromBookmark = "B"; diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index a7fa85f85f..fb7b709c4f 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -5,10 +5,10 @@ import { XmlComponent } from "file/xml-components"; import { SdtContent } from "./sdt-content"; import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; -import { ITableOfContentsProperties } from "./table-of-contents-properties"; +import { ITableOfContentsOptions } from "./table-of-contents-properties"; export class TableOfContents extends XmlComponent { - constructor(alias: string = "Table of Contents", properties?: ITableOfContentsProperties) { + constructor(alias: string = "Table of Contents", properties?: ITableOfContentsOptions) { super("w:sdt"); this.root.push(new SdtProperties(alias));