Merge branch 'feat/table-of-contents' of https://github.com/dolanmiu/docx into feat/table-of-contents

This commit is contained in:
Sergio Mendonça
2018-09-21 07:37:26 -03:00
14 changed files with 312 additions and 52 deletions

View File

@ -19,7 +19,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(17); expect(fileNames).has.length(18);
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");
@ -29,6 +29,7 @@ describe("Compiler", () => {
expect(fileNames).to.include("word/_rels/header1.xml.rels"); expect(fileNames).to.include("word/_rels/header1.xml.rels");
expect(fileNames).to.include("word/footer1.xml"); expect(fileNames).to.include("word/footer1.xml");
expect(fileNames).to.include("word/footnotes.xml"); expect(fileNames).to.include("word/footnotes.xml");
expect(fileNames).to.include("word/settings.xml");
expect(fileNames).to.include("word/_rels/footer1.xml.rels"); expect(fileNames).to.include("word/_rels/footer1.xml.rels");
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");
@ -47,7 +48,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(25); expect(fileNames).has.length(26);
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");

View File

@ -23,6 +23,7 @@ interface IXmlifyedFileMapping {
ContentTypes: IXmlifyedFile; ContentTypes: IXmlifyedFile;
AppProperties: IXmlifyedFile; AppProperties: IXmlifyedFile;
FootNotes: IXmlifyedFile; FootNotes: IXmlifyedFile;
Settings: IXmlifyedFile;
} }
export class Compiler { export class Compiler {
@ -122,6 +123,10 @@ export class Compiler {
data: xml(this.formatter.format(file.FootNotes)), data: xml(this.formatter.format(file.FootNotes)),
path: "word/footnotes.xml", path: "word/footnotes.xml",
}, },
Settings: {
data: xml(this.formatter.format(file.Settings)),
path: "word/settings.xml",
},
}; };
} }
} }

View File

