Merge pull request #407 from dolanmiu/feat/declaritive-tables

Feat/declarative tables
This commit is contained in:
Dolan
2019-09-29 04:43:37 +01:00
committed by GitHub
49 changed files with 2423 additions and 972 deletions

View File

@ -42,7 +42,7 @@ script:
- npm run ts-node -- ./demo/29-numbered-lists.ts - npm run ts-node -- ./demo/29-numbered-lists.ts
- npm run ts-node -- ./demo/30-template-document.ts - npm run ts-node -- ./demo/30-template-document.ts
- npm run ts-node -- ./demo/31-tables.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 e2e "My Document.docx" // Need to fix
- npm run ts-node -- ./demo/33-sequential-captions.ts - npm run ts-node -- ./demo/33-sequential-captions.ts
- npm run ts-node -- ./demo/34-floating-tables.ts - npm run ts-node -- ./demo/34-floating-tables.ts

View File

@ -1,7 +1,7 @@
// Setting styles with JavaScript configuration // Setting styles with JavaScript configuration
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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(); 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 image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, 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 image1 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));

View File

@ -1,23 +1,102 @@
// Add custom borders to table cell // Add custom borders to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, 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] }); 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) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);

View File

@ -1,24 +1,85 @@
// Add image to table cell // Add image to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, 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({ doc.addSection({
children: [table], 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) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);
}); });

View File

@ -1,28 +1,48 @@
// Example of how you would create a table and add data to it // 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 from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, 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({ doc.addSection({
children: [table], children: [table],
}); });

View File

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

View File

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

View File

