diff --git a/.travis.yml b/.travis.yml index 3c015ad296..3bf1b47690 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ script: - npm run ts-node -- ./demo/29-numbered-lists.ts - npm run ts-node -- ./demo/30-template-document.ts - npm run ts-node -- ./demo/31-tables.ts - - npm run ts-node -- ./demo/32-merge-table-cells.ts + - npm run ts-node -- ./demo/32-merge-and-shade-table-cells.ts # - npm run e2e "My Document.docx" // Need to fix - npm run ts-node -- ./demo/33-sequential-captions.ts - npm run ts-node -- ./demo/34-floating-tables.ts diff --git a/demo/11-declaritive-styles-2.ts b/demo/11-declaritive-styles-2.ts index bb6428d518..c6780c440f 100644 --- a/demo/11-declaritive-styles-2.ts +++ b/demo/11-declaritive-styles-2.ts @@ -1,7 +1,7 @@ // Setting styles with JavaScript configuration // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, Footer, HeadingLevel, Media, Packer, Paragraph, Table } from "../build"; +import { AlignmentType, Document, Footer, HeadingLevel, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); @@ -81,13 +81,37 @@ doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph") const image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const table = new Table({ - rows: 4, - columns: 4, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Test cell 1.")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Test cell 2.")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Test cell 3.")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Test cell 4.")], + }), + ], + }), + ], }); -table - .getRow(0) - .getCell(0) - .add(new Paragraph("Pole No.")); const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); diff --git a/demo/20-table-cell-borders.ts b/demo/20-table-cell-borders.ts index 13809a6164..028d9fdb04 100644 --- a/demo/20-table-cell-borders.ts +++ b/demo/20-table-cell-borders.ts @@ -1,23 +1,102 @@ // Add custom borders to table cell // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { BorderStyle, Document, Packer, Paragraph, Table } from "../build"; +import { BorderStyle, Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); const table = new Table({ - rows: 4, - columns: 4, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("Hello")], + borders: { + top: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "red", + }, + bottom: { + style: BorderStyle.DOUBLE, + size: 3, + color: "blue", + }, + left: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "green", + }, + right: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "#ff8000", + }, + }, + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], }); doc.addSection({ children: [table] }); -table - .getCell(2, 2) - .add(new Paragraph("Hello")) - .Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red") - .addBottomBorder(BorderStyle.DOUBLE, 3, "blue") - .addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green") - .addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000"); Packer.toBuffer(doc).then((buffer) => { fs.writeFileSync("My Document.docx", buffer); diff --git a/demo/24-images-to-table-cell.ts b/demo/24-images-to-table-cell.ts index 114be5491e..e37d1aa812 100644 --- a/demo/24-images-to-table-cell.ts +++ b/demo/24-images-to-table-cell.ts @@ -1,24 +1,85 @@ // Add image to table cell // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Media, Packer, Paragraph, Table } from "../build"; +import { Document, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); +const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); + const table = new Table({ - rows: 4, - columns: 4, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph(image)], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("Hello")], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], }); doc.addSection({ children: [table], }); -table.getCell(2, 2).add(new Paragraph("Hello")); - -const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); -table.getCell(1, 1).add(new Paragraph(image)); - Packer.toBuffer(doc).then((buffer) => { fs.writeFileSync("My Document.docx", buffer); }); diff --git a/demo/31-tables.ts b/demo/31-tables.ts index 050403bb99..7daec1506d 100644 --- a/demo/31-tables.ts +++ b/demo/31-tables.ts @@ -1,28 +1,48 @@ // Example of how you would create a table and add data to it // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph, Table, VerticalAlign } from "../build"; +import { Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign } from "../build"; const doc = new Document(); const table = new Table({ - rows: 2, - columns: 2, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({}), new Paragraph({})], + verticalAlign: VerticalAlign.CENTER, + }), + new TableCell({ + children: [new Paragraph({}), new Paragraph({})], + verticalAlign: VerticalAlign.CENTER, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [ + new Paragraph({ + text: + "Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah", + heading: HeadingLevel.HEADING_1, + }), + ], + }), + new TableCell({ + children: [ + new Paragraph({ + text: "This text should be in the middle of the cell", + }), + ], + verticalAlign: VerticalAlign.CENTER, + }), + ], + }), + ], }); -table - .getCell(1, 1) - .add(new Paragraph("This text should be in the middle of the cell")) - .setVerticalAlign(VerticalAlign.CENTER); - -table.getCell(1, 0).add( - new Paragraph({ - text: - "Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah", - heading: HeadingLevel.HEADING_1, - }), -); - doc.addSection({ children: [table], }); diff --git a/demo/32-merge-and-shade-table-cells.ts b/demo/32-merge-and-shade-table-cells.ts new file mode 100644 index 0000000000..77d6815d1e --- /dev/null +++ b/demo/32-merge-and-shade-table-cells.ts @@ -0,0 +1,231 @@ +// Example of how you would merge cells together (Rows and Columns) and apply shading +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build"; + +const doc = new Document(); + +const table = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Hello")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], +}); + +const table2 = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("World")], + margins: { + top: 1000, + bottom: 1000, + left: 1000, + right: 1000, + }, + columnSpan: 3, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], + width: { + size: 100, + type: WidthType.AUTO, + }, + columnWidths: [1000, 1000, 1000], +}); + +const table3 = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Foo")], + }), + new TableCell({ + children: [new Paragraph("v")], + columnSpan: 3, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Bar1")], + shading: { + fill: "b79c2f", + val: ShadingType.REVERSE_DIAGONAL_STRIPE, + color: "auto", + }, + }), + new TableCell({ + children: [new Paragraph("Bar2")], + shading: { + fill: "42c5f4", + val: ShadingType.PERCENT_95, + color: "auto", + }, + }), + new TableCell({ + children: [new Paragraph("Bar3")], + shading: { + fill: "880aa8", + val: ShadingType.PERCENT_10, + color: "e2df0b", + }, + }), + new TableCell({ + children: [new Paragraph("Bar4")], + shading: { + fill: "FF0000", + val: ShadingType.CLEAR, + color: "auto", + }, + }), + ], + }), + ], + width: { + size: 7000, + type: WidthType.DXA, + }, + margins: { + top: 400, + bottom: 400, + right: 400, + left: 400, + }, +}); + +const table4 = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("0,0")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("1,0")], + }), + new TableCell({ + children: [new Paragraph("1,1")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("2,0")], + columnSpan: 2, + }), + ], + }), + ], + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, +}); + +const table5 = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("0,0")], + }), + new TableCell({ + children: [new Paragraph("0,1")], + rowSpan: 2, + }), + new TableCell({ + children: [new Paragraph("0,2")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("1,2")], + rowSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, +}); + +doc.addSection({ + children: [ + table, + new Paragraph({ + text: "Another table", + heading: HeadingLevel.HEADING_2, + }), + table2, + new Paragraph({ + text: "Another table", + heading: HeadingLevel.HEADING_2, + }), + table3, + new Paragraph("Merging columns"), + table4, + new Paragraph("More Merging columns"), + table5, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/demo/32-merge-table-cells.ts b/demo/32-merge-table-cells.ts deleted file mode 100644 index cd850067e5..0000000000 --- a/demo/32-merge-table-cells.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Example of how you would merge cells together -// Import from 'docx' rather than '../build' if you install from npm -import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, WidthType } from "../build"; - -const doc = new Document(); - -const table = new Table({ - rows: 2, - columns: 2, -}); - -table.getCell(0, 0).add(new Paragraph("Hello")); -table.getRow(0).mergeCells(0, 1); - -const table2 = new Table({ - rows: 2, - columns: 3, - width: 100, - widthUnitType: WidthType.AUTO, - columnWidths: [1000, 1000, 1000], -}); - -table2 - .getCell(0, 0) - .add(new Paragraph("World")) - .setMargins({ - top: 1000, - bottom: 1000, - left: 1000, - right: 1000, - }); -table.getRow(0).mergeCells(0, 2); - -const table3 = new Table({ - rows: 2, - columns: 4, - width: 7000, - widthUnitType: WidthType.DXA, - margins: { - top: 400, - bottom: 400, - right: 400, - left: 400, - }, -}); - -table3.getCell(0, 0).add(new Paragraph("Foo")); -table3.getCell(0, 1).add(new Paragraph("v")); - -table3 - .getCell(1, 0) - .add(new Paragraph("Bar1")) - .setShading({ - fill: "b79c2f", - val: ShadingType.REVERSE_DIAGONAL_STRIPE, - color: "auto", - }); -table3 - .getCell(1, 1) - .add(new Paragraph("Bar2")) - .setShading({ - fill: "42c5f4", - val: ShadingType.PERCENT_95, - color: "auto", - }); -table3 - .getCell(1, 2) - .add(new Paragraph("Bar3")) - .setShading({ - fill: "880aa8", - val: ShadingType.PERCENT_10, - color: "e2df0b", - }); -table3 - .getCell(1, 3) - .add(new Paragraph("Bar4")) - .setShading({ - fill: "FF0000", - val: ShadingType.CLEAR, - color: "auto", - }); - -table3.getRow(0).mergeCells(0, 3); - -const table4 = new Table({ - rows: 2, - columns: 2, - width: 100, - widthUnitType: WidthType.PERCENTAGE, -}); - -doc.addSection({ - children: [ - table, - new Paragraph({ - text: "Another table", - heading: HeadingLevel.HEADING_2, - }), - table2, - new Paragraph({ - text: "Another table", - heading: HeadingLevel.HEADING_2, - }), - table3, - new Paragraph("hi"), - table4, - ], -}); - -Packer.toBuffer(doc).then((buffer) => { - fs.writeFileSync("My Document.docx", buffer); -}); diff --git a/demo/34-floating-tables.ts b/demo/34-floating-tables.ts index c5b8a18cf8..dadf80f9da 100644 --- a/demo/34-floating-tables.ts +++ b/demo/34-floating-tables.ts @@ -9,29 +9,48 @@ import { RelativeVerticalPosition, Table, TableAnchorType, + TableCell, TableLayoutType, + TableRow, WidthType, } from "../build"; const doc = new Document(); const table = new Table({ - rows: 2, - columns: 2, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Hello")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], float: { horizontalAnchor: TableAnchorType.MARGIN, verticalAnchor: TableAnchorType.MARGIN, relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT, relativeVerticalPosition: RelativeVerticalPosition.BOTTOM, }, - width: 4535, - widthUnitType: WidthType.DXA, + width: { + size: 4535, + type: WidthType.DXA, + }, layout: TableLayoutType.FIXED, }); -table.getCell(0, 0).add(new Paragraph("Hello")); -table.getRow(0).mergeCells(0, 1); - doc.addSection({ children: [table], }); diff --git a/demo/36-image-to-table-cell.ts b/demo/36-image-to-table-cell.ts index 1c7897c558..a0d87230a2 100644 --- a/demo/36-image-to-table-cell.ts +++ b/demo/36-image-to-table-cell.ts @@ -1,16 +1,61 @@ -// Add image to table cell +// Add image to table cell in a header and body // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Header, Media, Packer, Paragraph, Table } from "../build"; +import { Document, Header, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); const table = new Table({ - rows: 2, - columns: 2, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph(image)], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], }); -table.getCell(1, 1).add(new Paragraph(image)); // Adding same table in the body and in the header doc.addSection({ diff --git a/demo/4-basic-table.ts b/demo/4-basic-table.ts index 78367caab9..593fe821a5 100644 --- a/demo/4-basic-table.ts +++ b/demo/4-basic-table.ts @@ -1,17 +1,35 @@ // Example of how you would create a table and add data to it // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, Table } from "../build"; +import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); const table = new Table({ - rows: 4, - columns: 4, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Hello")], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("World")], + }), + ], + }), + ], }); -table.getCell(2, 2).add(new Paragraph("Hello")); - doc.addSection({ children: [table], }); diff --git a/demo/41-merge-table-cells-2.ts b/demo/41-merge-table-cells-2.ts index 1801eb4a6e..94e7bffe1d 100644 --- a/demo/41-merge-table-cells-2.ts +++ b/demo/41-merge-table-cells-2.ts @@ -1,52 +1,259 @@ -// Multiple cells merging in the same table +// Multiple cells merging in the same table - Rows and Columns // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, Table } from "../build"; +import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); const table = new Table({ - rows: 13, - columns: 6, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("0,0")], + }), + new TableCell({ + children: [new Paragraph("0,1")], + columnSpan: 2, + }), + new TableCell({ + children: [new Paragraph("0,3")], + }), + new TableCell({ + children: [new Paragraph("0,4")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("1,0")], + columnSpan: 2, + }), + new TableCell({ + children: [new Paragraph("1,2")], + columnSpan: 2, + }), + new TableCell({ + children: [new Paragraph("1,4")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("2,0")], + }), + new TableCell({ + children: [new Paragraph("2,1")], + columnSpan: 2, + }), + new TableCell({ + children: [new Paragraph("2,3")], + }), + new TableCell({ + children: [new Paragraph("2,4")], + columnSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("3,0")], + }), + new TableCell({ + children: [new Paragraph("3,1")], + }), + new TableCell({ + children: [new Paragraph("3,2")], + }), + new TableCell({ + children: [new Paragraph("3,3")], + }), + new TableCell({ + children: [new Paragraph("3,4")], + }), + new TableCell({ + children: [new Paragraph("3,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("4,0")], + columnSpan: 5, + }), + new TableCell({ + children: [new Paragraph("4,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], }); -let row = 0; -table.getCell(row, 0).add(new Paragraph("0,0")); -table.getCell(row, 1).add(new Paragraph("0,1")); -table.getCell(row, 3).add(new Paragraph("0,3")); -table.getCell(row, 4).add(new Paragraph("0,4")); -table.getRow(row).mergeCells(4, 5); -table.getRow(row).mergeCells(1, 2); -row = 1; -table.getCell(row, 0).add(new Paragraph("1,0")); -table.getCell(row, 2).add(new Paragraph("1,2")); -table.getCell(row, 4).add(new Paragraph("1,4")); -table.getRow(row).mergeCells(4, 5); -table.getRow(row).mergeCells(2, 3); -table.getRow(row).mergeCells(0, 1); - -row = 2; -table.getCell(row, 0).add(new Paragraph("2,0")); -table.getCell(row, 1).add(new Paragraph("2,1")); -table.getCell(row, 2).add(new Paragraph("2,2")); -table.getCell(row, 3).add(new Paragraph("2,3")); -table.getCell(row, 4).add(new Paragraph("2,4")); -table.getRow(row).mergeCells(4, 5); -table.getRow(row).mergeCells(1, 2); -row = 3; -table.getCell(row, 0).add(new Paragraph("3,0")); -table.getCell(row, 1).add(new Paragraph("3,1")); -table.getCell(row, 2).add(new Paragraph("3,2")); -table.getCell(row, 3).add(new Paragraph("3,3")); -table.getCell(row, 4).add(new Paragraph("3,4")); -table.getCell(row, 5).add(new Paragraph("3,5")); -row = 4; -table.getCell(row, 0).add(new Paragraph("4,0")); -table.getCell(row, 5).add(new Paragraph("4,5")); -table.getRow(row).mergeCells(0, 4); +const table2 = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("0,0")], + }), + new TableCell({ + children: [new Paragraph("0,1")], + rowSpan: 2, + }), + new TableCell({ + children: [new Paragraph("0,2")], + }), + new TableCell({ + children: [new Paragraph("0,3")], + }), + new TableCell({ + children: [new Paragraph("0,4")], + }), + new TableCell({ + children: [new Paragraph("0,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("1,0")], + }), + new TableCell({ + children: [new Paragraph("1,2")], + }), + new TableCell({ + children: [new Paragraph("1,3")], + }), + new TableCell({ + children: [new Paragraph("1,4")], + }), + new TableCell({ + children: [new Paragraph("1,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("2,0")], + }), + new TableCell({ + children: [new Paragraph("2,1")], + }), + new TableCell({ + children: [new Paragraph("2,2")], + }), + new TableCell({ + children: [new Paragraph("2,3")], + }), + new TableCell({ + children: [new Paragraph("2,4")], + }), + new TableCell({ + children: [new Paragraph("2,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("3,0")], + }), + new TableCell({ + children: [new Paragraph("3,1")], + }), + new TableCell({ + children: [new Paragraph("3,2")], + }), + new TableCell({ + children: [new Paragraph("3,3")], + }), + new TableCell({ + children: [new Paragraph("3,4")], + }), + new TableCell({ + children: [new Paragraph("3,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("4,0")], + }), + new TableCell({ + children: [new Paragraph("4,1")], + }), + new TableCell({ + children: [new Paragraph("4,2")], + }), + new TableCell({ + children: [new Paragraph("4,3")], + }), + new TableCell({ + children: [new Paragraph("4,4")], + }), + new TableCell({ + children: [new Paragraph("4,5")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], +}); doc.addSection({ - children: [table], + children: [table, new Paragraph(""), table2], }); Packer.toBuffer(doc).then((buffer) => { diff --git a/demo/43-images-to-table-cell-2.ts b/demo/43-images-to-table-cell-2.ts index 90d6322bb7..ab950d284c 100644 --- a/demo/43-images-to-table-cell-2.ts +++ b/demo/43-images-to-table-cell-2.ts @@ -1,18 +1,80 @@ // Add image to table cell // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, Table } from "../build"; +import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; const doc = new Document(); const table = new Table({ - rows: 4, - columns: 4, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + rowSpan: 2, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("Hello")], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + new TableCell({ + children: [], + }), + ], + }), + ], }); -table.getCell(2, 2).add(new Paragraph("Hello")); -table.getColumn(3).mergeCells(1, 2); - doc.addSection({ children: [table], }); diff --git a/demo/44-multiple-columns.ts b/demo/44-multiple-columns.ts index 3fdf328461..3985486620 100644 --- a/demo/44-multiple-columns.ts +++ b/demo/44-multiple-columns.ts @@ -8,7 +8,7 @@ const doc = new Document(); doc.addSection({ properties: { column: { - width: 708, + space: 708, count: 2, }, }, @@ -23,7 +23,7 @@ doc.addSection({ doc.addSection({ properties: { column: { - width: 708, + space: 708, count: 3, }, }, diff --git a/docs/usage/tables.md b/docs/usage/tables.md index bf6c16d7ea..ece0f6930d 100644 --- a/docs/usage/tables.md +++ b/docs/usage/tables.md @@ -1,253 +1,334 @@ # Tables -You can create tables with `docx`. More information can be found [here](http://officeopenxml.com/WPtable.php). +!> Paragraphs requires an understanding of [Sections](usage/sections.md). -## Create Table +## Intro -To create a table, simply create one with `new Table()`, then add it to the document: `doc.add()`. +* `Tables` contain a list of `Rows` +* `Rows` contain a list of `TableCells` +* `TableCells` contain a list of `Parahraphs` and/or `Tables`. You can add `Tables` as tables can be nested inside each other -```ts -const table = doc.add(new Table({ - rows: [NUMBER OF ROWS], - columns: [NUMBER OF COLUMNS] -}); -``` - -Alternatively, you can create a table object directly, and then add it in the `document` - -```ts -const table = new Table(4, 4); -doc.add(table); -``` - -The snippet below creates a table of 2 rows and 4 columns. +Create a simple table like so: ```ts const table = new Table({ - rows: 2, - columns: 4, + rows: [Array of `TableRow`s] }); -doc.add(table); ``` -## Rows and Columns - -You can get a row or a column from a table like so, where `index` is a number: - -### Get Row +Then add the table in the `section` ```ts -const row = doc.getRow(index); +doc.addSection({ + children: [table], +}); ``` -With this, you can merge a row by using the `mergeCells()` method, where `startIndex` is the row number you want to merge from, and `endIndex` is where you want it to merge to: +## Table + +### Set Width ```ts -row.mergeCells(startIndex, endIndex); +const table = new Table({ + ..., + width: { + size: [TABLE_WIDTH], + type: WidthType, + } +}); ``` -You can get a cell from a `row` by using the `getCell()` method, where `index` is the row index: - -```ts -row.getCell(index); -``` - -### Get Column - -```ts -const column = doc.getColumn(index); -``` - -Again, you can merge a row by using the `mergeCells()` method, where `startIndex` is the row number you want to merge from, and `endIndex` is where you want it to merge to: - -```ts -column.mergeCells(startIndex, endIndex); -``` - -You can get a cell from a `column` by using the `getCell()` method, where `index` is the column index: - -```ts -column.getCell(index); -``` - -## Cells - -To access the cell, use the `getCell()` method. - -```ts -const cell = table.getCell([ROW INDEX], [COLUMN INDEX]); -``` - -You can also get a cell from a `column` or a `row` with `getCell()`, mentioned previously. - For example: ```ts -const cell = table.getCell(0, 2); -const cell = row.getCell(0); +const table = new Table({ + ..., + width: { + size: 4535, + type: WidthType.DXA, + } +}); +``` -const cell = column.getCell(2); +### Pagination + +#### Prevent row pagination + +To prevent breaking contents of a row across multiple pages, call `cantSplit`: + +```ts +const table = new Table({ + rows: [], + cantSplit: true, +}); +``` + +## Table Row + +A table consists of multiple `table rows`. Table rows have a list of `children` which accepts a list of `table cells` explained below. You can create a simple `table row` like so: + +```ts +const tableRow = new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], +}); +``` + +Or preferably, add the tableRow directly into the `table` without declaring a variable: + +```ts +const table = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], +}); +``` + +### Options + +Here is a list of options you can add to the `table row`: + +| Property | Type | Notes | +| ----------- | ------------------------------------- | -------- | +| children | `Array` | Required | +| cantSplit | `boolean` | Optional | +| tableHeader | `boolean` | Optional | +| height | `{ value: number, rule: HeightRule }` | Optional | + +### Repeat row + +If a table is paginated on multiple pages, it is possible to repeat a row at the top of each new page by setting `tableHeader` to `true`: + +```ts +const row = new TableRow({ + ..., + tableHeader: true, +}); +``` + +## Table Cells + +Cells need to be added in the `table row`, you can create a table cell like: + +```ts +const tableCell = new TableCell({ + children: [new Paragraph("hello")], +}); +``` + +Or preferably, add the tableRow directly into the `table row` without declaring a variable: + +```ts +const tableRow = new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], +}); +``` + +### Options + +| Property | Type | Notes | +| ------------- | ----------------------------------- | ----------------------------------------------------------- | +| children | `Array` | Required. You can nest tables by adding a table into a cell | +| shading | `ITableShadingAttributesProperties` | Optional | +| margins | `ITableCellMarginOptions` | Optional | +| verticalAlign | `VerticalAlign` | Optional | +| columnSpan | `number` | Optional | +| rowSpan | `number` | Optional | +| borders | `BorderOptions` | Optional | +| width | `{ size: number type: WidthType }` | Optional | + +#### Border Options + +| Property | Type | Notes | +| -------- | ----------------------------------------------------- | -------- | +| top | `{ style: BorderStyle, size: number, color: string }` | Optional | +| bottom | `{ style: BorderStyle, size: number, color: string }` | Optional | +| left | `{ style: BorderStyle, size: number, color: string }` | Optional | +| right | `{ style: BorderStyle, size: number, color: string }` | Optional | + +##### Example + +```ts +const cell = new TableCell({ + ..., + borders: { + top: { + style: BorderStyle.DASH_DOT_STROKED, + size: 1, + color: "red", + }, + bottom: { + style: BorderStyle.THICK_THIN_MEDIUM_GAP, + size: 5, + color: "889900", + }, + }, +}); +``` + +##### Google DOCS + +Google DOCS does not support start and end borders, instead they use left and right borders. So to set left and right borders for Google DOCS you should use: + +```ts +const cell = new TableCell({ + ..., + borders: { + top: { + style: BorderStyle.DOT_DOT_DASH, + size: 3, + color: "green", + }, + bottom: { + style: BorderStyle.DOT_DOT_DASH, + size: 3, + color: "ff8000", + }, + }, +}); ``` ### Add paragraph to a cell -Once you have got the cell, you can add data to it with the `add()` method. +Once you have got the cell, you can add data to it: ```ts -cell.add(new Paragraph("Hello")); +const cell = new TableCell({ + children: [new Paragraph("Hello")], +}); ``` ### Set width of a cell You can specify the width of a cell using: -`cell.Properties.setWidth(width, format)` +```ts +const cell = new TableCell({ + ..., + width: { + size: number, + type: WidthType, + }, +}); +``` -format can be: +`WidthType` values can be: -- WidthType.AUTO -- WidthType.DXA: value is in twentieths of a point -- WidthType.NIL: is considered as zero -- WidthType.PCT: percent of table width +| Property | Notes | +| -------- | --------------------------------- | +| AUTO | | +| DXA | value is in twentieths of a point | +| NIL | is considered as zero | +| PCT | percent of table width | -### Example +#### Example ```ts cell.Properties.setWidth(100, WidthType.DXA); ``` -```ts -cell.Properties.setWidth("50%", WidthType.PCT); -``` +### Nested Tables -## Borders - -BorderStyle can be imported from `docx`. Size determines the thickness. HTML color can be a hex code or alias such as `red`. +To have a table within a table, simply add it in the `children` block of a `table cell`: ```ts -cell.Borders.addTopBorder([BorderStyle], [SIZE], [HTML COLOR]); +const cell = new TableCell({ + children: [new Table(...)], +}); ``` -```ts -cell.Borders.addBottomBorder([BorderStyle], [SIZE], [HTML COLOR]); -``` - -```ts -cell.Borders.addStartBorder([[BorderStyle]], [SIZE], [HTML COLOR]); -``` - -```ts -cell.Borders.addEndBorder([BorderStyle], [SIZE], [HTML COLOR]); -``` - -### Example - -```ts -import { BorderStyle } from "docx"; - -cell.Borders.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green"); -cell.Borders.addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000"); -``` - -### Google DOCS - -Google DOCS does not support start and end borders, instead they use left and right borders. So to set left and right borders for Google DOCS you should use: - -```ts -import { BorderStyle } from "docx"; - -cell.Borders.addLeftBorder(BorderStyle.DOT_DOT_DASH, 3, "green"); -cell.Borders.addRightBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000"); -``` - -## Set Width - -```ts -import { WidthType } from "docx"; - -table.setWidth([WIDTH], [OPTIONAL WidthType. Defaults to DXA]); -``` - -For example: - -```ts -table.setWidth(4535, WidthType.DXA); -``` - -## Vertical Align +### Vertical Align Sets the vertical alignment of the contents of the cell ```ts -import { VerticalAlign } from "docx"; - -cell.setVerticalAlign([VerticalAlign TYPE]); +const cell = new TableCell({ + ..., + verticalAlign: VerticalAlign, +}); ``` +`VerticalAlign` values can be: + +| Property | Notes | +| -------- | ------------------------------------------ | +| BOTTOM | Align the contents on the bottom | +| CENTER | Align the contents on the center | +| TOP | Align the contents on the top. The default | + For example, to center align a cell: ```ts -cell.setVerticalAlign(VerticalAlign.CENTER); +const cell = new TableCell({ + verticalAlign: VerticalAlign.CENTER, +}); ``` -## Rows +## Merging cells together -To get a row, use the `getRow` method on a `table`. There are a handful of methods which you can apply to a row which will be explained below. +### Row Merge + +When cell rows are merged, it counts as multiple rows, so be sure to remove excess cells. It is similar to how HTML's `rowspan` works. +https://www.w3schools.com/tags/att_td_rowspan.asp ```ts -table.getRow([ROW INDEX]); -``` - -## Merge cells together - -### Merging on a row - -First obtain the row, and call `mergeCells()`. The first argument is where the merge should start. The second argument is where the merge should end. - -```ts -table.getRow(0).mergeCells([FROM INDEX], [TO INDEX]); +const cell = new TableCell({ + ..., + rowSpan: [NUMBER_OF_CELLS_TO_MERGE], +}); ``` #### Example -This will merge 3 cells together starting from index `0`: +The example will merge three rows together. ```ts -table.getRow(0).mergeCells(0, 2); +const cell = new TableCell({ + ..., + rowSpan: 3, +}); ``` -### Merging on a column +### Column Merge -It has not been implemented yet, but it will follow a similar structure as merging a row. - -## Nested Tables - -To have a table within a table +When cell columns are merged, it counts as multiple columns, so be sure to remove excess cells. It is similar to how HTML's `colspan` works. +https://www.w3schools.com/tags/att_td_colspan.asp ```ts -cell.add(new Table(1, 1)); +const cell = new TableCell({ + ..., + columnSpan: [NUMBER_OF_CELLS_TO_MERGE], +}); ``` -## Pagination +#### Example -###Prevent row pagination -To prevent breaking contents of a row across multiple pages, call `cantSplit()`: +The example will merge three columns together. ```ts -table.getRow(0).setCantSplit(); -``` - -###Repeat row -If a table is paginated on multiple pages, it is possible to repeat a row at the top of each new page calling `setTableHeader()`: - -```ts -table.getRow(0).setTableHeader(); +const cell = new TableCell({ + ..., + columnSpan: 3, +}); ``` ## Examples -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/4-basic-table.ts_ @@ -255,7 +336,7 @@ _Source: https://github.com/dolanmiu/docx/blob/master/demo/4-basic-table.ts_ Example showing how to add colourful borders to tables -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/20-table-cell-borders.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/20-table-cell-borders.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/20-table-cell-borders.ts_ @@ -263,11 +344,11 @@ _Source: https://github.com/dolanmiu/docx/blob/master/demo/20-table-cell-borders Example showing how to add images to tables -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/24-images-to-table-cell.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/24-images-to-table-cell.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/24-images-to-table-cell.ts_ -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/36-image-to-table-cell.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/36-image-to-table-cell.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/36-image-to-table-cell.ts_ @@ -275,32 +356,32 @@ _Source: https://github.com/dolanmiu/docx/blob/master/demo/36-image-to-table-cel Example showing how align text in a table cell -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/31-tables.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/31-tables.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/31-tables.ts_ -### Merging rows +### Shading -Example showing merging of `rows` +Example showing merging of columns and rows and shading -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/32-merge-table-cells.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/32-merge-table-cells.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/32-merge-table-cells.ts_ -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/41-merge-table-cells-2.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/41-merge-table-cells-2.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/41-merge-table-cells-2.ts_ ### Merging columns -Example showing merging of `columns` +Example showing merging of columns and rows -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/43-images-to-table-cell-2.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/43-images-to-table-cell-2.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/43-images-to-table-cell-2.ts_ ### Floating tables -[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/34-floating-tables.ts ':include') +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/34-floating-tables.ts ":include") _Source: https://github.com/dolanmiu/docx/blob/master/demo/34-floating-tables.ts_ diff --git a/src/export/formatter.spec.ts b/src/export/formatter.spec.ts index bd6c9b7160..7ee05fa5d1 100644 --- a/src/export/formatter.spec.ts +++ b/src/export/formatter.spec.ts @@ -1,7 +1,8 @@ import { assert, expect } from "chai"; +import * as sinon from "sinon"; import { Formatter } from "export/formatter"; -import * as file from "file"; +import { Paragraph, TextRun } from "file"; import { CoreProperties } from "file/core-properties"; import { Attributes } from "file/xml-components"; @@ -14,22 +15,22 @@ describe("Formatter", () => { describe("#format()", () => { it("should format simple paragraph", () => { - const paragraph = new file.Paragraph(""); + const paragraph = new Paragraph(""); const newJson = formatter.format(paragraph); assert.isDefined(newJson["w:p"]); }); it("should remove xmlKeys", () => { - const paragraph = new file.Paragraph(""); + const paragraph = new Paragraph(""); const newJson = formatter.format(paragraph); const stringifiedJson = JSON.stringify(newJson); assert(stringifiedJson.indexOf("xmlKeys") < 0); }); it("should format simple paragraph with bold text", () => { - const paragraph = new file.Paragraph(""); + const paragraph = new Paragraph(""); paragraph.addRun( - new file.TextRun({ + new TextRun({ text: "test", bold: true, }), @@ -63,7 +64,7 @@ describe("Formatter", () => { }); it("should should change 'p' tag into 'w:p' tag", () => { - const paragraph = new file.Paragraph(""); + const paragraph = new Paragraph(""); const newJson = formatter.format(paragraph); assert.isDefined(newJson["w:p"]); }); @@ -76,5 +77,13 @@ describe("Formatter", () => { const newJson = formatter.format(properties); assert.isDefined(newJson["cp:coreProperties"]); }); + + it("should call the prep method only once", () => { + const paragraph = new Paragraph(""); + const spy = sinon.spy(paragraph, "prepForXml"); + + formatter.format(paragraph); + expect(spy.calledOnce).to.equal(true); + }); }); }); diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index ac271c02d9..7c48569793 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -1,7 +1,8 @@ /* tslint:disable:typedef space-before-function-paren */ import { expect } from "chai"; +import * as sinon from "sinon"; -import { File, Footer, Header } from "file"; +import { File, Footer, Header, Paragraph } from "file"; import { Compiler } from "./next-compiler"; @@ -72,5 +73,22 @@ describe("Compiler", () => { expect(fileNames).to.include("word/footer2.xml"); expect(fileNames).to.include("word/_rels/footer2.xml.rels"); }); + + it("should call the format method X times equalling X files to be formatted", () => { + // This test is required because before, there was a case where Document was formatted twice, which was inefficient + // This also caused issues such as running prepForXml multiple times as format() was ran multiple times. + const paragraph = new Paragraph(""); + const doc = new File(); + + doc.addSection({ + properties: {}, + children: [paragraph], + }); + // tslint:disable-next-line: no-string-literal + const spy = sinon.spy(compiler["formatter"], "format"); + + compiler.compile(file); + expect(spy.callCount).to.equal(10); + }); }); }); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 426d32174f..6a3e752098 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -68,13 +68,13 @@ export class Compiler { file.verifyUpdateFields(); const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1; + const documentXmlData = xml(this.formatter.format(file.Document), prettify); + const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media); + return { Relationships: { data: (() => { - const xmlData = xml(this.formatter.format(file.Document), prettify); - const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); - - mediaDatas.forEach((mediaData, i) => { + documentMediaDatas.forEach((mediaData, i) => { file.DocumentRelationships.createRelationship( documentRelationshipCount + i, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", @@ -88,9 +88,7 @@ export class Compiler { }, Document: { data: (() => { - const tempXmlData = xml(this.formatter.format(file.Document), prettify); - const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); - const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, documentRelationshipCount); + const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount); return xmlData; })(), diff --git a/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts b/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts new file mode 100644 index 0000000000..50570b4b77 --- /dev/null +++ b/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts @@ -0,0 +1,42 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { FooterReference } from "./footer-reference"; +import { FooterReferenceType } from "./footer-reference-attributes"; + +describe("footerReference", () => { + it("should create", () => { + const footer = new FooterReference({ + footerType: FooterReferenceType.DEFAULT, + footerId: 1, + }); + + const tree = new Formatter().format(footer); + + expect(tree).to.deep.equal({ + "w:footerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); + + it("should create without a footer type", () => { + const footer = new FooterReference({ + footerId: 1, + }); + + const tree = new Formatter().format(footer); + + expect(tree).to.deep.equal({ + "w:footerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); +}); diff --git a/src/file/document/body/section-properties/header-reference/header-reference.spec.ts b/src/file/document/body/section-properties/header-reference/header-reference.spec.ts new file mode 100644 index 0000000000..2d6b39e553 --- /dev/null +++ b/src/file/document/body/section-properties/header-reference/header-reference.spec.ts @@ -0,0 +1,42 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { HeaderReference } from "./header-reference"; +import { HeaderReferenceType } from "./header-reference-attributes"; + +describe("HeaderReference", () => { + it("should create", () => { + const footer = new HeaderReference({ + headerType: HeaderReferenceType.DEFAULT, + headerId: 1, + }); + + const tree = new Formatter().format(footer); + + expect(tree).to.deep.equal({ + "w:headerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); + + it("should create without a header type", () => { + const footer = new HeaderReference({ + headerId: 1, + }); + + const tree = new Formatter().format(footer); + + expect(tree).to.deep.equal({ + "w:headerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); +}); diff --git a/src/file/document/body/section-properties/section-properties.spec.ts b/src/file/document/body/section-properties/section-properties.spec.ts index 31828ff5f4..abdbf9e1ed 100644 --- a/src/file/document/body/section-properties/section-properties.spec.ts +++ b/src/file/document/body/section-properties/section-properties.spec.ts @@ -38,6 +38,7 @@ describe("SectionProperties", () => { }, pageNumberStart: 10, pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT, + titlePage: true, }); const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index e8c081d1ec..59b42e009c 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -6,7 +6,7 @@ import { Formatter } from "export/formatter"; import { File } from "./file"; import { Footer, Header } from "./header"; import { Paragraph } from "./paragraph"; -import { Table } from "./table"; +import { Table, TableCell, TableRow } from "./table"; import { TableOfContents } from "./table-of-contents"; describe("File", () => { @@ -108,8 +108,15 @@ describe("File", () => { file.addSection({ children: [ new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], }), ], }); diff --git a/src/file/footer-wrapper.spec.ts b/src/file/footer-wrapper.spec.ts index 3f6fb01b82..4f3e95acdb 100644 --- a/src/file/footer-wrapper.spec.ts +++ b/src/file/footer-wrapper.spec.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import { FooterWrapper } from "./footer-wrapper"; import { Media } from "./media"; import { Paragraph } from "./paragraph"; -import { Table } from "./table"; +import { Table, TableCell, TableRow } from "./table"; describe("FooterWrapper", () => { describe("#add", () => { @@ -21,22 +21,20 @@ describe("FooterWrapper", () => { const spy = sinon.spy(file.Footer, "add"); file.add( new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], }), ); expect(spy.called).to.equal(true); }); - - it("should call the underlying footer's addImage", () => { - const file = new FooterWrapper(new Media(), 1); - const spy = sinon.spy(file.Footer, "add"); - // tslint:disable-next-line:no-any - file.addImage({} as any); - - expect(spy.called).to.equal(true); - }); }); describe("#addChildElement", () => { diff --git a/src/file/footer-wrapper.ts b/src/file/footer-wrapper.ts index 01087e9861..8390887ba4 100644 --- a/src/file/footer-wrapper.ts +++ b/src/file/footer-wrapper.ts @@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components"; import { FooterReferenceType } from "./document"; import { Footer } from "./footer/footer"; -import { Image, Media } from "./media"; +import { Media } from "./media"; import { Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Table } from "./table"; @@ -25,11 +25,6 @@ export class FooterWrapper { this.footer.add(item); } - public addImage(image: Image): FooterWrapper { - this.footer.add(image.Paragraph); - return this; - } - public addChildElement(childElement: XmlComponent): void { this.footer.addChildElement(childElement); } diff --git a/src/file/footer/footer.spec.ts b/src/file/footer/footer.spec.ts new file mode 100644 index 0000000000..4f970a119a --- /dev/null +++ b/src/file/footer/footer.spec.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { Paragraph } from "../paragraph"; +import { Footer } from "./footer"; + +describe("Footer", () => { + it("should create", () => { + const footer = new Footer(1); + + const tree = new Formatter().format(footer); + + expect(tree).to.deep.equal({ + "w:ftr": { + _attr: { + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + }, + }, + }); + }); + + it("should create with initContent", () => { + const header = new Footer(1, new Paragraph({})); + + const tree = new Formatter().format(header); + + expect(tree).to.deep.equal({ + "w:ftr": {}, + }); + }); +}); diff --git a/src/file/header-wrapper.spec.ts b/src/file/header-wrapper.spec.ts index d07473f03a..00ee776a95 100644 --- a/src/file/header-wrapper.spec.ts +++ b/src/file/header-wrapper.spec.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import { HeaderWrapper } from "./header-wrapper"; import { Media } from "./media"; import { Paragraph } from "./paragraph"; -import { Table } from "./table"; +import { Table, TableCell, TableRow } from "./table"; describe("HeaderWrapper", () => { describe("#add", () => { @@ -21,8 +21,15 @@ describe("HeaderWrapper", () => { const spy = sinon.spy(wrapper.Header, "add"); wrapper.add( new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], }), ); @@ -30,17 +37,6 @@ describe("HeaderWrapper", () => { }); }); - describe("#addImage", () => { - it("should call the underlying header's addImage", () => { - const file = new HeaderWrapper(new Media(), 1); - const spy = sinon.spy(file.Header, "add"); - // tslint:disable-next-line:no-any - file.addImage({} as any); - - expect(spy.called).to.equal(true); - }); - }); - describe("#addChildElement", () => { it("should call the underlying header's addChildElement", () => { const file = new HeaderWrapper(new Media(), 1); diff --git a/src/file/header-wrapper.ts b/src/file/header-wrapper.ts index d698505e4a..407399a8fe 100644 --- a/src/file/header-wrapper.ts +++ b/src/file/header-wrapper.ts @@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components"; import { HeaderReferenceType } from "./document"; import { Header } from "./header/header"; -import { Image, Media } from "./media"; +import { Media } from "./media"; import { Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Table } from "./table"; @@ -27,11 +27,6 @@ export class HeaderWrapper { return this; } - public addImage(image: Image): HeaderWrapper { - this.header.add(image.Paragraph); - return this; - } - public addChildElement(childElement: XmlComponent | string): void { this.header.addChildElement(childElement); } diff --git a/src/file/header/header.spec.ts b/src/file/header/header.spec.ts new file mode 100644 index 0000000000..651c21b545 --- /dev/null +++ b/src/file/header/header.spec.ts @@ -0,0 +1,58 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { Paragraph } from "../paragraph"; +import { Header } from "./header"; + +describe("Header", () => { + it("should create", () => { + const header = new Header(1); + + const tree = new Formatter().format(header); + + expect(tree).to.deep.equal({ + "w:hdr": { + _attr: { + "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", + "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + }, + }, + }); + }); + + it("should create with initContent", () => { + const header = new Header(1, new Paragraph({})); + + const tree = new Formatter().format(header); + + expect(tree).to.deep.equal({ + "w:hdr": {}, + }); + }); +}); diff --git a/src/file/media/image.ts b/src/file/media/image.ts deleted file mode 100644 index 8255fe7398..0000000000 --- a/src/file/media/image.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ImageParagraph, PictureRun } from "../paragraph"; - -export class Image { - constructor(private readonly paragraph: ImageParagraph) {} - - public get Paragraph(): ImageParagraph { - return this.paragraph; - } - - public get Run(): PictureRun { - return this.paragraph.Run; - } -} diff --git a/src/file/media/index.ts b/src/file/media/index.ts index 2ccc436e68..3575274e26 100644 --- a/src/file/media/index.ts +++ b/src/file/media/index.ts @@ -1,3 +1,2 @@ export * from "./media"; export * from "./data"; -export * from "./image"; diff --git a/src/file/media/media.spec.ts b/src/file/media/media.spec.ts index b7ce0c106c..e566213ee5 100644 --- a/src/file/media/media.spec.ts +++ b/src/file/media/media.spec.ts @@ -80,6 +80,16 @@ describe("Media", () => { const image = new Media().addMedia(""); expect(image.stream).to.be.an.instanceof(Uint8Array); }); + + it("should use data as is if its not a string", () => { + // tslint:disable-next-line + ((process as any).atob as any) = () => "atob result"; + // tslint:disable-next-line:no-any + (Media as any).generateId = () => "test"; + + const image = new Media().addMedia(new Buffer("")); + expect(image.stream).to.be.an.instanceof(Uint8Array); + }); }); describe("#getMedia", () => { diff --git a/src/file/paragraph/formatting/border.spec.ts b/src/file/paragraph/formatting/border.spec.ts index f07a297479..6e89ffcfc8 100644 --- a/src/file/paragraph/formatting/border.spec.ts +++ b/src/file/paragraph/formatting/border.spec.ts @@ -1,11 +1,87 @@ -import { assert, expect } from "chai"; +import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { ThematicBreak } from "./border"; +import { Border, ThematicBreak } from "./border"; describe("Border", () => { - // TODO: Need tests here + describe("#constructor", () => { + it("should create", () => { + const border = new Border({ + top: { + color: "red", + space: 1, + value: "test", + size: 2, + }, + bottom: { + color: "red", + space: 3, + value: "test", + size: 4, + }, + left: { + color: "red", + space: 5, + value: "test", + size: 6, + }, + right: { + color: "red", + space: 7, + value: "test", + size: 8, + }, + }); + + const tree = new Formatter().format(border); + + expect(tree).to.deep.equal({ + "w:pBdr": [ + { + "w:top": { + _attr: { + "w:color": "red", + "w:space": 1, + "w:sz": 2, + "w:val": "test", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "red", + "w:space": 3, + "w:sz": 4, + "w:val": "test", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "red", + "w:space": 5, + "w:sz": 6, + "w:val": "test", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "red", + "w:space": 7, + "w:sz": 8, + "w:val": "test", + }, + }, + }, + ], + }); + }); + }); }); describe("ThematicBreak", () => { @@ -16,17 +92,6 @@ describe("ThematicBreak", () => { }); describe("#constructor()", () => { - it("should create valid JSON", () => { - const stringifiedJson = JSON.stringify(thematicBreak); - - try { - JSON.parse(stringifiedJson); - } catch (e) { - assert.isTrue(false); - } - assert.isTrue(true); - }); - it("should create a Thematic Break with correct border properties", () => { const tree = new Formatter().format(thematicBreak); expect(tree).to.deep.equal({ diff --git a/src/file/paragraph/image.spec.ts b/src/file/paragraph/image.spec.ts deleted file mode 100644 index 715c1c93ab..0000000000 --- a/src/file/paragraph/image.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// tslint:disable:object-literal-key-quotes -import { assert } from "chai"; - -import { ImageParagraph } from "./image"; - -describe("Image", () => { - let image: ImageParagraph; - - beforeEach(() => { - image = new ImageParagraph({ - stream: new Buffer(""), - path: "", - fileName: "test.png", - dimensions: { - pixels: { - x: 10, - y: 10, - }, - emus: { - x: 10, - y: 10, - }, - }, - }); - }); - - describe("#constructor()", () => { - it("should create valid JSON", () => { - const stringifiedJson = JSON.stringify(image); - - try { - JSON.parse(stringifiedJson); - } catch (e) { - assert.isTrue(false); - } - assert.isTrue(true); - }); - }); -}); diff --git a/src/file/paragraph/image.ts b/src/file/paragraph/image.ts deleted file mode 100644 index 4fa3d97b9f..0000000000 --- a/src/file/paragraph/image.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IDrawingOptions } from "../drawing"; -import { IMediaData } from "../media"; -import { Paragraph } from "./paragraph"; -import { PictureRun } from "./run"; - -export class ImageParagraph extends Paragraph { - private readonly pictureRun: PictureRun; - - constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) { - super({}); - this.pictureRun = new PictureRun(imageData, drawingOptions); - this.root.push(this.pictureRun); - } - - public get Run(): PictureRun { - return this.pictureRun; - } -} diff --git a/src/file/paragraph/index.ts b/src/file/paragraph/index.ts index 222cb1bf4f..68a1b0b800 100644 --- a/src/file/paragraph/index.ts +++ b/src/file/paragraph/index.ts @@ -3,4 +3,3 @@ export * from "./paragraph"; export * from "./properties"; export * from "./run"; export * from "./links"; -export * from "./image"; diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 3f39ba68bc..24c3e1717d 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,6 +1,5 @@ // http://officeopenxml.com/WPparagraph.php import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; -import { Image } from "file/media"; import { Num } from "file/numbering/num"; import { XmlComponent } from "file/xml-components"; @@ -190,13 +189,6 @@ export class Paragraph extends XmlComponent { return this; } - public addImage(image: Image): PictureRun { - const run = image.Run; - this.addRun(run); - - return run; - } - public pageBreak(): Paragraph { this.root.push(new PageBreak()); return this; diff --git a/src/file/table/grid.ts b/src/file/table/grid.ts index b91f6ac9d4..5ce6486fcc 100644 --- a/src/file/table/grid.ts +++ b/src/file/table/grid.ts @@ -2,9 +2,11 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class TableGrid extends XmlComponent { - constructor(cols: number[]) { + constructor(widths: number[]) { super("w:tblGrid"); - cols.forEach((col) => this.root.push(new GridCol(col))); + for (const width of widths) { + this.root.push(new GridCol(width)); + } } } diff --git a/src/file/table/table-cell/table-cell-components.ts b/src/file/table/table-cell/table-cell-components.ts index 518b8a67b2..e16c2f6885 100644 --- a/src/file/table/table-cell/table-cell-components.ts +++ b/src/file/table/table-cell/table-cell-components.ts @@ -103,7 +103,7 @@ export class GridSpan extends XmlComponent { /** * Vertical merge types. */ -export enum VMergeType { +export enum VerticalMergeType { /** * Cell that is merged with upper one. */ @@ -114,19 +114,19 @@ export enum VMergeType { RESTART = "restart", } -class VMergeAttributes extends XmlAttributeComponent<{ readonly val: VMergeType }> { +class VerticalMergeAttributes extends XmlAttributeComponent<{ readonly val: VerticalMergeType }> { protected readonly xmlKeys = { val: "w:val" }; } /** * Vertical merge element. Should be used in a table cell. */ -export class VMerge extends XmlComponent { - constructor(value: VMergeType) { +export class VerticalMerge extends XmlComponent { + constructor(value: VerticalMergeType) { super("w:vMerge"); this.root.push( - new VMergeAttributes({ + new VerticalMergeAttributes({ val: value, }), ); diff --git a/src/file/table/table-cell/table-cell-properties.spec.ts b/src/file/table/table-cell/table-cell-properties.spec.ts index 65bfd92fe6..c3452e757c 100644 --- a/src/file/table/table-cell/table-cell-properties.spec.ts +++ b/src/file/table/table-cell/table-cell-properties.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { BorderStyle } from "file/styles"; -import { VerticalAlign, VMergeType, WidthType } from "./table-cell-components"; +import { VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components"; import { TableCellProperties } from "./table-cell-properties"; describe("TableCellProperties", () => { @@ -30,7 +30,7 @@ describe("TableCellProperties", () => { describe("#addVerticalMerge", () => { it("adds vertical merge", () => { const properties = new TableCellProperties(); - properties.addVerticalMerge(VMergeType.CONTINUE); + properties.addVerticalMerge(VerticalMergeType.CONTINUE); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }); }); @@ -73,6 +73,54 @@ describe("TableCellProperties", () => { }); }); + describe("#addMargins", () => { + it("sets shading", () => { + const properties = new TableCellProperties(); + properties.addMargins({}); + const tree = new Formatter().format(properties); + expect(tree).to.deep.equal({ + "w:tcPr": [ + { + "w:tcMar": [ + { + "w:top": { + _attr: { + "w:type": "dxa", + "w:w": 0, + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:type": "dxa", + "w:w": 0, + }, + }, + }, + { + "w:end": { + _attr: { + "w:type": "dxa", + "w:w": 0, + }, + }, + }, + { + "w:start": { + _attr: { + "w:type": "dxa", + "w:w": 0, + }, + }, + }, + ], + }, + ], + }); + }); + }); + describe("#Borders", () => { it("should return the TableCellBorders if Border has borders", () => { const properties = new TableCellProperties(); diff --git a/src/file/table/table-cell/table-cell-properties.ts b/src/file/table/table-cell/table-cell-properties.ts index 5a2544b9cd..aed56aaeda 100644 --- a/src/file/table/table-cell/table-cell-properties.ts +++ b/src/file/table/table-cell/table-cell-properties.ts @@ -2,7 +2,16 @@ import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; import { ITableShadingAttributesProperties, TableShading } from "../shading"; import { ITableCellMarginOptions, TableCellMargin } from "./cell-margin/table-cell-margins"; -import { GridSpan, TableCellBorders, TableCellWidth, VAlign, VerticalAlign, VMerge, VMergeType, WidthType } from "./table-cell-components"; +import { + GridSpan, + TableCellBorders, + TableCellWidth, + VAlign, + VerticalAlign, + VerticalMerge, + VerticalMergeType, + WidthType, +} from "./table-cell-components"; export class TableCellProperties extends IgnoreIfEmptyXmlComponent { private readonly cellBorder: TableCellBorders; @@ -23,8 +32,8 @@ export class TableCellProperties extends IgnoreIfEmptyXmlComponent { return this; } - public addVerticalMerge(type: VMergeType): TableCellProperties { - this.root.push(new VMerge(type)); + public addVerticalMerge(type: VerticalMergeType): TableCellProperties { + this.root.push(new VerticalMerge(type)); return this; } diff --git a/src/file/table/table-cell/table-cell.spec.ts b/src/file/table/table-cell/table-cell.spec.ts index 8ac54ed85a..4dd86ffc9c 100644 --- a/src/file/table/table-cell/table-cell.spec.ts +++ b/src/file/table/table-cell/table-cell.spec.ts @@ -3,7 +3,9 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { BorderStyle } from "file/styles"; -import { TableCellBorders, TableCellWidth, WidthType } from "./table-cell-components"; +import { ShadingType } from "../shading"; +import { TableCell } from "./table-cell"; +import { TableCellBorders, TableCellWidth, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components"; describe("TableCellBorders", () => { describe("#prepForXml", () => { @@ -222,3 +224,332 @@ describe("TableCellWidth", () => { }); }); }); + +describe("TableCell", () => { + describe("#constructor", () => { + it("should create", () => { + const cell = new TableCell({ + children: [], + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with vertical align", () => { + const cell = new TableCell({ + children: [], + verticalAlign: VerticalAlign.CENTER, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:vAlign": { + _attr: { + "w:val": "center", + }, + }, + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with vertical merge", () => { + const cell = new TableCell({ + children: [], + verticalMerge: VerticalMergeType.RESTART, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:vMerge": { + _attr: { + "w:val": "restart", + }, + }, + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with margins", () => { + const cell = new TableCell({ + children: [], + margins: { + top: 1, + left: 1, + bottom: 1, + right: 1, + }, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:tcMar": [ + { + "w:top": { + _attr: { + "w:type": "dxa", + "w:w": 1, + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:type": "dxa", + "w:w": 1, + }, + }, + }, + { + "w:end": { + _attr: { + "w:type": "dxa", + "w:w": 1, + }, + }, + }, + { + "w:start": { + _attr: { + "w:type": "dxa", + "w:w": 1, + }, + }, + }, + ], + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with shading", () => { + const cell = new TableCell({ + children: [], + shading: { + fill: "red", + color: "blue", + val: ShadingType.PERCENT_10, + }, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:shd": { + _attr: { + "w:color": "blue", + "w:fill": "red", + "w:val": "pct10", + }, + }, + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with column span", () => { + const cell = new TableCell({ + children: [], + columnSpan: 2, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:gridSpan": { + _attr: { + "w:val": 2, + }, + }, + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + describe("rowSpan", () => { + it("should not create with row span if its less than 1", () => { + const cell = new TableCell({ + children: [], + rowSpan: 0, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with row span if its greater than 1", () => { + const cell = new TableCell({ + children: [], + rowSpan: 2, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:vMerge": { + _attr: { + "w:val": "restart", + }, + }, + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + + it("should create with borders", () => { + const cell = new TableCell({ + children: [], + borders: { + top: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "red", + }, + bottom: { + style: BorderStyle.DOUBLE, + size: 3, + color: "blue", + }, + left: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "green", + }, + right: { + style: BorderStyle.DASH_DOT_STROKED, + size: 3, + color: "#ff8000", + }, + }, + }); + + const tree = new Formatter().format(cell); + + expect(tree).to.deep.equal({ + "w:tc": [ + { + "w:tcPr": [ + { + "w:tcBorders": [ + { + "w:top": { + _attr: { + "w:color": "red", + "w:sz": 3, + "w:val": "dashDotStroked", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "blue", + "w:sz": 3, + "w:val": "double", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "green", + "w:sz": 3, + "w:val": "dashDotStroked", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "#ff8000", + "w:sz": 3, + "w:val": "dashDotStroked", + }, + }, + }, + ], + }, + ], + }, + { + "w:p": {}, + }, + ], + }); + }); + }); + }); +}); diff --git a/src/file/table/table-cell/table-cell.ts b/src/file/table/table-cell/table-cell.ts index 1c546ab934..5c34d17903 100644 --- a/src/file/table/table-cell/table-cell.ts +++ b/src/file/table/table-cell/table-cell.ts @@ -1,77 +1,112 @@ // http://officeopenxml.com/WPtableGrid.php import { Paragraph } from "file/paragraph"; +import { BorderStyle } from "file/styles"; import { IXmlableObject, XmlComponent } from "file/xml-components"; import { ITableShadingAttributesProperties } from "../shading"; import { Table } from "../table"; import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins"; -import { TableCellBorders, VerticalAlign, VMergeType } from "./table-cell-components"; +import { VerticalAlign, VerticalMergeType } from "./table-cell-components"; import { TableCellProperties } from "./table-cell-properties"; export interface ITableCellOptions { readonly shading?: ITableShadingAttributesProperties; + readonly margins?: ITableCellMarginOptions; + readonly verticalAlign?: VerticalAlign; + readonly verticalMerge?: VerticalMergeType; + readonly columnSpan?: number; + readonly rowSpan?: number; + readonly borders?: { + readonly top?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly bottom?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly left?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly right?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + }; + readonly children: Array; } export class TableCell extends XmlComponent { private readonly properties: TableCellProperties; - constructor() { + constructor(readonly options: ITableCellOptions) { super("w:tc"); this.properties = new TableCellProperties(); this.root.push(this.properties); - } - public add(item: Paragraph | Table): TableCell { - this.root.push(item); + for (const child of options.children) { + this.root.push(child); + } - return this; + if (options.verticalAlign) { + this.properties.setVerticalAlign(options.verticalAlign); + } + + if (options.verticalMerge) { + this.properties.addVerticalMerge(options.verticalMerge); + } + + if (options.margins) { + this.properties.addMargins(options.margins); + } + + if (options.shading) { + this.properties.setShading(options.shading); + } + + if (options.columnSpan) { + this.properties.addGridSpan(options.columnSpan); + } + + if (options.rowSpan && options.rowSpan > 1) { + this.properties.addVerticalMerge(VerticalMergeType.RESTART); + } + + if (options.borders) { + if (options.borders.top) { + this.properties.Borders.addTopBorder(options.borders.top.style, options.borders.top.size, options.borders.top.color); + } + if (options.borders.bottom) { + this.properties.Borders.addBottomBorder( + options.borders.bottom.style, + options.borders.bottom.size, + options.borders.bottom.color, + ); + } + if (options.borders.left) { + this.properties.Borders.addLeftBorder(options.borders.left.style, options.borders.left.size, options.borders.left.color); + } + if (options.borders.right) { + this.properties.Borders.addRightBorder( + options.borders.right.style, + options.borders.right.size, + options.borders.right.color, + ); + } + } } public prepForXml(): IXmlableObject | undefined { // Cells must end with a paragraph if (!(this.root[this.root.length - 1] instanceof Paragraph)) { - const para = new Paragraph({}); - this.add(para); + this.root.push(new Paragraph({})); } return super.prepForXml(); } - - public setVerticalAlign(type: VerticalAlign): TableCell { - this.properties.setVerticalAlign(type); - - return this; - } - - public addGridSpan(cellSpan: number): TableCell { - this.properties.addGridSpan(cellSpan); - - return this; - } - - public addVerticalMerge(type: VMergeType): TableCell { - this.properties.addVerticalMerge(type); - - return this; - } - - public setMargins(margins: ITableCellMarginOptions): TableCell { - this.properties.addMargins(margins); - - return this; - } - - public setShading(attrs: ITableShadingAttributesProperties): TableCell { - this.properties.setShading(attrs); - - return this; - } - - public get Borders(): TableCellBorders { - return this.properties.Borders; - } - - public get Properties(): TableCellProperties { - return this.properties; - } } diff --git a/src/file/table/table-column.spec.ts b/src/file/table/table-column.spec.ts deleted file mode 100644 index aa031423a3..0000000000 --- a/src/file/table/table-column.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { TableCell } from "./table-cell"; -import { TableColumn } from "./table-column"; - -import { EMPTY_OBJECT } from "file/xml-components"; - -describe("TableColumn", () => { - let cells: TableCell[]; - beforeEach(() => { - cells = [new TableCell(), new TableCell(), new TableCell()]; - }); - - describe("#getCell", () => { - it("should get the correct cell", () => { - const tableColumn = new TableColumn(cells); - const cell = tableColumn.getCell(0); - - expect(cell).to.deep.equal(cells[0]); - - const cell2 = tableColumn.getCell(1); - - expect(cell2).to.deep.equal(cells[1]); - }); - - it("should throw an error if index is out of bounds", () => { - const tableColumn = new TableColumn(cells); - - expect(() => tableColumn.getCell(9)).to.throw(); - }); - }); - - describe("#mergeCells", () => { - it("should add vMerge to correct cells", () => { - const tableColumn = new TableColumn(cells); - tableColumn.mergeCells(0, 2); - - const tree = new Formatter().format(cells[0]); - expect(tree).to.deep.equal({ - "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "restart" } } }] }, { "w:p": EMPTY_OBJECT }], - }); - - const tree2 = new Formatter().format(cells[1]); - expect(tree2).to.deep.equal({ - "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": EMPTY_OBJECT }], - }); - - const tree3 = new Formatter().format(cells[2]); - expect(tree3).to.deep.equal({ - "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": EMPTY_OBJECT }], - }); - }); - }); -}); diff --git a/src/file/table/table-column.ts b/src/file/table/table-column.ts deleted file mode 100644 index 5752bde472..0000000000 --- a/src/file/table/table-column.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TableCell, VMergeType } from "./table-cell"; - -export class TableColumn { - constructor(private readonly cells: TableCell[]) {} - - public getCell(index: number): TableCell { - const cell = this.cells[index]; - - if (!cell) { - throw Error("Index out of bounds when trying to get cell on column"); - } - - return cell; - } - - public mergeCells(startIndex: number, endIndex: number): TableCell { - this.cells[startIndex].addVerticalMerge(VMergeType.RESTART); - - for (let i = startIndex + 1; i <= endIndex; i++) { - this.cells[i].addVerticalMerge(VMergeType.CONTINUE); - } - - return this.cells[startIndex]; - } -} diff --git a/src/file/table/table-properties/table-cell-margin.spec.ts b/src/file/table/table-properties/table-cell-margin.spec.ts index 7f0d13d218..2a89db7a4a 100644 --- a/src/file/table/table-properties/table-cell-margin.spec.ts +++ b/src/file/table/table-properties/table-cell-margin.spec.ts @@ -14,38 +14,66 @@ describe("TableCellMargin", () => { }); describe("#addTopMargin", () => { - it("adds a table cell top margin", () => { + it("should add a table cell top margin", () => { const cellMargin = new TableCellMargin(); cellMargin.addTopMargin(1234, WidthType.DXA); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); }); + + it("should add a table cell top margin using default width type", () => { + const cellMargin = new TableCellMargin(); + cellMargin.addTopMargin(1234); + const tree = new Formatter().format(cellMargin); + expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); + }); }); describe("#addLeftMargin", () => { - it("adds a table cell left margin", () => { + it("should add a table cell left margin", () => { const cellMargin = new TableCellMargin(); cellMargin.addLeftMargin(1234, WidthType.DXA); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); }); + + it("should add a table cell left margin using default width type", () => { + const cellMargin = new TableCellMargin(); + cellMargin.addLeftMargin(1234); + const tree = new Formatter().format(cellMargin); + expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); + }); }); describe("#addBottomMargin", () => { - it("adds a table cell bottom margin", () => { + it("should add a table cell bottom margin", () => { const cellMargin = new TableCellMargin(); cellMargin.addBottomMargin(1234, WidthType.DXA); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); }); + + it("should add a table cell bottom margin using default width type", () => { + const cellMargin = new TableCellMargin(); + cellMargin.addBottomMargin(1234); + const tree = new Formatter().format(cellMargin); + expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); + }); }); describe("#addRightMargin", () => { - it("adds a table cell right margin", () => { + it("should add a table cell right margin", () => { const cellMargin = new TableCellMargin(); cellMargin.addRightMargin(1234, WidthType.DXA); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); }); + + it("should add a table cell right margin using default width type", () => { + const cellMargin = new TableCellMargin(); + cellMargin.addRightMargin(1234); + const tree = new Formatter().format(cellMargin); + expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); + }); }); }); diff --git a/src/file/table/table-properties/table-properties.spec.ts b/src/file/table/table-properties/table-properties.spec.ts index dbd1c6b371..960d117b7c 100644 --- a/src/file/table/table-properties/table-properties.spec.ts +++ b/src/file/table/table-properties/table-properties.spec.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { ShadingType } from "../shading"; import { WidthType } from "../table-cell"; import { TableLayoutType } from "./table-layout"; import { TableProperties } from "./table-properties"; @@ -66,4 +67,29 @@ describe("TableProperties", () => { }); }); }); + + describe("#setShading", () => { + it("sets the shading of the table", () => { + const tp = new TableProperties(); + tp.setShading({ + fill: "b79c2f", + val: ShadingType.REVERSE_DIAGONAL_STRIPE, + color: "auto", + }); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [ + { + "w:shd": { + _attr: { + "w:color": "auto", + "w:fill": "b79c2f", + "w:val": "reverseDiagStripe", + }, + }, + }, + ], + }); + }); + }); }); diff --git a/src/file/table/table-row/table-row.spec.ts b/src/file/table/table-row/table-row.spec.ts index 3e9a479949..e013153cd8 100644 --- a/src/file/table/table-row/table-row.spec.ts +++ b/src/file/table/table-row/table-row.spec.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { Paragraph } from "file/paragraph"; import { HeightRule } from "file/table/table-row/table-row-height"; import { EMPTY_OBJECT } from "file/xml-components"; import { TableCell } from "../table-cell"; @@ -10,7 +11,9 @@ import { TableRow } from "./table-row"; describe("TableRow", () => { describe("#constructor", () => { it("should create with no cells", () => { - const tableRow = new TableRow([]); + const tableRow = new TableRow({ + children: [], + }); const tree = new Formatter().format(tableRow); expect(tree).to.deep.equal({ "w:tr": EMPTY_OBJECT, @@ -18,7 +21,13 @@ describe("TableRow", () => { }); it("should create with one cell", () => { - const tableRow = new TableRow([new TableCell()]); + const tableRow = new TableRow({ + children: [ + new TableCell({ + children: [], + }), + ], + }); const tree = new Formatter().format(tableRow); expect(tree).to.deep.equal({ "w:tr": [ @@ -32,46 +41,61 @@ describe("TableRow", () => { ], }); }); - }); - describe("#getCell", () => { - it("should get the cell", () => { - const cell = new TableCell(); - const tableRow = new TableRow([cell]); - - expect(tableRow.getCell(0)).to.equal(cell); + it("should create with cant split", () => { + const tableRow = new TableRow({ + children: [], + cantSplit: true, + }); + const tree = new Formatter().format(tableRow); + expect(tree).to.deep.equal({ + "w:tr": [ + { + "w:trPr": [ + { + "w:cantSplit": { + _attr: { + "w:val": true, + }, + }, + }, + ], + }, + ], + }); }); - it("should throw an error if index is out of bounds", () => { - const cell = new TableCell(); - const tableRow = new TableRow([cell]); - - expect(() => tableRow.getCell(1)).to.throw(); + it("should create with table header", () => { + const tableRow = new TableRow({ + children: [], + tableHeader: true, + }); + const tree = new Formatter().format(tableRow); + expect(tree).to.deep.equal({ + "w:tr": [ + { + "w:trPr": [ + { + "w:tblHeader": { + _attr: { + "w:val": true, + }, + }, + }, + ], + }, + ], + }); }); - }); - describe("#addGridSpan", () => { - it("should merge the cell", () => { - const tableRow = new TableRow([new TableCell(), new TableCell()]); - - tableRow.addGridSpan(0, 2); - expect(() => tableRow.getCell(1)).to.throw(); - }); - }); - - describe("#mergeCells", () => { - it("should merge the cell", () => { - const tableRow = new TableRow([new TableCell(), new TableCell()]); - - tableRow.mergeCells(0, 1); - expect(() => tableRow.getCell(1)).to.throw(); - }); - }); - - describe("#setHeight", () => { it("should set row height", () => { - const tableRow = new TableRow([]); - tableRow.setHeight(100, HeightRule.EXACT); + const tableRow = new TableRow({ + children: [], + height: { + height: 100, + rule: HeightRule.EXACT, + }, + }); const tree = new Formatter().format(tableRow); expect(tree).to.deep.equal({ "w:tr": [ @@ -91,4 +115,71 @@ describe("TableRow", () => { }); }); }); + + describe("#addCellToIndex", () => { + it("should add cell to correct index with no initial properties", () => { + const tableRow = new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("test")], + }), + ], + tableHeader: true, + }); + + tableRow.addCellToIndex( + new TableCell({ + children: [], + }), + 0, + ); + + const tree = new Formatter().format(tableRow); + + expect(tree).to.deep.equal({ + "w:tr": [ + { + "w:trPr": [ + { + "w:tblHeader": { + _attr: { + "w:val": true, + }, + }, + }, + ], + }, + { + "w:tc": [ + { + "w:p": {}, + }, + ], + }, + { + "w:tc": [ + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test", + ], + }, + ], + }, + ], + }, + ], + }, + ], + }); + }); + }); }); diff --git a/src/file/table/table-row/table-row.ts b/src/file/table/table-row/table-row.ts index f811392348..466b7eb320 100644 --- a/src/file/table/table-row/table-row.ts +++ b/src/file/table/table-row/table-row.ts @@ -3,56 +3,51 @@ import { XmlComponent } from "file/xml-components"; import { TableCell } from "../table-cell"; import { TableRowProperties } from "./table-row-properties"; +export interface ITableRowOptions { + readonly cantSplit?: boolean; + readonly tableHeader?: boolean; + readonly height?: { + readonly height: number; + readonly rule: HeightRule; + }; + readonly children: TableCell[]; +} + export class TableRow extends XmlComponent { private readonly properties: TableRowProperties; - constructor(private readonly cells: TableCell[]) { + constructor(private readonly options: ITableRowOptions) { super("w:tr"); this.properties = new TableRowProperties(); this.root.push(this.properties); - cells.forEach((c) => this.root.push(c)); - } - public getCell(index: number): TableCell { - const cell = this.cells[index]; - - if (!cell) { - throw Error("Index out of bounds when trying to get cell on row"); + for (const child of options.children) { + this.root.push(child); } - return cell; + if (options.cantSplit) { + this.properties.setCantSplit(); + } + + if (options.tableHeader) { + this.properties.setTableHeader(); + } + + if (options.height) { + this.properties.setHeight(options.height.height, options.height.rule); + } } - public addGridSpan(index: number, cellSpan: number): TableCell { - const remainCell = this.cells[index]; - remainCell.addGridSpan(cellSpan); - this.cells.splice(index + 1, cellSpan - 1); - this.root.splice(index + 2, cellSpan - 1); - - return remainCell; + public get CellCount(): number { + return this.options.children.length; } - public mergeCells(startIndex: number, endIndex: number): TableCell { - const cellSpan = endIndex - startIndex + 1; - - return this.addGridSpan(startIndex, cellSpan); + public get Children(): TableCell[] { + return this.options.children; } - public setCantSplit(): TableRow { - this.properties.setCantSplit(); - - return this; - } - - public setTableHeader(): TableRow { - this.properties.setTableHeader(); - - return this; - } - - public setHeight(height: number, rule: HeightRule): TableRow { - this.properties.setHeight(height, rule); - - return this; + public addCellToIndex(cell: TableCell, index: number): void { + // Offset because properties is also in root. + this.root.splice(index + 1, 0, cell); } } diff --git a/src/file/table/table.spec.ts b/src/file/table/table.spec.ts index 7c93c5fc8e..d2aec26051 100644 --- a/src/file/table/table.spec.ts +++ b/src/file/table/table.spec.ts @@ -8,8 +8,9 @@ import { Table } from "./table"; // import { WidthType } from "./table-cell"; import { RelativeHorizontalPosition, RelativeVerticalPosition, TableAnchorType } from "./table-properties"; -import { EMPTY_OBJECT } from "file/xml-components"; +import { TableCell, WidthType } from "./table-cell"; import { TableLayoutType } from "./table-properties/table-layout"; +import { TableRow } from "./table-row"; const DEFAULT_TABLE_PROPERTIES = { "w:tblCellMar": [ @@ -118,11 +119,62 @@ describe("Table", () => { describe("#constructor", () => { it("creates a table with the correct number of rows and columns", () => { const table = new Table({ - rows: 3, - columns: 2, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], }); const tree = new Formatter().format(table); - const cell = { "w:tc": [{ "w:p": EMPTY_OBJECT }] }; + const cell = { + "w:tc": [ + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "hello", + ], + }, + ], + }, + ], + }, + ], + }; expect(tree).to.deep.equal({ "w:tbl": [ { "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] }, @@ -138,8 +190,15 @@ describe("Table", () => { it("sets the table to fixed width layout", () => { const table = new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], layout: TableLayoutType.FIXED, }); const tree = new Formatter().format(table); @@ -151,130 +210,60 @@ describe("Table", () => { "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }], }); }); - }); - describe("#getRow and Row#getCell", () => { - const table = new Table({ - rows: 2, - columns: 2, - }); - - it("should return the correct row", () => { - table - .getRow(0) - .getCell(0) - .add(new Paragraph("A1")); - table - .getRow(0) - .getCell(1) - .add(new Paragraph("B1")); - table - .getRow(1) - .getCell(0) - .add(new Paragraph("A2")); - table - .getRow(1) - .getCell(1) - .add(new Paragraph("B2")); - const tree = new Formatter().format(table); - const cell = (c) => ({ - "w:tc": [ - { - "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, c] }] }], - }, - ], - }); - expect(tree).to.deep.equal({ - "w:tbl": [ - { "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] }, - { - "w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }], - }, - { "w:tr": [cell("A1"), cell("B1")] }, - { "w:tr": [cell("A2"), cell("B2")] }, - ], - }); - }); - - it("throws an exception if index is out of bounds", () => { - expect(() => table.getCell(9, 9)).to.throw(); - }); - }); - - describe("#getColumn", () => { - const table = new Table({ - rows: 2, - columns: 2, - }); - - it("should get correct cell", () => { - const column = table.getColumn(0); - - expect(column.getCell(0)).to.equal(table.getCell(0, 0)); - expect(column.getCell(1)).to.equal(table.getCell(1, 0)); - }); - }); - - describe("#getCell", () => { - it("should returns the correct cell", () => { + it("should set the table to provided width", () => { const table = new Table({ - rows: 2, - columns: 2, - }); - table.getCell(0, 0).add(new Paragraph("A1")); - table.getCell(0, 1).add(new Paragraph("B1")); - table.getCell(1, 0).add(new Paragraph("A2")); - table.getCell(1, 1).add(new Paragraph("B2")); - const tree = new Formatter().format(table); - const cell = (c) => ({ - "w:tc": [ - { - "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, c] }] }], - }, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), ], + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + layout: TableLayoutType.FIXED, }); - expect(tree).to.deep.equal({ - "w:tbl": [ - { "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] }, + 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": [ + DEFAULT_TABLE_PROPERTIES, + BORDERS, { - "w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }], + "w:tblW": { + _attr: { + "w:type": "pct", + "w:w": "100%", + }, + }, }, - { "w:tr": [cell("A1"), cell("B1")] }, - { "w:tr": [cell("A2"), cell("B2")] }, + { "w:tblLayout": { _attr: { "w:type": "fixed" } } }, ], }); }); }); - // describe("#setWidth", () => { - // it("should set the preferred width on the table", () => { - // const table = new Table({rows: 1,columns: 1,}).setWidth(1000, WidthType.PERCENTAGE); - // 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": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": { _attr: { "w:type": "pct", "w:w": "1000%" } } }], - // }); - // }); - - // it("sets the preferred width on the table with a default of AUTO", () => { - // const table = new Table({rows: 1,columns: 1,}).setWidth(1000); - // const tree = new Formatter().format(table); - - // expect(tree["w:tbl"][0]).to.deep.equal({ - // "w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": { _attr: { "w:type": "auto", "w:w": 1000 } } }], - // }); - // }); - // }); - describe("Cell", () => { describe("#prepForXml", () => { it("inserts a paragraph at the end of the cell if it is empty", () => { const table = new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], }); const tree = new Formatter().format(table); expect(tree) @@ -282,72 +271,119 @@ describe("Table", () => { .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:p": EMPTY_OBJECT }], - }); - }); - - it("inserts a paragraph at the end of the cell even if it has a child table", () => { - const parentTable = new Table({ - rows: 1, - columns: 1, - }); - parentTable.getCell(0, 0).add( - new Table({ - rows: 1, - columns: 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": EMPTY_OBJECT, - }); - }); - - it("does not insert a paragraph if it already ends with one", () => { - const parentTable = new Table({ - rows: 1, - columns: 1, - }); - parentTable.getCell(0, 0).add(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:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "Hello"] }] }], + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "hello", + ], + }, + ], + }, + ], }, ], }); }); + + // it("inserts a paragraph at the end of the cell even if it has a child table", () => { + // const table = new Table({ + // rows: [ + // new TableRow({ + // children: [ + // new TableCell({ + // children: [new Paragraph("hello")], + // }), + // ], + // }), + // ], + // }); + // table.getCell(0, 0).add( + // new Table({ + // rows: [ + // new TableRow({ + // children: [ + // new TableCell({ + // children: [new Paragraph("hello")], + // }), + // ], + // }), + // ], + // }), + // ); + // 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); + // 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": EMPTY_OBJECT, + // }); + // }); + + // it("does not insert a paragraph if it already ends with one", () => { + // const table = new Table({ + // rows: [ + // new TableRow({ + // children: [ + // new TableCell({ + // children: [new Paragraph("hello")], + // }), + // ], + // }), + // ], + // }); + // table.getCell(0, 0).add(new Paragraph("Hello")); + // 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:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "Hello"] }] }], + // }, + // ], + // }); + // }); }); }); describe("#float", () => { it("sets the table float properties", () => { const table = new Table({ - rows: 1, - columns: 1, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("hello")], + }), + ], + }), + ], float: { horizontalAnchor: TableAnchorType.MARGIN, verticalAnchor: TableAnchorType.PAGE, diff --git a/src/file/table/table.ts b/src/file/table/table.ts index c56bc4d62c..08bd47eda2 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -1,12 +1,11 @@ // http://officeopenxml.com/WPtableGrid.php import { XmlComponent } from "file/xml-components"; - import { TableGrid } from "./grid"; -import { TableCell, WidthType } from "./table-cell"; -import { TableColumn } from "./table-column"; +import { TableCell, VerticalMergeType, WidthType } from "./table-cell"; import { ITableFloatOptions, TableProperties } from "./table-properties"; import { TableLayoutType } from "./table-properties/table-layout"; import { TableRow } from "./table-row"; + /* 0-width columns don't get rendered correctly, so we need to give them some value. A reasonable default would be @@ -18,10 +17,11 @@ import { TableRow } from "./table-row"; algorithm will expand columns to fit its content */ export interface ITableOptions { - readonly rows: number; - readonly columns: number; - readonly width?: number; - readonly widthUnitType?: WidthType; + readonly rows: TableRow[]; + readonly width?: { + readonly size: number; + readonly type?: WidthType; + }; readonly columnWidths?: number[]; readonly margins?: { readonly marginUnitType?: WidthType; @@ -36,14 +36,11 @@ export interface ITableOptions { export class Table extends XmlComponent { private readonly properties: TableProperties; - private readonly rows: TableRow[]; constructor({ rows, - columns, - width = 100, - widthUnitType = WidthType.AUTO, - columnWidths = Array(columns).fill(100), + width, + columnWidths = Array(Math.max(...rows.map((row) => row.CellCount))).fill(100), margins: { marginUnitType, top, bottom, right, left } = { marginUnitType: WidthType.AUTO, top: 0, bottom: 0, right: 0, left: 0 }, float, layout, @@ -52,26 +49,45 @@ export class Table extends XmlComponent { this.properties = new TableProperties(); this.root.push(this.properties); this.properties.setBorder(); - this.properties.setWidth(width, widthUnitType); + + if (width) { + this.properties.setWidth(width.size, width.type); + } else { + this.properties.setWidth(100); + } + this.properties.CellMargin.addBottomMargin(bottom || 0, marginUnitType); this.properties.CellMargin.addTopMargin(top || 0, marginUnitType); this.properties.CellMargin.addLeftMargin(left || 0, marginUnitType); this.properties.CellMargin.addRightMargin(right || 0, marginUnitType); - const grid = new TableGrid(columnWidths); - this.root.push(grid); + this.root.push(new TableGrid(columnWidths)); - this.rows = Array(rows) - .fill(0) - .map(() => { - const cells = Array(columns) - .fill(0) - .map(() => new TableCell()); - const row = new TableRow(cells); - return row; + for (const row of rows) { + this.root.push(row); + } + + for (const row of rows) { + row.Children.forEach((cell, cellIndex) => { + const column = rows.map((r) => r.Children[cellIndex]); + // Row Span has to be added in this method and not the constructor because it needs to know information about the column which happens after Table Cell construction + // Row Span of 1 will crash word as it will add RESTART and not a corresponding CONTINUE + if (cell.options.rowSpan && cell.options.rowSpan > 1) { + const thisCellsColumnIndex = column.indexOf(cell); + const endColumnIndex = thisCellsColumnIndex + (cell.options.rowSpan - 1); + + for (let i = thisCellsColumnIndex + 1; i <= endColumnIndex; i++) { + rows[i].addCellToIndex( + new TableCell({ + children: [], + verticalMerge: VerticalMergeType.CONTINUE, + }), + i, + ); + } + } }); - - this.rows.forEach((x) => this.root.push(x)); + } if (float) { this.properties.setTableFloatProperties(float); @@ -81,24 +97,4 @@ export class Table extends XmlComponent { this.properties.setLayout(layout); } } - - public getRow(index: number): TableRow { - const row = this.rows[index]; - - if (!row) { - throw Error("Index out of bounds when trying to get row on table"); - } - - return row; - } - - public getColumn(index: number): TableColumn { - // This is a convinence method for people who like to work with columns - const cells = this.rows.map((row) => row.getCell(index)); - return new TableColumn(cells); - } - - public getCell(row: number, col: number): TableCell { - return this.getRow(row).getCell(col); - } }