@ -12,6 +12,7 @@ import { Bookmark, Hyperlink, Paragraph, Run, TextRun } from "./paragraph";
import { Begin, End, Separate } from "./paragraph/run/field"; import { Begin, End, Separate } from "./paragraph/run/field";
import { Tab } from "./paragraph/run/tab"; import { Tab } from "./paragraph/run/tab";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Settings } from "./settings";
import { Styles } from "./styles"; import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { ExternalStylesFactory } from "./styles/external-styles-factory";
import { DefaultStylesFactory } from "./styles/factory"; import { DefaultStylesFactory } from "./styles/factory";
@ -29,6 +30,7 @@ export class File {
private readonly headerWrapper: HeaderWrapper[] = []; private readonly headerWrapper: HeaderWrapper[] = [];
private readonly footerWrapper: FooterWrapper[] = []; private readonly footerWrapper: FooterWrapper[] = [];
private readonly footNotes: FootNotes; private readonly footNotes: FootNotes;
private readonly settings: Settings;
private readonly contentTypes: ContentTypes; private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties; private readonly appProperties: AppProperties;
@ -109,6 +111,7 @@ export class File {
sectionPropertiesOptions.footerId = footer.Footer.ReferenceId; sectionPropertiesOptions.footerId = footer.Footer.ReferenceId;
} }
this.document = new Document(sectionPropertiesOptions); this.document = new Document(sectionPropertiesOptions);
this.settings = new Settings();
} }
public addTableOfContents(toc: TableOfContents): void { public addTableOfContents(toc: TableOfContents): void {
@ -286,9 +289,17 @@ export class File {
return this.footNotes; return this.footNotes;
} }
public get Settings(): Settings {
return this.settings;
}
public generateTablesOfContents(): void { public generateTablesOfContents(): void {
// console.log("generateTablesOfContents"); // console.log("generateTablesOfContents");
this.document.getTablesOfContents().forEach((child) => this.generateContent(child)); const TOCs = this.document.getTablesOfContents();
if (TOCs && TOCs.length) {
this.settings.addUpdateFields();
TOCs.forEach((child) => this.generateContent(child));
}
} }
private generateContent(toc: TableOfContents): void { private generateContent(toc: TableOfContents): void {

View File

@ -1,7 +1,7 @@
import { assert } from "chai"; import { assert } from "chai";
import { Utility } from "../../../tests/utility"; import { Utility } from "../../../tests/utility";
import { LeftTabStop, MaxRightTabStop } from "./tab-stop"; import { LeftTabStop, MaxRightTabStop, RightTabStop } from "./tab-stop";
describe("LeftTabStop", () => { describe("LeftTabStop", () => {
let tabStop: LeftTabStop; let tabStop: LeftTabStop;
@ -28,7 +28,28 @@ describe("LeftTabStop", () => {
}); });
describe("RightTabStop", () => { describe("RightTabStop", () => {
// TODO let tabStop: RightTabStop;
beforeEach(() => {
tabStop = new RightTabStop(100, "dot");
});
describe("#constructor()", () => {
it("should create a Tab Stop with correct attributes", () => {
const newJson = Utility.jsonify(tabStop);
const attributes = {
val: "right",
pos: 100,
leader: "dot",
};
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
});
it("should create a Tab Stop with w:tab", () => {
const newJson = Utility.jsonify(tabStop);
assert.equal(newJson.root[0].rootKey, "w:tab");
});
});
}); });
describe("MaxRightTabStop", () => { describe("MaxRightTabStop", () => {

View File

@ -11,7 +11,7 @@ export class TabStop extends XmlComponent {
export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start"; export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start";
export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore"; export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore";
export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader: LeaderType }> { export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader?: LeaderType }> {
protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" }; protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" };
} }
@ -22,7 +22,7 @@ export class TabStopItem extends XmlComponent {
new TabAttributes({ new TabAttributes({
val: value, val: value,
pos: position, pos: position,
leader: leader || "none", leader,
}), }),
); );
} }

View File

@ -0,0 +1,2 @@
export * from "./settings";
export * from "./update-fields";

View File

@ -0,0 +1,60 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { Settings } from "./";
describe("Settings", () => {
describe("#constructor", () => {
it("should create a empty Settings with correct rootKey", () => {
const settings = new Settings();
const tree = new Formatter().format(settings);
let keys = Object.keys(tree);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("w:settings");
expect(tree["w:settings"]).is.an.instanceof(Array);
expect(tree["w:settings"]).has.length(1);
keys = Object.keys(tree["w:settings"][0]);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("_attr");
});
});
describe("#addUpdateFields", () => {
const assertSettingsWithUpdateFields = (settings: Settings) => {
const tree = new Formatter().format(settings);
let keys = Object.keys(tree);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("w:settings");
const rootArray = tree["w:settings"];
expect(rootArray).is.an.instanceof(Array);
expect(rootArray).has.length(2);
keys = Object.keys(rootArray[0]);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("_attr");
keys = Object.keys(rootArray[1]);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("w:updateFields");
const updateFieldsArray = rootArray[1]["w:updateFields"];
keys = Object.keys(updateFieldsArray[0]);
expect(keys).is.an.instanceof(Array);
expect(keys).has.length(1);
expect(keys[0]).to.be.equal("_attr");
const updateFieldsAttr = updateFieldsArray[0]._attr;
expect(updateFieldsAttr["w:val"]).to.be.equal(true);
};
it("should add a UpdateFields with value true", () => {
const settings = new Settings();
settings.addUpdateFields();
assertSettingsWithUpdateFields(settings);
});
it("should add a UpdateFields with value true only once", () => {
const settings = new Settings();
settings.addUpdateFields();
assertSettingsWithUpdateFields(settings);
settings.addUpdateFields();
assertSettingsWithUpdateFields(settings);
});
});
});

View File

@ -0,0 +1,73 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { UpdateFields } from "./update-fields";
export interface ISettingsAttributesProperties {
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 SettingsAttributes extends XmlAttributeComponent<ISettingsAttributesProperties> {
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",
};
}
export class Settings extends XmlComponent {
constructor() {
super("w:settings");
this.root.push(
new SettingsAttributes({
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",
}),
);
}
public addUpdateFields(): void {
if (!this.root.find((child) => child instanceof UpdateFields)) {
this.addChildElement(new UpdateFields());
}
}
}

View File

@ -0,0 +1,40 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { UpdateFields } from "./";
const UF_TRUE = {
"w:updateFields": [
{
_attr: {
"w:val": true,
},
},
],
};
const UF_FALSE = {
"w:updateFields": [
{
_attr: {
"w:val": false,
},
},
],
};
describe("Update Fields", () => {
describe("#constructor", () => {
it("should construct a Update Fields with TRUE value by default", () => {
const uf = new UpdateFields();
const tree = new Formatter().format(uf);
expect(tree).to.be.deep.equal(UF_TRUE);
});
it("should construct a Update Fields with TRUE value", () => {
const uf = new UpdateFields(true);
const tree = new Formatter().format(uf);
expect(tree).to.be.deep.equal(UF_TRUE);
});
it("should construct a Update Fields with FALSE value", () => {
const uf = new UpdateFields(false);
const tree = new Formatter().format(uf);
expect(tree).to.be.deep.equal(UF_FALSE);
});
});
});

View File

@ -0,0 +1,19 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export interface IUpdateFieldsAttributesProperties {
enabled: boolean;
}
export class UpdateFieldsAttributes extends XmlAttributeComponent<IUpdateFieldsAttributesProperties> {
protected xmlKeys = {
enabled: "w:val",
};
}
export class UpdateFields extends XmlComponent {
constructor(enabled: boolean = true) {
super("w:updateFields");
this.root.push(
new UpdateFieldsAttributes({
enabled,
}),
);
}
}

View File

@ -1,9 +1,9 @@
import { Paragraph } from "file/paragraph"; import { Paragraph } from "file/paragraph";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
export class StdContent extends XmlComponent { export class SdtContent extends XmlComponent {
constructor() { constructor() {
super("w:stdContent"); super("w:sdtContent");
} }
public addGeneratedContent(paragraph: Paragraph): void { public addGeneratedContent(paragraph: Paragraph): void {

View File

@ -1,9 +1,9 @@
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { Alias } from "./alias"; import { Alias } from "./alias";
export class StdProperties extends XmlComponent { export class SdtProperties extends XmlComponent {
constructor(alias: string) { constructor(alias: string) {
super("w:stdPr"); super("w:sdtPr");
this.root.push(new Alias(alias)); this.root.push(new Alias(alias));
} }
} }

View File

@ -4,39 +4,14 @@ import { Formatter } from "../../export/formatter";
import { TableOfContents } from "./"; import { TableOfContents } from "./";
const DEFAULT_TOC = { const DEFAULT_TOC = {
"w:p": [ "w:sdt": [
{ {
"w:pPr": [], "w:sdtPr": [
},
{
"w:r": [
{ {
"w:rPr": [], "w:alias": [
},
{
"w:fldChar": [
{ {
_attr: { _attr: {
"w:fldCharType": "begin", "w:val": "Table of Contents",
},
},
],
},
{
"w:instrText": [
{
_attr: {
"xml:space": "preserve",
},
},
'TOC \\o "1-6"',
],
},
{
"w:fldChar": [
{
_attr: {
"w:fldCharType": "separate",
}, },
}, },
], ],
@ -44,16 +19,69 @@ const DEFAULT_TOC = {
], ],
}, },
{ {
"w:r": [ "w:sdtContent": [
{ {
"w:rPr": [], "w:p": [
{
"w:pPr": [],
},
{
"w:r": [
{
"w:rPr": [],
},
{
"w:fldChar": [
{
_attr: {
"w:fldCharType": "begin",
},
},
],
},
{
"w:instrText": [
{
_attr: {
"xml:space": "preserve",
},
},
'TOC \\o "1-6"',
],
},
{
"w:fldChar": [
{
_attr: {
"w:fldCharType": "separate",
},
},
],
},
],
},
],
}, },
{ {
"w:fldChar": [ "w:p": [
{ {
_attr: { "w:pPr": [],
"w:fldCharType": "end", },
}, {
"w:r": [
{
"w:rPr": [],
},
{
"w:fldChar": [
{
_attr: {
"w:fldCharType": "end",
},
},
],
},
],
}, },
], ],
}, },

View File

@ -3,22 +3,22 @@ import { Paragraph } from "file/paragraph";
import { Run } from "file/paragraph/run"; import { Run } from "file/paragraph/run";
import { Begin, End, Separate } from "file/paragraph/run/field"; import { Begin, End, Separate } from "file/paragraph/run/field";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { StdContent } from "./std-content"; import { SdtContent } from "./sdt-content";
import { StdProperties } from "./std-properties"; import { SdtProperties } from "./sdt-properties";
import { TableOfContentsInstruction } from "./table-of-contents-instruction"; import { TableOfContentsInstruction } from "./table-of-contents-instruction";
export class TableOfContents extends XmlComponent { export class TableOfContents extends XmlComponent {
// private readonly tocProperties: TableOfContentsProperties; // private readonly tocProperties: TableOfContentsProperties;
private readonly properties: StdProperties; private readonly properties: SdtProperties;
private readonly content: StdContent; private readonly content: SdtContent;
private readonly instruction: TableOfContentsInstruction; private readonly instruction: TableOfContentsInstruction;
constructor(/*tocProperties?: TableOfContentsProperties*/) { constructor(/*tocProperties?: TableOfContentsProperties*/) {
super("w:sdt"); super("w:sdt");
this.properties = new StdProperties("Table of Contents"); this.properties = new SdtProperties("Table of Contents");
this.content = new StdContent(); this.content = new SdtContent();
this.instruction = new TableOfContentsInstruction(); this.instruction = new TableOfContentsInstruction();
this.root.push(this.properties); this.root.push(this.properties);
this.root.push(this.content); this.root.push(this.content);