#627 Add footnotes relationships

This commit is contained in:
Dolan Miu
2021-03-01 23:35:52 +00:00
parent c6e9696be0
commit f90e84a88d
12 changed files with 107 additions and 66 deletions

View File

@ -1,9 +1,24 @@
// 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 { Document, ExternalHyperlink, Footer, Media, Packer, Paragraph, TextRun } from "../build"; import { Document, ExternalHyperlink, Footer, FootnoteReferenceRun, Media, Packer, Paragraph, TextRun } from "../build";
const doc = new Document({}); const doc = new Document({
footnotes: [
new Paragraph({
children: [
new TextRun("Click here for the "),
new ExternalHyperlink({
child: new TextRun({
text: "Footnotes external hyperlink",
style: "Hyperlink",
}),
link: "http://www.example.com",
}),
],
}),
],
});
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
@ -54,6 +69,7 @@ doc.addSection({
}), }),
link: "http://www.example.com", link: "http://www.example.com",
}), }),
new FootnoteReferenceRun(1)
], ],
}), }),
new Paragraph({ new Paragraph({

View File

@ -22,13 +22,14 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array); expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(14); expect(fileNames).has.length(15);
expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml"); expect(fileNames).to.include("docProps/core.xml");
expect(fileNames).to.include("docProps/app.xml"); expect(fileNames).to.include("docProps/app.xml");
expect(fileNames).to.include("word/numbering.xml"); expect(fileNames).to.include("word/numbering.xml");
expect(fileNames).to.include("word/footnotes.xml"); expect(fileNames).to.include("word/footnotes.xml");
expect(fileNames).to.include("word/_rels/footnotes.xml.rels");
expect(fileNames).to.include("word/settings.xml"); expect(fileNames).to.include("word/settings.xml");
expect(fileNames).to.include("word/_rels/document.xml.rels"); expect(fileNames).to.include("word/_rels/document.xml.rels");
expect(fileNames).to.include("[Content_Types].xml"); expect(fileNames).to.include("[Content_Types].xml");
@ -62,7 +63,7 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array); expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(22); expect(fileNames).has.length(23);
expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels"); expect(fileNames).to.include("word/_rels/header1.xml.rels");
@ -88,7 +89,7 @@ describe("Compiler", () => {
const spy = sinon.spy(compiler["formatter"], "format"); const spy = sinon.spy(compiler["formatter"], "format");
compiler.compile(file); compiler.compile(file);
expect(spy.callCount).to.equal(10); expect(spy.callCount).to.equal(11);
}); });
}); });
}); });

View File

@ -25,6 +25,7 @@ interface IXmlifyedFileMapping {
readonly ContentTypes: IXmlifyedFile; readonly ContentTypes: IXmlifyedFile;
readonly AppProperties: IXmlifyedFile; readonly AppProperties: IXmlifyedFile;
readonly FootNotes: IXmlifyedFile; readonly FootNotes: IXmlifyedFile;
readonly FootNotesRelationships: IXmlifyedFile;
readonly Settings: IXmlifyedFile; readonly Settings: IXmlifyedFile;
} }
@ -184,9 +185,13 @@ export class Compiler {
path: "docProps/app.xml", path: "docProps/app.xml",
}, },
FootNotes: { FootNotes: {
data: xml(this.formatter.format(file.FootNotes, file.Document), prettify), data: xml(this.formatter.format(file.FootNotes.View, file.FootNotes), prettify),
path: "word/footnotes.xml", path: "word/footnotes.xml",
}, },
FootNotesRelationships: {
data: xml(this.formatter.format(file.FootNotes.Relationships, file.FootNotes), prettify),
path: "word/_rels/footnotes.xml.rels",
},
Settings: { Settings: {
data: xml(this.formatter.format(file.Settings, file.Document), prettify), data: xml(this.formatter.format(file.Settings, file.Document), prettify),
path: "word/settings.xml", path: "word/settings.xml",

View File

@ -1,50 +1,16 @@
import { expect } from "chai"; import { expect } from "chai";
import * as sinon from "sinon";
import { FooterWrapper } from "./footer-wrapper"; import { DocumentWrapper } from "./document-wrapper";
import { Media } from "./media";
import { Paragraph } from "./paragraph";
import { Table, TableCell, TableRow } from "./table";
describe("FooterWrapper", () => { describe("DocumentWrapper", () => {
describe("#add", () => { describe("#constructor", () => {
it("should call the underlying footer's addParagraph", () => { it("should create", () => {
const file = new FooterWrapper(new Media(), 1); const file = new DocumentWrapper({ background: {} });
const spy = sinon.spy(file.View, "add");
file.add(new Paragraph({}));
expect(spy.called).to.equal(true); // tslint:disable-next-line: no-unused-expression
}); expect(file.View).to.be.ok;
// tslint:disable-next-line: no-unused-expression
it("should call the underlying footer's addParagraph", () => { expect(file.Relationships).to.be.ok;
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.View, "add");
file.add(
new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}),
);
expect(spy.called).to.equal(true);
});
});
describe("#addChildElement", () => {
it("should call the underlying footer's addChildElement", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.View, "addChildElement");
// tslint:disable-next-line:no-any
file.addChildElement({} as any);
expect(spy.called).to.equal(true);
}); });
}); });
}); });

