#532 Make hyperlinks work on the Header and Footer

This commit is contained in:
Dolan Miu
2021-03-01 03:28:35 +00:00
parent 655b40d418
commit c6e9696be0
9 changed files with 53 additions and 36 deletions

View File

@ -1,7 +1,7 @@
// Example on how to add hyperlinks to websites // Example on how to add hyperlinks to websites
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { ExternalHyperlink, Document, Packer, Paragraph, Media, TextRun, Footer } from "../build"; import { Document, ExternalHyperlink, Footer, Media, Packer, Paragraph, TextRun } from "../build";
const doc = new Document({}); const doc = new Document({});
@ -26,6 +26,24 @@ doc.addSection({
], ],
}), }),
}, },
headers: {
default: new Footer({
children: [
new Paragraph({
children: [
new TextRun("Click here for the "),
new ExternalHyperlink({
child: new TextRun({
text: "Header external hyperlink",
style: "Hyperlink",
}),
link: "http://www.google.com",
}),
],
}),
],
}),
},
children: [ children: [
new Paragraph({ new Paragraph({
children: [ children: [

View File

@ -1,8 +1,8 @@
import { IViewWrapper } from "file/document-wrapper";
import { BaseXmlComponent, IXmlableObject } from "file/xml-components"; import { BaseXmlComponent, IXmlableObject } from "file/xml-components";
import { File } from "../file";
export class Formatter { export class Formatter {
public format(input: BaseXmlComponent, file?: File): IXmlableObject { public format(input: BaseXmlComponent, file?: IViewWrapper): IXmlableObject {
const output = input.prepForXml(file); const output = input.prepForXml(file);
if (output) { if (output) {

View File

@ -71,7 +71,7 @@ export class Compiler {
file.verifyUpdateFields(); file.verifyUpdateFields();
const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1; const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1;
const documentXmlData = xml(this.formatter.format(file.Document.View, file), prettify); const documentXmlData = xml(this.formatter.format(file.Document.View, file.Document), prettify);
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media); const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
return { return {
@ -85,7 +85,7 @@ export class Compiler {
); );
}); });
return xml(this.formatter.format(file.Document.Relationships, file), prettify); return xml(this.formatter.format(file.Document.Relationships, file.Document), prettify);
})(), })(),
path: "word/_rels/document.xml.rels", path: "word/_rels/document.xml.rels",
}, },
@ -99,11 +99,11 @@ export class Compiler {
path: "word/document.xml", path: "word/document.xml",
}, },
Styles: { Styles: {
data: xml(this.formatter.format(file.Styles, file), prettify), data: xml(this.formatter.format(file.Styles, file.Document), prettify),
path: "word/styles.xml", path: "word/styles.xml",
}, },
Properties: { Properties: {
data: xml(this.formatter.format(file.CoreProperties, file), { data: xml(this.formatter.format(file.CoreProperties, file.Document), {
declaration: { declaration: {
standalone: "yes", standalone: "yes",
encoding: "UTF-8", encoding: "UTF-8",
@ -112,15 +112,15 @@ export class Compiler {
path: "docProps/core.xml", path: "docProps/core.xml",
}, },
Numbering: { Numbering: {
data: xml(this.formatter.format(file.Numbering, file), prettify), data: xml(this.formatter.format(file.Numbering, file.Document), prettify),
path: "word/numbering.xml", path: "word/numbering.xml",
}, },
FileRelationships: { FileRelationships: {
data: xml(this.formatter.format(file.FileRelationships, file), prettify), data: xml(this.formatter.format(file.FileRelationships, file.Document), prettify),
path: "_rels/.rels", path: "_rels/.rels",
}, },
HeaderRelationships: file.Headers.map((headerWrapper, index) => { HeaderRelationships: file.Headers.map((headerWrapper, index) => {
const xmlData = xml(this.formatter.format(headerWrapper.View, file), prettify); const xmlData = xml(this.formatter.format(headerWrapper.View, headerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => { mediaDatas.forEach((mediaData, i) => {
@ -132,12 +132,12 @@ export class Compiler {
}); });
return { return {
data: xml(this.formatter.format(headerWrapper.Relationships, file), prettify), data: xml(this.formatter.format(headerWrapper.Relationships, headerWrapper), prettify),
path: `word/_rels/header${index + 1}.xml.rels`, path: `word/_rels/header${index + 1}.xml.rels`,
}; };
}), }),
FooterRelationships: file.Footers.map((footerWrapper, index) => { FooterRelationships: file.Footers.map((footerWrapper, index) => {
const xmlData = xml(this.formatter.format(footerWrapper.View, file), prettify); const xmlData = xml(this.formatter.format(footerWrapper.View, footerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => { mediaDatas.forEach((mediaData, i) => {
@ -149,12 +149,12 @@ export class Compiler {
}); });
return { return {
data: xml(this.formatter.format(footerWrapper.Relationships, file), prettify), data: xml(this.formatter.format(footerWrapper.Relationships, footerWrapper), prettify),
path: `word/_rels/footer${index + 1}.xml.rels`, path: `word/_rels/footer${index + 1}.xml.rels`,
}; };
}), }),
Headers: file.Headers.map((headerWrapper, index) => { Headers: file.Headers.map((headerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(headerWrapper.View, file), prettify); const tempXmlData = xml(this.formatter.format(headerWrapper.View, headerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own // TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
@ -165,7 +165,7 @@ export class Compiler {
}; };
}), }),
Footers: file.Footers.map((footerWrapper, index) => { Footers: file.Footers.map((footerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(footerWrapper.View, file), prettify); const tempXmlData = xml(this.formatter.format(footerWrapper.View, footerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own // TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
@ -176,19 +176,19 @@ export class Compiler {
}; };
}), }),
ContentTypes: { ContentTypes: {
data: xml(this.formatter.format(file.ContentTypes, file), prettify), data: xml(this.formatter.format(file.ContentTypes, file.Document), prettify),
path: "[Content_Types].xml", path: "[Content_Types].xml",
}, },
AppProperties: { AppProperties: {
data: xml(this.formatter.format(file.AppProperties, file), prettify), data: xml(this.formatter.format(file.AppProperties, file.Document), prettify),
path: "docProps/app.xml", path: "docProps/app.xml",
}, },
FootNotes: { FootNotes: {
data: xml(this.formatter.format(file.FootNotes, file), prettify), data: xml(this.formatter.format(file.FootNotes, file.Document), prettify),
path: "word/footnotes.xml", path: "word/footnotes.xml",
}, },
Settings: { Settings: {
data: xml(this.formatter.format(file.Settings, file), prettify), data: xml(this.formatter.format(file.Settings, file.Document), prettify),
path: "word/settings.xml", path: "word/settings.xml",
}, },
}; };

View File

@ -1,6 +1,7 @@
import { IViewWrapper } from "file/document-wrapper";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
import { File } from "../../../file";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties"; import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
export class Body extends XmlComponent { export class Body extends XmlComponent {
@ -25,7 +26,7 @@ export class Body extends XmlComponent {
this.sections.push(new SectionProperties(options)); this.sections.push(new SectionProperties(options));
} }
public prepForXml(file?: File): IXmlableObject | undefined { public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
if (this.sections.length === 1) { if (this.sections.length === 1) {
this.root.splice(0, 1); this.root.splice(0, 1);
this.root.push(this.sections.pop() as SectionProperties); this.root.push(this.sections.pop() as SectionProperties);

View File

@ -5,7 +5,7 @@ import { stub } from "sinon";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components"; import { EMPTY_OBJECT } from "file/xml-components";
import { File } from "../file"; import { IViewWrapper } from "../document-wrapper";
import { ShadingType } from "../table/shading"; import { ShadingType } from "../table/shading";
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
import { Bookmark, ExternalHyperlink } from "./links"; import { Bookmark, ExternalHyperlink } from "./links";
@ -804,12 +804,10 @@ describe("Paragraph", () => {
], ],
}); });
const fileMock = ({ const fileMock = ({
Document: { Relationships: {
Relationships: { createRelationship: () => ({}),
createRelationship: () => ({}),
},
}, },
} as unknown) as File; } as unknown) as IViewWrapper;
paragraph.prepForXml(fileMock); paragraph.prepForXml(fileMock);
const tree = new Formatter().format(paragraph); const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({

View File

@ -4,7 +4,7 @@ import * as shortid from "shortid";
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { File } from "../file"; import { IViewWrapper } from "../document-wrapper";
import { TargetModeType } from "../relationships/relationship/relationship"; import { TargetModeType } from "../relationships/relationship/relationship";
import { DeletedTextRun, InsertedTextRun } from "../track-revision"; import { DeletedTextRun, InsertedTextRun } from "../track-revision";
import { PageBreak } from "./formatting/page-break"; import { PageBreak } from "./formatting/page-break";
@ -74,12 +74,12 @@ export class Paragraph extends XmlComponent {
} }
} }
public prepForXml(file: File): IXmlableObject | undefined { public prepForXml(file: IViewWrapper): IXmlableObject | undefined {
for (const element of this.root) { for (const element of this.root) {
if (element instanceof ExternalHyperlink) { if (element instanceof ExternalHyperlink) {
const index = this.root.indexOf(element); const index = this.root.indexOf(element);
const concreteHyperlink = new ConcreteHyperlink(element.options.child, shortid.generate().toLowerCase()); const concreteHyperlink = new ConcreteHyperlink(element.options.child, shortid.generate().toLowerCase());
file.Document.Relationships.createRelationship( file.Relationships.createRelationship(
concreteHyperlink.linkId, concreteHyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
element.options.link, element.options.link,

View File

@ -1,9 +1,9 @@
// http://officeopenxml.com/WPtableGrid.php // http://officeopenxml.com/WPtableGrid.php
import { IViewWrapper } from "file/document-wrapper";
import { Paragraph } from "file/paragraph"; import { Paragraph } from "file/paragraph";
import { BorderStyle } from "file/styles"; import { BorderStyle } from "file/styles";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { File } from "../../file";
import { ITableShadingAttributesProperties } from "../shading"; import { ITableShadingAttributesProperties } from "../shading";
import { Table } from "../table"; import { Table } from "../table";
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins"; import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins";
@ -115,7 +115,7 @@ export class TableCell extends XmlComponent {
} }
} }
public prepForXml(file?: File): IXmlableObject | undefined { public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
// Cells must end with a paragraph // Cells must end with a paragraph
if (!(this.root[this.root.length - 1] instanceof Paragraph)) { if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
this.root.push(new Paragraph({})); this.root.push(new Paragraph({}));

View File

@ -1,4 +1,4 @@
import { File } from "../file"; import { IViewWrapper } from "../document-wrapper";
import { IXmlableObject } from "./xmlable-object"; import { IXmlableObject } from "./xmlable-object";
export abstract class BaseXmlComponent { export abstract class BaseXmlComponent {
@ -10,7 +10,7 @@ export abstract class BaseXmlComponent {
this.rootKey = rootKey; this.rootKey = rootKey;
} }
public abstract prepForXml(file?: File): IXmlableObject | undefined; public abstract prepForXml(file?: IViewWrapper): IXmlableObject | undefined;
public get IsDeleted(): boolean { public get IsDeleted(): boolean {
return this.deleted; return this.deleted;

View File

@ -1,4 +1,4 @@
import { File } from "../file"; import { IViewWrapper } from "../document-wrapper";
import { BaseXmlComponent } from "./base"; import { BaseXmlComponent } from "./base";
import { IXmlableObject } from "./xmlable-object"; import { IXmlableObject } from "./xmlable-object";
@ -13,7 +13,7 @@ export abstract class XmlComponent extends BaseXmlComponent {
this.root = new Array<BaseXmlComponent | string>(); this.root = new Array<BaseXmlComponent | string>();
} }
public prepForXml(file?: File): IXmlableObject | undefined { public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
const children = this.root const children = this.root
.filter((c) => { .filter((c) => {
if (c instanceof BaseXmlComponent) { if (c instanceof BaseXmlComponent) {