Compare commits

...

46 Commits
4.1.0 ... 4.2.0

Author SHA1 Message Date
e02ac43c07 Version bump 2018-10-10 20:39:11 +01:00
ccffdad4c0 Merge pull request #171 from amitm02/master
Contextual spacing
2018-09-26 15:57:16 +01:00
2fb5845501 contextual spacing 2018-09-26 17:47:17 +03:00
ddd84a1765 Merge pull request #3 from dolanmiu/master
from base
2018-09-26 17:27:41 +03:00
f6a13aed86 Merge pull request #169 from dolanmiu/feat/table-of-contents
Feat/table of contents
2018-09-26 02:28:44 +01:00
2da3ba0262 Rename variables 2018-09-26 02:17:39 +01:00
e08c7cbbfb Add table of contents to side bar 2018-09-26 02:12:10 +01:00
a6de5d8a21 Merge pull request #168 from dolanmiu/feat/table-of-contents
Change documentation properties to options
2018-09-26 02:10:03 +01:00
00a20b7cfc Merge pull request #166 from dolanmiu/feat/table-of-contents
Feat/table of contents
2018-09-25 23:32:58 +01:00
f27c95191b Change documentation properties to options 2018-09-25 21:19:04 +01:00
c140d2c37c ITableOfContentsProperties to ITableOfContentsOptions 2018-09-25 21:09:30 +01:00
3a42f2a2f0 Make API simplier with interfaces 2018-09-25 20:05:35 +01:00
00efedaa09 Fix spelling and linting and improve readme 2018-09-25 19:49:44 +01:00
72e3d229dc unnecessary comment removed 2018-09-25 02:36:00 -03:00
b12d6ef484 initial documentation 2018-09-25 02:35:08 -03:00
8ee6fd3e67 demo updated 2018-09-25 01:33:44 -03:00
808c5b00a0 table of contents with all the options 2018-09-25 01:18:47 -03:00
4de6b51e76 removed getStyles and clearPageBreak that became useless 2018-09-25 00:10:27 -03:00
4ca81df401 TOC content generation aborted 2018-09-21 11:16:14 -03:00
8b463b3bb6 updated clone deep dependency and make fields dirty to be updated when word is opened 2018-09-21 10:26:28 -03:00
17d696e33a merge with master, demo27 became demo28 2018-09-21 07:40:58 -03:00
2f59867db6 Merge branch 'feat/table-of-contents' of https://github.com/dolanmiu/docx into feat/table-of-contents 2018-09-21 07:37:26 -03:00
bf1f702e5a created a clone method for xml-components 2018-09-20 15:13:18 -03:00
1cff104bae correct sdt objects of table of contents 2018-09-20 10:47:10 -03:00
4805efad2e organized imports 2018-09-20 10:31:49 -03:00
12e2ae9e91 making leader a option field and test improvments 2018-09-20 10:30:16 -03:00
c07b5cf709 added settings.xml back 2018-09-20 10:11:59 -03:00
0684738ec2 removed lodash 2018-09-20 07:09:17 -03:00
7cd8864fb9 added stdPr and stdContent to table of contents 2018-09-19 11:01:07 -03:00
baf0f17bd6 added a method for clear the page breaks of a paragraph 2018-09-18 13:53:30 -03:00
8e911698a5 generating the content for a table of contents 2018-09-18 05:24:19 -03:00
0eb36be053 there is no need for the settings.xml file anymore 2018-09-17 22:09:05 -03:00
146d0ad9e5 Moved addTableOfContents to File and creating a settings.xml and applying updateFields=true when there is a table of contents 2018-09-10 10:01:26 -03:00
aedfca377f elements need to be inside runs 2018-09-04 18:22:08 -03:00
7926f6c189 escape bars 2018-09-04 17:29:24 -03:00
e9a007d446 create default properties for table-of-contents instruction 2018-09-04 12:05:41 -03:00
12c1f82efe demo27 for TableOfContents 2018-09-04 11:03:17 -03:00
b1711ae293 first simple version of TOC working 2018-09-03 11:20:36 -03:00
a367951d07 moved Begin, Separate and End from page-number.ts to field.ts 2018-09-03 10:54:53 -03:00
c55f82c425 test improvment 2018-09-03 10:48:50 -03:00
d1044d262e table of contents extending paragraph 2018-08-31 10:22:40 -03:00
8d83219bb6 wrote some initial code 2018-08-29 12:06:01 -03:00
a710483918 Merge pull request #2 from dolanmiu/master
merge from upstream
2018-08-27 18:11:47 +03:00
4f8ecf631b Merge branch 'master' into feat/table-of-contents 2018-08-23 06:21:01 -03:00
a6a8012b39 wrote some inital code 2018-08-22 10:30:19 -03:00
d5b6225a90 starting table of contents 2018-08-21 07:19:46 -03:00
34 changed files with 989 additions and 61 deletions