View File

@ -1,10 +1,11 @@
import { Document, IDocumentOptions } from "./document"; import { Document, IDocumentOptions } from "./document";
import { Footer } from "./footer"; import { Footer } from "./footer";
import { FootNotes } from "./footnotes";
import { Header } from "./header/header"; import { Header } from "./header/header";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
export interface IViewWrapper { export interface IViewWrapper {
readonly View: Document | Footer | Header; readonly View: Document | Footer | Header | FootNotes;
readonly Relationships: Relationships; readonly Relationships: Relationships;
} }

View File

@ -242,7 +242,7 @@ describe("File", () => {
footnotes: [new Paragraph("hello")], footnotes: [new Paragraph("hello")],
}); });
const tree = new Formatter().format(wrapper.FootNotes); const tree = new Formatter().format(wrapper.FootNotes.View);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:footnotes": [ "w:footnotes": [

View File

@ -11,7 +11,7 @@ import {
import { IPageMarginAttributes } from "./document/body/section-properties/page-margin/page-margin-attributes"; import { IPageMarginAttributes } from "./document/body/section-properties/page-margin/page-margin-attributes";
import { IFileProperties } from "./file-properties"; import { IFileProperties } from "./file-properties";
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper"; import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
import { FootNotes } from "./footnotes"; import { FootnotesWrapper } from "./footnotes-wrapper";
import { Footer, Header } from "./header"; import { Footer, Header } from "./header";
import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
import { Media } from "./media"; import { Media } from "./media";
@ -53,7 +53,7 @@ export class File {
private readonly numbering: Numbering; private readonly numbering: Numbering;
private readonly media: Media; private readonly media: Media;
private readonly fileRelationships: Relationships; private readonly fileRelationships: Relationships;
private readonly footNotes: FootNotes; private readonly footnotesWrapper: FootnotesWrapper;
private readonly settings: Settings; private readonly settings: Settings;
private readonly contentTypes: ContentTypes; private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties; private readonly appProperties: AppProperties;
@ -76,10 +76,9 @@ export class File {
config: [], config: [],
}, },
); );
// this.documentWrapper.Relationships = new Relationships();
this.fileRelationships = new Relationships(); this.fileRelationships = new Relationships();
this.appProperties = new AppProperties(); this.appProperties = new AppProperties();
this.footNotes = new FootNotes(); this.footnotesWrapper = new FootnotesWrapper();
this.contentTypes = new ContentTypes(); this.contentTypes = new ContentTypes();
this.documentWrapper = new DocumentWrapper({ background: options.background || {} }); this.documentWrapper = new DocumentWrapper({ background: options.background || {} });
this.settings = new Settings({ this.settings = new Settings({
@ -138,7 +137,7 @@ export class File {
if (options.footnotes) { if (options.footnotes) {
for (const paragraph of options.footnotes) { for (const paragraph of options.footnotes) {
this.footNotes.createFootNote(paragraph); this.footnotesWrapper.View.createFootNote(paragraph);
} }
} }
@ -305,8 +304,8 @@ export class File {
return this.appProperties; return this.appProperties;
} }
public get FootNotes(): FootNotes { public get FootNotes(): FootnotesWrapper {
return this.footNotes; return this.footnotesWrapper;
} }
public get Settings(): Settings { public get Settings(): Settings {

View File

@ -0,0 +1,16 @@
import { expect } from "chai";
import { FootnotesWrapper } from "./footnotes-wrapper";
describe("FootnotesWrapper", () => {
describe("#constructor", () => {
it("should create", () => {
const file = new FootnotesWrapper();
// tslint:disable-next-line: no-unused-expression
expect(file.View).to.be.ok;
// tslint:disable-next-line: no-unused-expression
expect(file.Relationships).to.be.ok;
});
});
});

View File

@ -0,0 +1,21 @@
import { IViewWrapper } from "./document-wrapper";
import { FootNotes } from "./footnotes/footnotes";
import { Relationships } from "./relationships";
export class FootnotesWrapper implements IViewWrapper {
private readonly footnotess: FootNotes;
private readonly relationships: Relationships;
constructor() {
this.footnotess = new FootNotes();
this.relationships = new Relationships();
}
public get View(): FootNotes {
return this.footnotess;
}
public get Relationships(): Relationships {
return this.relationships;
}
}

View File

@ -7,7 +7,10 @@ import { Footnote, FootnoteType } from "./footnote";
describe("Footnote", () => { describe("Footnote", () => {
describe("#constructor", () => { describe("#constructor", () => {
it("should create a footnote with a footnote type", () => { 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,
});
const tree = new Formatter().format(footnote); const tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);
@ -15,7 +18,9 @@ describe("Footnote", () => {
}); });
it("should create a footnote without a footnote type", () => { it("should create a footnote without a footnote type", () => {
const footnote = new Footnote(1); const footnote = new Footnote({
id: 1,
});
const tree = new Formatter().format(footnote); const tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);

View File

@ -9,13 +9,18 @@ export enum FootnoteType {
CONTINUATION_SEPERATOR = "continuationSeparator", CONTINUATION_SEPERATOR = "continuationSeparator",
} }
export interface IFootnoteOptions {
readonly id: number;
readonly type?: FootnoteType;
}
export class Footnote extends XmlComponent { export class Footnote extends XmlComponent {
constructor(id: number, type?: FootnoteType) { constructor(options: IFootnoteOptions) {
super("w:footnote"); super("w:footnote");
this.root.push( this.root.push(
new FootnoteAttributes({ new FootnoteAttributes({
type: type, type: options.type,
id: id, id: options.id,
}), }),
); );
} }

View File

@ -36,7 +36,10 @@ export class FootNotes extends XmlComponent {
}), }),
); );
const begin = new Footnote(-1, FootnoteType.SEPERATOR); const begin = new Footnote({
id: -1,
type: FootnoteType.SEPERATOR,
});
begin.add( begin.add(
new Paragraph({ new Paragraph({
spacing: { spacing: {
@ -49,7 +52,10 @@ export class FootNotes extends XmlComponent {
); );
this.root.push(begin); this.root.push(begin);
const spacing = new Footnote(0, FootnoteType.CONTINUATION_SEPERATOR); const spacing = new Footnote({
id: 0,
type: FootnoteType.CONTINUATION_SEPERATOR,
});
spacing.add( spacing.add(
new Paragraph({ new Paragraph({
spacing: { spacing: {
@ -64,7 +70,7 @@ export class FootNotes extends XmlComponent {
} }
public createFootNote(paragraph: Paragraph): void { public createFootNote(paragraph: Paragraph): void {
const footnote = new Footnote(this.currentId); const footnote = new Footnote({ id: this.currentId });
footnote.add(paragraph); footnote.add(paragraph);
this.root.push(footnote); this.root.push(footnote);