diff --git a/.gitignore b/.gitignore
index 4f8777b466..e6143b4db7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,8 +48,15 @@ docs/.nojekyll
!.vscode/extensions.json
.history
+# IntelliJ
+.idea
+
# Lock files
package-lock.json
+yarn.lock
# Documents
My Document.docx
+
+# Temporary folder
+tmp
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000000..469d080845
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v8
\ No newline at end of file
diff --git a/README.md b/README.md
index 7e25666ee0..020cb78f96 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -17,11 +17,20 @@
[![PRs Welcome][pr-image]][pr-url]
-
+
# Demo
+## Browser
+
+Here are examples of `docx` being used with basic `HTML/JS` in a browser environment.
+
+* https://codepen.io/anon/pen/dqoVgQ
+* https://jsfiddle.net/3xhezb5w/2
+
+## Node
+
Press `endpoint` on the `RunKit` website:

@@ -33,9 +42,11 @@ Press `endpoint` on the `RunKit` website:
* https://runkit.com/dolanmiu/docx-demo5 - Images
* https://runkit.com/dolanmiu/docx-demo6 - Margins
* https://runkit.com/dolanmiu/docx-demo7 - Landscape
-* https://runkit.com/dolanmiu/docx-demo8/1.0.1 - Header and Footer
+* https://runkit.com/dolanmiu/docx-demo8 - Header and Footer
* https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx**
+More [here](https://docx.js.org/#/examples) and [here](https://github.com/dolanmiu/docx/tree/master/demo)
+
# How to use & Documentation
Please refer to the [documentation at https://docx.js.org/](https://docx.js.org/) for details on how to use this library, examples and much more!
diff --git a/demo/demo27.ts b/demo/demo27.ts
new file mode 100644
index 0000000000..e04fc07e02
--- /dev/null
+++ b/demo/demo27.ts
@@ -0,0 +1,34 @@
+import * as fs from "fs";
+import { Document, Packer } from "../build";
+
+const doc = new Document();
+const myStyles = doc.Styles;
+
+// The first argument is an ID you use to apply the style to paragraphs
+// The second argument is a human-friendly name to show in the UI
+myStyles.createParagraphStyle("myWonkyStyle", "My Wonky Style")
+ .basedOn("Normal")
+ .next("Normal")
+ .color("990000")
+ .italics()
+ .indent({left: 720}) // 720 TWIP === 720 / 20 pt === .5 in
+ .spacing({line: 276}); // 276 / 240 = 1.15x line spacing
+
+myStyles.createParagraphStyle("Heading2", "Heading 2")
+ .basedOn("Normal")
+ .next("Normal")
+ .quickFormat()
+ .size(26) // 26 half-points === 13pt font
+ .bold()
+ .underline("double", "FF0000")
+ .spacing({before: 240, after: 120}); // TWIP for both
+
+doc.createParagraph("Hello").style("myWonkyStyle");
+doc.createParagraph("World").heading2(); // Uses the Heading2 style
+
+const packer = new Packer();
+
+packer.toBuffer(doc).then((buffer) => {
+ fs.writeFileSync("My Document.docx", buffer);
+ console.log("Document created successfully at project root!");
+});
diff --git a/demo/demo28.ts b/demo/demo28.ts
new file mode 100644
index 0000000000..d13acf5b46
--- /dev/null
+++ b/demo/demo28.ts
@@ -0,0 +1,43 @@
+// Creates two paragraphs, one with a border and one without
+// Import from 'docx' rather than '../build' if you install from npm
+import * as fs from "fs";
+import { File, Packer, Paragraph, StyleLevel, TableOfContents } from "../build";
+
+const doc = new File();
+
+// The first argument is an ID you use to apply the style to paragraphs
+// The second argument is a human-friendly name to show in the UI
+doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style")
+ .basedOn("Heading1")
+ .next("Heading1")
+ .color("990000")
+ .italics();
+
+// WordprocessingML docs for TableOfContents can be found here:
+// http://officeopenxml.com/WPtableOfContents.php
+
+// Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle,
+// making the entries be hyperlinks for the paragraph
+const toc = new TableOfContents("Summary", {
+ hyperlink: true,
+ headingStyleRange: "1-5",
+ stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)],
+});
+
+doc.addTableOfContents(toc);
+
+doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore());
+doc.addParagraph(new Paragraph("I'm a little text very nicely written.'"));
+
+doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore());
+doc.addParagraph(new Paragraph("I'm a other text very nicely written.'"));
+doc.addParagraph(new Paragraph("Header #2.1").heading2());
+doc.addParagraph(new Paragraph("I'm a another text very nicely written.'"));
+
+doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore());
+
+const packer = new Packer();
+
+packer.toBuffer(doc).then((buffer) => {
+ fs.writeFileSync("My Document.docx", buffer);
+});
diff --git a/docs/README.md b/docs/README.md
index f3134b8fa3..4fc5757477 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,5 +1,5 @@
-
+
@@ -54,6 +54,10 @@ exporter.pack("My First Document");
[@h4buli](https://github.com/h4buli)
+
+
+
+
---
Made with 💖
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 0b159a95a8..4b9a626a03 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -16,6 +16,7 @@
* [Bullet Points](usage/bullet-points.md)
* [Numbering](usage/numbering.md)
* [Tab Stops](usage/tab-stops.md)
+ * [Table of Contents](usage/table-of-contents.md)
* Styling
* [Styling with JS](usage/styling-with-js.md)
* [Styling with XML](usage/styling-with-xml.md)
diff --git a/docs/usage/styling-with-js.md b/docs/usage/styling-with-js.md
index 3e482bca14..4f362192af 100644
--- a/docs/usage/styling-with-js.md
+++ b/docs/usage/styling-with-js.md
@@ -20,6 +20,7 @@ const name = new TextRun("Name:")
* `.size(halfPts)`: Set the font size, measured in half-points
* `.font(name)`: Set the run's font
* `.style(name)`: Apply a named run style
+ * `.characterSpacing(value)`: Set the character spacing adjustment (in TWIPs)
* For paragraph formatting:
* `.heading1()`, `.heading2()`, `.heading3()`, `.heading4()`, `.heading5()`, `.title()`: apply the appropriate style to the paragraph
* `.left()`, `.center()`, `.right()`, `.justified()`: set the paragraph's alignment
diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md
new file mode 100644
index 0000000000..7c226833cd
--- /dev/null
+++ b/docs/usage/table-of-contents.md
@@ -0,0 +1,76 @@
+# Table of Contents
+
+You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php).
+
+>Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically.
+>This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?".
+>You have say yes to Word generate the content of all table of contents.
+
+The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251).
+
+## How to
+
+All you need to do is create a `TableOfContents` object and assign it to the document.
+
+```js
+const toc = new TableOfContents("Summary", {
+ hyperlink: true,
+ headingStyleRange: "1-5",
+ stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)]
+});
+
+doc.addTableOfContents(toc);
+```
+
+## Table of Contents Options
+
+Here is the list of all options that you can use to generate your tables of contents:
+
+| Option | Type | TOC Field Switch | Description |
+| --- | --- | --- | --- |
+|captionLabel|string|`\a`|Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use ``\c`` to build a table of captions with labels and numbers.|
+|entriesFromBookmark|string|`\b`|Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument.|
+|captionLabelIncludingNumbers|string|`\c`|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.|
+|sequenceAndPageNumbersSeparator|string|`\d`|When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).|
+|tcFieldIdentifier|string|`\f`|Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter).|
+|hyperlink|boolean|`\h`|Makes the table of contents entries hyperlinks.|
+|tcFieldLevelRange|string|`\l`|Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.|
+|pageNumbersEntryLevelsRange|string|`\n`|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`.|
+|headingStyleRange|string|`\o`|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.|
+|entryAndPageNumberSeparator|string|`\p`|`text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.|
+|seqFieldIdentifierForPrefix|string|`\s`|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field.|
+|stylesWithLevels|StyleLevel[]|`\t`| Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`.|
+|useAppliedParagraphOutlineLevel|boolean|`\u`|Uses the applied paragraph outline level.|
+|preserveTabInEntries|boolean|`\w`|Preserves tab entries within table entries.|
+|preserveNewLineInEntries|boolean|`\x`|Preserves newline characters within table entries.|
+|hideTabAndPageNumbersInWebView|boolean|`\z`|Hides tab leader and page numbers in web page view (§17.18.102).|
+
+## Examples
+
+```js
+// Let's define the options for generate a TOC for heading 1-5 and MySpectacularStyle,
+// making the entries be hyperlinks for the paragraph
+const toc = new TableOfContents("Summary", {
+ hyperlink: true,
+ headingStyleRange: "1-5",
+ stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)]
+});
+
+doc.addTableOfContents(toc);
+
+doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore());
+doc.addParagraph(new Paragraph("I'm a little text, very nicely written.'"));
+
+doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore());
+doc.addParagraph(new Paragraph("I'm another text very nicely written.'"));
+doc.addParagraph(new Paragraph("Header #2.1").heading2());
+doc.addParagraph(new Paragraph("I'm another text very nicely written.'"));
+
+doc.addParagraph(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore());
+```
+
+### Complete example
+
+[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo28.ts ":include")
+
+_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo28.ts_
diff --git a/logo/logo-small.gif b/logo/logo-small.gif
new file mode 100644
index 0000000000..4b4a567c02
Binary files /dev/null and b/logo/logo-small.gif differ
diff --git a/logo/logo-small.png b/logo/logo-small.png
new file mode 100644
index 0000000000..966fd4b1e7
Binary files /dev/null and b/logo/logo-small.png differ
diff --git a/logo/logo-small.psd b/logo/logo-small.psd
new file mode 100644
index 0000000000..4c6b32123c
Binary files /dev/null and b/logo/logo-small.psd differ
diff --git a/logo/logo.psd b/logo/logo.psd
new file mode 100644
index 0000000000..9763168e2c
Binary files /dev/null and b/logo/logo.psd differ
diff --git a/package.json b/package.json
index 330ad72bac..e63dd166bf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "docx",
- "version": "4.0.0",
+ "version": "4.1.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js",
"scripts": {
@@ -82,5 +82,8 @@
"typedoc": "^0.11.1",
"typescript": "2.9.2",
"webpack": "^3.10.0"
+ },
+ "engines": {
+ "node": ">=8"
}
}
diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts
index fc02d4a814..774b71c26f 100644
--- a/src/export/packer/next-compiler.spec.ts
+++ b/src/export/packer/next-compiler.spec.ts
@@ -19,7 +19,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(17);
+ expect(fileNames).has.length(18);
expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.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/footer1.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/document.xml.rels");
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);
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/_rels/header1.xml.rels");
diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts
index f06c09e196..1ee02bf589 100644
--- a/src/export/packer/next-compiler.ts
+++ b/src/export/packer/next-compiler.ts
@@ -23,6 +23,7 @@ interface IXmlifyedFileMapping {
ContentTypes: IXmlifyedFile;
AppProperties: IXmlifyedFile;
FootNotes: IXmlifyedFile;
+ Settings: IXmlifyedFile;
}
export class Compiler {
@@ -62,6 +63,7 @@ export class Compiler {
}
private xmlifyFile(file: File): IXmlifyedFileMapping {
+ file.verifyUpdateFields();
return {
Document: {
data: xml(this.formatter.format(file.Document), true),
@@ -120,6 +122,10 @@ export class Compiler {
data: xml(this.formatter.format(file.FootNotes)),
path: "word/footnotes.xml",
},
+ Settings: {
+ data: xml(this.formatter.format(file.Settings)),
+ path: "word/settings.xml",
+ },
};
}
}
diff --git a/src/file/document/body/body.ts b/src/file/document/body/body.ts
index e274fa359f..941a913bf9 100644
--- a/src/file/document/body/body.ts
+++ b/src/file/document/body/body.ts
@@ -1,5 +1,5 @@
import { IXmlableObject, XmlComponent } from "file/xml-components";
-import { Paragraph, ParagraphProperties } from "../..";
+import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
export class Body extends XmlComponent {
@@ -53,6 +53,14 @@ export class Body extends XmlComponent {
return this.defaultSection;
}
+ public getTablesOfContents(): TableOfContents[] {
+ return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[];
+ }
+
+ public getParagraphs(): Paragraph[] {
+ return this.root.filter((child) => child instanceof Paragraph) as Paragraph[];
+ }
+
private createSectionParagraph(section: SectionProperties): Paragraph {
const paragraph = new Paragraph();
const properties = new ParagraphProperties();
diff --git a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts b/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts
index a9d1ae46ee..e486ae5c72 100644
--- a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts
+++ b/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts
@@ -8,6 +8,7 @@ export interface IPageMarginAttributes {
header?: number;
footer?: number;
gutter?: number;
+ mirror?: boolean;
}
export class PageMarginAttributes extends XmlAttributeComponent {
@@ -19,5 +20,6 @@ export class PageMarginAttributes extends XmlAttributeComponent {
header: 708,
footer: 708,
gutter: 0,
+ mirror: false,
space: 708,
linePitch: 360,
headerId: 100,
@@ -40,6 +41,7 @@ describe("SectionProperties", () => {
"w:left": 1440,
"w:header": 708,
"w:gutter": 0,
+ "w:mirrorMargins": false,
},
},
],
@@ -69,6 +71,7 @@ describe("SectionProperties", () => {
"w:left": 1440,
"w:header": 708,
"w:gutter": 0,
+ "w:mirrorMargins": false,
},
},
],
@@ -99,6 +102,7 @@ describe("SectionProperties", () => {
"w:left": 1440,
"w:header": 708,
"w:gutter": 0,
+ "w:mirrorMargins": false,
},
},
],
@@ -124,6 +128,7 @@ describe("SectionProperties", () => {
"w:left": 1440,
"w:header": 708,
"w:gutter": 0,
+ "w:mirrorMargins": false,
},
},
],
@@ -150,6 +155,7 @@ describe("SectionProperties", () => {
"w:left": 1440,
"w:header": 708,
"w:gutter": 0,
+ "w:mirrorMargins": false,
},
},
],
diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts
index c23964cd14..5042c8a3ab 100644
--- a/src/file/document/body/section-properties/section-properties.ts
+++ b/src/file/document/body/section-properties/section-properties.ts
@@ -38,6 +38,7 @@ export class SectionProperties extends XmlComponent {
header: 708,
footer: 708,
gutter: 0,
+ mirror: false,
space: 708,
linePitch: 360,
orientation: PageOrientation.PORTRAIT,
@@ -69,6 +70,7 @@ export class SectionProperties extends XmlComponent {
mergedOptions.header,
mergedOptions.footer,
mergedOptions.gutter,
+ mergedOptions.mirror,
),
);
this.root.push(new Columns(mergedOptions.space));
diff --git a/src/file/document/document.ts b/src/file/document/document.ts
index 72e48852d7..d9dae1020f 100644
--- a/src/file/document/document.ts
+++ b/src/file/document/document.ts
@@ -2,6 +2,7 @@
import { XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph";
import { Table } from "../table";
+import { TableOfContents } from "../table-of-contents";
import { Body } from "./body";
import { SectionPropertiesOptions } from "./body/section-properties";
import { DocumentAttributes } from "./document-attributes";
@@ -41,6 +42,11 @@ export class Document extends XmlComponent {
return this;
}
+ public addTableOfContents(toc: TableOfContents): Document {
+ this.body.push(toc);
+ return this;
+ }
+
public createParagraph(text?: string): Paragraph {
const para = new Paragraph(text);
this.addParagraph(para);
@@ -60,4 +66,12 @@ export class Document extends XmlComponent {
public get Body(): Body {
return this.body;
}
+
+ public getTablesOfContents(): TableOfContents[] {
+ return this.body.getTablesOfContents();
+ }
+
+ public getParagraphs(): Paragraph[] {
+ return this.body.getParagraphs();
+ }
}
diff --git a/src/file/document/index.ts b/src/file/document/index.ts
index 6b128299f6..3430666623 100644
--- a/src/file/document/index.ts
+++ b/src/file/document/index.ts
@@ -1,2 +1,3 @@
export * from "./document";
+export * from "./document-attributes";
export * from "./body";
diff --git a/src/file/file.ts b/src/file/file.ts
index 472baf28b5..afa268a5a7 100644
--- a/src/file/file.ts
+++ b/src/file/file.ts
@@ -10,14 +10,16 @@ import { Image, Media } from "./media";
import { Numbering } from "./numbering";
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
import { Relationships } from "./relationships";
+import { Settings } from "./settings";
import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
import { DefaultStylesFactory } from "./styles/factory";
import { Table } from "./table";
+import { TableOfContents } from "./table-of-contents";
export class File {
private readonly document: Document;
- private readonly styles: Styles;
+ private styles: Styles;
private readonly coreProperties: CoreProperties;
private readonly numbering: Numbering;
private readonly media: Media;
@@ -26,6 +28,7 @@ export class File {
private readonly headerWrapper: HeaderWrapper[] = [];
private readonly footerWrapper: FooterWrapper[] = [];
private readonly footNotes: FootNotes;
+ private readonly settings: Settings;
private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties;
@@ -105,6 +108,11 @@ export class File {
sectionPropertiesOptions.footerId = footer.Footer.ReferenceId;
}
this.document = new Document(sectionPropertiesOptions);
+ this.settings = new Settings();
+ }
+
+ public addTableOfContents(toc: TableOfContents): void {
+ this.document.addTableOfContents(toc);
}
public addParagraph(paragraph: Paragraph): void {
@@ -214,6 +222,10 @@ export class File {
return this.styles;
}
+ public set Styles(styles: Styles) {
+ this.styles = styles;
+ }
+
public get CoreProperties(): CoreProperties {
return this.coreProperties;
}
@@ -277,4 +289,14 @@ export class File {
public get FootNotes(): FootNotes {
return this.footNotes;
}
+
+ public get Settings(): Settings {
+ return this.settings;
+ }
+
+ public verifyUpdateFields(): void {
+ if (this.document.getTablesOfContents().length) {
+ this.settings.addUpdateFields();
+ }
+ }
}
diff --git a/src/file/index.ts b/src/file/index.ts
index d1e04ffcf7..8705f40011 100644
--- a/src/file/index.ts
+++ b/src/file/index.ts
@@ -6,4 +6,5 @@ export * from "./media";
export * from "./drawing";
export * from "./document";
export * from "./styles";
+export * from "./table-of-contents";
export * from "./xml-components";
diff --git a/src/file/paragraph/formatting/style.ts b/src/file/paragraph/formatting/style.ts
index 8a4ed4f9ad..30d57eb997 100644
--- a/src/file/paragraph/formatting/style.ts
+++ b/src/file/paragraph/formatting/style.ts
@@ -1,11 +1,14 @@
import { Attributes, XmlComponent } from "file/xml-components";
export class Style extends XmlComponent {
- constructor(type: string) {
+ public readonly styleId: string;
+
+ constructor(styleId: string) {
super("w:pStyle");
+ this.styleId = styleId;
this.root.push(
new Attributes({
- val: type,
+ val: styleId,
}),
);
}
diff --git a/src/file/paragraph/formatting/tab-stop.spec.ts b/src/file/paragraph/formatting/tab-stop.spec.ts
index bb9fa80d96..5b324623f5 100644
--- a/src/file/paragraph/formatting/tab-stop.spec.ts
+++ b/src/file/paragraph/formatting/tab-stop.spec.ts
@@ -1,7 +1,7 @@
import { assert } from "chai";
import { Utility } from "../../../tests/utility";
-import { LeftTabStop, MaxRightTabStop } from "./tab-stop";
+import { LeftTabStop, MaxRightTabStop, RightTabStop } from "./tab-stop";
describe("LeftTabStop", () => {
let tabStop: LeftTabStop;
@@ -28,7 +28,28 @@ describe("LeftTabStop", () => {
});
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", () => {
diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts
index 86f61fb747..5dc4f68e4f 100644
--- a/src/file/paragraph/formatting/tab-stop.ts
+++ b/src/file/paragraph/formatting/tab-stop.ts
@@ -2,50 +2,52 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class TabStop extends XmlComponent {
- constructor(tab: Tab) {
+ constructor(tab: TabStopItem) {
super("w:tabs");
this.root.push(tab);
}
}
export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start";
+export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore";
-export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number }> {
- protected xmlKeys = { val: "w:val", pos: "w:pos" };
+export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader?: LeaderType }> {
+ protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" };
}
-export class Tab extends XmlComponent {
- constructor(value: TabValue, position: string | number) {
+export class TabStopItem extends XmlComponent {
+ constructor(value: TabValue, position: string | number, leader?: LeaderType) {
super("w:tab");
this.root.push(
new TabAttributes({
val: value,
pos: position,
+ leader,
}),
);
}
}
export class MaxRightTabStop extends TabStop {
- constructor() {
- super(new Tab("right", 9026));
+ constructor(leader?: LeaderType) {
+ super(new TabStopItem("right", 9026, leader));
}
}
export class LeftTabStop extends TabStop {
- constructor(position: number) {
- super(new Tab("left", position));
+ constructor(position: number, leader?: LeaderType) {
+ super(new TabStopItem("left", position, leader));
}
}
export class RightTabStop extends TabStop {
- constructor(position: number) {
- super(new Tab("right", position));
+ constructor(position: number, leader?: LeaderType) {
+ super(new TabStopItem("right", position, leader));
}
}
export class CenterTabStop extends TabStop {
- constructor(position: number) {
- super(new Tab("center", position));
+ constructor(position: number, leader?: LeaderType) {
+ super(new TabStopItem("center", position, leader));
}
}
diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts
index 42b34e0c0d..b7930970b5 100644
--- a/src/file/paragraph/paragraph.ts
+++ b/src/file/paragraph/paragraph.ts
@@ -12,7 +12,7 @@ import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreak, PageBreakBefore } from "./formatting/page-break";
import { ISpacingProperties, Spacing } from "./formatting/spacing";
import { Style } from "./formatting/style";
-import { CenterTabStop, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
+import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { Bookmark, Hyperlink } from "./links";
import { ParagraphProperties } from "./properties";
@@ -30,6 +30,10 @@ export class Paragraph extends XmlComponent {
}
}
+ public get paragraphProperties(): ParagraphProperties {
+ return this.properties;
+ }
+
public get Borders(): Border {
return this.properties.paragraphBorder;
}
@@ -155,23 +159,23 @@ export class Paragraph extends XmlComponent {
return this;
}
- public maxRightTabStop(): Paragraph {
- this.properties.push(new MaxRightTabStop());
+ public maxRightTabStop(leader?: LeaderType): Paragraph {
+ this.properties.push(new MaxRightTabStop(leader));
return this;
}
- public leftTabStop(position: number): Paragraph {
- this.properties.push(new LeftTabStop(position));
+ public leftTabStop(position: number, leader?: LeaderType): Paragraph {
+ this.properties.push(new LeftTabStop(position, leader));
return this;
}
- public rightTabStop(position: number): Paragraph {
- this.properties.push(new RightTabStop(position));
+ public rightTabStop(position: number, leader?: LeaderType): Paragraph {
+ this.properties.push(new RightTabStop(position, leader));
return this;
}
- public centerTabStop(position: number): Paragraph {
- this.properties.push(new CenterTabStop(position));
+ public centerTabStop(position: number, leader?: LeaderType): Paragraph {
+ this.properties.push(new CenterTabStop(position, leader));
return this;
}
@@ -232,7 +236,8 @@ export class Paragraph extends XmlComponent {
return this;
}
- public get Properties(): ParagraphProperties {
- return this.properties;
+ public addTabStop(run: Run): Paragraph {
+ this.root.splice(1, 0, run);
+ return this;
}
}
diff --git a/src/file/paragraph/run/field.ts b/src/file/paragraph/run/field.ts
new file mode 100644
index 0000000000..0e72c1d624
--- /dev/null
+++ b/src/file/paragraph/run/field.ts
@@ -0,0 +1,26 @@
+import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
+
+class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate"; dirty?: boolean }> {
+ protected xmlKeys = { type: "w:fldCharType", dirty: "w:dirty" };
+}
+
+export class Begin extends XmlComponent {
+ constructor(dirty?: boolean) {
+ super("w:fldChar");
+ this.root.push(new FidCharAttrs({ type: "begin", dirty }));
+ }
+}
+
+export class Separate extends XmlComponent {
+ constructor(dirty?: boolean) {
+ super("w:fldChar");
+ this.root.push(new FidCharAttrs({ type: "separate", dirty }));
+ }
+}
+
+export class End extends XmlComponent {
+ constructor(dirty?: boolean) {
+ super("w:fldChar");
+ this.root.push(new FidCharAttrs({ type: "end", dirty }));
+ }
+}
diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts
index 8b1be933ec..30d437e268 100644
--- a/src/file/paragraph/run/formatting.ts
+++ b/src/file/paragraph/run/formatting.ts
@@ -25,6 +25,17 @@ export class BoldComplexScript extends XmlComponent {
}
}
+export class CharacterSpacing extends XmlComponent {
+ constructor(value: number) {
+ super("w:spacing");
+ this.root.push(
+ new Attributes({
+ val: value,
+ }),
+ );
+ }
+}
+
export class Italics extends XmlComponent {
constructor() {
super("w:i");
diff --git a/src/file/paragraph/run/page-number.ts b/src/file/paragraph/run/page-number.ts
index 4048c4f79a..ab3592fd18 100644
--- a/src/file/paragraph/run/page-number.ts
+++ b/src/file/paragraph/run/page-number.ts
@@ -1,20 +1,9 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
-class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> {
- protected xmlKeys = { type: "w:fldCharType" };
-}
-
class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> {
protected xmlKeys = { space: "xml:space" };
}
-export class Begin extends XmlComponent {
- constructor() {
- super("w:fldChar");
- this.root.push(new FidCharAttrs({ type: "begin" }));
- }
-}
-
export class Page extends XmlComponent {
constructor() {
super("w:instrText");
@@ -22,17 +11,3 @@ export class Page extends XmlComponent {
this.root.push("PAGE");
}
}
-
-export class Separate extends XmlComponent {
- constructor() {
- super("w:fldChar");
- this.root.push(new FidCharAttrs({ type: "separate" }));
- }
-}
-
-export class End extends XmlComponent {
- constructor() {
- super("w:fldChar");
- this.root.push(new FidCharAttrs({ type: "end" }));
- }
-}
diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts
index 70b344842f..b7722e1e44 100644
--- a/src/file/paragraph/run/run.ts
+++ b/src/file/paragraph/run/run.ts
@@ -1,6 +1,7 @@
// http://officeopenxml.com/WPtext.php
import { Break } from "./break";
import { Caps, SmallCaps } from "./caps";
+import { Begin, End, Separate } from "./field";
import {
Bold,
BoldComplexScript,
@@ -13,7 +14,7 @@ import {
SizeComplexScript,
Strike,
} from "./formatting";
-import { Begin, End, Page, Separate } from "./page-number";
+import { Page } from "./page-number";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
diff --git a/src/file/settings/index.ts b/src/file/settings/index.ts
new file mode 100644
index 0000000000..d750485a16
--- /dev/null
+++ b/src/file/settings/index.ts
@@ -0,0 +1,2 @@
+export * from "./settings";
+export * from "./update-fields";
diff --git a/src/file/settings/settings.spec.ts b/src/file/settings/settings.spec.ts
new file mode 100644
index 0000000000..9ba0016319
--- /dev/null
+++ b/src/file/settings/settings.spec.ts
@@ -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);
+ });
+ });
+});
diff --git a/src/file/settings/settings.ts b/src/file/settings/settings.ts
new file mode 100644
index 0000000000..d955de0177
--- /dev/null
+++ b/src/file/settings/settings.ts
@@ -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 {
+ 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());
+ }
+ }
+}
diff --git a/src/file/settings/update-fields.spec.ts b/src/file/settings/update-fields.spec.ts
new file mode 100644
index 0000000000..70fad8685c
--- /dev/null
+++ b/src/file/settings/update-fields.spec.ts
@@ -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);
+ });
+ });
+});
diff --git a/src/file/settings/update-fields.ts b/src/file/settings/update-fields.ts
new file mode 100644
index 0000000000..782aa13264
--- /dev/null
+++ b/src/file/settings/update-fields.ts
@@ -0,0 +1,19 @@
+import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
+export interface IUpdateFieldsAttributesProperties {
+ enabled: boolean;
+}
+export class UpdateFieldsAttributes extends XmlAttributeComponent {
+ protected xmlKeys = {
+ enabled: "w:val",
+ };
+}
+export class UpdateFields extends XmlComponent {
+ constructor(enabled: boolean = true) {
+ super("w:updateFields");
+ this.root.push(
+ new UpdateFieldsAttributes({
+ enabled,
+ }),
+ );
+ }
+}
diff --git a/src/file/styles/style/index.ts b/src/file/styles/style/index.ts
index 655cc28d2a..ee2d6d84aa 100644
--- a/src/file/styles/style/index.ts
+++ b/src/file/styles/style/index.ts
@@ -47,12 +47,14 @@ export class ParagraphStyle extends Style {
this.root.push(this.runProperties);
}
- public addParagraphProperty(property: XmlComponent): void {
+ public addParagraphProperty(property: XmlComponent): ParagraphStyle {
this.paragraphProperties.push(property);
+ return this;
}
- public addRunProperty(property: XmlComponent): void {
+ public addRunProperty(property: XmlComponent): ParagraphStyle {
this.runProperties.push(property);
+ return this;
}
public basedOn(parentId: string): ParagraphStyle {
@@ -73,121 +75,101 @@ export class ParagraphStyle extends Style {
// ---------- Run formatting ---------------------- //
public size(twips: number): ParagraphStyle {
- this.addRunProperty(new formatting.Size(twips));
- this.addRunProperty(new formatting.SizeComplexScript(twips));
- return this;
+ return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
public bold(): ParagraphStyle {
- this.addRunProperty(new formatting.Bold());
- return this;
+ return this.addRunProperty(new formatting.Bold());
}
public italics(): ParagraphStyle {
- this.addRunProperty(new formatting.Italics());
- return this;
+ return this.addRunProperty(new formatting.Italics());
}
public smallCaps(): ParagraphStyle {
- this.addRunProperty(new formatting.SmallCaps());
- return this;
+ return this.addRunProperty(new formatting.SmallCaps());
}
public allCaps(): ParagraphStyle {
- this.addRunProperty(new formatting.Caps());
- return this;
+ return this.addRunProperty(new formatting.Caps());
}
public strike(): ParagraphStyle {
- this.addRunProperty(new formatting.Strike());
- return this;
+ return this.addRunProperty(new formatting.Strike());
}
public doubleStrike(): ParagraphStyle {
- this.addRunProperty(new formatting.DoubleStrike());
- return this;
+ return this.addRunProperty(new formatting.DoubleStrike());
}
public subScript(): ParagraphStyle {
- this.addRunProperty(new formatting.SubScript());
- return this;
+ return this.addRunProperty(new formatting.SubScript());
}
public superScript(): ParagraphStyle {
- this.addRunProperty(new formatting.SuperScript());
- return this;
+ return this.addRunProperty(new formatting.SuperScript());
}
public underline(underlineType?: string, color?: string): ParagraphStyle {
- this.addRunProperty(new formatting.Underline(underlineType, color));
- return this;
+ return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public color(color: string): ParagraphStyle {
- this.addRunProperty(new formatting.Color(color));
- return this;
+ return this.addRunProperty(new formatting.Color(color));
}
public font(fontName: string): ParagraphStyle {
- this.addRunProperty(new formatting.RunFonts(fontName));
- return this;
+ return this.addRunProperty(new formatting.RunFonts(fontName));
+ }
+
+ public characterSpacing(value: number): ParagraphStyle {
+ return this.addRunProperty(new formatting.CharacterSpacing(value));
}
// --------------------- Paragraph formatting ------------------------ //
public center(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Alignment("center"));
- return this;
+ return this.addParagraphProperty(new paragraph.Alignment("center"));
}
public left(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Alignment("left"));
- return this;
+ return this.addParagraphProperty(new paragraph.Alignment("left"));
}
public right(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Alignment("right"));
- return this;
+ return this.addParagraphProperty(new paragraph.Alignment("right"));
}
public justified(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Alignment("both"));
- return this;
+ return this.addParagraphProperty(new paragraph.Alignment("both"));
}
public thematicBreak(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.ThematicBreak());
- return this;
+ return this.addParagraphProperty(new paragraph.ThematicBreak());
}
public maxRightTabStop(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.MaxRightTabStop());
- return this;
+ return this.addParagraphProperty(new paragraph.MaxRightTabStop());
}
public leftTabStop(position: number): ParagraphStyle {
- this.addParagraphProperty(new paragraph.LeftTabStop(position));
- return this;
+ return this.addParagraphProperty(new paragraph.LeftTabStop(position));
}
public indent(attrs: object): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Indent(attrs));
- return this;
+ return this.addParagraphProperty(new paragraph.Indent(attrs));
}
public spacing(params: paragraph.ISpacingProperties): ParagraphStyle {
- this.addParagraphProperty(new paragraph.Spacing(params));
- return this;
+ return this.addParagraphProperty(new paragraph.Spacing(params));
}
public keepNext(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.KeepNext());
- return this;
+ return this.addParagraphProperty(new paragraph.KeepNext());
}
public keepLines(): ParagraphStyle {
- this.addParagraphProperty(new paragraph.KeepLines());
- return this;
+ return this.addParagraphProperty(new paragraph.KeepLines());
}
}
@@ -267,24 +249,21 @@ export class CharacterStyle extends Style {
return this;
}
- public addRunProperty(property: XmlComponent): void {
+ public addRunProperty(property: XmlComponent): CharacterStyle {
this.runProperties.push(property);
+ return this;
}
public color(color: string): CharacterStyle {
- this.addRunProperty(new formatting.Color(color));
- return this;
+ return this.addRunProperty(new formatting.Color(color));
}
public underline(underlineType?: string, color?: string): CharacterStyle {
- this.addRunProperty(new formatting.Underline(underlineType, color));
- return this;
+ return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public size(twips: number): CharacterStyle {
- this.addRunProperty(new formatting.Size(twips));
- this.addRunProperty(new formatting.SizeComplexScript(twips));
- return this;
+ return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
}
diff --git a/src/file/styles/styles.spec.ts b/src/file/styles/styles.spec.ts
index 1c828337b2..6262b3cb9e 100644
--- a/src/file/styles/styles.spec.ts
+++ b/src/file/styles/styles.spec.ts
@@ -219,6 +219,20 @@ describe("ParagraphStyle", () => {
});
});
+ it("#character spacing", () => {
+ const style = new ParagraphStyle("myStyleId").characterSpacing(24);
+ const tree = new Formatter().format(style);
+ expect(tree).to.deep.equal({
+ "w:style": [
+ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
+ { "w:pPr": [] },
+ {
+ "w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }],
+ },
+ ],
+ });
+ });
+
it("#left", () => {
const style = new ParagraphStyle("myStyleId").left();
const tree = new Formatter().format(style);
diff --git a/src/file/table-of-contents/alias.ts b/src/file/table-of-contents/alias.ts
new file mode 100644
index 0000000000..d3a3235391
--- /dev/null
+++ b/src/file/table-of-contents/alias.ts
@@ -0,0 +1,12 @@
+import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
+
+class AliasAttributes extends XmlAttributeComponent<{ alias: string }> {
+ protected xmlKeys = { alias: "w:val" };
+}
+
+export class Alias extends XmlComponent {
+ constructor(alias: string) {
+ super("w:alias");
+ this.root.push(new AliasAttributes({ alias }));
+ }
+}
diff --git a/src/file/table-of-contents/field-instruction.ts b/src/file/table-of-contents/field-instruction.ts
new file mode 100644
index 0000000000..766e6fc2f2
--- /dev/null
+++ b/src/file/table-of-contents/field-instruction.ts
@@ -0,0 +1,76 @@
+// http://officeopenxml.com/WPfieldInstructions.php
+import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
+import { ITableOfContentsOptions } from "./table-of-contents-properties";
+
+enum SpaceType {
+ DEFAULT = "default",
+ PRESERVE = "preserve",
+}
+
+class TextAttributes extends XmlAttributeComponent<{ space: SpaceType }> {
+ protected xmlKeys = { space: "xml:space" };
+}
+
+export class FieldInstruction extends XmlComponent {
+ private readonly properties: ITableOfContentsOptions;
+
+ constructor(properties: ITableOfContentsOptions = {}) {
+ super("w:instrText");
+
+ this.properties = properties;
+
+ this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
+ let instruction = "TOC";
+
+ if (this.properties.captionLabel) {
+ instruction = `${instruction} \\a "${this.properties.captionLabel}"`;
+ }
+ if (this.properties.entriesFromBookmark) {
+ instruction = `${instruction} \\b "${this.properties.entriesFromBookmark}"`;
+ }
+ if (this.properties.captionLabelIncludingNumbers) {
+ instruction = `${instruction} \\c "${this.properties.captionLabelIncludingNumbers}"`;
+ }
+ if (this.properties.sequenceAndPageNumbersSeparator) {
+ instruction = `${instruction} \\d "${this.properties.sequenceAndPageNumbersSeparator}"`;
+ }
+ if (this.properties.tcFieldIdentifier) {
+ instruction = `${instruction} \\f "${this.properties.tcFieldIdentifier}"`;
+ }
+ if (this.properties.hyperlink) {
+ instruction = `${instruction} \\h`;
+ }
+ if (this.properties.tcFieldLevelRange) {
+ instruction = `${instruction} \\l "${this.properties.tcFieldLevelRange}`;
+ }
+ if (this.properties.pageNumbersEntryLevelsRange) {
+ instruction = `${instruction} \\n "${this.properties.pageNumbersEntryLevelsRange}`;
+ }
+ if (this.properties.headingStyleRange) {
+ instruction = `${instruction} \\o "${this.properties.headingStyleRange}`;
+ }
+ if (this.properties.entryAndPageNumberSeparator) {
+ instruction = `${instruction} \\p "${this.properties.entryAndPageNumberSeparator}`;
+ }
+ if (this.properties.seqFieldIdentifierForPrefix) {
+ instruction = `${instruction} \\s "${this.properties.seqFieldIdentifierForPrefix}`;
+ }
+ if (this.properties.stylesWithLevels && this.properties.stylesWithLevels.length) {
+ const styles = this.properties.stylesWithLevels.map((sl) => `${sl.styleName},${sl.level}`).join(",");
+ instruction = `${instruction} \\t "${styles}"`;
+ }
+ if (this.properties.useAppliedParagraphOutlineLevel) {
+ instruction = `${instruction} \\u`;
+ }
+ if (this.properties.preserveTabInEntries) {
+ instruction = `${instruction} \\w`;
+ }
+ if (this.properties.preserveNewLineInEntries) {
+ instruction = `${instruction} \\x`;
+ }
+ if (this.properties.hideTabAndPageNumbersInWebView) {
+ instruction = `${instruction} \\z`;
+ }
+ this.root.push(instruction);
+ }
+}
diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts
new file mode 100644
index 0000000000..f36b16b738
--- /dev/null
+++ b/src/file/table-of-contents/index.ts
@@ -0,0 +1,2 @@
+export * from "./table-of-contents";
+export * from "./table-of-contents-properties";
diff --git a/src/file/table-of-contents/sdt-content.ts b/src/file/table-of-contents/sdt-content.ts
new file mode 100644
index 0000000000..5228ff447f
--- /dev/null
+++ b/src/file/table-of-contents/sdt-content.ts
@@ -0,0 +1,7 @@
+import { XmlComponent } from "file/xml-components";
+
+export class StructuredDocumentTagContent extends XmlComponent {
+ constructor() {
+ super("w:sdtContent");
+ }
+}
diff --git a/src/file/table-of-contents/sdt-properties.ts b/src/file/table-of-contents/sdt-properties.ts
new file mode 100644
index 0000000000..3f7019f561
--- /dev/null
+++ b/src/file/table-of-contents/sdt-properties.ts
@@ -0,0 +1,10 @@
+// http://www.datypic.com/sc/ooxml/e-w_sdtPr-1.html
+import { XmlComponent } from "file/xml-components";
+import { Alias } from "./alias";
+
+export class StructuredDocumentTagProperties extends XmlComponent {
+ constructor(alias: string) {
+ super("w:sdtPr");
+ this.root.push(new Alias(alias));
+ }
+}
diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts
new file mode 100644
index 0000000000..bc312429af
--- /dev/null
+++ b/src/file/table-of-contents/table-of-contents-properties.ts
@@ -0,0 +1,122 @@
+export class StyleLevel {
+ public styleName: string;
+ public level: number;
+
+ constructor(styleName: string, level: number) {
+ this.styleName = styleName;
+ this.level = level;
+ }
+}
+
+/**
+ * Options according to this docs:
+ * https://www.ecma-international.org/publications/standards/Ecma-376.htm
+ * Part 1 - Page 1251
+ *
+ * Short Guide:
+ * http://officeopenxml.com/WPtableOfContents.php
+ */
+export interface ITableOfContentsOptions {
+ /**
+ * \a option - Includes captioned items, but omits caption labels and numbers.
+ * The identifier designated by text in this switch's field-argument corresponds to the caption label.
+ * Use captionLabelIncludingNumbers (\c) to build a table of captions with labels and numbers.
+ */
+ captionLabel?: string;
+
+ /**
+ * \b option - Includes entries only from the portion of the document marked by
+ * the bookmark named by text in this switch's field-argument.
+ */
+ entriesFromBookmark?: string;
+
+ /**
+ * \c option - Includes figures, tables, charts, and other items that are numbered
+ * by a SEQ field (§17.16.5.56). The sequence identifier designated by text in this switch's
+ * field-argument, which corresponds to the caption label, shall match the identifier in the
+ * corresponding SEQ field.
+ */
+ captionLabelIncludingNumbers?: string;
+
+ /**
+ * \d option - When used with \s, the text in this switch's field-argument defines
+ * the separator between sequence and page numbers. The default separator is a hyphen (-).
+ */
+ sequenceAndPageNumbersSeparator?: string;
+
+ /**
+ * \f option - Includes only those TC fields whose identifier exactly matches the
+ * text in this switch's field-argument (which is typically a letter).
+ */
+ tcFieldIdentifier?: string;
+
+ /**
+ * \h option - Makes the table of contents entries hyperlinks.
+ */
+ hyperlink?: boolean;
+
+ /**
+ * \l option - Includes TC fields that assign entries to one of the levels specified
+ * by text in this switch's field-argument as a range having the form startLevel-endLevel,
+ * where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel.
+ * TC fields that assign entries to lower levels are skipped.
+ */
+ tcFieldLevelRange?: string;
+
+ /**
+ * \n option - Without field-argument, omits page numbers from the table of contents.
+ * Page numbers are omitted from all levels unless a range of entry levels is specified by
+ * text in this switch's field-argument. A range is specified as for \l.
+ */
+ pageNumbersEntryLevelsRange?: string;
+
+ /**
+ * \o option - Uses paragraphs formatted with all or the specified range of builtin
+ * heading styles. Headings in a style range are specified by text in this switch's
+ * field-argument using the notation specified as for \l, where each integer corresponds
+ * to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1).
+ * If no heading range is specified, all heading levels used in the document are listed.
+ */
+ headingStyleRange?: string;
+
+ /**
+ * \p option - Text in this switch's field-argument specifies a sequence of characters
+ * that separate an entry and its page number. The default is a tab with leader dots.
+ */
+ entryAndPageNumberSeparator?: string;
+
+ /**
+ * \s option - For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number.
+ * The prefix depends on the type of entry. text in this switch's field-argument shall match the
+ * identifier in the SEQ field.
+ */
+ seqFieldIdentifierForPrefix?: string;
+
+ /**
+ * \t field-argument Uses paragraphs formatted with styles other than the built-in heading styles.
+ * Text in this switch's field-argument specifies those styles as a set of comma-separated doublets,
+ * with each doublet being a comma-separated set of style name and table of content level.
+ * \t can be combined with \o.
+ */
+ stylesWithLevels?: StyleLevel[];
+
+ /**
+ * \u Uses the applied paragraph outline level.
+ */
+ useAppliedParagraphOutlineLevel?: boolean;
+
+ /**
+ * \w Preserves tab entries within table entries.
+ */
+ preserveTabInEntries?: boolean;
+
+ /**
+ * \x Preserves newline characters within table entries.
+ */
+ preserveNewLineInEntries?: boolean;
+
+ /**
+ * \z Hides tab leader and page numbers in web page view (§17.18.102).
+ */
+ hideTabAndPageNumbersInWebView?: boolean;
+}
diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts
new file mode 100644
index 0000000000..7d7d4a55b6
--- /dev/null
+++ b/src/file/table-of-contents/table-of-contents.spec.ts
@@ -0,0 +1,219 @@
+import { expect } from "chai";
+
+import { Formatter } from "../../export/formatter";
+import { ITableOfContentsOptions, StyleLevel, TableOfContents } from "./";
+
+describe("Table of Contents", () => {
+ describe("#constructor", () => {
+ it("should construct a TOC without options", () => {
+ const toc = new TableOfContents();
+ const tree = new Formatter().format(toc);
+ expect(tree).to.be.deep.equal(DEFAULT_TOC);
+ });
+
+ it("should construct a TOC with all the options and alias", () => {
+ const props: ITableOfContentsOptions = {};
+
+ props.captionLabel = "A";
+ props.entriesFromBookmark = "B";
+ props.captionLabelIncludingNumbers = "C";
+ props.sequenceAndPageNumbersSeparator = "D";
+ props.tcFieldIdentifier = "F";
+ props.hyperlink = true;
+ props.tcFieldLevelRange = "L";
+ props.pageNumbersEntryLevelsRange = "N";
+ props.headingStyleRange = "O";
+ props.entryAndPageNumberSeparator = "P";
+ props.seqFieldIdentifierForPrefix = "S";
+
+ const styles = new Array();
+ styles.push(new StyleLevel("SL", 1));
+ styles.push(new StyleLevel("SL", 2));
+ props.stylesWithLevels = styles;
+ props.useAppliedParagraphOutlineLevel = true;
+ props.preserveTabInEntries = true;
+ props.preserveNewLineInEntries = true;
+ props.hideTabAndPageNumbersInWebView = true;
+
+ const toc = new TableOfContents("Summary", props);
+ const tree = new Formatter().format(toc);
+ expect(tree).to.be.deep.equal(COMPLETE_TOC);
+ });
+ });
+});
+
+const DEFAULT_TOC = {
+ "w:sdt": [
+ {
+ "w:sdtPr": [
+ {
+ "w:alias": [
+ {
+ _attr: {
+ "w:val": "Table of Contents",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ "w:sdtContent": [
+ {
+ "w:p": [
+ {
+ "w:pPr": [],
+ },
+ {
+ "w:r": [
+ {
+ "w:rPr": [],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "begin",
+ "w:dirty": true,
+ },
+ },
+ ],
+ },
+ {
+ "w:instrText": [
+ {
+ _attr: {
+ "xml:space": "preserve",
+ },
+ },
+ "TOC",
+ ],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "separate",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ "w:p": [
+ {
+ "w:pPr": [],
+ },
+ {
+ "w:r": [
+ {
+ "w:rPr": [],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "end",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+const COMPLETE_TOC = {
+ "w:sdt": [
+ {
+ "w:sdtPr": [
+ {
+ "w:alias": [
+ {
+ _attr: {
+ "w:val": "Summary",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ "w:sdtContent": [
+ {
+ "w:p": [
+ {
+ "w:pPr": [],
+ },
+ {
+ "w:r": [
+ {
+ "w:rPr": [],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "begin",
+ "w:dirty": true,
+ },
+ },
+ ],
+ },
+ {
+ "w:instrText": [
+ {
+ _attr: {
+ "xml:space": "preserve",
+ },
+ },
+ 'TOC \\a "A" \\b "B" \\c "C" \\d "D" \\f "F" \\h \\l "L \\n "N \\o "O \\p "P \\s "S \\t "SL,1,SL,2" \\u \\w \\x \\z',
+ ],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "separate",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ "w:p": [
+ {
+ "w:pPr": [],
+ },
+ {
+ "w:r": [
+ {
+ "w:rPr": [],
+ },
+ {
+ "w:fldChar": [
+ {
+ _attr: {
+ "w:fldCharType": "end",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts
new file mode 100644
index 0000000000..7b1ff0f66d
--- /dev/null
+++ b/src/file/table-of-contents/table-of-contents.ts
@@ -0,0 +1,35 @@
+// http://officeopenxml.com/WPtableOfContents.php
+// http://www.datypic.com/sc/ooxml/e-w_sdt-1.html
+import { Paragraph } from "file/paragraph";
+import { Run } from "file/paragraph/run";
+import { Begin, End, Separate } from "file/paragraph/run/field";
+import { XmlComponent } from "file/xml-components";
+import { FieldInstruction } from "./field-instruction";
+import { StructuredDocumentTagContent } from "./sdt-content";
+import { StructuredDocumentTagProperties } from "./sdt-properties";
+import { ITableOfContentsOptions } from "./table-of-contents-properties";
+
+export class TableOfContents extends XmlComponent {
+ constructor(alias: string = "Table of Contents", properties?: ITableOfContentsOptions) {
+ super("w:sdt");
+ this.root.push(new StructuredDocumentTagProperties(alias));
+
+ const content = new StructuredDocumentTagContent();
+
+ const beginParagraph = new Paragraph();
+ const beginRun = new Run();
+ beginRun.addChildElement(new Begin(true));
+ beginRun.addChildElement(new FieldInstruction(properties));
+ beginRun.addChildElement(new Separate());
+ beginParagraph.addRun(beginRun);
+ content.addChildElement(beginParagraph);
+
+ const endParagraph = new Paragraph();
+ const endRun = new Run();
+ endRun.addChildElement(new End());
+ endParagraph.addRun(endRun);
+ content.addChildElement(endParagraph);
+
+ this.root.push(content);
+ }
+}
diff --git a/src/file/table/properties.ts b/src/file/table/properties.ts
index 7c5b0bad76..91ef393df3 100644
--- a/src/file/table/properties.ts
+++ b/src/file/table/properties.ts
@@ -3,13 +3,13 @@ import { WidthType } from "./table-cell";
import { TableCellMargin } from "./table-cell-margin";
export class TableProperties extends XmlComponent {
- private readonly cellMargain: TableCellMargin;
+ private readonly cellMargin: TableCellMargin;
constructor() {
super("w:tblPr");
- this.cellMargain = new TableCellMargin();
- this.root.push(this.cellMargain);
+ this.cellMargin = new TableCellMargin();
+ this.root.push(this.cellMargin);
}
public setWidth(type: WidthType, w: number | string): TableProperties {
@@ -28,7 +28,7 @@ export class TableProperties extends XmlComponent {
}
public get CellMargin(): TableCellMargin {
- return this.cellMargain;
+ return this.cellMargin;
}
}
diff --git a/src/file/xml-components/xml-component.ts b/src/file/xml-components/xml-component.ts
index 0ed604e9ee..fae843db29 100644
--- a/src/file/xml-components/xml-component.ts
+++ b/src/file/xml-components/xml-component.ts
@@ -30,7 +30,6 @@ export abstract class XmlComponent extends BaseXmlComponent {
};
}
- // TODO: Unused method
public addChildElement(child: XmlComponent | string): XmlComponent {
this.root.push(child);