From 9c11653313e0cdb4319c5e9f74dfd16b7952e23f Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 1 Jan 2020 20:22:42 +0000 Subject: [PATCH 1/6] Multiple paragraphs in footnotes --- demo/17-footnotes.ts | 16 ++--- src/file/core-properties/properties.ts | 6 +- src/file/file.spec.ts | 6 +- src/file/file.ts | 9 ++- .../footnotes/footnote/footnote-attributes.ts | 6 +- src/file/footnotes/footnote/footnote.spec.ts | 11 ++- src/file/footnotes/footnote/footnote.ts | 19 ++++-- .../footnotes/footnote/run/reference-run.ts | 6 +- src/file/footnotes/footnotes.ts | 68 ++++++++++--------- 9 files changed, 86 insertions(+), 61 deletions(-) diff --git a/demo/17-footnotes.ts b/demo/17-footnotes.ts index a41f84a3c8..0ba23c07cb 100644 --- a/demo/17-footnotes.ts +++ b/demo/17-footnotes.ts @@ -4,14 +4,14 @@ import * as fs from "fs"; import { Document, FootnoteReferenceRun, Packer, Paragraph, TextRun } from "../build"; const doc = new Document({ - footnotes: [ - new Paragraph("Foo"), - new Paragraph("Test"), - new Paragraph("My amazing reference"), - new Paragraph("Foo1"), - new Paragraph("Test1"), - new Paragraph("My amazing reference1"), - ], + footnotes: { + 1: { children: [new Paragraph("Foo"), new Paragraph("Bar")] }, + 2: { children: [new Paragraph("Test")] }, + 3: { children: [new Paragraph("My amazing reference")] }, + 4: { children: [new Paragraph("Foo1")] }, + 5: { children: [new Paragraph("Test1")] }, + 6: { children: [new Paragraph("My amazing reference1")] }, + }, }); doc.addSection({ diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index ceddff39e0..c6b4f8348d 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -28,7 +28,11 @@ export interface IPropertiesOptions { readonly externalStyles?: string; readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; - readonly footnotes?: Paragraph[]; + readonly footnotes?: { + readonly [key: string]: { + readonly children: Paragraph[]; + }; + }; readonly hyperlinks?: { readonly [key: string]: IInternalHyperlinkDefinition | IExternalHyperlinkDefinition; }; diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index 6ec30e1be9..8864f62d25 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -254,7 +254,11 @@ describe("File", () => { describe("#createFootnote", () => { it("should create footnote", () => { const wrapper = new File({ - footnotes: [new Paragraph("hello")], + footnotes: { + 1: { + children: [new Paragraph("hello")], + }, + }, }); const tree = new Formatter().format(wrapper.FootNotes); diff --git a/src/file/file.ts b/src/file/file.ts index 59af9fa7f7..962eefca21 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -1,4 +1,5 @@ import * as shortid from "shortid"; + import { AppProperties } from "./app-properties/app-properties"; import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; @@ -145,8 +146,12 @@ export class File { } if (options.footnotes) { - for (const paragraph of options.footnotes) { - this.footNotes.createFootNote(paragraph); + for (const key in options.footnotes) { + if (!options.footnotes[key]) { + continue; + } + + this.footNotes.createFootNote(parseFloat(key), options.footnotes[key].children); } } diff --git a/src/file/footnotes/footnote/footnote-attributes.ts b/src/file/footnotes/footnote/footnote-attributes.ts index ee978d0ef2..027f9213fe 100644 --- a/src/file/footnotes/footnote/footnote-attributes.ts +++ b/src/file/footnotes/footnote/footnote-attributes.ts @@ -1,11 +1,9 @@ import { XmlAttributeComponent } from "file/xml-components"; -export interface IFootnoteAttributesProperties { +export class FootnoteAttributes extends XmlAttributeComponent<{ readonly type?: string; readonly id: number; -} - -export class FootnoteAttributes extends XmlAttributeComponent { +}> { protected readonly xmlKeys = { type: "w:type", id: "w:id", diff --git a/src/file/footnotes/footnote/footnote.spec.ts b/src/file/footnotes/footnote/footnote.spec.ts index 8594cd75fd..ab1778f687 100644 --- a/src/file/footnotes/footnote/footnote.spec.ts +++ b/src/file/footnotes/footnote/footnote.spec.ts @@ -7,7 +7,11 @@ import { Footnote, FootnoteType } from "./footnote"; describe("Footnote", () => { describe("#constructor", () => { it("should create a footnote with a footnote type", () => { - const footnote = new Footnote(1, FootnoteType.SEPERATOR); + const footnote = new Footnote({ + id: 1, + type: FootnoteType.SEPERATOR, + children: [], + }); const tree = new Formatter().format(footnote); expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); @@ -15,7 +19,10 @@ describe("Footnote", () => { }); it("should create a footnote without a footnote type", () => { - const footnote = new Footnote(1); + const footnote = new Footnote({ + id: 1, + children: [], + }); const tree = new Formatter().format(footnote); expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); diff --git a/src/file/footnotes/footnote/footnote.ts b/src/file/footnotes/footnote/footnote.ts index 60e6e533c8..b890e5a0c4 100644 --- a/src/file/footnotes/footnote/footnote.ts +++ b/src/file/footnotes/footnote/footnote.ts @@ -10,18 +10,23 @@ export enum FootnoteType { } export class Footnote extends XmlComponent { - constructor(id: number, type?: FootnoteType) { + constructor(options: { readonly id: number; readonly type?: FootnoteType; readonly children: Paragraph[] }) { super("w:footnote"); this.root.push( new FootnoteAttributes({ - type: type, - id: id, + type: options.type, + id: options.id, }), ); - } - public add(paragraph: Paragraph): void { - paragraph.addRunToFront(new FootnoteRefRun()); - this.root.push(paragraph); + for (let i = 0; i < options.children.length; i++) { + const child = options.children[i]; + + if (i === 0) { + child.addRunToFront(new FootnoteRefRun()); + } + + this.root.push(child); + } } } diff --git a/src/file/footnotes/footnote/run/reference-run.ts b/src/file/footnotes/footnote/run/reference-run.ts index 7dec91473d..3bfd772f50 100644 --- a/src/file/footnotes/footnote/run/reference-run.ts +++ b/src/file/footnotes/footnote/run/reference-run.ts @@ -2,11 +2,9 @@ import { Run } from "file/paragraph/run"; import { Style } from "file/paragraph/run/style"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -export interface IFootNoteReferenceRunAttributesProperties { +export class FootNoteReferenceRunAttributes extends XmlAttributeComponent<{ readonly id: number; -} - -export class FootNoteReferenceRunAttributes extends XmlAttributeComponent { +}> { protected readonly xmlKeys = { id: "w:id", }; diff --git a/src/file/footnotes/footnotes.ts b/src/file/footnotes/footnotes.ts index 91ba1d199d..965c587fc9 100644 --- a/src/file/footnotes/footnotes.ts +++ b/src/file/footnotes/footnotes.ts @@ -1,4 +1,5 @@ import { XmlComponent } from "file/xml-components"; + import { Paragraph } from "../paragraph"; import { Footnote, FootnoteType } from "./footnote/footnote"; import { ContinuationSeperatorRun } from "./footnote/run/continuation-seperator-run"; @@ -6,14 +7,9 @@ import { SeperatorRun } from "./footnote/run/seperator-run"; import { FootnotesAttributes } from "./footnotes-attributes"; export class FootNotes extends XmlComponent { - // tslint:disable-next-line:readonly-keyword - private currentId: number; - constructor() { super("w:footnotes"); - this.currentId = 1; - this.root.push( new FootnotesAttributes({ wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", @@ -36,38 +32,46 @@ export class FootNotes extends XmlComponent { }), ); - const begin = new Footnote(-1, FootnoteType.SEPERATOR); - begin.add( - new Paragraph({ - spacing: { - after: 0, - line: 240, - lineRule: "auto", - }, - children: [new SeperatorRun()], - }), - ); + const begin = new Footnote({ + id: -1, + type: FootnoteType.SEPERATOR, + children: [ + new Paragraph({ + spacing: { + after: 0, + line: 240, + lineRule: "auto", + }, + children: [new SeperatorRun()], + }), + ], + }); + this.root.push(begin); - const spacing = new Footnote(0, FootnoteType.CONTINUATION_SEPERATOR); - spacing.add( - new Paragraph({ - spacing: { - after: 0, - line: 240, - lineRule: "auto", - }, - children: [new ContinuationSeperatorRun()], - }), - ); + const spacing = new Footnote({ + id: 0, + type: FootnoteType.CONTINUATION_SEPERATOR, + children: [ + new Paragraph({ + spacing: { + after: 0, + line: 240, + lineRule: "auto", + }, + children: [new ContinuationSeperatorRun()], + }), + ], + }); + this.root.push(spacing); } - public createFootNote(paragraph: Paragraph): void { - const footnote = new Footnote(this.currentId); - footnote.add(paragraph); + public createFootNote(id: number, paragraph: Paragraph[]): void { + const footnote = new Footnote({ + id: id, + children: paragraph, + }); this.root.push(footnote); - - this.currentId++; } } From db7c4da609190950f7baab788c26dedbffcd45aa Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 1 Jan 2020 22:54:42 +0000 Subject: [PATCH 2/6] Add ability to add multiple text runs to a bookmark --- demo/21-bookmarks.ts | 9 +++++++-- package-lock.json | 2 +- src/file/file.spec.ts | 16 +++++++++++++++- src/file/paragraph/links/bookmark.spec.ts | 6 +++++- src/file/paragraph/links/bookmark.ts | 12 ++++++------ src/file/paragraph/paragraph.spec.ts | 8 +++++++- src/file/paragraph/paragraph.ts | 4 +++- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index 1ad50eb9b1..689ff30ae4 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -1,7 +1,7 @@ // This demo shows how to create bookmarks then link to them with internal hyperlinks // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build"; +import { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph, TextRun } from "../build"; const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; @@ -22,7 +22,12 @@ doc.addSection({ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, - children: [new Bookmark("myAnchorId", "Lorem Ipsum")], + children: [ + new Bookmark({ + id: "myAnchorId", + children: [new TextRun("Lorem Ipsum")], + }), + ], }), new Paragraph("\n"), new Paragraph(LOREM_IPSUM), diff --git a/package-lock.json b/package-lock.json index 792ca7e14b..974c0fcb0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "5.0.0-rc7", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index 8864f62d25..d9beb46a3c 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -5,7 +5,7 @@ import { Formatter } from "export/formatter"; import { File } from "./file"; import { Footer, Header } from "./header"; -import { HyperlinkRef, Paragraph } from "./paragraph"; +import { HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph"; import { Table, TableCell, TableRow } from "./table"; import { TableOfContents } from "./table-of-contents"; @@ -241,6 +241,20 @@ describe("File", () => { expect(spy.called).to.equal(true); }); + + it.only("should create hyperlinks", () => { + const wrapper = new File({ + hyperlinks: { + myHyperLink: { + link: "test.com", + text: "test", + type: HyperlinkType.EXTERNAL, + }, + }, + }); + + expect(wrapper.HyperlinkCache.myHyperLink).to.not.be.undefined(""); + }); }); describe("#HyperlinkCache", () => { diff --git a/src/file/paragraph/links/bookmark.spec.ts b/src/file/paragraph/links/bookmark.spec.ts index fe342fc374..7975fefc96 100644 --- a/src/file/paragraph/links/bookmark.spec.ts +++ b/src/file/paragraph/links/bookmark.spec.ts @@ -2,13 +2,17 @@ import { assert, expect } from "chai"; import { Utility } from "tests/utility"; +import { TextRun } from "../run"; import { Bookmark } from "./bookmark"; describe("Bookmark", () => { let bookmark: Bookmark; beforeEach(() => { - bookmark = new Bookmark("anchor", "Internal Link"); + bookmark = new Bookmark({ + id: "anchor", + children: [new TextRun("Internal Link")], + }); }); it("should create a bookmark with three root elements", () => { diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 261a756864..cf217155a2 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -6,24 +6,24 @@ import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attri export class Bookmark { public readonly start: BookmarkStart; - public readonly text: TextRun; + public readonly children: TextRun[]; public readonly end: BookmarkEnd; - constructor(name: string, text: string) { + constructor(options: { readonly id: string; readonly children: TextRun[] }) { const linkId = shortid.generate().toLowerCase(); - this.start = new BookmarkStart(name, linkId); - this.text = new TextRun(text); + this.start = new BookmarkStart(options.id, linkId); + this.children = options.children; this.end = new BookmarkEnd(linkId); } } export class BookmarkStart extends XmlComponent { - constructor(name: string, linkId: string) { + constructor(id: string, linkId: string) { super("w:bookmarkStart"); const attributes = new BookmarkStartAttributes({ - name, + name: id, id: linkId, }); this.root.push(attributes); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index cdbe99c7d8..2bbe3c79b1 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -8,6 +8,7 @@ import { EMPTY_OBJECT } from "file/xml-components"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { Bookmark } from "./links"; import { Paragraph } from "./paragraph"; +import { TextRun } from "./run"; describe("Paragraph", () => { describe("#constructor()", () => { @@ -646,7 +647,12 @@ describe("Paragraph", () => { return "test-unique-id"; }); const paragraph = new Paragraph({ - children: [new Bookmark("test-id", "test")], + children: [ + new Bookmark({ + id: "test-id", + children: [new TextRun("test")], + }), + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index c97d8d34fd..a61e827be6 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -150,7 +150,9 @@ export class Paragraph extends XmlComponent { for (const child of options.children) { if (child instanceof Bookmark) { this.root.push(child.start); - this.root.push(child.text); + for (const textRun of child.children) { + this.root.push(textRun); + } this.root.push(child.end); continue; } From a0d8911e1897fdf87bca924e244489f197afe485 Mon Sep 17 00:00:00 2001 From: Dolan Date: Mon, 8 Mar 2021 04:18:26 +0000 Subject: [PATCH 3/6] Add tests --- src/file/footnotes/footnote/footnote.spec.ts | 130 +++++++++++++++++++ src/file/paragraph/paragraph.spec.ts | 22 ++++ 2 files changed, 152 insertions(+) diff --git a/src/file/footnotes/footnote/footnote.spec.ts b/src/file/footnotes/footnote/footnote.spec.ts index ab1778f687..e2ce67d7b3 100644 --- a/src/file/footnotes/footnote/footnote.spec.ts +++ b/src/file/footnotes/footnote/footnote.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { Paragraph, TextRun } from "file/paragraph"; import { Footnote, FootnoteType } from "./footnote"; @@ -28,5 +29,134 @@ describe("Footnote", () => { expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); expect(tree["w:footnote"]).to.deep.equal({ _attr: { "w:id": 1 } }); }); + + it("should append footnote ref run on the first footnote paragraph", () => { + const footnote = new Footnote({ + id: 1, + children: [new Paragraph({ children: [new TextRun("test-footnote")] })], + }); + const tree = new Formatter().format(footnote); + + expect(tree).to.deep.equal({ + "w:footnote": [ + { + _attr: { + "w:id": 1, + }, + }, + { + "w:p": [ + { + "w:r": [ + { + "w:rPr": [ + { + "w:rStyle": { + _attr: { + "w:val": "FootnoteReference", + }, + }, + }, + ], + }, + { + "w:footnoteRef": {}, + }, + ], + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote", + ], + }, + ], + }, + ], + }, + ], + }); + }); + + it("should add multiple paragraphs", () => { + const footnote = new Footnote({ + id: 1, + children: [ + new Paragraph({ children: [new TextRun("test-footnote")] }), + new Paragraph({ children: [new TextRun("test-footnote-2")] }), + ], + }); + const tree = new Formatter().format(footnote); + + expect(tree).to.deep.equal({ + "w:footnote": [ + { + _attr: { + "w:id": 1, + }, + }, + { + "w:p": [ + { + "w:r": [ + { + "w:rPr": [ + { + "w:rStyle": { + _attr: { + "w:val": "FootnoteReference", + }, + }, + }, + ], + }, + { + "w:footnoteRef": {}, + }, + ], + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote", + ], + }, + ], + }, + ], + }, + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote-2", + ], + }, + ], + }, + ], + }, + ], + }); + }); }); }); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index fe9ab8360e..0aa4d98b2d 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -618,6 +618,28 @@ describe("Paragraph", () => { ], }); }); + + it("should not add ListParagraph style when custom is true", () => { + const paragraph = new Paragraph({ + numbering: { + reference: "test id", + level: 0, + custom: true, + }, + }); + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:pPr": [ + { + "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }], + }, + ], + }, + ], + }); + }); }); it("it should add bookmark", () => { From 65302dd39be6bbb6cf59e6b37342280755a38100 Mon Sep 17 00:00:00 2001 From: Dolan Date: Mon, 8 Mar 2021 04:22:20 +0000 Subject: [PATCH 4/6] Increase coverage limit --- .nycrc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.nycrc b/.nycrc index 3a897e4c79..e41ab1facb 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "lines": 97.77, - "functions": 93.89, - "branches": 94.83, - "statements": 97.75, + "lines": 97.83, + "functions": 94.33, + "branches": 94.88, + "statements": 97.82, "include": [ "src/**/*.ts" ], From 993782129c8af5a9969c3861ddd7a8bc590ce3e3 Mon Sep 17 00:00:00 2001 From: Dolan Date: Mon, 8 Mar 2021 04:33:15 +0000 Subject: [PATCH 5/6] Simplify for loop --- .nycrc | 6 +++--- src/file/file.ts | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.nycrc b/.nycrc index e41ab1facb..628fef9ec2 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "lines": 97.83, + "lines": 97.85, "functions": 94.33, - "branches": 94.88, - "statements": 97.82, + "branches": 94.99, + "statements": 97.86, "include": [ "src/**/*.ts" ], diff --git a/src/file/file.ts b/src/file/file.ts index 89634a7978..55464d04e9 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -139,11 +139,8 @@ export class File { } if (options.footnotes) { + // tslint:disable-next-line: forin for (const key in options.footnotes) { - if (!options.footnotes[key]) { - continue; - } - this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children); } } From fdf076424584ef822d0dfa5110b38f5c4e482ed6 Mon Sep 17 00:00:00 2001 From: Dolan Date: Mon, 8 Mar 2021 04:36:53 +0000 Subject: [PATCH 6/6] Alter coverage limits --- .nycrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.nycrc b/.nycrc index 628fef9ec2..5afadd762c 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "lines": 97.85, + "lines": 97.86, "functions": 94.33, "branches": 94.99, - "statements": 97.86, + "statements": 97.85, "include": [ "src/**/*.ts" ],