From f1fb0c4f2218b18bbc431afd52bfea99faba6516 Mon Sep 17 00:00:00 2001 From: felipe Date: Tue, 7 Mar 2017 19:22:10 +0100 Subject: [PATCH 01/26] initial attempt at Table --- ts/docx/document/index.ts | 6 +++ ts/docx/index.ts | 1 + ts/docx/table.ts | 3 -- ts/docx/table/grid.ts | 17 ++++++++ ts/docx/table/index.ts | 83 +++++++++++++++++++++++++++++++++++++ ts/docx/table/properties.ts | 24 +++++++++++ 6 files changed, 131 insertions(+), 3 deletions(-) delete mode 100644 ts/docx/table.ts create mode 100644 ts/docx/table/grid.ts create mode 100644 ts/docx/table/index.ts create mode 100644 ts/docx/table/properties.ts diff --git a/ts/docx/document/index.ts b/ts/docx/document/index.ts index 555ebf55bf..a6e8db42b4 100644 --- a/ts/docx/document/index.ts +++ b/ts/docx/document/index.ts @@ -1,7 +1,9 @@ import { Paragraph } from "../paragraph"; +import { Table } from "../table"; import { XmlComponent } from "../xml-components"; import { Body } from "./body"; import { DocumentAttributes } from "./document-attributes"; + export class Document extends XmlComponent { private body: Body; @@ -39,4 +41,8 @@ export class Document extends XmlComponent { this.addParagraph(para); return para; } + + public addTable(table: Table): void { + this.body.push(table); + } } diff --git a/ts/docx/index.ts b/ts/docx/index.ts index 3c7ccdd38a..ad1049df20 100644 --- a/ts/docx/index.ts +++ b/ts/docx/index.ts @@ -2,3 +2,4 @@ export { Document } from "./document"; export { Paragraph } from "./paragraph"; export { Run } from "./run"; export { TextRun } from "./run/text-run"; +export { Table } from './table'; diff --git a/ts/docx/table.ts b/ts/docx/table.ts deleted file mode 100644 index fa2be84ac5..0000000000 --- a/ts/docx/table.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class Table { - -} diff --git a/ts/docx/table/grid.ts b/ts/docx/table/grid.ts new file mode 100644 index 0000000000..70404104c0 --- /dev/null +++ b/ts/docx/table/grid.ts @@ -0,0 +1,17 @@ +import {XmlComponent, Attributes} from "../xml-components"; + +export class TableGrid extends XmlComponent { + private cols: Array; + constructor(cols: Array) { + super('w:tblGrid'); + this.cols = cols; + cols.forEach(col => this.root.push(col)); + } +} + +export class GridCol extends XmlComponent { + constructor(width?: number) { + super('w:gridCol'); + this.root.push(new Attributes({w: width.toString()})) + } +} diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts new file mode 100644 index 0000000000..757af155eb --- /dev/null +++ b/ts/docx/table/index.ts @@ -0,0 +1,83 @@ +import {XmlComponent, Attributes} from "../xml-components"; +import {Paragraph} from "../paragraph"; +import {TableProperties} from "./properties"; +import {TableGrid, GridCol} from './grid'; + +export class Table extends XmlComponent { + properties: TableProperties; + private rows: Array; + private grid: TableGrid; + + constructor(rows: number, cols: number) { + super('w:tbl'); + this.properties = new TableProperties(); + this.root.push(this.properties); + + const gridCols = []; + for (let i = 0; i++; i < cols) { + gridCols.push(new GridCol()); + } + this.grid = new TableGrid(gridCols); + this.root.push(this.grid); + + this.rows = []; + for (let i = 0; i < rows; i++) { + const cells = []; + for (let j = 0; j < cols; j++) { + cells.push(new TableCell()); + } + const row = new TableRow(cells); + this.rows.push(row); + this.root.push(row); + } + } + + getRow(ix: number): TableRow { + return this.rows[ix]; + } +} + +class TableRow extends XmlComponent { + private properties: TableRowProperties; + private cells: Array; + + constructor(cells: Array) { + super('w:tr'); + this.properties = new TableRowProperties(); + this.root.push(this.properties); + this.cells = cells; + cells.forEach(c => this.root.push(c)) + } + + getCell(ix: number): TableCell { + return this.cells[ix]; + } +} + +class TableRowProperties extends XmlComponent { + constructor() { + super('w:trPr'); + } +} + +class TableCell extends XmlComponent { + private properties: TableCellProperties; + content: any; + + constructor() { + super('w:tc'); + this.properties = new TableCellProperties(); + this.root.push(this.properties); + this.root.push() + // Table cells can have any block-level content, but for now + // we only allow a single paragraph: + this.content = new Paragraph(); + this.root.push(this.content); + } +} + +class TableCellProperties extends XmlComponent { + constructor() { + super('w:tcPr'); + } +} diff --git a/ts/docx/table/properties.ts b/ts/docx/table/properties.ts new file mode 100644 index 0000000000..d37fef0c1b --- /dev/null +++ b/ts/docx/table/properties.ts @@ -0,0 +1,24 @@ +import {XmlComponent, Attributes} from "../xml-components"; + +export class TableProperties extends XmlComponent { + private width: PreferredTableWidth; + + constructor() { + super('w:tblPr'); + } + + setWidth(type: string, w: string) { + this.width = new PreferredTableWidth(type, w); + this.root.push(this.width); + } +} + +class PreferredTableWidth extends XmlComponent { + constructor(type: string, w: string) { + super('w:tblW'); + this.root.push(new Attributes({ + type, + w, + })) + } +} From e7e5c61a90d18484353f66ee5b48b855f2e82268 Mon Sep 17 00:00:00 2001 From: felipe Date: Fri, 10 Mar 2017 16:51:19 +0100 Subject: [PATCH 02/26] clean up initial table attempt (linter, interfaces, etc.) --- ts/docx/index.ts | 2 +- ts/docx/table/grid.ts | 18 ++++++++------- ts/docx/table/index.ts | 44 ++++++++++++++++++++----------------- ts/docx/table/properties.ts | 32 ++++++++++++++++----------- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/ts/docx/index.ts b/ts/docx/index.ts index ad1049df20..875821c29a 100644 --- a/ts/docx/index.ts +++ b/ts/docx/index.ts @@ -2,4 +2,4 @@ export { Document } from "./document"; export { Paragraph } from "./paragraph"; export { Run } from "./run"; export { TextRun } from "./run/text-run"; -export { Table } from './table'; +export { Table } from "./table"; diff --git a/ts/docx/table/grid.ts b/ts/docx/table/grid.ts index 70404104c0..91c38ddd21 100644 --- a/ts/docx/table/grid.ts +++ b/ts/docx/table/grid.ts @@ -1,17 +1,19 @@ -import {XmlComponent, Attributes} from "../xml-components"; +import { XmlAttributeComponent, XmlComponent } from "../xml-components"; export class TableGrid extends XmlComponent { - private cols: Array; - constructor(cols: Array) { - super('w:tblGrid'); - this.cols = cols; - cols.forEach(col => this.root.push(col)); + constructor(cols: number[]) { + super("w:tblGrid"); + cols.forEach((col) => this.root.push(new GridCol(col))); } } +class GridColAttributes extends XmlAttributeComponent<{w: number}> { + protected xmlKeys = {w: "w:w"}; +} + export class GridCol extends XmlComponent { constructor(width?: number) { - super('w:gridCol'); - this.root.push(new Attributes({w: width.toString()})) + super("w:gridCol"); + this.root.push(new GridColAttributes({w: width})); } } diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index 757af155eb..15656c6e45 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -1,21 +1,22 @@ -import {XmlComponent, Attributes} from "../xml-components"; -import {Paragraph} from "../paragraph"; -import {TableProperties} from "./properties"; -import {TableGrid, GridCol} from './grid'; +import { Paragraph } from "../paragraph"; +import { XmlComponent } from "../xml-components"; + +import { GridCol, TableGrid } from "./grid"; +import { TableProperties } from "./properties"; export class Table extends XmlComponent { - properties: TableProperties; - private rows: Array; + private properties: TableProperties; + private rows: TableRow[]; private grid: TableGrid; constructor(rows: number, cols: number) { - super('w:tbl'); + super("w:tbl"); this.properties = new TableProperties(); this.root.push(this.properties); - const gridCols = []; + const gridCols: number[] = []; for (let i = 0; i++; i < cols) { - gridCols.push(new GridCol()); + gridCols.push(0); } this.grid = new TableGrid(gridCols); this.root.push(this.grid); @@ -32,43 +33,46 @@ export class Table extends XmlComponent { } } - getRow(ix: number): TableRow { + public getRow(ix: number): TableRow { return this.rows[ix]; } + + public getCell(row: number, col: number): TableCell { + return this.getRow(row).getCell(col); + } } class TableRow extends XmlComponent { private properties: TableRowProperties; - private cells: Array; + private cells: TableCell[]; - constructor(cells: Array) { - super('w:tr'); + constructor(cells: TableCell[]) { + super("w:tr"); this.properties = new TableRowProperties(); this.root.push(this.properties); this.cells = cells; - cells.forEach(c => this.root.push(c)) + cells.forEach((c) => this.root.push(c)); } - getCell(ix: number): TableCell { + public getCell(ix: number): TableCell { return this.cells[ix]; } } class TableRowProperties extends XmlComponent { constructor() { - super('w:trPr'); + super("w:trPr"); } } class TableCell extends XmlComponent { + public content: XmlComponent; private properties: TableCellProperties; - content: any; constructor() { - super('w:tc'); + super("w:tc"); this.properties = new TableCellProperties(); this.root.push(this.properties); - this.root.push() // Table cells can have any block-level content, but for now // we only allow a single paragraph: this.content = new Paragraph(); @@ -78,6 +82,6 @@ class TableCell extends XmlComponent { class TableCellProperties extends XmlComponent { constructor() { - super('w:tcPr'); + super("w:tcPr"); } } diff --git a/ts/docx/table/properties.ts b/ts/docx/table/properties.ts index d37fef0c1b..7890f47696 100644 --- a/ts/docx/table/properties.ts +++ b/ts/docx/table/properties.ts @@ -1,24 +1,30 @@ -import {XmlComponent, Attributes} from "../xml-components"; +import { XmlAttributeComponent, XmlComponent } from "../xml-components"; + +type widthTypes = "dxa" | "pct" | "nil" | "auto"; export class TableProperties extends XmlComponent { - private width: PreferredTableWidth; - constructor() { - super('w:tblPr'); + super("w:tblPr"); } - setWidth(type: string, w: string) { - this.width = new PreferredTableWidth(type, w); - this.root.push(this.width); + public setWidth(type: widthTypes, w: number | string): TableProperties { + this.root.push(new PreferredTableWidth(type, w)); + return this; } } +interface ITableWidth { + type: widthTypes; + w: number | string; +} + +class TableWidthAttributes extends XmlAttributeComponent { + protected xmlKeys = {type: "w:type", w: "w:w"}; +} + class PreferredTableWidth extends XmlComponent { - constructor(type: string, w: string) { - super('w:tblW'); - this.root.push(new Attributes({ - type, - w, - })) + constructor(type: widthTypes, w: number | string) { + super("w:tblW"); + this.root.push(new TableWidthAttributes({type, w})); } } From c0b0649f375eb6c544d4d3052944ada9123b8270 Mon Sep 17 00:00:00 2001 From: felipe Date: Fri, 10 Mar 2017 17:38:04 +0100 Subject: [PATCH 03/26] added tests for table v1 --- ts/docx/table/index.ts | 6 +-- ts/tests/docx/table/testGrid.ts | 47 +++++++++++++++++++++++ ts/tests/docx/table/testProperties.ts | 25 +++++++++++++ ts/tests/docx/table/testTable.ts | 54 +++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 ts/tests/docx/table/testGrid.ts create mode 100644 ts/tests/docx/table/testProperties.ts create mode 100644 ts/tests/docx/table/testTable.ts diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index 15656c6e45..f205f9a96b 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -1,7 +1,7 @@ import { Paragraph } from "../paragraph"; import { XmlComponent } from "../xml-components"; -import { GridCol, TableGrid } from "./grid"; +import { TableGrid } from "./grid"; import { TableProperties } from "./properties"; export class Table extends XmlComponent { @@ -15,7 +15,7 @@ export class Table extends XmlComponent { this.root.push(this.properties); const gridCols: number[] = []; - for (let i = 0; i++; i < cols) { + for (let i = 0; i < cols; i++) { gridCols.push(0); } this.grid = new TableGrid(gridCols); @@ -66,7 +66,7 @@ class TableRowProperties extends XmlComponent { } class TableCell extends XmlComponent { - public content: XmlComponent; + public content: Paragraph; private properties: TableCellProperties; constructor() { diff --git a/ts/tests/docx/table/testGrid.ts b/ts/tests/docx/table/testGrid.ts new file mode 100644 index 0000000000..edb0b50d4b --- /dev/null +++ b/ts/tests/docx/table/testGrid.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; +import { GridCol, TableGrid } from "../../../docx/table/grid"; +import { Formatter } from "../../../export/formatter"; + +describe("GridCol", () => { + describe("#constructor", () => { + it("sets the width attribute to the value given", () => { + const grid = new GridCol(1234); + const tree = new Formatter().format(grid); + expect(tree).to.deep.equal({ + "w:gridCol": [{_attr: {"w:w": 1234}}], + }); + }); + + it("does not set a width attribute if not given", () => { + const grid = new GridCol(); + const tree = new Formatter().format(grid); + expect(tree).to.deep.equal({ + "w:gridCol": [{_attr: {}}], + }); + }); + }); +}); + +describe("TableGrid", () => { + describe("#constructor", () => { + it("creates a column for each width given", () => { + const grid = new TableGrid([1234, 321, 123]); + const tree = new Formatter().format(grid); + expect(tree).to.deep.equal({ + "w:tblGrid": [ + {"w:gridCol": [{_attr: {"w:w": 1234}}]}, + {"w:gridCol": [{_attr: {"w:w": 321}}]}, + {"w:gridCol": [{_attr: {"w:w": 123}}]}, + ], + }); + }); + + it("does not set a width attribute if not given", () => { + const grid = new GridCol(); + const tree = new Formatter().format(grid); + expect(tree).to.deep.equal({ + "w:gridCol": [{_attr: {}}], + }); + }); + }); +}); diff --git a/ts/tests/docx/table/testProperties.ts b/ts/tests/docx/table/testProperties.ts new file mode 100644 index 0000000000..50da89489e --- /dev/null +++ b/ts/tests/docx/table/testProperties.ts @@ -0,0 +1,25 @@ +import { expect } from "chai"; +import { TableProperties } from "../../../docx/table/properties"; +import { Formatter } from "../../../export/formatter"; + +describe("TableProperties", () => { + describe("#constructor", () => { + it("creates an initially empty property object", () => { + const tp = new TableProperties(); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({"w:tblPr": []}); + }); + }); + + describe("#setWidth", () => { + it("adds a table width property", () => { + const tp = new TableProperties().setWidth("dxa", 1234); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [ + {"w:tblW": [{_attr: {"w:type": "dxa", "w:w": 1234}}]}, + ], + }); + }); + }); +}); diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts new file mode 100644 index 0000000000..9a5d855da0 --- /dev/null +++ b/ts/tests/docx/table/testTable.ts @@ -0,0 +1,54 @@ +import { expect } from "chai"; +import { Table } from "../../../docx/table"; +import { Formatter } from "../../../export/formatter"; + +describe("Table", () => { + describe("#constructor", () => { + it("creates a table with the correct number of rows and columns", () => { + const table = new Table(3, 2); + const tree = new Formatter().format(table); + const cell = {"w:tc": [{"w:tcPr": []}, {"w:p": [{"w:pPr": []}]}]}; + expect(tree).to.deep.equal({ + "w:tbl": [ + {"w:tblPr": []}, + {"w:tblGrid": [ + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + ]}, + {"w:tr": [{"w:trPr": []}, cell, cell]}, + {"w:tr": [{"w:trPr": []}, cell, cell]}, + {"w:tr": [{"w:trPr": []}, cell, cell]}, + ], + }); + }); + }); + + describe("#getRow and Row#getCell", () => { + it("returns the correct row", () => { + const table = new Table(2, 2); + table.getRow(0).getCell(0).content.createTextRun("A1"); + table.getRow(0).getCell(1).content.createTextRun("B1"); + table.getRow(1).getCell(0).content.createTextRun("A2"); + table.getRow(1).getCell(1).content.createTextRun("B2"); + const tree = new Formatter().format(table); + const cell = (c) => ({"w:tc": [ + {"w:tcPr": []}, + {"w:p": [ + {"w:pPr": []}, + {"w:r": [{"w:rPr": []}, {"w:t": [c]}]}, + ]}, + ]}); + expect(tree).to.deep.equal({ + "w:tbl": [ + {"w:tblPr": []}, + {"w:tblGrid": [ + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + ]}, + {"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]}, + {"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]}, + ], + }); + }); + }); +}); From a45048a4640a76fa1c39e0da475c9f273037db8d Mon Sep 17 00:00:00 2001 From: felipe Date: Fri, 10 Mar 2017 17:45:16 +0100 Subject: [PATCH 04/26] fix a handful of strict null errors; make test config also be strict These errors only arose because I didn't run `npm build`, only `npm test`. Then, when I tried building the null checks failed. Keeping the two config fiels in sync will help prevent this issue in the future --- ts/docx/table/grid.ts | 4 +++- ts/docx/table/index.ts | 2 +- ts/test-tsconfig.json | 5 +++++ ts/tests/docx/table/testGrid.ts | 12 +----------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/ts/docx/table/grid.ts b/ts/docx/table/grid.ts index 91c38ddd21..3731891177 100644 --- a/ts/docx/table/grid.ts +++ b/ts/docx/table/grid.ts @@ -14,6 +14,8 @@ class GridColAttributes extends XmlAttributeComponent<{w: number}> { export class GridCol extends XmlComponent { constructor(width?: number) { super("w:gridCol"); - this.root.push(new GridColAttributes({w: width})); + if (width !== undefined) { + this.root.push(new GridColAttributes({w: width})); + } } } diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index f205f9a96b..485dc4fcf4 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -23,7 +23,7 @@ export class Table extends XmlComponent { this.rows = []; for (let i = 0; i < rows; i++) { - const cells = []; + const cells: TableCell[] = []; for (let j = 0; j < cols; j++) { cells.push(new TableCell()); } diff --git a/ts/test-tsconfig.json b/ts/test-tsconfig.json index 844ea999ff..255c0e089c 100644 --- a/ts/test-tsconfig.json +++ b/ts/test-tsconfig.json @@ -1,7 +1,12 @@ { "compilerOptions": { "target": "es6", + "strictNullChecks": true, + "sourceMap": true, + "removeComments": true, + "preserveConstEnums": true, "outDir": "../build-tests", + "sourceRoot": "./", "rootDir": "./", "module": "commonjs" } diff --git a/ts/tests/docx/table/testGrid.ts b/ts/tests/docx/table/testGrid.ts index edb0b50d4b..5eb234fd29 100644 --- a/ts/tests/docx/table/testGrid.ts +++ b/ts/tests/docx/table/testGrid.ts @@ -15,9 +15,7 @@ describe("GridCol", () => { it("does not set a width attribute if not given", () => { const grid = new GridCol(); const tree = new Formatter().format(grid); - expect(tree).to.deep.equal({ - "w:gridCol": [{_attr: {}}], - }); + expect(tree).to.deep.equal({"w:gridCol": []}); }); }); }); @@ -35,13 +33,5 @@ describe("TableGrid", () => { ], }); }); - - it("does not set a width attribute if not given", () => { - const grid = new GridCol(); - const tree = new Formatter().format(grid); - expect(tree).to.deep.equal({ - "w:gridCol": [{_attr: {}}], - }); - }); }); }); From 7b6f5bbaefbccd7344fb8580128a8492766b3f0a Mon Sep 17 00:00:00 2001 From: felipe Date: Fri, 10 Mar 2017 17:48:05 +0100 Subject: [PATCH 05/26] forgot to test #getCell --- ts/tests/docx/table/testTable.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index 9a5d855da0..268c854d61 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -51,4 +51,33 @@ describe("Table", () => { }); }); }); + + describe("#getCell", () => { + it("returns the correct cell", () => { + const table = new Table(2, 2); + table.getCell(0, 0).content.createTextRun("A1"); + table.getCell(0, 1).content.createTextRun("B1"); + table.getCell(1, 0).content.createTextRun("A2"); + table.getCell(1, 1).content.createTextRun("B2"); + const tree = new Formatter().format(table); + const cell = (c) => ({"w:tc": [ + {"w:tcPr": []}, + {"w:p": [ + {"w:pPr": []}, + {"w:r": [{"w:rPr": []}, {"w:t": [c]}]}, + ]}, + ]}); + expect(tree).to.deep.equal({ + "w:tbl": [ + {"w:tblPr": []}, + {"w:tblGrid": [ + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 0}}]}, + ]}, + {"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]}, + {"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]}, + ], + }); + }); + }); }); From 62a238de8452947af238aa51f82f46793741e357 Mon Sep 17 00:00:00 2001 From: felipe Date: Fri, 10 Mar 2017 18:54:35 +0100 Subject: [PATCH 06/26] more sane width management --- ts/docx/table/index.ts | 19 +++++++++++++++++-- ts/docx/table/properties.ts | 2 +- ts/tests/docx/table/testTable.ts | 25 +++++++++++++++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index 485dc4fcf4..82fb8e5f29 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -2,7 +2,7 @@ import { Paragraph } from "../paragraph"; import { XmlComponent } from "../xml-components"; import { TableGrid } from "./grid"; -import { TableProperties } from "./properties"; +import { TableProperties, widthTypes } from "./properties"; export class Table extends XmlComponent { private properties: TableProperties; @@ -16,7 +16,17 @@ export class Table extends XmlComponent { const gridCols: number[] = []; for (let i = 0; i < cols; i++) { - gridCols.push(0); + /* + 0-width columns don't get rendered correctly, so we need + to give them some value. A reasonable default would be + ~6in / numCols, but if we do that it becomes very hard + to resize the table using setWidth, unless the layout + algorithm is set to 'fixed'. Instead, the approach here + means even in 'auto' layout, setting a width on the + table will make it look reasonable, as the layout + algorithm will expand columns to fit its content + */ + gridCols.push(1); } this.grid = new TableGrid(gridCols); this.root.push(this.grid); @@ -40,6 +50,11 @@ export class Table extends XmlComponent { public getCell(row: number, col: number): TableCell { return this.getRow(row).getCell(col); } + + public setWidth(type: widthTypes, width: number | string): Table { + this.properties.setWidth(type, width); + return this; + } } class TableRow extends XmlComponent { diff --git a/ts/docx/table/properties.ts b/ts/docx/table/properties.ts index 7890f47696..5a9c8d4e26 100644 --- a/ts/docx/table/properties.ts +++ b/ts/docx/table/properties.ts @@ -1,6 +1,6 @@ import { XmlAttributeComponent, XmlComponent } from "../xml-components"; -type widthTypes = "dxa" | "pct" | "nil" | "auto"; +export type widthTypes = "dxa" | "pct" | "nil" | "auto"; export class TableProperties extends XmlComponent { constructor() { diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index 268c854d61..fd4bff95f6 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -12,8 +12,8 @@ describe("Table", () => { "w:tbl": [ {"w:tblPr": []}, {"w:tblGrid": [ - {"w:gridCol": [{_attr: {"w:w": 0}}]}, - {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, ]}, {"w:tr": [{"w:trPr": []}, cell, cell]}, {"w:tr": [{"w:trPr": []}, cell, cell]}, @@ -42,8 +42,8 @@ describe("Table", () => { "w:tbl": [ {"w:tblPr": []}, {"w:tblGrid": [ - {"w:gridCol": [{_attr: {"w:w": 0}}]}, - {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, ]}, {"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]}, {"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]}, @@ -71,8 +71,8 @@ describe("Table", () => { "w:tbl": [ {"w:tblPr": []}, {"w:tblGrid": [ - {"w:gridCol": [{_attr: {"w:w": 0}}]}, - {"w:gridCol": [{_attr: {"w:w": 0}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, ]}, {"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]}, {"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]}, @@ -80,4 +80,17 @@ describe("Table", () => { }); }); }); + + describe("#setWidth", () => { + it("sets the preferred width on the table", () => { + const table = new Table(2, 2).setWidth("pct", 1000) + const tree = new Formatter().format(table); + expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1); + expect(tree["w:tbl"][0]).to.deep.equal({ + "w:tblPr": [ + {"w:tblW": [{_attr: {"w:type": "pct", "w:w": 1000}}]}, + ], + }); + }); + }); }); From 210b97d00b54070aa223ff7b6fde2837bb20e65f Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 09:02:36 +0100 Subject: [PATCH 07/26] added document#createTable --- ts/docx/document/index.ts | 7 +++++++ ts/tests/docx/document/documentTest.ts | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/ts/docx/document/index.ts b/ts/docx/document/index.ts index a6e8db42b4..7b0d99aaed 100644 --- a/ts/docx/document/index.ts +++ b/ts/docx/document/index.ts @@ -45,4 +45,11 @@ export class Document extends XmlComponent { public addTable(table: Table): void { this.body.push(table); } + + public createTable(rows: number, cols: number): Table { + const table = new Table(rows, cols); + this.addTable(table); + return table; + } + } diff --git a/ts/tests/docx/document/documentTest.ts b/ts/tests/docx/document/documentTest.ts index 98b9a9cf97..abb6ab568b 100644 --- a/ts/tests/docx/document/documentTest.ts +++ b/ts/tests/docx/document/documentTest.ts @@ -46,4 +46,28 @@ describe("Document", () => { }); }); }); + + describe("#createTable", () => { + it("should create a new table and append it to body", () => { + const table = document.createTable(2, 3); + expect(table).to.be.an.instanceof(docx.Table); + const body = new Formatter().format(document)["w:document"][1]["w:body"]; + expect(body).to.be.an("array").which.has.length.at.least(1); + expect(body[0]).to.have.property("w:tbl"); + }); + + it("should create a table with the correct dimensions", () => { + const table = document.createTable(2, 3); + const body = new Formatter().format(document)["w:document"][1]["w:body"]; + expect(body).to.be.an("array").which.has.length.at.least(1); + expect(body[0]).to.have.property("w:tbl").which.includes({ + "w:tblGrid": [ + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + {"w:gridCol": [{_attr: {"w:w": 1}}]}, + ], + }); + expect(body[0]["w:tbl"].filter((x) => x["w:tr"])).to.have.length(2); + }); + }); }); From c10b576a3ace3fe9a9321f704a86c8f767f69e63 Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 09:59:29 +0100 Subject: [PATCH 08/26] added fixedWidthLayout option and method --- ts/docx/table/index.ts | 5 +++++ ts/docx/table/properties.ts | 18 ++++++++++++++++++ ts/tests/docx/table/testProperties.ts | 12 ++++++++++++ ts/tests/docx/table/testTable.ts | 13 +++++++++++++ 4 files changed, 48 insertions(+) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index 82fb8e5f29..995fd2224b 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -55,6 +55,11 @@ export class Table extends XmlComponent { this.properties.setWidth(type, width); return this; } + + public fixedWidthLayout(): Table { + this.properties.fixedWidthLayout(); + return this; + } } class TableRow extends XmlComponent { diff --git a/ts/docx/table/properties.ts b/ts/docx/table/properties.ts index 5a9c8d4e26..ab487e58fb 100644 --- a/ts/docx/table/properties.ts +++ b/ts/docx/table/properties.ts @@ -11,6 +11,11 @@ export class TableProperties extends XmlComponent { this.root.push(new PreferredTableWidth(type, w)); return this; } + + public fixedWidthLayout(): TableProperties { + this.root.push(new TableLayout("fixed")); + return this; + } } interface ITableWidth { @@ -28,3 +33,16 @@ class PreferredTableWidth extends XmlComponent { this.root.push(new TableWidthAttributes({type, w})); } } + +type tableLayout = "autofit" | "fixed"; + +class TableLayoutAttributes extends XmlAttributeComponent<{type: tableLayout}> { + protected xmlKeys = {type: "w:type"}; +} + +class TableLayout extends XmlComponent { + constructor(type: tableLayout) { + super("w:tblLayout"); + this.root.push(new TableLayoutAttributes({type})); + } +} diff --git a/ts/tests/docx/table/testProperties.ts b/ts/tests/docx/table/testProperties.ts index 50da89489e..427419cbd8 100644 --- a/ts/tests/docx/table/testProperties.ts +++ b/ts/tests/docx/table/testProperties.ts @@ -22,4 +22,16 @@ describe("TableProperties", () => { }); }); }); + + describe("#fixedWidthLayout", () => { + it("sets the table to fixed width layout", () => { + const tp = new TableProperties().fixedWidthLayout(); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [ + {"w:tblLayout": [{_attr: {"w:type": "fixed"}}]}, + ], + }); + }); + }); }); diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index fd4bff95f6..aa108cf396 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -93,4 +93,17 @@ describe("Table", () => { }); }); }); + + describe("#fixedWidthLayout", () => { + it("sets the table to fixed width layout", () => { + const table = new Table(2, 2).fixedWidthLayout(); + const tree = new Formatter().format(table); + expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1); + expect(tree["w:tbl"][0]).to.deep.equal({ + "w:tblPr": [ + {"w:tblLayout": [{_attr: {"w:type": "fixed"}}]}, + ], + }); + }); + }); }); From bd3eb3e2147e990f8594c4b1151db5774f72d682 Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 10:22:30 +0100 Subject: [PATCH 09/26] move the cell-paragraph validation into prepForXml Instead of forcing table cells to only have a single paragraph as their content, we now check whether they end in a paragraph (and insert one if necessary) during #prepForXml --- ts/docx/table/index.ts | 21 +++++++--- ts/tests/docx/table/testTable.ts | 70 ++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index 995fd2224b..ec9d2dcfe0 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -86,17 +86,28 @@ class TableRowProperties extends XmlComponent { } class TableCell extends XmlComponent { - public content: Paragraph; private properties: TableCellProperties; constructor() { super("w:tc"); this.properties = new TableCellProperties(); this.root.push(this.properties); - // Table cells can have any block-level content, but for now - // we only allow a single paragraph: - this.content = new Paragraph(); - this.root.push(this.content); + } + + public push(content: Paragraph | Table): TableCell { + this.root.push(content); + return this + } + + public prepForXml(): object { + // Cells must end with a paragraph + const retval = super.prepForXml(); + const content = retval["w:tc"]; + if (!content[content.length - 1]["w:p"]) { + content.push(new Paragraph().prepForXml()); + } + return retval + } } } diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index aa108cf396..1c1eb0aba6 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -1,4 +1,5 @@ import { expect } from "chai"; +import { Paragraph } from "../../../docx/paragraph"; import { Table } from "../../../docx/table"; import { Formatter } from "../../../export/formatter"; @@ -26,10 +27,10 @@ describe("Table", () => { describe("#getRow and Row#getCell", () => { it("returns the correct row", () => { const table = new Table(2, 2); - table.getRow(0).getCell(0).content.createTextRun("A1"); - table.getRow(0).getCell(1).content.createTextRun("B1"); - table.getRow(1).getCell(0).content.createTextRun("A2"); - table.getRow(1).getCell(1).content.createTextRun("B2"); + table.getRow(0).getCell(0).push(new Paragraph("A1")); + table.getRow(0).getCell(1).push(new Paragraph("B1")); + table.getRow(1).getCell(0).push(new Paragraph("A2")); + table.getRow(1).getCell(1).push(new Paragraph("B2")); const tree = new Formatter().format(table); const cell = (c) => ({"w:tc": [ {"w:tcPr": []}, @@ -55,10 +56,10 @@ describe("Table", () => { describe("#getCell", () => { it("returns the correct cell", () => { const table = new Table(2, 2); - table.getCell(0, 0).content.createTextRun("A1"); - table.getCell(0, 1).content.createTextRun("B1"); - table.getCell(1, 0).content.createTextRun("A2"); - table.getCell(1, 1).content.createTextRun("B2"); + table.getCell(0, 0).push(new Paragraph("A1")); + table.getCell(0, 1).push(new Paragraph("B1")); + table.getCell(1, 0).push(new Paragraph("A2")); + table.getCell(1, 1).push(new Paragraph("B2")); const tree = new Formatter().format(table); const cell = (c) => ({"w:tc": [ {"w:tcPr": []}, @@ -106,4 +107,57 @@ describe("Table", () => { }); }); }); + + describe("Cell", () => { + describe("#prepForXml", () => { + it("inserts a paragraph at the end of the cell if it is empty", () => { + const table = new Table(1, 1); + const tree = new Formatter().format(table); + expect(tree).to.have.property("w:tbl").which.is.an("array"); + const row = tree["w:tbl"].find((x) => x["w:tr"]); + expect(row).not.to.be.undefined; + expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1); + expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({ + "w:tc": [ + {"w:tcPr": []}, + {"w:p": [{"w:pPr": []}]}, + ], + }); + }); + + it("inserts a paragraph at the end of the cell even if it has a child table", () => { + const parentTable = new Table(1, 1); + parentTable.getCell(0, 0).push(new Table(1, 1)); + const tree = new Formatter().format(parentTable); + expect(tree).to.have.property("w:tbl").which.is.an("array"); + const row = tree["w:tbl"].find((x) => x["w:tr"]); + expect(row).not.to.be.undefined; + expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1); + const cell = row["w:tr"].find((x) => x["w:tc"]); + expect(cell).not.to.be.undefined; + expect(cell["w:tc"][cell["w:tc"].length - 1]).to.deep.equal({ + "w:p": [{"w:pPr": []}], + }); + }); + + it("does not insert a paragraph if it already ends with one", () => { + const parentTable = new Table(1, 1); + parentTable.getCell(0, 0).push(new Paragraph("Hello")); + const tree = new Formatter().format(parentTable); + expect(tree).to.have.property("w:tbl").which.is.an("array"); + const row = tree["w:tbl"].find((x) => x["w:tr"]); + expect(row).not.to.be.undefined; + expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1); + expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({ + "w:tc": [ + {"w:tcPr": []}, + {"w:p": [ + {"w:pPr": []}, + {"w:r": [{"w:rPr": []}, {"w:t": ["Hello"]}]}, + ]}, + ], + }); + }); + }); + }); }); From bd9d6b74f5029bbe6beaea88c8198ae9da258390 Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 10:30:08 +0100 Subject: [PATCH 10/26] added Cell#createParagraph method --- ts/docx/table/index.ts | 5 +++++ ts/tests/docx/table/testTable.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index ec9d2dcfe0..c407989b5e 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -108,6 +108,11 @@ class TableCell extends XmlComponent { } return retval } + + public createParagraph(text?: string): Paragraph { + const para = new Paragraph(text); + this.push(para); + return para; } } diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index 1c1eb0aba6..1bb9aaf41e 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -159,5 +159,27 @@ describe("Table", () => { }); }); }); + + describe("#createParagraph", () => { + it("inserts a new paragraph in the cell", () => { + const table = new Table(1, 1); + const para = table.getCell(0, 0).createParagraph("Test paragraph"); + expect(para).to.be.an.instanceof(Paragraph); + const tree = new Formatter().format(table); + expect(tree).to.have.property("w:tbl").which.is.an("array"); + const row = tree["w:tbl"].find((x) => x["w:tr"]); + expect(row).not.to.be.undefined; + expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1); + expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({ + "w:tc": [ + {"w:tcPr": []}, + {"w:p": [ + {"w:pPr": []}, + {"w:r": [{"w:rPr": []}, {"w:t": ["Test paragraph"]}]}, + ]}, + ], + }); + }); + }); }); }); From 70f4613e1ba0b7cdbaa6a728536ab79d2beae1bd Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 10:30:25 +0100 Subject: [PATCH 11/26] appease the linter --- ts/docx/table/index.ts | 4 ++-- ts/tests/docx/document/documentTest.ts | 2 +- ts/tests/docx/table/testTable.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index c407989b5e..c3b403b7bb 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -96,7 +96,7 @@ class TableCell extends XmlComponent { public push(content: Paragraph | Table): TableCell { this.root.push(content); - return this + return this; } public prepForXml(): object { @@ -106,7 +106,7 @@ class TableCell extends XmlComponent { if (!content[content.length - 1]["w:p"]) { content.push(new Paragraph().prepForXml()); } - return retval + return retval; } public createParagraph(text?: string): Paragraph { diff --git a/ts/tests/docx/document/documentTest.ts b/ts/tests/docx/document/documentTest.ts index abb6ab568b..9531768e2d 100644 --- a/ts/tests/docx/document/documentTest.ts +++ b/ts/tests/docx/document/documentTest.ts @@ -57,7 +57,7 @@ describe("Document", () => { }); it("should create a table with the correct dimensions", () => { - const table = document.createTable(2, 3); + document.createTable(2, 3); const body = new Formatter().format(document)["w:document"][1]["w:body"]; expect(body).to.be.an("array").which.has.length.at.least(1); expect(body[0]).to.have.property("w:tbl").which.includes({ diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index 1bb9aaf41e..e6c9b23f0b 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -84,7 +84,7 @@ describe("Table", () => { describe("#setWidth", () => { it("sets the preferred width on the table", () => { - const table = new Table(2, 2).setWidth("pct", 1000) + const table = new Table(2, 2).setWidth("pct", 1000); const tree = new Formatter().format(table); expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1); expect(tree["w:tbl"][0]).to.deep.equal({ From fb6a4383ff8135f701fb2482ccbe011badb206c0 Mon Sep 17 00:00:00 2001 From: felipe Date: Sat, 11 Mar 2017 21:15:45 +0100 Subject: [PATCH 12/26] renamed cell#push to cell#addContent --- ts/docx/table/index.ts | 4 ++-- ts/tests/docx/table/testTable.ts | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ts/docx/table/index.ts b/ts/docx/table/index.ts index c3b403b7bb..f6be964129 100644 --- a/ts/docx/table/index.ts +++ b/ts/docx/table/index.ts @@ -94,7 +94,7 @@ class TableCell extends XmlComponent { this.root.push(this.properties); } - public push(content: Paragraph | Table): TableCell { + public addContent(content: Paragraph | Table): TableCell { this.root.push(content); return this; } @@ -111,7 +111,7 @@ class TableCell extends XmlComponent { public createParagraph(text?: string): Paragraph { const para = new Paragraph(text); - this.push(para); + this.addContent(para); return para; } } diff --git a/ts/tests/docx/table/testTable.ts b/ts/tests/docx/table/testTable.ts index e6c9b23f0b..a88cc1b79b 100644 --- a/ts/tests/docx/table/testTable.ts +++ b/ts/tests/docx/table/testTable.ts @@ -27,10 +27,10 @@ describe("Table", () => { describe("#getRow and Row#getCell", () => { it("returns the correct row", () => { const table = new Table(2, 2); - table.getRow(0).getCell(0).push(new Paragraph("A1")); - table.getRow(0).getCell(1).push(new Paragraph("B1")); - table.getRow(1).getCell(0).push(new Paragraph("A2")); - table.getRow(1).getCell(1).push(new Paragraph("B2")); + table.getRow(0).getCell(0).addContent(new Paragraph("A1")); + table.getRow(0).getCell(1).addContent(new Paragraph("B1")); + table.getRow(1).getCell(0).addContent(new Paragraph("A2")); + table.getRow(1).getCell(1).addContent(new Paragraph("B2")); const tree = new Formatter().format(table); const cell = (c) => ({"w:tc": [ {"w:tcPr": []}, @@ -56,10 +56,10 @@ describe("Table", () => { describe("#getCell", () => { it("returns the correct cell", () => { const table = new Table(2, 2); - table.getCell(0, 0).push(new Paragraph("A1")); - table.getCell(0, 1).push(new Paragraph("B1")); - table.getCell(1, 0).push(new Paragraph("A2")); - table.getCell(1, 1).push(new Paragraph("B2")); + table.getCell(0, 0).addContent(new Paragraph("A1")); + table.getCell(0, 1).addContent(new Paragraph("B1")); + table.getCell(1, 0).addContent(new Paragraph("A2")); + table.getCell(1, 1).addContent(new Paragraph("B2")); const tree = new Formatter().format(table); const cell = (c) => ({"w:tc": [ {"w:tcPr": []}, @@ -127,7 +127,7 @@ describe("Table", () => { it("inserts a paragraph at the end of the cell even if it has a child table", () => { const parentTable = new Table(1, 1); - parentTable.getCell(0, 0).push(new Table(1, 1)); + parentTable.getCell(0, 0).addContent(new Table(1, 1)); const tree = new Formatter().format(parentTable); expect(tree).to.have.property("w:tbl").which.is.an("array"); const row = tree["w:tbl"].find((x) => x["w:tr"]); @@ -142,7 +142,7 @@ describe("Table", () => { it("does not insert a paragraph if it already ends with one", () => { const parentTable = new Table(1, 1); - parentTable.getCell(0, 0).push(new Paragraph("Hello")); + parentTable.getCell(0, 0).addContent(new Paragraph("Hello")); const tree = new Formatter().format(parentTable); expect(tree).to.have.property("w:tbl").which.is.an("array"); const row = tree["w:tbl"].find((x) => x["w:tr"]); From d85c6ce56af142ccb8c9f10a797024687d6b6fba Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:28:52 +0100 Subject: [PATCH 13/26] allow Run#underline to take style and color arguments --- ts/docx/run/index.ts | 4 ++-- ts/tests/docx/run/runTest.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ts/docx/run/index.ts b/ts/docx/run/index.ts index 6585de6c01..80417d05fb 100644 --- a/ts/docx/run/index.ts +++ b/ts/docx/run/index.ts @@ -29,8 +29,8 @@ export class Run extends XmlComponent { return this; } - public underline(): Run { - this.properties.push(new Underline()); + public underline(underlineType?: string, color?: string): Run { + this.properties.push(new Underline(underlineType, color)); return this; } diff --git a/ts/tests/docx/run/runTest.ts b/ts/tests/docx/run/runTest.ts index 1bdc1718f0..a4587a05c8 100644 --- a/ts/tests/docx/run/runTest.ts +++ b/ts/tests/docx/run/runTest.ts @@ -32,6 +32,26 @@ describe("Run", () => { const newJson = Utility.jsonify(run); assert.equal(newJson.root[0].root[0].rootKey, "w:u"); }); + + it("should default to 'single' and no color", () => { + run.underline(); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + {"w:rPr": [{"w:u": [{_attr: {"w:val": "single"}}]}]}, + ], + }); + }); + + it("should set the style type and color if given", () => { + run.underline("double", "990011"); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + {"w:rPr": [{"w:u": [{_attr: {"w:val": "double", "w:color": "990011"}}]}]}, + ], + }); + }); }); describe("#smallCaps()", () => { From 3dbd917667a6b3d229ca37f73893f3da8e09f964 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:31:36 +0100 Subject: [PATCH 14/26] added #color and #size methods to Run --- ts/docx/run/index.ts | 12 +++++++++++- ts/tests/docx/run/runTest.ts | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ts/docx/run/index.ts b/ts/docx/run/index.ts index 80417d05fb..bf974ed29b 100644 --- a/ts/docx/run/index.ts +++ b/ts/docx/run/index.ts @@ -1,6 +1,6 @@ import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; -import { Bold, Italics } from "./formatting"; +import { Bold, Color, Italics, Size } from "./formatting"; import { RunProperties } from "./properties"; import { RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; @@ -34,6 +34,16 @@ export class Run extends XmlComponent { return this; } + public color(color: string): Run { + this.properties.push(new Color(color)); + return this; + } + + public size(size: number): Run { + this.properties.push(new Size(size)); + return this; + } + public break(): Run { this.root.splice(1, 0, new Break()); return this; diff --git a/ts/tests/docx/run/runTest.ts b/ts/tests/docx/run/runTest.ts index a4587a05c8..42e180bba7 100644 --- a/ts/tests/docx/run/runTest.ts +++ b/ts/tests/docx/run/runTest.ts @@ -117,4 +117,28 @@ describe("Run", () => { }); }); }); + + describe("#color", () => { + it("should set the run to the color given", () => { + run.color("001122"); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + {"w:rPr": [{"w:color": [{_attr: {"w:val": "001122"}}]}]}, + ], + }); + }); + }); + + describe("#size", () => { + it("should set the run to the given size", () => { + run.size(24); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + {"w:rPr": [{"w:sz": [{_attr: {"w:val": 24}}]}]}, + ], + }); + }); + }); }); From 90049a31ee5af6cef29565d3adc7bbc37db3c129 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:35:15 +0100 Subject: [PATCH 15/26] added #style method to runs --- ts/docx/run/index.ts | 6 ++++++ ts/docx/run/style.ts | 13 +++++++++++++ ts/tests/docx/run/runTest.ts | 12 ++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 ts/docx/run/style.ts diff --git a/ts/docx/run/index.ts b/ts/docx/run/index.ts index bf974ed29b..090bbf04eb 100644 --- a/ts/docx/run/index.ts +++ b/ts/docx/run/index.ts @@ -5,6 +5,7 @@ import { RunProperties } from "./properties"; import { RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; import { DoubleStrike, Strike } from "./strike"; +import { Style } from "./style"; import { Tab } from "./tab"; import { Underline } from "./underline"; @@ -88,4 +89,9 @@ export class Run extends XmlComponent { this.properties.push(new RunFonts(fontName)); return this; } + + public style(styleId: string): Run { + this.properties.push(new Style(styleId)); + return this; + } } diff --git a/ts/docx/run/style.ts b/ts/docx/run/style.ts new file mode 100644 index 0000000000..713752377e --- /dev/null +++ b/ts/docx/run/style.ts @@ -0,0 +1,13 @@ +import { XmlAttributeComponent, XmlComponent } from "../xml-components"; + +class StyleAttributes extends XmlAttributeComponent<{val: string}> { + protected xmlKeys = {val: "w:val"}; +} + +export class Style extends XmlComponent { + + constructor(styleId: string) { + super("w:rStyle"); + this.root.push(new StyleAttributes({val: styleId})); + } +} diff --git a/ts/tests/docx/run/runTest.ts b/ts/tests/docx/run/runTest.ts index 42e180bba7..f8f7826def 100644 --- a/ts/tests/docx/run/runTest.ts +++ b/ts/tests/docx/run/runTest.ts @@ -141,4 +141,16 @@ describe("Run", () => { }); }); }); + + describe("#style", () => { + it("should set the style to the given styleId", () => { + run.style("myRunStyle"); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + {"w:rPr": [{"w:rStyle": [{_attr: {"w:val": "myRunStyle"}}]}]}, + ], + }); + }); + }); }); From 9a90818729c3c355f4a0a07384a51725f53b2c1e Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:43:00 +0100 Subject: [PATCH 16/26] added #smallCaps and #allCaps methods to ParagraphStyle --- ts/styles/style/index.ts | 12 ++++++++++++ ts/tests/stylesTest.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index e5e3561eef..6b9b64fdd9 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -74,6 +74,8 @@ export class ParagraphStyle extends Style { return this; } + // ---------- Run formatting ---------------------- // + public size(twips: number): ParagraphStyle { this.addRunProperty(new formatting.Size(twips)); return this; @@ -89,6 +91,16 @@ export class ParagraphStyle extends Style { return this; } + public smallCaps(): ParagraphStyle { + this.addRunProperty(new formatting.SmallCaps()); + return this; + } + + public allCaps(): ParagraphStyle { + this.addRunProperty(new formatting.Caps()); + return this; + } + public underline(underlineType?: string, color?: string): ParagraphStyle { this.addRunProperty(new formatting.Underline(underlineType, color)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index dfbd1ca2d3..03214ec701 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -230,6 +230,36 @@ describe("ParagraphStyle", () => { }); }); + it("#smallCaps", () => { + const style = new ParagraphStyle("myStyleId") + .smallCaps(); + 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:smallCaps": [{_attr: {"w:val": true}}]}, + ]}, + ], + }); + }); + + it("#allCaps", () => { + const style = new ParagraphStyle("myStyleId") + .allCaps(); + 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:caps": [{_attr: {"w:val": true}}]}, + ]}, + ], + }); + }); + it("#bold", () => { const style = new ParagraphStyle("myStyleId") .bold(); From 371398bc2c9dd6101dac876b9105ff49f294d017 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:44:42 +0100 Subject: [PATCH 17/26] added #strike and #doubleStrike methods to paragraphStyle --- ts/styles/style/index.ts | 10 ++++++++++ ts/tests/stylesTest.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index 6b9b64fdd9..af37157bd9 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -101,6 +101,16 @@ export class ParagraphStyle extends Style { return this; } + public strike(): ParagraphStyle { + this.addRunProperty(new formatting.Strike()); + return this; + } + + public doubleStrike(): ParagraphStyle { + this.addRunProperty(new formatting.DoubleStrike()); + return this; + } + public underline(underlineType?: string, color?: string): ParagraphStyle { this.addRunProperty(new formatting.Underline(underlineType, color)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index 03214ec701..c7a87f6b40 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -260,6 +260,36 @@ describe("ParagraphStyle", () => { }); }); + it("#strike", () => { + const style = new ParagraphStyle("myStyleId") + .strike(); + 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:strike": [{_attr: {"w:val": true}}]}, + ]}, + ], + }); + }); + + it("#doubleStrike", () => { + const style = new ParagraphStyle("myStyleId") + .doubleStrike(); + 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:dstrike": [{_attr: {"w:val": true}}]}, + ]}, + ], + }); + }); + it("#bold", () => { const style = new ParagraphStyle("myStyleId") .bold(); From 8c6cd012e7eba97ef1bb85b7b6d3c33235d15751 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:51:01 +0100 Subject: [PATCH 18/26] eliminated duplicate implementation of strikethrough --- ts/docx/run/index.ts | 3 +-- ts/docx/run/strike.ts | 15 --------------- ts/tests/docx/run/strikeTests.ts | 2 +- 3 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 ts/docx/run/strike.ts diff --git a/ts/docx/run/index.ts b/ts/docx/run/index.ts index 090bbf04eb..bf5465306a 100644 --- a/ts/docx/run/index.ts +++ b/ts/docx/run/index.ts @@ -1,10 +1,9 @@ import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; -import { Bold, Color, Italics, Size } from "./formatting"; +import { Bold, Color, DoubleStrike, Italics, Size, Strike } from "./formatting"; import { RunProperties } from "./properties"; import { RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; -import { DoubleStrike, Strike } from "./strike"; import { Style } from "./style"; import { Tab } from "./tab"; import { Underline } from "./underline"; diff --git a/ts/docx/run/strike.ts b/ts/docx/run/strike.ts deleted file mode 100644 index 7c8c575f0b..0000000000 --- a/ts/docx/run/strike.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { XmlComponent } from "../xml-components"; - -export class Strike extends XmlComponent { - - constructor() { - super("w:strike"); - } -} - -export class DoubleStrike extends XmlComponent { - - constructor() { - super("w:dstrike"); - } -} diff --git a/ts/tests/docx/run/strikeTests.ts b/ts/tests/docx/run/strikeTests.ts index ef591566a6..44dcece140 100644 --- a/ts/tests/docx/run/strikeTests.ts +++ b/ts/tests/docx/run/strikeTests.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { DoubleStrike, Strike } from "../../../docx/run/strike"; +import { DoubleStrike, Strike } from "../../../docx/run/formatting"; import { Utility } from "../../utility"; describe("Strike", () => { From 7f4d1bf3e7447cce62636caef95faf217773c379 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:52:03 +0100 Subject: [PATCH 19/26] added #SubScript and #SuperScript methods to ParagraphStyle --- ts/docx/run/formatting.ts | 1 + ts/styles/style/index.ts | 10 ++++++++++ ts/tests/stylesTest.ts | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/ts/docx/run/formatting.ts b/ts/docx/run/formatting.ts index d8f0257e03..7236c24f5e 100644 --- a/ts/docx/run/formatting.ts +++ b/ts/docx/run/formatting.ts @@ -1,5 +1,6 @@ import { Attributes, XmlComponent } from "../xml-components"; export { Underline } from "./underline"; +export { SubScript, SuperScript } from "./script"; export class Bold extends XmlComponent { diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index af37157bd9..5d9e221410 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -111,6 +111,16 @@ export class ParagraphStyle extends Style { return this; } + public subScript(): ParagraphStyle { + this.addRunProperty(new formatting.SubScript()); + return this; + } + + public superScript(): ParagraphStyle { + this.addRunProperty(new formatting.SuperScript()); + return this; + } + public underline(underlineType?: string, color?: string): ParagraphStyle { this.addRunProperty(new formatting.Underline(underlineType, color)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index c7a87f6b40..b56cb687fd 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -290,6 +290,36 @@ describe("ParagraphStyle", () => { }); }); + it("#subScript", () => { + const style = new ParagraphStyle("myStyleId") + .subScript(); + 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:vertAlign": [{_attr: {"w:val": "subscript"}}]}, + ]}, + ], + }); + }); + + it("#superScript", () => { + const style = new ParagraphStyle("myStyleId") + .superScript(); + 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:vertAlign": [{_attr: {"w:val": "superscript"}}]}, + ]}, + ], + }); + }); + it("#bold", () => { const style = new ParagraphStyle("myStyleId") .bold(); From 1cd681bc2d77bd57c449b368ceb0686d12e62aff Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 17:53:32 +0100 Subject: [PATCH 20/26] added #font method to paragraph styles --- ts/docx/run/formatting.ts | 1 + ts/styles/style/index.ts | 7 +++++++ ts/tests/stylesTest.ts | 13 +++++++++++++ 3 files changed, 21 insertions(+) diff --git a/ts/docx/run/formatting.ts b/ts/docx/run/formatting.ts index 7236c24f5e..2685f07fda 100644 --- a/ts/docx/run/formatting.ts +++ b/ts/docx/run/formatting.ts @@ -1,6 +1,7 @@ import { Attributes, XmlComponent } from "../xml-components"; export { Underline } from "./underline"; export { SubScript, SuperScript } from "./script"; +export { RunFonts } from "./run-fonts"; export class Bold extends XmlComponent { diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index 5d9e221410..3cf06cefb8 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -131,6 +131,13 @@ export class ParagraphStyle extends Style { return this; } + public font(fontName: string): ParagraphStyle { + this.addRunProperty(new formatting.RunFonts(fontName)); + return this; + } + + // --------------------- Paragraph formatting ------------------------ // + public indent(left: number, hanging?: number): ParagraphStyle { this.addParagraphProperty(new Indent(left, hanging)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index b56cb687fd..4180d182fd 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -320,6 +320,19 @@ describe("ParagraphStyle", () => { }); }); + it("#font", () => { + const style = new ParagraphStyle("myStyleId") + .font("Times"); + 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:rFonts": [{_attr: {"w:ascii": "Times", "w:hAnsi": "Times"}}]}]}, + ], + }); + }); + it("#bold", () => { const style = new ParagraphStyle("myStyleId") .bold(); From b3524971ace3778b0852334bec806e1ffa5fcba4 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 21:35:30 +0100 Subject: [PATCH 21/26] clean up imports into paragraph and paragraph style (this prep for importing it from styles) --- ts/docx/paragraph/alignment.ts | 15 +++++++++++++++ ts/docx/paragraph/formatting.ts | 9 +++++++++ ts/docx/paragraph/index.ts | 13 ++----------- ts/styles/style/index.ts | 14 ++++++-------- 4 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 ts/docx/paragraph/alignment.ts create mode 100644 ts/docx/paragraph/formatting.ts diff --git a/ts/docx/paragraph/alignment.ts b/ts/docx/paragraph/alignment.ts new file mode 100644 index 0000000000..94e0568515 --- /dev/null +++ b/ts/docx/paragraph/alignment.ts @@ -0,0 +1,15 @@ +import { XmlAttributeComponent, XmlComponent } from "../xml-components"; + +type alignmentOptions = "left" | "center" | "right" | "both"; + +class AlignmentAttributes extends XmlAttributeComponent<{val: alignmentOptions}> { + protected xmlKeys = {val: "w:val"}; +} + +export class Alignment extends XmlComponent { + + constructor(type: alignmentOptions) { + super("w:jc"); + this.root.push(new AlignmentAttributes({val: type})); + } +} diff --git a/ts/docx/paragraph/formatting.ts b/ts/docx/paragraph/formatting.ts new file mode 100644 index 0000000000..8b76c0fa32 --- /dev/null +++ b/ts/docx/paragraph/formatting.ts @@ -0,0 +1,9 @@ +export { Alignment } from "./alignment"; +export { ThematicBreak } from "./border"; +export { Indent } from "./indent"; +export { PageBreak } from "./page-break"; +export { ParagraphProperties } from "./properties"; +export { ISpacingProperties, Spacing } from "./spacing"; +export { Style } from "./style"; +export { LeftTabStop, MaxRightTabStop } from "./tab-stop"; +export { NumberProperties } from "./unordered-list"; diff --git a/ts/docx/paragraph/index.ts b/ts/docx/paragraph/index.ts index 5bf0912d03..a85c014764 100644 --- a/ts/docx/paragraph/index.ts +++ b/ts/docx/paragraph/index.ts @@ -1,7 +1,8 @@ import { Num } from "../../numbering/num"; import { TextRun } from "../run/text-run"; -import { Attributes, XmlComponent } from "../xml-components"; +import { XmlComponent } from "../xml-components"; +import { Alignment } from "./alignment"; import { ThematicBreak } from "./border"; import { Indent } from "./indent"; import { PageBreak } from "./page-break"; @@ -11,16 +12,6 @@ import { Style } from "./style"; import { LeftTabStop, MaxRightTabStop } from "./tab-stop"; import { NumberProperties } from "./unordered-list"; -class Alignment extends XmlComponent { - - constructor(type: string) { - super("w:jc"); - this.root.push(new Attributes({ - val: type, - })); - } -} - export class Paragraph extends XmlComponent { private properties: ParagraphProperties; diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index 3cf06cefb8..db4b1c1924 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -1,6 +1,4 @@ -import { Indent } from "../../docx/paragraph/indent"; -import { ParagraphProperties } from "../../docx/paragraph/properties"; -import { ISpacingProperties, Spacing } from "../../docx/paragraph/spacing"; +import * as paragraph from "../../docx/paragraph/formatting"; import * as formatting from "../../docx/run/formatting"; import { RunProperties } from "../../docx/run/properties"; import { XmlAttributeComponent, XmlComponent } from "../../docx/xml-components"; @@ -40,12 +38,12 @@ export class Style extends XmlComponent { export class ParagraphStyle extends Style { - private paragraphProperties: ParagraphProperties; + private paragraphProperties: paragraph.ParagraphProperties; private runProperties: RunProperties; constructor(styleId: string, name?: string) { super({type: "paragraph", styleId: styleId}, name); - this.paragraphProperties = new ParagraphProperties(); + this.paragraphProperties = new paragraph.ParagraphProperties(); this.runProperties = new RunProperties(); this.root.push(this.paragraphProperties); this.root.push(this.runProperties); @@ -139,12 +137,12 @@ export class ParagraphStyle extends Style { // --------------------- Paragraph formatting ------------------------ // public indent(left: number, hanging?: number): ParagraphStyle { - this.addParagraphProperty(new Indent(left, hanging)); + this.addParagraphProperty(new paragraph.Indent(left, hanging)); return this; } - public spacing(params: ISpacingProperties): ParagraphStyle { - this.addParagraphProperty(new Spacing(params)); + public spacing(params: paragraph.ISpacingProperties): ParagraphStyle { + this.addParagraphProperty(new paragraph.Spacing(params)); return this; }; } From df8ba1e8b8ab6361926f5e85e8c019926303bf85 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 21:40:01 +0100 Subject: [PATCH 22/26] added alignment methods to paragraph style --- ts/styles/style/index.ts | 20 ++++++++++++++ ts/tests/stylesTest.ts | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index db4b1c1924..41493c9188 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -136,6 +136,26 @@ export class ParagraphStyle extends Style { // --------------------- Paragraph formatting ------------------------ // + public center(): ParagraphStyle { + this.addParagraphProperty(new paragraph.Alignment("center")); + return this; + } + + public left(): ParagraphStyle { + this.addParagraphProperty(new paragraph.Alignment("left")); + return this; + } + + public right(): ParagraphStyle { + this.addParagraphProperty(new paragraph.Alignment("right")); + return this; + } + + public justified(): ParagraphStyle { + this.addParagraphProperty(new paragraph.Alignment("both")); + return this; + } + public indent(left: number, hanging?: number): ParagraphStyle { this.addParagraphProperty(new paragraph.Indent(left, hanging)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index 4180d182fd..68eeb6915d 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -212,6 +212,66 @@ describe("ParagraphStyle", () => { ], }); }); + + it("#center", () => { + const style = new ParagraphStyle("myStyleId") + .center(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:jc": [{_attr: {"w:val": "center"}}]}, + ]}, + {"w:rPr": []}, + ], + }); + }); + + it("#left", () => { + const style = new ParagraphStyle("myStyleId") + .left(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:jc": [{_attr: {"w:val": "left"}}]}, + ]}, + {"w:rPr": []}, + ], + }); + }); + + it("#right", () => { + const style = new ParagraphStyle("myStyleId") + .right(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:jc": [{_attr: {"w:val": "right"}}]}, + ]}, + {"w:rPr": []}, + ], + }); + }); + + it("#justified", () => { + const style = new ParagraphStyle("myStyleId") + .justified(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:jc": [{_attr: {"w:val": "both"}}]}, + ]}, + {"w:rPr": []}, + ], + }); + }); }); describe("formatting methods: run properties", () => { From 6689f76c2809373f73c84c968b16302c6aadb758 Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 21:58:12 +0100 Subject: [PATCH 23/26] added #thematicBreak method to paragraph styles --- ts/styles/style/index.ts | 5 +++++ ts/tests/stylesTest.ts | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index 41493c9188..35e456d937 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -156,6 +156,11 @@ export class ParagraphStyle extends Style { return this; } + public thematicBreak(): ParagraphStyle { + this.addParagraphProperty(new paragraph.ThematicBreak()); + return this; + } + public indent(left: number, hanging?: number): ParagraphStyle { this.addParagraphProperty(new paragraph.Indent(left, hanging)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index 68eeb6915d..821ff2a9f3 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -272,6 +272,26 @@ describe("ParagraphStyle", () => { ], }); }); + + it("#thematicBreak", () => { + const style = new ParagraphStyle("myStyleId") + .thematicBreak(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:pBdr": [{"w:bottom": [{_attr: { + "w:color": "auto", + "w:space": "1", + "w:val": "single", + "w:sz": "6", + }}]}]}, + ]}, + {"w:rPr": []}, + ], + }); + }); }); describe("formatting methods: run properties", () => { From 0b78e33444909888cc6870c06b49e744f9d92aad Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 22:04:57 +0100 Subject: [PATCH 24/26] added #leftTabStop and #maxRightTabStop methods to paragraph styles --- ts/styles/style/index.ts | 10 ++++++++++ ts/tests/stylesTest.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/ts/styles/style/index.ts b/ts/styles/style/index.ts index 35e456d937..e70936655b 100644 --- a/ts/styles/style/index.ts +++ b/ts/styles/style/index.ts @@ -161,6 +161,16 @@ export class ParagraphStyle extends Style { return this; } + public maxRightTabStop(): ParagraphStyle { + this.addParagraphProperty(new paragraph.MaxRightTabStop()); + return this; + } + + public leftTabStop(position: number): ParagraphStyle { + this.addParagraphProperty(new paragraph.LeftTabStop(position)); + return this; + } + public indent(left: number, hanging?: number): ParagraphStyle { this.addParagraphProperty(new paragraph.Indent(left, hanging)); return this; diff --git a/ts/tests/stylesTest.ts b/ts/tests/stylesTest.ts index 821ff2a9f3..d00fb4cc0d 100644 --- a/ts/tests/stylesTest.ts +++ b/ts/tests/stylesTest.ts @@ -292,6 +292,40 @@ describe("ParagraphStyle", () => { ], }); }); + + it("#leftTabStop", () => { + const style = new ParagraphStyle("myStyleId") + .leftTabStop(1200); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:tabs": [ + {"w:tab": [{_attr: {"w:val": "left", "w:pos": 1200}}]}, + ]}, + ]}, + {"w:rPr": []}, + ], + }); + }); + + it("#maxRightTabStop", () => { + const style = new ParagraphStyle("myStyleId") + .maxRightTabStop(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + {_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}}, + {"w:pPr": [ + {"w:tabs": [ + {"w:tab": [{_attr: {"w:val": "right", "w:pos": 9026}}]}, + ]}, + ]}, + {"w:rPr": []}, + ], + }); + }); }); describe("formatting methods: run properties", () => { From a6e40d9d922ce2d32f28db182ee52c6c7120516b Mon Sep 17 00:00:00 2001 From: felipe Date: Sun, 12 Mar 2017 22:06:11 +0100 Subject: [PATCH 25/26] tightened type checking for tab stops --- ts/docx/paragraph/tab-stop.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ts/docx/paragraph/tab-stop.ts b/ts/docx/paragraph/tab-stop.ts index f531f6ba84..6154c33d2a 100644 --- a/ts/docx/paragraph/tab-stop.ts +++ b/ts/docx/paragraph/tab-stop.ts @@ -1,4 +1,4 @@ -import { Attributes, XmlComponent } from "../xml-components"; +import { XmlAttributeComponent, XmlComponent } from "../xml-components"; class TabStop extends XmlComponent { @@ -8,11 +8,17 @@ class TabStop extends XmlComponent { } } +export type tabOptions = "left" | "right"; + +class TabAttributes extends XmlAttributeComponent<{val: tabOptions, pos: string | number}> { + protected xmlKeys = {val: "w:val", pos: "w:pos"}; +} + class Tab extends XmlComponent { - constructor(value: string, position: string | number) { + constructor(value: tabOptions, position: string | number) { super("w:tab"); - this.root.push(new Attributes({ + this.root.push(new TabAttributes({ val: value, pos: position, })); From f17360d45aee89b8746212b37a57c533f2958778 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sun, 12 Mar 2017 21:34:49 +0000 Subject: [PATCH 26/26] add simple demo page --- demo/demo.js | 15 +++++++++++++++ package.json | 3 ++- ts/export/packer/express.ts | 2 +- ts/export/packer/local.ts | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 demo/demo.js diff --git a/demo/demo.js b/demo/demo.js new file mode 100644 index 0000000000..58e7fdfd8d --- /dev/null +++ b/demo/demo.js @@ -0,0 +1,15 @@ +const docx = require('../build'); + +var doc = new docx.Document(); + +var paragraph = new docx.Paragraph("Hello World"); +var institutionText = new docx.TextRun("University College London").bold(); +var dateText = new docx.TextRun("5th Dec 2015").tab().bold(); +paragraph.addText(institutionText); +paragraph.addText(dateText); + +doc.addParagraph(paragraph); +var exporter = new docx.LocalPacker(doc); +exporter.pack('My Document'); + +console.log('Document created succesfully at project root!'); \ No newline at end of file diff --git a/package.json b/package.json index 9574cc3ac4..2cee1629ad 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "mocha ./build-tests --recursive", "prepublishOnly": "npm run build", "lint": "tslint --project ./ts", - "build": "rimraf ./build && tsc -p ts" + "build": "rimraf ./build && tsc -p ts", + "demo": "npm run build && node ./demo/demo.js" }, "files": [ "ts", diff --git a/ts/export/packer/express.ts b/ts/export/packer/express.ts index 539fac40d4..737c220779 100644 --- a/ts/export/packer/express.ts +++ b/ts/export/packer/express.ts @@ -18,7 +18,7 @@ export class ExpressPacker extends Packer { } public pack(name: string): void { - this.res.attachment(name + ".docx"); + this.res.attachment(`${name}.docx`); super.pack(this.res); } } diff --git a/ts/export/packer/local.ts b/ts/export/packer/local.ts index 6b8c371c94..319f2e16f1 100644 --- a/ts/export/packer/local.ts +++ b/ts/export/packer/local.ts @@ -13,7 +13,7 @@ export class LocalPacker extends Packer { } public pack(path: string): void { - this.stream = fs.createWriteStream(path); + this.stream = fs.createWriteStream(`${path}.docx`); super.pack(this.stream); } }