3
.gitignore vendored
View File

@ -57,3 +57,6 @@ yarn.lock
# Documents
My Document.docx
# Temporary folder
tmp

43
demo/demo28.ts Normal file
View File

@ -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);
});

32
demo/demo29.ts Normal file
View File

@ -0,0 +1,32 @@
import * as fs from "fs";
import { Document, Indent, Numbering, Packer, Paragraph } from "../build";
const doc = new Document();
const numbering = new Numbering();
const abstractNum = numbering.createAbstractNumbering();
abstractNum.createLevel(0, "upperRoman", "%1", "start").addParagraphProperty(new Indent({ left: 720, hanging: 260 }));
const concrete = numbering.createConcreteNumbering(abstractNum);
const item1 = new Paragraph("line with contextual spacing");
const item2 = new Paragraph("line with contextual spacing");
const item3 = new Paragraph("line without contextual spacing");
const item4 = new Paragraph("line without contextual spacing");
item1.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(true);
item2.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(true);
item3.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(false);
item4.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(false);
doc.addParagraph(item1);
doc.addParagraph(item2);
doc.addParagraph(item3);
doc.addParagraph(item4);
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -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)

View File

@ -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_

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "4.1.0",
"version": "4.2.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js",
"scripts": {

View File

@ -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");

View File

@ -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",
},
};
}
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -10,10 +10,12 @@ 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;
@ -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 {
@ -281,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();
}
}
}

View File

@ -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";

View File

@ -1,5 +1,5 @@
// http://officeopenxml.com/WPspacing.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components";
export interface ISpacingProperties {
after?: number;
@ -23,3 +23,14 @@ export class Spacing extends XmlComponent {
this.root.push(new SpacingAttributes(opts));
}
}
export class ContextualSpacing extends XmlComponent {
constructor(value: boolean) {
super("w:contextualSpacing");
this.root.push(
new Attributes({
val: value === false ? 0 : 1,
}),
);
}
}

View File

@ -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,
}),
);
}

View File

@ -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", () => {

View File

@ -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));
}
}

View File

@ -10,9 +10,9 @@ import { Border, ThematicBreak } from "./formatting/border";
import { IIndentAttributesProperties, Indent } from "./formatting/indent";
import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreak, PageBreakBefore } from "./formatting/page-break";
import { ISpacingProperties, Spacing } from "./formatting/spacing";
import { ContextualSpacing, 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;
}
@ -207,6 +211,11 @@ export class Paragraph extends XmlComponent {
return this;
}
public contextualSpacing(value: boolean): Paragraph {
this.properties.push(new ContextualSpacing(value));
return this;
}
public keepNext(): Paragraph {
this.properties.push(new KeepNext());
return this;
@ -232,7 +241,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;
}
}

View File

@ -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 }));
}
}

View File

@ -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" }));
}
}

View File

@ -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";

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

@ -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 }));
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,2 @@
export * from "./table-of-contents";
export * from "./table-of-contents-properties";

View File

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

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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<StyleLevel>();
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",
},
},
],
},
],
},
],
},
],
},
],
};

View File

@ -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);
}
}

View File

@ -30,7 +30,6 @@ export abstract class XmlComponent extends BaseXmlComponent {
};
}
// TODO: Unused method
public addChildElement(child: XmlComponent | string): XmlComponent {
this.root.push(child);