From 8ceb38963e83e419fd517b5e282e1a5309afc9aa Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Tue, 24 Jul 2018 15:56:13 -0400 Subject: [PATCH 1/4] initial functionality --- src/file/file.ts | 14 ++++- .../paragraph/links/bookmark-attributes.ts | 23 ++++++++ src/file/paragraph/links/bookmark.ts | 57 +++++++++++++++++++ .../paragraph/links/hyperlink-attributes.ts | 2 + src/file/paragraph/links/hyperlink.ts | 18 ++++-- src/file/paragraph/links/index.ts | 1 + src/file/paragraph/paragraph.ts | 10 +++- 7 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/file/paragraph/links/bookmark-attributes.ts create mode 100644 src/file/paragraph/links/bookmark.ts diff --git a/src/file/file.ts b/src/file/file.ts index ae9cf9deac..ddc55841b3 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -10,7 +10,7 @@ import { FootNotes } from "./footnotes"; import { HeaderWrapper } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; -import { Hyperlink, Paragraph, PictureRun } from "./paragraph"; +import { Bookmark, Hyperlink, Paragraph, PictureRun } from "./paragraph"; import { Relationships } from "./relationships"; import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; @@ -157,6 +157,18 @@ export class File { return hyperlink; } + public createInternalHyperLink(anchor: string, text?: string): Hyperlink { + text = text === undefined ? anchor : text; + const hyperlink = new Hyperlink(text, this.docRelationships.RelationshipCount, anchor); + return hyperlink; + } + + public createBookmark(name: string, text?: string): Bookmark { + text = text === undefined ? name : text; + const bookmark = new Bookmark(name, text, this.docRelationships.RelationshipCount); + return bookmark; + } + public addSection(sectionPropertiesOptions: SectionPropertiesOptions): void { this.document.Body.addSection(sectionPropertiesOptions); } diff --git a/src/file/paragraph/links/bookmark-attributes.ts b/src/file/paragraph/links/bookmark-attributes.ts new file mode 100644 index 0000000000..670c462306 --- /dev/null +++ b/src/file/paragraph/links/bookmark-attributes.ts @@ -0,0 +1,23 @@ +import { XmlAttributeComponent } from "file/xml-components"; + +export interface IBookmarkStartAttributesProperties { + id: string; + name: string; +} + +export class BookmarkStartAttributes extends XmlAttributeComponent { + protected xmlKeys = { + id: "w:id", + name: "w:name", + }; +} + +export interface IBookmarkEndAttributesProperties { + id: string; +} + +export class BookmarkEndAttributes extends XmlAttributeComponent { + protected xmlKeys = { + id: "w:id", + }; +} diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts new file mode 100644 index 0000000000..21a9b7e983 --- /dev/null +++ b/src/file/paragraph/links/bookmark.ts @@ -0,0 +1,57 @@ +// http://officeopenxml.com/WPbookmark.php + +import { XmlComponent } from "file/xml-components"; +import { TextRun } from "../run"; +import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; + +export class Bookmark { + + public linkId: number; + + public readonly start: BookmarkStart; + + public readonly text: TextRun; + + public readonly end: BookmarkEnd; + + constructor(name: string, text: string, relationshipsCount: number) { + this.linkId = relationshipsCount + 1; + + this.start = new BookmarkStart(name, text, this.linkId); + this.text = new TextRun(text); + this.end = new BookmarkEnd(this.linkId); + } +} + +export class BookmarkStart extends XmlComponent { + + public linkId: number; + + constructor(name: string, text: string, relationshipsCount: number) { + super("w:bookmarkStart"); + + this.linkId = relationshipsCount; + const id = `${this.linkId}`; + const attributes = new BookmarkStartAttributes({ + name, + id, + }); + this.root.push(attributes); + } +} + +export class BookmarkEnd extends XmlComponent { + + public linkId: number; + + constructor(relationshipsCount: number) { + super("w:bookmarkEnd"); + + this.linkId = relationshipsCount; + const id = `${this.linkId}`; + const attributes = new BookmarkEndAttributes({ + id, + }); + this.root.push(attributes); + } +} diff --git a/src/file/paragraph/links/hyperlink-attributes.ts b/src/file/paragraph/links/hyperlink-attributes.ts index d51f5a8b65..6d68e265b4 100644 --- a/src/file/paragraph/links/hyperlink-attributes.ts +++ b/src/file/paragraph/links/hyperlink-attributes.ts @@ -2,6 +2,7 @@ import { XmlAttributeComponent } from "file/xml-components"; export interface IHyperlinkAttributesProperties { id?: string; + anchor?: string; history: number; } @@ -9,5 +10,6 @@ export class HyperlinkAttributes extends XmlAttributeComponent Date: Tue, 24 Jul 2018 16:00:55 -0400 Subject: [PATCH 2/4] style fix --- src/file/paragraph/links/bookmark.ts | 3 --- src/file/paragraph/links/index.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 21a9b7e983..661ca4118a 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -5,7 +5,6 @@ import { TextRun } from "../run"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; export class Bookmark { - public linkId: number; public readonly start: BookmarkStart; @@ -24,7 +23,6 @@ export class Bookmark { } export class BookmarkStart extends XmlComponent { - public linkId: number; constructor(name: string, text: string, relationshipsCount: number) { @@ -41,7 +39,6 @@ export class BookmarkStart extends XmlComponent { } export class BookmarkEnd extends XmlComponent { - public linkId: number; constructor(relationshipsCount: number) { diff --git a/src/file/paragraph/links/index.ts b/src/file/paragraph/links/index.ts index 8981572b09..09084aa8c7 100644 --- a/src/file/paragraph/links/index.ts +++ b/src/file/paragraph/links/index.ts @@ -1,2 +1,2 @@ export * from "./hyperlink"; -export * from "./bookmark"; \ No newline at end of file +export * from "./bookmark"; From 985ea30d36fce1b2086855781b2db7ebeaa10880 Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Tue, 24 Jul 2018 16:56:27 -0400 Subject: [PATCH 3/4] tests --- src/file/file.ts | 2 ++ src/file/paragraph/links/bookmark.spec.ts | 42 ++++++++++++++++++++++ src/file/paragraph/links/bookmark.ts | 4 +-- src/file/paragraph/links/hyperlink.spec.ts | 17 ++++++++- 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/file/paragraph/links/bookmark.spec.ts diff --git a/src/file/file.ts b/src/file/file.ts index ddc55841b3..866ddd1480 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -160,6 +160,8 @@ export class File { public createInternalHyperLink(anchor: string, text?: string): Hyperlink { text = text === undefined ? anchor : text; const hyperlink = new Hyperlink(text, this.docRelationships.RelationshipCount, anchor); + // NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark + // we don't need to create a new relationship. return hyperlink; } diff --git a/src/file/paragraph/links/bookmark.spec.ts b/src/file/paragraph/links/bookmark.spec.ts new file mode 100644 index 0000000000..08d0aec91c --- /dev/null +++ b/src/file/paragraph/links/bookmark.spec.ts @@ -0,0 +1,42 @@ +import { assert } from "chai"; + +import { Utility } from "../../../tests/utility"; +import { Bookmark } from "./"; + +describe("Bookmark", () => { + let bookmark: Bookmark; + + beforeEach(() => { + bookmark = new Bookmark("anchor", "Internal Link", 0); + }); + + it("should create a bookmark with three root elements", () => { + const newJson = Utility.jsonify(bookmark); + assert.equal(newJson.rootKey, undefined); + assert.equal(newJson.start.rootKey, "w:bookmarkStart"); + assert.equal(newJson.text.rootKey, "w:r"); + assert.equal(newJson.end.rootKey, "w:bookmarkEnd"); + }); + + it("should create a bookmark with the correct attributes on the bookmark start element", () => { + const newJson = Utility.jsonify(bookmark); + const attributes = { + name: "anchor", + id: "1", + }; + assert.equal(JSON.stringify(newJson.start.root[0].root), JSON.stringify(attributes)); + }); + + it("should create a bookmark with the correct attributes on the text element", () => { + const newJson = Utility.jsonify(bookmark); + assert.equal(JSON.stringify(newJson.text.root[1].root[1]), JSON.stringify("Internal Link")); + }); + + it("should create a bookmark with the correct attributes on the bookmark end element", () => { + const newJson = Utility.jsonify(bookmark); + const attributes = { + id: "1", + }; + assert.equal(JSON.stringify(newJson.end.root[0].root), JSON.stringify(attributes)); + }); +}); diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 661ca4118a..de4fabdaa2 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -16,7 +16,7 @@ export class Bookmark { constructor(name: string, text: string, relationshipsCount: number) { this.linkId = relationshipsCount + 1; - this.start = new BookmarkStart(name, text, this.linkId); + this.start = new BookmarkStart(name, this.linkId); this.text = new TextRun(text); this.end = new BookmarkEnd(this.linkId); } @@ -25,7 +25,7 @@ export class Bookmark { export class BookmarkStart extends XmlComponent { public linkId: number; - constructor(name: string, text: string, relationshipsCount: number) { + constructor(name: string, relationshipsCount: number) { super("w:bookmarkStart"); this.linkId = relationshipsCount; diff --git a/src/file/paragraph/links/hyperlink.spec.ts b/src/file/paragraph/links/hyperlink.spec.ts index e3f0b58c49..d6432b432a 100644 --- a/src/file/paragraph/links/hyperlink.spec.ts +++ b/src/file/paragraph/links/hyperlink.spec.ts @@ -20,8 +20,8 @@ describe("Hyperlink", () => { it("should create a hyperlink with right attributes", () => { const newJson = Utility.jsonify(hyperlink); const attributes = { - id: "rId1", history: 1, + id: "rId1", }; assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes)); }); @@ -36,5 +36,20 @@ describe("Hyperlink", () => { }; expect(tree["w:hyperlink"][1]).to.deep.equal(runJson); }); + + describe("with optional anchor parameter", () => { + beforeEach(() => { + hyperlink = new Hyperlink("Anchor Text", 0, "anchor"); + }); + + it("should create an internal link with anchor tag", () => { + const newJson = Utility.jsonify(hyperlink); + const attributes = { + history: 1, + anchor: "anchor", + }; + assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes)); + }); + }); }); }); From 305dd847699b94112df4d490f51277ff36e8a25f Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Tue, 24 Jul 2018 17:15:23 -0400 Subject: [PATCH 4/4] adding a demo --- demo/demo21.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 demo/demo21.js diff --git a/demo/demo21.js b/demo/demo21.js new file mode 100644 index 0000000000..fd74e61b31 --- /dev/null +++ b/demo/demo21.js @@ -0,0 +1,31 @@ +/** This demo shows how to create bookmarks then link to them with internal hyperlinks */ + +const docx = require("../build"); + +const loremIpsum = "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."; + +const doc = new docx.Document({ + creator: 'Clippy', + title: 'Sample Document', + description: 'A brief example of using docx with bookmarks and internal hyperlinks', +}); + +const anchorId = "anchorID"; + +// First create the bookmark +const bookmark = doc.createBookmark(anchorId, "Lorem Ipsum"); +// That has header styling +doc.createParagraph().addBookmark(bookmark).heading1(); +doc.createParagraph("\n"); + +doc.createParagraph(loremIpsum); +doc.createParagraph().pageBreak(); + +// Now the link back up to the bookmark +const hyperlink = doc.createInternalHyperLink(anchorId, `Click me!`); +doc.createParagraph().addHyperLink(hyperlink); + +var exporter = new docx.LocalPacker(doc); +exporter.pack("My Document"); + +console.log("Document created successfully at project root!");