Merge pull request #87 from dolanmiu/feat/footnotes

Feat/footnotes
This commit is contained in:
Dolan
2018-06-30 23:53:42 +01:00
committed by GitHub
26 changed files with 405 additions and 13 deletions

17
demo/demo17.js Normal file
View File

@ -0,0 +1,17 @@
const docx = require('../build');
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World").referenceFootnote(1);
var paragraph2 = new docx.Paragraph("Hello World").referenceFootnote(2);
doc.addParagraph(paragraph);
doc.addParagraph(paragraph2);
doc.createFootnote(new docx.Paragraph("Test"));
doc.createFootnote(new docx.Paragraph("My amazing reference"));
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

View File

@ -26,7 +26,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(12);
expect(fileNames).has.length(13);
expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml");
@ -35,6 +35,7 @@ describe("Compiler", () => {
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");
expect(fileNames).to.include("word/footer1.xml");
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("[Content_Types].xml");
@ -56,7 +57,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(20);
expect(fileNames).has.length(21);
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");

View File

@ -35,6 +35,7 @@ export class Compiler {
const xmlFileRelationships = xml(this.formatter.format(this.file.FileRelationships));
const xmlContentTypes = xml(this.formatter.format(this.file.ContentTypes));
const xmlAppProperties = xml(this.formatter.format(this.file.AppProperties));
const xmlFootnotes = xml(this.formatter.format(this.file.FootNotes));
this.archive.append(xmlDocument, {
name: "word/document.xml",
@ -80,6 +81,10 @@ export class Compiler {
});
}
this.archive.append(xmlFootnotes, {
name: "word/footnotes.xml",
});
this.archive.append(xmlRelationships, {
name: "word/_rels/document.xml.rels",
});

View File

@ -83,7 +83,7 @@ describe("ContentTypes", () => {
contentTypes.addFooter(102);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][13]).to.deep.equal({
expect(tree["Types"][14]).to.deep.equal({
Override: [
{
_attr: {
@ -94,7 +94,7 @@ describe("ContentTypes", () => {
],
});
expect(tree["Types"][14]).to.deep.equal({
expect(tree["Types"][15]).to.deep.equal({
Override: [
{
_attr: {
@ -113,7 +113,7 @@ describe("ContentTypes", () => {
contentTypes.addHeader(202);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][13]).to.deep.equal({
expect(tree["Types"][14]).to.deep.equal({
Override: [
{
_attr: {
@ -124,7 +124,7 @@ describe("ContentTypes", () => {
],
});
expect(tree["Types"][14]).to.deep.equal({
expect(tree["Types"][15]).to.deep.equal({
Override: [
{
_attr: {

View File

@ -29,6 +29,7 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Override("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", "/word/numbering.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml"));
}
public addFooter(index: number): void {

View File

@ -6,6 +6,7 @@ import { Document } from "./document";
import { FooterReferenceType, HeaderReference, HeaderReferenceType } from "./document/body/section-properties";
import { SectionPropertiesOptions } from "./document/body/section-properties/section-properties";
import { FooterWrapper } from "./footer-wrapper";
import { FootNotes } from "./footnotes";
import { HeaderWrapper } from "./header-wrapper";
import { Media } from "./media";
import { Numbering } from "./numbering";
@ -26,6 +27,7 @@ export class File {
private readonly fileRelationships: Relationships;
private readonly headerWrapper: HeaderWrapper[] = [];
private readonly footerWrapper: FooterWrapper[] = [];
private readonly footNotes: FootNotes;
private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties;
@ -63,6 +65,12 @@ export class File {
"numbering.xml",
);
this.contentTypes = new ContentTypes();
this.docRelationships.createRelationship(
this.nextId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
"footnotes.xml",
);
this.media = new Media();
const header = this.createHeader();
@ -86,6 +94,7 @@ export class File {
);
this.appProperties = new AppProperties();
this.footNotes = new FootNotes();
if (!sectionPropertiesOptions) {
sectionPropertiesOptions = {
footerType: FooterReferenceType.DEFAULT,
@ -152,6 +161,10 @@ export class File {
this.document.Body.addSection(sectionPropertiesOptions);
}
public createFootnote(paragraph: Paragraph): void {
this.footNotes.createFootNote(paragraph);
}
/**
* Creates new header.
*/
@ -262,4 +275,8 @@ export class File {
public get AppProperties(): AppProperties {
return this.appProperties;
}
public get FootNotes(): FootNotes {
return this.footNotes;
}
}

View File

@ -0,0 +1,13 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IFootnoteAttributesProperties {
type?: string;
id: number;
}
export class FootnoteAttributes extends XmlAttributeComponent<IFootnoteAttributesProperties> {
protected xmlKeys = {
type: "w:type",
id: "w:id",
};
}

View File

@ -0,0 +1,25 @@
import { expect } from "chai";
import { Formatter } from "../../../export/formatter";
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 tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);
expect(tree["w:footnote"]).to.be.an.instanceof(Array);
expect(tree["w:footnote"][0]).to.deep.equal({ _attr: { "w:type": "separator", "w:id": 1 } });
});
it("should create a footnote without a footnote type", () => {
const footnote = new Footnote(1);
const tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);
expect(tree["w:footnote"]).to.be.an.instanceof(Array);
expect(tree["w:footnote"][0]).to.deep.equal({ _attr: { "w:id": 1 } });
});
});
});

View File

@ -0,0 +1,26 @@
import { XmlComponent } from "file/xml-components";
import { Paragraph } from "../../paragraph";
import { FootnoteAttributes } from "./footnote-attributes";
import { FootnoteRefRun } from "./run/footnote-ref-run";
export enum FootnoteType {
SEPERATOR = "separator",
CONTINUATION_SEPERATOR = "continuationSeparator",
}
export class Footnote extends XmlComponent {
constructor(id: number, type?: FootnoteType) {
super("w:footnote");
this.root.push(
new FootnoteAttributes({
type: type,
id: id,
}),
);
}
public addParagraph(paragraph: Paragraph): void {
paragraph.addRunToFront(new FootnoteRefRun());
this.root.push(paragraph);
}
}

View File

@ -0,0 +1,10 @@
import { Run } from "file/paragraph";
import { ContinuationSeperator } from "./continuation-seperator";
export class ContinuationSeperatorRun extends Run {
constructor() {
super();
this.root.push(new ContinuationSeperator());
}
}

View File

@ -0,0 +1,7 @@
import { XmlComponent } from "file/xml-components";
export class ContinuationSeperator extends XmlComponent {
constructor() {
super("w:continuationSeparator");
}
}

View File

@ -0,0 +1,11 @@
import { Run } from "file/paragraph";
import { FootnoteRef } from "./footnote-ref";
export class FootnoteRefRun extends Run {
constructor() {
super();
this.style("FootnoteReference");
this.root.push(new FootnoteRef());
}
}

View File

@ -0,0 +1,7 @@
import { XmlComponent } from "file/xml-components";
export class FootnoteRef extends XmlComponent {
constructor() {
super("w:footnoteRef");
}
}

View File

@ -0,0 +1,35 @@
import { Run } from "file/paragraph/run";
import { Style } from "file/paragraph/run/style";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export interface IFootNoteReferenceRunAttributesProperties {
id: number;
}
export class FootNoteReferenceRunAttributes extends XmlAttributeComponent<IFootNoteReferenceRunAttributesProperties> {
protected xmlKeys = {
id: "w:id",
};
}
export class FootnoteReference extends XmlComponent {
constructor(id: number) {
super("w:footnoteReference");
this.root.push(
new FootNoteReferenceRunAttributes({
id: id,
}),
);
}
}
export class FootnoteReferenceRun extends Run {
constructor(id: number) {
super();
this.properties.push(new Style("FootnoteReference"));
this.root.push(new FootnoteReference(id));
}
}

View File

@ -0,0 +1,10 @@
import { Run } from "file/paragraph";
import { Seperator } from "./seperator";
export class SeperatorRun extends Run {
constructor() {
super();
this.root.push(new Seperator());
}
}

View File

@ -0,0 +1,7 @@
import { XmlComponent } from "file/xml-components";
export class Seperator extends XmlComponent {
constructor() {
super("w:separator");
}
}

View File

@ -0,0 +1,43 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IFootnotesAttributesProperties {
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 FootnotesAttributes extends XmlAttributeComponent<IFootnotesAttributesProperties> {
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",
};
}

View File

@ -0,0 +1,70 @@
import { XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph";
import { Footnote, FootnoteType } from "./footnote/footnote";
import { ContinuationSeperatorRun } from "./footnote/run/continuation-seperator-run";
import { SeperatorRun } from "./footnote/run/seperator-run";
import { FootnotesAttributes } from "./footnotes-attributes";
export class FootNotes extends XmlComponent {
private counter: number;
constructor() {
super("w:footnotes");
this.counter = 1;
this.root.push(
new FootnotesAttributes({
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",
}),
);
const begin = new Footnote(-1, FootnoteType.SEPERATOR);
begin.addParagraph(
new Paragraph()
.spacing({
after: 0,
line: 240,
lineRule: "auto",
})
.addRun(new SeperatorRun()),
);
this.root.push(begin);
const spacing = new Footnote(0, FootnoteType.CONTINUATION_SEPERATOR);
spacing.addParagraph(
new Paragraph()
.spacing({
after: 0,
line: 240,
lineRule: "auto",
})
.addRun(new ContinuationSeperatorRun()),
);
this.root.push(spacing);
}
public createFootNote(paragraph: Paragraph): void {
const footnote = new Footnote(this.counter);
footnote.addParagraph(paragraph);
this.root.push(footnote);
this.counter++;
}
}

View File

@ -0,0 +1 @@
export * from "./footnotes";

View File

@ -5,6 +5,7 @@ export interface ISpacingProperties {
after?: number;
before?: number;
line?: number;
lineRule?: string;
}
class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
@ -12,6 +13,7 @@ class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
after: "w:after",
before: "w:before",
line: "w:line",
lineRule: "w:lineRule",
};
}

View File

@ -1,8 +1,8 @@
// http://officeopenxml.com/WPparagraph.php
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { IMediaData } from "file/media";
import { Num } from "file/numbering/num";
import { XmlComponent } from "file/xml-components";
import { PictureRun, Run, TextRun } from "./run";
import { Alignment } from "./formatting/alignment";
import { ThematicBreak } from "./formatting/border";
@ -15,6 +15,7 @@ import { CenterTabStop, LeftTabStop, MaxRightTabStop, RightTabStop } from "./for
import { NumberProperties } from "./formatting/unordered-list";
import { Hyperlink } from "./links";
import { ParagraphProperties } from "./properties";
import { PictureRun, Run, TextRun } from "./run";
export class Paragraph extends XmlComponent {
private properties: ParagraphProperties;
@ -181,4 +182,14 @@ export class Paragraph extends XmlComponent {
this.properties.push(new KeepLines());
return this;
}
public referenceFootnote(id: number): Paragraph {
this.root.push(new FootnoteReferenceRun(id));
return this;
}
public addRunToFront(run: Run): Paragraph {
this.root.splice(1, 0, run);
return this;
}
}

View File

@ -13,7 +13,7 @@ import { Underline } from "./underline";
import { XmlComponent } from "file/xml-components";
export class Run extends XmlComponent {
private properties: RunProperties;
protected properties: RunProperties;
constructor() {
super("w:r");

View File

@ -14,7 +14,8 @@ export type RelationshipType =
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
| "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
export type TargetModeType = "External";

View File

@ -3,6 +3,9 @@ import { Color, Italics, Size } from "../paragraph/run/formatting";
import { Styles } from "./";
import {
FootnoteReferenceStyle,
FootnoteText,
FootnoteTextChar,
Heading1Style,
Heading2Style,
Heading3Style,
@ -65,6 +68,16 @@ export class DefaultStylesFactory {
const hyperLinkStyle = new HyperlinkStyle();
styles.push(hyperLinkStyle);
const footnoteReferenceStyle = new FootnoteReferenceStyle();
styles.push(footnoteReferenceStyle);
const footnoteTextStyle = new FootnoteText();
styles.push(footnoteTextStyle);
const footnoteTextCharStyle = new FootnoteTextChar();
styles.push(footnoteTextCharStyle);
return styles;
}
}

View File

@ -44,7 +44,11 @@ export class UiPriority extends XmlComponent {
}
}
export class UnhideWhenUsed extends XmlComponent {}
export class UnhideWhenUsed extends XmlComponent {
constructor() {
super("w:unhideWhenUsed");
}
}
export class QuickFormat extends XmlComponent {
constructor() {
@ -56,4 +60,8 @@ export class TableProperties extends XmlComponent {}
export class RsId extends XmlComponent {}
export class SemiHidden extends XmlComponent {}
export class SemiHidden extends XmlComponent {
constructor() {
super("w:semiHidden");
}
}

View File

@ -3,7 +3,7 @@ import * as paragraph from "../../paragraph";
import * as formatting from "../../paragraph/run/formatting";
import { RunProperties } from "../../paragraph/run/properties";
import { BasedOn, Name, Next, QuickFormat, UiPriority, UnhideWhenUsed } from "./components";
import { BasedOn, Link, Name, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
export interface IStyleAttributes {
type?: string;
@ -258,7 +258,7 @@ export class CharacterStyle extends Style {
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
this.root.push(new UiPriority("99"));
this.root.push(new UnhideWhenUsed(""));
this.root.push(new UnhideWhenUsed());
}
public basedOn(parentId: string): CharacterStyle {
@ -279,6 +279,11 @@ export class CharacterStyle extends Style {
this.addRunProperty(new formatting.Underline(underlineType, color));
return this;
}
public size(twips: number): CharacterStyle {
this.addRunProperty(new formatting.Size(twips));
return this;
}
}
export class HyperlinkStyle extends CharacterStyle {
@ -289,3 +294,49 @@ export class HyperlinkStyle extends CharacterStyle {
.underline("single");
}
}
export class FootnoteReferenceStyle extends Style {
private readonly runProperties: RunProperties;
constructor() {
super({ type: "character", styleId: "FootnoteReference" });
this.root.push(new Name("footnote reference"));
this.root.push(new BasedOn("DefaultParagraphFont"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.root.push(new UnhideWhenUsed());
this.runProperties = new RunProperties();
this.runProperties.addChildElement(new formatting.SuperScript());
this.root.push(this.runProperties);
}
}
export class FootnoteText extends ParagraphStyle {
constructor() {
super("FootnoteText");
this.root.push(new Name("footnote text"));
this.root.push(new BasedOn("Normal"));
this.root.push(new Link("FootnoteTextChar"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.root.push(new UnhideWhenUsed());
this.spacing({
after: 0,
line: 240,
lineRule: "auto",
});
this.size(20);
}
}
export class FootnoteTextChar extends CharacterStyle {
constructor() {
super("FootnoteTextChar", "Footnote Text Char");
this.basedOn("DefaultParagraphFont");
this.root.push(new Link("FootnoteText"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.size(20);
}
}