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] 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());