@ -9,29 +9,48 @@ import {
RelativeVerticalPosition, RelativeVerticalPosition,
Table, Table,
TableAnchorType, TableAnchorType,
TableCell,
TableLayoutType, TableLayoutType,
TableRow,
WidthType, WidthType,
} from "../build"; } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
float: { float: {
horizontalAnchor: TableAnchorType.MARGIN, horizontalAnchor: TableAnchorType.MARGIN,
verticalAnchor: TableAnchorType.MARGIN, verticalAnchor: TableAnchorType.MARGIN,
relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT, relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT,
relativeVerticalPosition: RelativeVerticalPosition.BOTTOM, relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
}, },
width: 4535, width: {
widthUnitType: WidthType.DXA, size: 4535,
type: WidthType.DXA,
},
layout: TableLayoutType.FIXED, layout: TableLayoutType.FIXED,
}); });
table.getCell(0, 0).add(new Paragraph("Hello"));
table.getRow(0).mergeCells(0, 1);
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -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 from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, 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 // Adding same table in the body and in the header
doc.addSection({ doc.addSection({

View File

@ -1,17 +1,35 @@
// Example of how you would create a table and add data to it // 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 from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, 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({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -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 from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const table = new Table({ const table = new Table({
rows: 13, rows: [
columns: 6, 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; const table2 = new Table({
table.getCell(row, 0).add(new Paragraph("0,0")); rows: [
table.getCell(row, 1).add(new Paragraph("0,1")); new TableRow({
table.getCell(row, 3).add(new Paragraph("0,3")); children: [
table.getCell(row, 4).add(new Paragraph("0,4")); new TableCell({
table.getRow(row).mergeCells(4, 5); children: [new Paragraph("0,0")],
table.getRow(row).mergeCells(1, 2); }),
row = 1; new TableCell({
table.getCell(row, 0).add(new Paragraph("1,0")); children: [new Paragraph("0,1")],
table.getCell(row, 2).add(new Paragraph("1,2")); rowSpan: 2,
table.getCell(row, 4).add(new Paragraph("1,4")); }),
table.getRow(row).mergeCells(4, 5); new TableCell({
table.getRow(row).mergeCells(2, 3); children: [new Paragraph("0,2")],
table.getRow(row).mergeCells(0, 1); }),
new TableCell({
row = 2; children: [new Paragraph("0,3")],
table.getCell(row, 0).add(new Paragraph("2,0")); }),
table.getCell(row, 1).add(new Paragraph("2,1")); new TableCell({
table.getCell(row, 2).add(new Paragraph("2,2")); children: [new Paragraph("0,4")],
table.getCell(row, 3).add(new Paragraph("2,3")); }),
table.getCell(row, 4).add(new Paragraph("2,4")); new TableCell({
table.getRow(row).mergeCells(4, 5); children: [new Paragraph("0,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")); new TableRow({
table.getCell(row, 2).add(new Paragraph("3,2")); children: [
table.getCell(row, 3).add(new Paragraph("3,3")); new TableCell({
table.getCell(row, 4).add(new Paragraph("3,4")); children: [new Paragraph("1,0")],
table.getCell(row, 5).add(new Paragraph("3,5")); }),
row = 4; new TableCell({
table.getCell(row, 0).add(new Paragraph("4,0")); children: [new Paragraph("1,2")],
table.getCell(row, 5).add(new Paragraph("4,5")); }),
table.getRow(row).mergeCells(0, 4); 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({ doc.addSection({
children: [table], children: [table, new Paragraph(""), table2],
}); });
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {

View File

@ -1,18 +1,80 @@
// Add image to table cell // Add image to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; 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 doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, 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({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -8,7 +8,7 @@ const doc = new Document();
doc.addSection({ doc.addSection({
properties: { properties: {
column: { column: {
width: 708, space: 708,
count: 2, count: 2,
}, },
}, },
@ -23,7 +23,7 @@ doc.addSection({
doc.addSection({ doc.addSection({
properties: { properties: {
column: { column: {
width: 708, space: 708,
count: 3, count: 3,
}, },
}, },

View File

@ -1,253 +1,334 @@
# Tables # 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 Create a simple table like so:
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.
```ts ```ts
const table = new Table({ const table = new Table({
rows: 2, rows: [Array of `TableRow`s]
columns: 4,
}); });
doc.add(table);
``` ```
## Rows and Columns Then add the table in the `section`
You can get a row or a column from a table like so, where `index` is a number:
### Get Row
```ts ```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 ```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: For example:
```ts ```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<TableCell>` | 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<Paragraph | Table>` | 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 ### 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 ```ts
cell.add(new Paragraph("Hello")); const cell = new TableCell({
children: [new Paragraph("Hello")],
});
``` ```
### Set width of a cell ### Set width of a cell
You can specify the width of a cell using: 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 | Property | Notes |
- WidthType.DXA: value is in twentieths of a point | -------- | --------------------------------- |
- WidthType.NIL: is considered as zero | AUTO | |
- WidthType.PCT: percent of table width | DXA | value is in twentieths of a point |
| NIL | is considered as zero |
| PCT | percent of table width |
### Example #### Example
```ts ```ts
cell.Properties.setWidth(100, WidthType.DXA); cell.Properties.setWidth(100, WidthType.DXA);
``` ```
```ts ### Nested Tables
cell.Properties.setWidth("50%", WidthType.PCT);
```
## Borders To have a table within a table, simply add it in the `children` block of a `table cell`:
BorderStyle can be imported from `docx`. Size determines the thickness. HTML color can be a hex code or alias such as `red`.
```ts ```ts
cell.Borders.addTopBorder([BorderStyle], [SIZE], [HTML COLOR]); const cell = new TableCell({
children: [new Table(...)],
});
``` ```
```ts ### Vertical Align
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
Sets the vertical alignment of the contents of the cell Sets the vertical alignment of the contents of the cell
```ts ```ts
import { VerticalAlign } from "docx"; const cell = new TableCell({
...,
cell.setVerticalAlign([VerticalAlign TYPE]); 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: For example, to center align a cell:
```ts ```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 ```ts
table.getRow([ROW INDEX]); const cell = new TableCell({
``` ...,
rowSpan: [NUMBER_OF_CELLS_TO_MERGE],
## 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]);
``` ```
#### Example #### Example
This will merge 3 cells together starting from index `0`: The example will merge three rows together.
```ts ```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. 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
## Nested Tables
To have a table within a table
```ts ```ts
cell.add(new Table(1, 1)); const cell = new TableCell({
...,
columnSpan: [NUMBER_OF_CELLS_TO_MERGE],
});
``` ```
## Pagination #### Example
###Prevent row pagination The example will merge three columns together.
To prevent breaking contents of a row across multiple pages, call `cantSplit()`:
```ts ```ts
table.getRow(0).setCantSplit(); const cell = new TableCell({
``` ...,
columnSpan: 3,
###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();
``` ```
## Examples ## 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_ _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 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_ _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 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_ _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_ _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 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_ _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_ _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_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/41-merge-table-cells-2.ts_
### Merging columns ### 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_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/43-images-to-table-cell-2.ts_
### Floating tables ### 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_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/34-floating-tables.ts_

View File

@ -1,7 +1,8 @@
import { assert, expect } from "chai"; import { assert, expect } from "chai";
import * as sinon from "sinon";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import * as file from "file"; import { Paragraph, TextRun } from "file";
import { CoreProperties } from "file/core-properties"; import { CoreProperties } from "file/core-properties";
import { Attributes } from "file/xml-components"; import { Attributes } from "file/xml-components";
@ -14,22 +15,22 @@ describe("Formatter", () => {
describe("#format()", () => { describe("#format()", () => {
it("should format simple paragraph", () => { it("should format simple paragraph", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]); assert.isDefined(newJson["w:p"]);
}); });
it("should remove xmlKeys", () => { it("should remove xmlKeys", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
const stringifiedJson = JSON.stringify(newJson); const stringifiedJson = JSON.stringify(newJson);
assert(stringifiedJson.indexOf("xmlKeys") < 0); assert(stringifiedJson.indexOf("xmlKeys") < 0);
}); });
it("should format simple paragraph with bold text", () => { it("should format simple paragraph with bold text", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
paragraph.addRun( paragraph.addRun(
new file.TextRun({ new TextRun({
text: "test", text: "test",
bold: true, bold: true,
}), }),
@ -63,7 +64,7 @@ describe("Formatter", () => {
}); });
it("should should change 'p' tag into 'w:p' tag", () => { it("should should change 'p' tag into 'w:p' tag", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]); assert.isDefined(newJson["w:p"]);
}); });
@ -76,5 +77,13 @@ describe("Formatter", () => {
const newJson = formatter.format(properties); const newJson = formatter.format(properties);
assert.isDefined(newJson["cp:coreProperties"]); 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);
});
}); });
}); });

View File

@ -1,7 +1,8 @@
/* tslint:disable:typedef space-before-function-paren */ /* tslint:disable:typedef space-before-function-paren */
import { expect } from "chai"; 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"; import { Compiler } from "./next-compiler";
@ -72,5 +73,22 @@ describe("Compiler", () => {
expect(fileNames).to.include("word/footer2.xml"); expect(fileNames).to.include("word/footer2.xml");
expect(fileNames).to.include("word/_rels/footer2.xml.rels"); 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);
});
}); });
}); });

View File

@ -68,13 +68,13 @@ export class Compiler {
file.verifyUpdateFields(); file.verifyUpdateFields();
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1; const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
const documentXmlData = xml(this.formatter.format(file.Document), prettify);
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
return { return {
Relationships: { Relationships: {
data: (() => { data: (() => {
const xmlData = xml(this.formatter.format(file.Document), prettify); documentMediaDatas.forEach((mediaData, i) => {
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
file.DocumentRelationships.createRelationship( file.DocumentRelationships.createRelationship(
documentRelationshipCount + i, documentRelationshipCount + i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
@ -88,9 +88,7 @@ export class Compiler {
}, },
Document: { Document: {
data: (() => { data: (() => {
const tempXmlData = xml(this.formatter.format(file.Document), prettify); const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, documentRelationshipCount);
return xmlData; return xmlData;
})(), })(),

View File

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

View File

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

View File

@ -38,6 +38,7 @@ describe("SectionProperties", () => {
}, },
pageNumberStart: 10, pageNumberStart: 10,
pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT, pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT,
titlePage: true,
}); });
const tree = new Formatter().format(properties); const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);

View File

@ -6,7 +6,7 @@ import { Formatter } from "export/formatter";
import { File } from "./file"; import { File } from "./file";
import { Footer, Header } from "./header"; import { Footer, Header } from "./header";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
import { TableOfContents } from "./table-of-contents"; import { TableOfContents } from "./table-of-contents";
describe("File", () => { describe("File", () => {
@ -108,8 +108,15 @@ describe("File", () => {
file.addSection({ file.addSection({
children: [ children: [
new Table({ new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}), }),
], ],
}); });

View File

@ -4,7 +4,7 @@ import * as sinon from "sinon";
import { FooterWrapper } from "./footer-wrapper"; import { FooterWrapper } from "./footer-wrapper";
import { Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
describe("FooterWrapper", () => { describe("FooterWrapper", () => {
describe("#add", () => { describe("#add", () => {
@ -21,22 +21,20 @@ describe("FooterWrapper", () => {
const spy = sinon.spy(file.Footer, "add"); const spy = sinon.spy(file.Footer, "add");
file.add( file.add(
new Table({ new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}), }),
); );
expect(spy.called).to.equal(true); 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", () => { describe("#addChildElement", () => {

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { FooterReferenceType } from "./document"; import { FooterReferenceType } from "./document";
import { Footer } from "./footer/footer"; import { Footer } from "./footer/footer";
import { Image, Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -25,11 +25,6 @@ export class FooterWrapper {
this.footer.add(item); this.footer.add(item);
} }
public addImage(image: Image): FooterWrapper {
this.footer.add(image.Paragraph);
return this;
}
public addChildElement(childElement: XmlComponent): void { public addChildElement(childElement: XmlComponent): void {
this.footer.addChildElement(childElement); this.footer.addChildElement(childElement);
} }

View File

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

View File

@ -4,7 +4,7 @@ import * as sinon from "sinon";
import { HeaderWrapper } from "./header-wrapper"; import { HeaderWrapper } from "./header-wrapper";
import { Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
describe("HeaderWrapper", () => { describe("HeaderWrapper", () => {
describe("#add", () => { describe("#add", () => {
@ -21,8 +21,15 @@ describe("HeaderWrapper", () => {
const spy = sinon.spy(wrapper.Header, "add"); const spy = sinon.spy(wrapper.Header, "add");
wrapper.add( wrapper.add(
new Table({ new Table({
rows: 1, rows: [
columns: 1, 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", () => { describe("#addChildElement", () => {
it("should call the underlying header's addChildElement", () => { it("should call the underlying header's addChildElement", () => {
const file = new HeaderWrapper(new Media(), 1); const file = new HeaderWrapper(new Media(), 1);

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { HeaderReferenceType } from "./document"; import { HeaderReferenceType } from "./document";
import { Header } from "./header/header"; import { Header } from "./header/header";
import { Image, Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -27,11 +27,6 @@ export class HeaderWrapper {
return this; return this;
} }
public addImage(image: Image): HeaderWrapper {
this.header.add(image.Paragraph);
return this;
}
public addChildElement(childElement: XmlComponent | string): void { public addChildElement(childElement: XmlComponent | string): void {
this.header.addChildElement(childElement); this.header.addChildElement(childElement);
} }

View File

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

View File

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

View File

@ -1,3 +1,2 @@
export * from "./media"; export * from "./media";
export * from "./data"; export * from "./data";
export * from "./image";

View File

@ -80,6 +80,16 @@ describe("Media", () => {
const image = new Media().addMedia(""); const image = new Media().addMedia("");
expect(image.stream).to.be.an.instanceof(Uint8Array); 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", () => { describe("#getMedia", () => {

View File

@ -1,11 +1,87 @@
import { assert, expect } from "chai"; import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { ThematicBreak } from "./border"; import { Border, ThematicBreak } from "./border";
describe("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", () => { describe("ThematicBreak", () => {
@ -16,17 +92,6 @@ describe("ThematicBreak", () => {
}); });
describe("#constructor()", () => { 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", () => { it("should create a Thematic Break with correct border properties", () => {
const tree = new Formatter().format(thematicBreak); const tree = new Formatter().format(thematicBreak);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({

View File

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

View File

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

View File

@ -3,4 +3,3 @@ export * from "./paragraph";
export * from "./properties"; export * from "./properties";
export * from "./run"; export * from "./run";
export * from "./links"; export * from "./links";
export * from "./image";

View File

@ -1,6 +1,5 @@
// http://officeopenxml.com/WPparagraph.php // http://officeopenxml.com/WPparagraph.php
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { Image } from "file/media";
import { Num } from "file/numbering/num"; import { Num } from "file/numbering/num";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
@ -190,13 +189,6 @@ export class Paragraph extends XmlComponent {
return this; return this;
} }
public addImage(image: Image): PictureRun {
const run = image.Run;
this.addRun(run);
return run;
}
public pageBreak(): Paragraph { public pageBreak(): Paragraph {
this.root.push(new PageBreak()); this.root.push(new PageBreak());
return this; return this;

View File

@ -2,9 +2,11 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class TableGrid extends XmlComponent { export class TableGrid extends XmlComponent {
constructor(cols: number[]) { constructor(widths: number[]) {
super("w:tblGrid"); super("w:tblGrid");
cols.forEach((col) => this.root.push(new GridCol(col))); for (const width of widths) {
this.root.push(new GridCol(width));
}
} }
} }

View File

@ -103,7 +103,7 @@ export class GridSpan extends XmlComponent {
/** /**
* Vertical merge types. * Vertical merge types.
*/ */
export enum VMergeType { export enum VerticalMergeType {
/** /**
* Cell that is merged with upper one. * Cell that is merged with upper one.
*/ */
@ -114,19 +114,19 @@ export enum VMergeType {
RESTART = "restart", RESTART = "restart",
} }
class VMergeAttributes extends XmlAttributeComponent<{ readonly val: VMergeType }> { class VerticalMergeAttributes extends XmlAttributeComponent<{ readonly val: VerticalMergeType }> {
protected readonly xmlKeys = { val: "w:val" }; protected readonly xmlKeys = { val: "w:val" };
} }
/** /**
* Vertical merge element. Should be used in a table cell. * Vertical merge element. Should be used in a table cell.
*/ */
export class VMerge extends XmlComponent { export class VerticalMerge extends XmlComponent {
constructor(value: VMergeType) { constructor(value: VerticalMergeType) {
super("w:vMerge"); super("w:vMerge");
this.root.push( this.root.push(
new VMergeAttributes({ new VerticalMergeAttributes({
val: value, val: value,
}), }),
); );

View File

@ -3,7 +3,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { BorderStyle } from "file/styles"; 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"; import { TableCellProperties } from "./table-cell-properties";
describe("TableCellProperties", () => { describe("TableCellProperties", () => {
@ -30,7 +30,7 @@ describe("TableCellProperties", () => {
describe("#addVerticalMerge", () => { describe("#addVerticalMerge", () => {
it("adds vertical merge", () => { it("adds vertical merge", () => {
const properties = new TableCellProperties(); const properties = new TableCellProperties();
properties.addVerticalMerge(VMergeType.CONTINUE); properties.addVerticalMerge(VerticalMergeType.CONTINUE);
const tree = new Formatter().format(properties); const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }); 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", () => { describe("#Borders", () => {
it("should return the TableCellBorders if Border has borders", () => { it("should return the TableCellBorders if Border has borders", () => {
const properties = new TableCellProperties(); const properties = new TableCellProperties();

View File

@ -2,7 +2,16 @@ import { IgnoreIfEmptyXmlComponent } from "file/xml-components";
import { ITableShadingAttributesProperties, TableShading } from "../shading"; import { ITableShadingAttributesProperties, TableShading } from "../shading";
import { ITableCellMarginOptions, TableCellMargin } from "./cell-margin/table-cell-margins"; 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 { export class TableCellProperties extends IgnoreIfEmptyXmlComponent {
private readonly cellBorder: TableCellBorders; private readonly cellBorder: TableCellBorders;
@ -23,8 +32,8 @@ export class TableCellProperties extends IgnoreIfEmptyXmlComponent {
return this; return this;
} }
public addVerticalMerge(type: VMergeType): TableCellProperties { public addVerticalMerge(type: VerticalMergeType): TableCellProperties {
this.root.push(new VMerge(type)); this.root.push(new VerticalMerge(type));
return this; return this;
} }

View File

@ -3,7 +3,9 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { BorderStyle } from "file/styles"; 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("TableCellBorders", () => {
describe("#prepForXml", () => { 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": {},
},
],
});
});
});
});
});

View File

@ -1,77 +1,112 @@
// http://officeopenxml.com/WPtableGrid.php // http://officeopenxml.com/WPtableGrid.php
import { Paragraph } from "file/paragraph"; import { Paragraph } from "file/paragraph";
import { BorderStyle } from "file/styles";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { ITableShadingAttributesProperties } from "../shading"; import { ITableShadingAttributesProperties } from "../shading";
import { Table } from "../table"; import { Table } from "../table";
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins"; 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"; import { TableCellProperties } from "./table-cell-properties";
export interface ITableCellOptions { export interface ITableCellOptions {
readonly shading?: ITableShadingAttributesProperties; 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<Paragraph | Table>;
} }
export class TableCell extends XmlComponent { export class TableCell extends XmlComponent {
private readonly properties: TableCellProperties; private readonly properties: TableCellProperties;
constructor() { constructor(readonly options: ITableCellOptions) {
super("w:tc"); super("w:tc");
this.properties = new TableCellProperties(); this.properties = new TableCellProperties();
this.root.push(this.properties); this.root.push(this.properties);
}
public add(item: Paragraph | Table): TableCell { for (const child of options.children) {
this.root.push(item); 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 { public prepForXml(): IXmlableObject | undefined {
// Cells must end with a paragraph // Cells must end with a paragraph
if (!(this.root[this.root.length - 1] instanceof Paragraph)) { if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
const para = new Paragraph({}); this.root.push(new Paragraph({}));
this.add(para);
} }
return super.prepForXml(); 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;
}
} }

View File

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

View File

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

View File

@ -14,38 +14,66 @@ describe("TableCellMargin", () => {
}); });
describe("#addTopMargin", () => { describe("#addTopMargin", () => {
it("adds a table cell top margin", () => { it("should add a table cell top margin", () => {
const cellMargin = new TableCellMargin(); const cellMargin = new TableCellMargin();
cellMargin.addTopMargin(1234, WidthType.DXA); cellMargin.addTopMargin(1234, WidthType.DXA);
const tree = new Formatter().format(cellMargin); const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); 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", () => { describe("#addLeftMargin", () => {
it("adds a table cell left margin", () => { it("should add a table cell left margin", () => {
const cellMargin = new TableCellMargin(); const cellMargin = new TableCellMargin();
cellMargin.addLeftMargin(1234, WidthType.DXA); cellMargin.addLeftMargin(1234, WidthType.DXA);
const tree = new Formatter().format(cellMargin); const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); 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", () => { describe("#addBottomMargin", () => {
it("adds a table cell bottom margin", () => { it("should add a table cell bottom margin", () => {
const cellMargin = new TableCellMargin(); const cellMargin = new TableCellMargin();
cellMargin.addBottomMargin(1234, WidthType.DXA); cellMargin.addBottomMargin(1234, WidthType.DXA);
const tree = new Formatter().format(cellMargin); const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); 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", () => { describe("#addRightMargin", () => {
it("adds a table cell right margin", () => { it("should add a table cell right margin", () => {
const cellMargin = new TableCellMargin(); const cellMargin = new TableCellMargin();
cellMargin.addRightMargin(1234, WidthType.DXA); cellMargin.addRightMargin(1234, WidthType.DXA);
const tree = new Formatter().format(cellMargin); const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); 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 } } }] });
});
}); });
}); });

View File

@ -2,6 +2,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { ShadingType } from "../shading";
import { WidthType } from "../table-cell"; import { WidthType } from "../table-cell";
import { TableLayoutType } from "./table-layout"; import { TableLayoutType } from "./table-layout";
import { TableProperties } from "./table-properties"; 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",
},
},
},
],
});
});
});
}); });

View File

@ -2,6 +2,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { Paragraph } from "file/paragraph";
import { HeightRule } from "file/table/table-row/table-row-height"; import { HeightRule } from "file/table/table-row/table-row-height";
import { EMPTY_OBJECT } from "file/xml-components"; import { EMPTY_OBJECT } from "file/xml-components";
import { TableCell } from "../table-cell"; import { TableCell } from "../table-cell";
@ -10,7 +11,9 @@ import { TableRow } from "./table-row";
describe("TableRow", () => { describe("TableRow", () => {
describe("#constructor", () => { describe("#constructor", () => {
it("should create with no cells", () => { it("should create with no cells", () => {
const tableRow = new TableRow([]); const tableRow = new TableRow({
children: [],
});
const tree = new Formatter().format(tableRow); const tree = new Formatter().format(tableRow);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:tr": EMPTY_OBJECT, "w:tr": EMPTY_OBJECT,
@ -18,7 +21,13 @@ describe("TableRow", () => {
}); });
it("should create with one cell", () => { 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); const tree = new Formatter().format(tableRow);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:tr": [ "w:tr": [
@ -32,46 +41,61 @@ describe("TableRow", () => {
], ],
}); });
}); });
});
describe("#getCell", () => { it("should create with cant split", () => {
it("should get the cell", () => { const tableRow = new TableRow({
const cell = new TableCell(); children: [],
const tableRow = new TableRow([cell]); cantSplit: true,
});
expect(tableRow.getCell(0)).to.equal(cell); 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", () => { it("should create with table header", () => {
const cell = new TableCell(); const tableRow = new TableRow({
const tableRow = new TableRow([cell]); children: [],
tableHeader: true,
expect(() => tableRow.getCell(1)).to.throw(); });
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", () => { it("should set row height", () => {
const tableRow = new TableRow([]); const tableRow = new TableRow({
tableRow.setHeight(100, HeightRule.EXACT); children: [],
height: {
height: 100,
rule: HeightRule.EXACT,
},
});
const tree = new Formatter().format(tableRow); const tree = new Formatter().format(tableRow);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:tr": [ "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",
],
},
],
},
],
},
],
},
],
});
});
});
}); });

View File

@ -3,56 +3,51 @@ import { XmlComponent } from "file/xml-components";
import { TableCell } from "../table-cell"; import { TableCell } from "../table-cell";
import { TableRowProperties } from "./table-row-properties"; 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 { export class TableRow extends XmlComponent {
private readonly properties: TableRowProperties; private readonly properties: TableRowProperties;
constructor(private readonly cells: TableCell[]) { constructor(private readonly options: ITableRowOptions) {
super("w:tr"); super("w:tr");
this.properties = new TableRowProperties(); this.properties = new TableRowProperties();
this.root.push(this.properties); this.root.push(this.properties);
cells.forEach((c) => this.root.push(c));
}
public getCell(index: number): TableCell { for (const child of options.children) {
const cell = this.cells[index]; this.root.push(child);
if (!cell) {
throw Error("Index out of bounds when trying to get cell on row");
} }
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 { public get CellCount(): number {
const remainCell = this.cells[index]; return this.options.children.length;
remainCell.addGridSpan(cellSpan);
this.cells.splice(index + 1, cellSpan - 1);
this.root.splice(index + 2, cellSpan - 1);
return remainCell;
} }
public mergeCells(startIndex: number, endIndex: number): TableCell { public get Children(): TableCell[] {
const cellSpan = endIndex - startIndex + 1; return this.options.children;
return this.addGridSpan(startIndex, cellSpan);
} }
public setCantSplit(): TableRow { public addCellToIndex(cell: TableCell, index: number): void {
this.properties.setCantSplit(); // Offset because properties is also in root.
this.root.splice(index + 1, 0, cell);
return this;
}
public setTableHeader(): TableRow {
this.properties.setTableHeader();
return this;
}
public setHeight(height: number, rule: HeightRule): TableRow {
this.properties.setHeight(height, rule);
return this;
} }
} }

View File

@ -8,8 +8,9 @@ import { Table } from "./table";
// import { WidthType } from "./table-cell"; // import { WidthType } from "./table-cell";
import { RelativeHorizontalPosition, RelativeVerticalPosition, TableAnchorType } from "./table-properties"; 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 { TableLayoutType } from "./table-properties/table-layout";
import { TableRow } from "./table-row";
const DEFAULT_TABLE_PROPERTIES = { const DEFAULT_TABLE_PROPERTIES = {
"w:tblCellMar": [ "w:tblCellMar": [
@ -118,11 +119,62 @@ describe("Table", () => {
describe("#constructor", () => { describe("#constructor", () => {
it("creates a table with the correct number of rows and columns", () => { it("creates a table with the correct number of rows and columns", () => {
const table = new Table({ const table = new Table({
rows: 3, rows: [
columns: 2, 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 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({ expect(tree).to.deep.equal({
"w:tbl": [ "w:tbl": [
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] }, { "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] },
@ -138,8 +190,15 @@ describe("Table", () => {
it("sets the table to fixed width layout", () => { it("sets the table to fixed width layout", () => {
const table = new Table({ const table = new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
layout: TableLayoutType.FIXED, layout: TableLayoutType.FIXED,
}); });
const tree = new Formatter().format(table); 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" } } }], "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }],
}); });
}); });
});
describe("#getRow and Row#getCell", () => { it("should set the table to provided width", () => {
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", () => {
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
}); children: [
table.getCell(0, 0).add(new Paragraph("A1")); new TableCell({
table.getCell(0, 1).add(new Paragraph("B1")); children: [new Paragraph("hello")],
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] }] }],
},
], ],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
layout: TableLayoutType.FIXED,
}); });
expect(tree).to.deep.equal({ const tree = new Formatter().format(table);
"w:tbl": [ expect(tree)
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] }, .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:tblLayout": { _attr: { "w:type": "fixed" } } },
{ "w:tr": [cell("A2"), cell("B2")] },
], ],
}); });
}); });
}); });
// 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("Cell", () => {
describe("#prepForXml", () => { describe("#prepForXml", () => {
it("inserts a paragraph at the end of the cell if it is empty", () => { it("inserts a paragraph at the end of the cell if it is empty", () => {
const table = new Table({ const table = new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}); });
const tree = new Formatter().format(table); const tree = new Formatter().format(table);
expect(tree) expect(tree)
@ -282,72 +271,119 @@ describe("Table", () => {
.which.is.an("array"); .which.is.an("array");
const row = tree["w:tbl"].find((x) => x["w:tr"]); const row = tree["w:tbl"].find((x) => x["w:tr"]);
expect(row).not.to.be.undefined; 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"]) expect(row["w:tr"])
.to.be.an("array") .to.be.an("array")
.which.has.length.at.least(1); .which.has.length.at.least(1);
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({ expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
"w:tc": [ "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", () => { describe("#float", () => {
it("sets the table float properties", () => { it("sets the table float properties", () => {
const table = new Table({ const table = new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
float: { float: {
horizontalAnchor: TableAnchorType.MARGIN, horizontalAnchor: TableAnchorType.MARGIN,
verticalAnchor: TableAnchorType.PAGE, verticalAnchor: TableAnchorType.PAGE,

View File

@ -1,12 +1,11 @@
// http://officeopenxml.com/WPtableGrid.php // http://officeopenxml.com/WPtableGrid.php
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { TableGrid } from "./grid"; import { TableGrid } from "./grid";
import { TableCell, WidthType } from "./table-cell"; import { TableCell, VerticalMergeType, WidthType } from "./table-cell";
import { TableColumn } from "./table-column";
import { ITableFloatOptions, TableProperties } from "./table-properties"; import { ITableFloatOptions, TableProperties } from "./table-properties";
import { TableLayoutType } from "./table-properties/table-layout"; import { TableLayoutType } from "./table-properties/table-layout";
import { TableRow } from "./table-row"; import { TableRow } from "./table-row";
/* /*
0-width columns don't get rendered correctly, so we need 0-width columns don't get rendered correctly, so we need
to give them some value. A reasonable default would be 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 algorithm will expand columns to fit its content
*/ */
export interface ITableOptions { export interface ITableOptions {
readonly rows: number; readonly rows: TableRow[];
readonly columns: number; readonly width?: {
readonly width?: number; readonly size: number;
readonly widthUnitType?: WidthType; readonly type?: WidthType;
};
readonly columnWidths?: number[]; readonly columnWidths?: number[];
readonly margins?: { readonly margins?: {
readonly marginUnitType?: WidthType; readonly marginUnitType?: WidthType;
@ -36,14 +36,11 @@ export interface ITableOptions {
export class Table extends XmlComponent { export class Table extends XmlComponent {
private readonly properties: TableProperties; private readonly properties: TableProperties;
private readonly rows: TableRow[];
constructor({ constructor({
rows, rows,
columns, width,
width = 100, columnWidths = Array<number>(Math.max(...rows.map((row) => row.CellCount))).fill(100),
widthUnitType = WidthType.AUTO,
columnWidths = Array<number>(columns).fill(100),
margins: { marginUnitType, top, bottom, right, left } = { marginUnitType: WidthType.AUTO, top: 0, bottom: 0, right: 0, left: 0 }, margins: { marginUnitType, top, bottom, right, left } = { marginUnitType: WidthType.AUTO, top: 0, bottom: 0, right: 0, left: 0 },
float, float,
layout, layout,
@ -52,26 +49,45 @@ export class Table extends XmlComponent {
this.properties = new TableProperties(); this.properties = new TableProperties();
this.root.push(this.properties); this.root.push(this.properties);
this.properties.setBorder(); 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.addBottomMargin(bottom || 0, marginUnitType);
this.properties.CellMargin.addTopMargin(top || 0, marginUnitType); this.properties.CellMargin.addTopMargin(top || 0, marginUnitType);
this.properties.CellMargin.addLeftMargin(left || 0, marginUnitType); this.properties.CellMargin.addLeftMargin(left || 0, marginUnitType);
this.properties.CellMargin.addRightMargin(right || 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) for (const row of rows) {
.fill(0) this.root.push(row);
.map(() => { }
const cells = Array(columns)
.fill(0) for (const row of rows) {
.map(() => new TableCell()); row.Children.forEach((cell, cellIndex) => {
const row = new TableRow(cells); const column = rows.map((r) => r.Children[cellIndex]);
return row; // 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) { if (float) {
this.properties.setTableFloatProperties(float); this.properties.setTableFloatProperties(float);
@ -81,24 +97,4 @@ export class Table extends XmlComponent {
this.properties.setLayout(layout); 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);
}
} }