Compare commits

...

38 Commits

Author SHA1 Message Date
38f2638ea0 Version bump 2019-09-29 04:45:30 +01:00
2b874e3f69 Merge pull request #407 from dolanmiu/feat/declaritive-tables
Feat/declarative tables
2019-09-29 04:43:37 +01:00
b43ed12c84 Fix tests 2019-09-29 04:38:07 +01:00
59be381213 Update travis config 2019-09-29 04:25:40 +01:00
172c333357 Add tests and clean up code 2019-09-29 04:17:21 +01:00
c5eb3d5670 Add addMargin test 2019-09-26 02:24:43 +01:00
44b95f2f15 Add shading test 2019-09-26 02:14:52 +01:00
bd888219fc Amend table documentation 2019-09-26 02:03:17 +01:00
2842619196 Update table documentation 2019-09-25 01:59:30 +01:00
b2de74a0e6 Fix tests 2019-09-25 01:09:53 +01:00
7aa4134e2b Refactor row merging to table level 2019-09-25 00:57:24 +01:00
cc36ea7542 Optimise formatting to not over-format the same files 2019-09-22 20:45:24 +01:00
c11af71ed7 Add test such that it only should call prep once 2019-09-22 19:09:34 +01:00
a9d4ebc898 Add declarative column merge 2019-09-22 02:39:38 +01:00
d2f82052b4 Improve documentation 2019-09-19 22:49:09 +01:00
f88a309d55 Merge branch 'master' of github.com:dolanmiu/docx into feat/declaritive-tables 2019-09-13 00:52:26 +01:00
418adca9f3 Declarative tables 2019-09-13 00:51:20 +01:00
993f8f81f7 Merge pull request #392 from wainstead/parameter-fix
Fix: probable copy/paste error
2019-09-01 16:19:03 +01:00
99718784f1 Fix: probable copy/paste error
Fix the argument to "left:" which should likely be 2160
2019-08-30 09:29:45 -04:00
59fc1ed632 Merge pull request #391 from bokuweb/remove-extra-line
fix: try to remove unnecessary paragraph
2019-08-29 20:34:32 +01:00
cb74868247 fix: try to remove unnecessary paragraph 2019-08-29 20:52:05 +09:00
bd6ae2c0dc Remove unnecessary documentation 2019-08-22 22:49:57 +01:00
60a599a550 Improve bullet points documentation 2019-08-22 00:28:42 +01:00
4ac55a787e Fix includes 2019-08-21 00:53:49 +01:00
cb52a1ef42 Formatting 2019-08-21 00:51:26 +01:00
7827d158d7 Updated documentation 2019-08-21 00:05:46 +01:00
535f2d75b0 Fix embed links in documentation 2019-08-20 22:23:14 +01:00
5ecdb48d43 Stop unnecessary casting 2019-08-15 00:48:36 +01:00
2502fe7f39 Increase thresholds 2019-08-14 15:50:12 +01:00
02487fbb22 Merge pull request #376 from mforman1/Numbering
Add total number of pages in a section
2019-08-14 13:29:35 +01:00
2abff6991f Rename demo 2019-08-14 14:13:28 +03:00
49e85275c3 Fix tests 2019-08-13 10:53:29 +03:00
96efdf8b1a Merge branch 'master' of github.com:dolanmiu/docx into Numbering
# Conflicts:
#	src/file/paragraph/run/run.ts
2019-08-13 10:40:49 +03:00
b52a4adaff Merge pull request #375 from fpirsch/fpirsch-compression
Enable zip compression
2019-08-09 11:17:22 +01:00
a9fc40dad4 Add total number of pages in a section 2019-08-09 11:56:22 +03:00
b609a17362 Update packer.ts 2019-08-09 09:41:58 +02:00
4b49fdbf8e Update packer.ts 2019-08-09 00:19:44 +01:00
fd52c8cf47 Enable zip compression 2019-08-08 16:40:16 +02:00
68 changed files with 2632 additions and 1117 deletions

8
.nycrc
View File

@ -1,9 +1,9 @@
{ {
"check-coverage": true, "check-coverage": true,
"lines": 87.54, "lines": 92.34,
"functions": 83.61, "functions": 88.27,
"branches": 72.57, "branches": 84.64,
"statements": 87.32, "statements": 92.15,
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"
], ],

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

@ -10,7 +10,7 @@ const numbering = new Numbering();
const abstractNum = numbering.createAbstractNumbering(); const abstractNum = numbering.createAbstractNumbering();
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 });
abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 }); abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 });
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 14402160, hanging: 1700 }); abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 2160, hanging: 1700 });
const concrete = numbering.createConcreteNumbering(abstractNum); const concrete = numbering.createConcreteNumbering(abstractNum);

View File

@ -1,27 +1,47 @@
// 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({
table children: [new Paragraph({}), new Paragraph({})],
.getCell(1, 1) verticalAlign: VerticalAlign.CENTER,
.add(new Paragraph("This text should be in the middle of the cell")) }),
.setVerticalAlign(VerticalAlign.CENTER); new TableCell({
children: [new Paragraph({}), new Paragraph({})],
table.getCell(1, 0).add( verticalAlign: VerticalAlign.CENTER,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [
new Paragraph({ new Paragraph({
text: 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", "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, heading: HeadingLevel.HEADING_1,
}), }),
); ],
}),
new TableCell({
children: [
new Paragraph({
text: "This text should be in the middle of the cell",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
],
}),
],
});
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

@ -0,0 +1,49 @@
// Multiple sections with total number of pages in each section
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, PageNumberFormat, TextRun } from "../build";
const doc = new Document();
const header = doc.createHeader();
header.createParagraph("Header on another page");
const footer = doc.createFooter();
footer.createParagraph("Foo Bar corp. ")
.center()
.addRun(new TextRun("Page Number: ").pageNumber())
.addRun(new TextRun(" to ").numberOfTotalPagesSection());
doc.addSection({
headers: {
default: header,
},
footers: {
default: footer,
},
pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL,
});
doc.createParagraph("Section 1").pageBreak();
doc.createParagraph("Section 1").pageBreak();
doc.addSection({
headers: {
default: header,
},
footers: {
default: footer,
},
pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL,
});
doc.createParagraph("Section 2").pageBreak();
doc.createParagraph("Section 2").pageBreak();
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -5,17 +5,16 @@
To make a bullet point, simply make a paragraph into a bullet point: To make a bullet point, simply make a paragraph into a bullet point:
```ts ```ts
const text = new docx.TextRun("Bullet points"); const text = new TextRun("Bullet points");
const paragraph = new docx.Paragraph(text).bullet(); const paragraph = new Paragraph({
text: "Bullet points",
const text2 = new docx.TextRun("Are awesome"); bullet: {
const paragraph2 = new docx.Paragraph(text2).bullet(); level: 0, // How deep you want the bullet to me
},
doc.add(paragraph); });
doc.add(paragraph2);
``` ```
### This will produce: ### This will produce:
* Bullet points - Bullet points
* Are awesome - Are awesome

View File

@ -2,52 +2,46 @@
!> Headers and Footers requires an understanding of [Sections](usage/sections.md). !> Headers and Footers requires an understanding of [Sections](usage/sections.md).
Every Section has a sections which you can define its Headers and Footers:
```ts
doc.addSection({
headers: {
default: new Header({ // The standard default header
children: [],
}),
first: new Header({ // The first header
children: [],
}),
even: new Header({ // The header on every other page
children: [],
}),
},
footers: {
default: new Footer({ // The standard default footer
children: [],
}),
first: new Footer({ // The first footer
children: [],
}),
even: new Footer({ // The footer on every other page
children: [],
}),
},
children: [],
});
```
If you want more head
## Example ## Example
Creating Headers and footers is simple. Access the `Header` and `Footer` by doing so like this: Example showing basic header and footer
```ts [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/8-header-footer.ts ':include')
doc.Header;
doc.Footer;
```
You can call the same methods as you would with a `File`: _Source: https://github.com/dolanmiu/docx/blob/master/demo/8-header-footer.ts_
```ts
doc.Header.createParagraph("Header text");
doc.Footer.createParagraph("Footer text");
```
Even add images:
```ts
doc.Header.createImage([BUFFER_OF_YOUR_IMAGE]);
doc.Footer.createImage([BUFFER_OF_YOUR_IMAGE]);
```
Refer to [`demo8.ts`](https://github.com/dolanmiu/docx/blob/master/demo/demo8.ts) for more information.
## Multiple Headers and Footers ## Multiple Headers and Footers
Also all the supported section properties are implemented according to: http://officeopenxml.com/WPsection.php More headers and footers can be accomplished by creating more `Section`. New headers and footers can be set per `Section`
### Example
```ts
const header = this.document.createHeader();
const footer = this.document.createFooter();
// Add new section with another header and footer
doc.addSection({
headers: {
default: header
},
footers: {
default: footer
},
pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
});
```

View File

@ -227,22 +227,22 @@ Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
Importing Images from file system path Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo5.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/5-images.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo5.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/5-images.ts_
### Add images to header and footer ### Add images to header and footer
Example showing how to add image to headers and footers Example showing how to add image to headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo9.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/9-images-in-header-and-footer.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo9.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/9-images-in-header-and-footer.ts_
### Floating images ### Floating images
Example showing how to float images on top of text and optimally give a `margin` Example showing how to float images on top of text and optimally give a `margin`
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo38.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/38-text-wrapping.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo38.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/38-text-wrapping.ts_

View File

@ -86,11 +86,3 @@ topLevelP.setNumbering(concrete, 0);
subP.setNumbering(concrete, 1); subP.setNumbering(concrete, 1);
subSubP.setNumbering(concrete, 2); subSubP.setNumbering(concrete, 2);
``` ```
Finally, you need to let your exporter know about your numbering
styles when you're ready to render the document:
```ts
const packer = new Packer(doc, undefined, undefined, numbering);
packer.pack(myOutput);
```

View File

@ -61,6 +61,6 @@ doc.Header.createParagraph().addRun(new TextRun("Page ").pageNumber()).addRun(ne
Adding page numbers to Header and Footer Adding page numbers to Header and Footer
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo39.ts ':include') [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/39-page-numbers.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo39.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/39-page-numbers.ts_

View File

@ -265,7 +265,7 @@ const paragraph = new Paragraph({
![Page Break Before in Word](https://user-images.githubusercontent.com/34742290/40176503-df3a8398-59db-11e8-8b9c-d719f13aa8b4.png) ![Page Break Before in Word](https://user-images.githubusercontent.com/34742290/40176503-df3a8398-59db-11e8-8b9c-d719f13aa8b4.png)
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo15.ts Example: https://github.com/dolanmiu/docx/blob/master/demo/15-page-break-before.ts
## Page break control ## Page break control

View File

@ -44,4 +44,4 @@ doc.add(paragraph);
doc.createParagraph("Some normal text"); doc.createParagraph("Some normal text");
``` ```
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo13.ts Example: https://github.com/dolanmiu/docx/blob/master/demo/13-xml-styles.ts

View File

@ -2,9 +2,9 @@
You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php). You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php).
>Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically. > Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically.
>This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?". > This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?".
>You have say yes to Word generate the content of all table of contents. > You have say yes to Word generate the content of all table of contents.
The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251). The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251).
@ -16,7 +16,7 @@ All you need to do is create a `TableOfContents` object and assign it to the doc
const toc = new TableOfContents("Summary", { const toc = new TableOfContents("Summary", {
hyperlink: true, hyperlink: true,
headingStyleRange: "1-5", headingStyleRange: "1-5",
stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)],
}); });
doc.addTableOfContents(toc); doc.addTableOfContents(toc);
@ -27,23 +27,23 @@ doc.addTableOfContents(toc);
Here is the list of all options that you can use to generate your tables of contents: Here is the list of all options that you can use to generate your tables of contents:
| Option | Type | TOC Field Switch | Description | | Option | Type | TOC Field Switch | Description |
| --- | --- | --- | --- | | ------------------------------- | ------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|captionLabel|string|`\a`|Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use ``\c`` to build a table of captions with labels and numbers.| | captionLabel | string | `\a` | Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use `\c` to build a table of captions with labels and numbers. |
|entriesFromBookmark|string|`\b`|Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument.| | entriesFromBookmark | string | `\b` | Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument. |
|captionLabelIncludingNumbers|string|`\c`|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.| | captionLabelIncludingNumbers | string | `\c` | Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field. |
|sequenceAndPageNumbersSeparator|string|`\d`|When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).| | sequenceAndPageNumbersSeparator | string | `\d` | When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-). |
|tcFieldIdentifier|string|`\f`|Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter).| | tcFieldIdentifier | string | `\f` | Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter). |
|hyperlink|boolean|`\h`|Makes the table of contents entries hyperlinks.| | hyperlink | boolean | `\h` | Makes the table of contents entries hyperlinks. |
|tcFieldLevelRange|string|`\l`|Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.| | tcFieldLevelRange | string | `\l` | Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped. |
|pageNumbersEntryLevelsRange|string|`\n`|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`.| | pageNumbersEntryLevelsRange | string | `\n` | Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`. |
|headingStyleRange|string|`\o`|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.| | headingStyleRange | string | `\o` | Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed. |
|entryAndPageNumberSeparator|string|`\p`|`text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.| | entryAndPageNumberSeparator | string | `\p` | `text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots. |
|seqFieldIdentifierForPrefix|string|`\s`|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field.| | seqFieldIdentifierForPrefix | string | `\s` | For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field. |
|stylesWithLevels|StyleLevel[]|`\t`| Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`.| | stylesWithLevels | StyleLevel[] | `\t` | Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`. |
|useAppliedParagraphOutlineLevel|boolean|`\u`|Uses the applied paragraph outline level.| | useAppliedParagraphOutlineLevel | boolean | `\u` | Uses the applied paragraph outline level. |
|preserveTabInEntries|boolean|`\w`|Preserves tab entries within table entries.| | preserveTabInEntries | boolean | `\w` | Preserves tab entries within table entries. |
|preserveNewLineInEntries|boolean|`\x`|Preserves newline characters within table entries.| | preserveNewLineInEntries | boolean | `\x` | Preserves newline characters within table entries. |
|hideTabAndPageNumbersInWebView|boolean|`\z`|Hides tab leader and page numbers in web page view (§17.18.102).| | hideTabAndPageNumbersInWebView | boolean | `\z` | Hides tab leader and page numbers in web page view (§17.18.102). |
## Examples ## Examples
@ -53,7 +53,7 @@ Here is the list of all options that you can use to generate your tables of cont
const toc = new TableOfContents("Summary", { const toc = new TableOfContents("Summary", {
hyperlink: true, hyperlink: true,
headingStyleRange: "1-5", headingStyleRange: "1-5",
stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)],
}); });
doc.addTableOfContents(toc); doc.addTableOfContents(toc);
@ -71,6 +71,6 @@ doc.add(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pag
### Complete example ### Complete example
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo28.ts ':include') [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/28-table-of-contents.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo28.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/28-table-of-contents.ts_

View File

@ -1,306 +1,387 @@
# 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/demo4.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/demo4.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/4-basic-table.ts_
### Custom borders ### Custom borders
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/demo20.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/demo20.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/20-table-cell-borders.ts_
### Adding images ### Adding images
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/demo24.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/demo24.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/demo36.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/demo36.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/36-image-to-table-cell.ts_
### Alignment of text in a cell ### Alignment of text in a cell
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/demo31.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/31-tables.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo31.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/demo32.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/demo32.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/32-merge-table-cells.ts_
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo41.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/demo41.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/demo43.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/demo43.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/demo34.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/demo34.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/34-floating-tables.ts_

View File

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

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

@ -4,30 +4,33 @@ import { Compiler } from "./next-compiler";
export class Packer { export class Packer {
public static async toBuffer(file: File, prettify?: boolean): Promise<Buffer> { public static async toBuffer(file: File, prettify?: boolean): Promise<Buffer> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "nodebuffer", type: "nodebuffer",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as Buffer; compression: "DEFLATE",
});
return zipData; return zipData;
} }
public static async toBase64String(file: File, prettify?: boolean): Promise<string> { public static async toBase64String(file: File, prettify?: boolean): Promise<string> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "base64", type: "base64",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as string; compression: "DEFLATE",
});
return zipData; return zipData;
} }
public static async toBlob(file: File, prettify?: boolean): Promise<Blob> { public static async toBlob(file: File, prettify?: boolean): Promise<Blob> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "blob", type: "blob",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as Blob; compression: "DEFLATE",
});
return zipData; return zipData;
} }

View File

@ -22,9 +22,6 @@ describe("Body", () => {
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:body": [ "w:body": [
{
"w:p": {},
},
{ {
"w:sectPr": [ "w:sectPr": [
{ "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } }, { "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } },

View File

@ -1,5 +1,5 @@
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; import { TableOfContents } from "../..";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties"; import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
export class Body extends XmlComponent { export class Body extends XmlComponent {
@ -18,9 +18,6 @@ export class Body extends XmlComponent {
* @param options new section options * @param options new section options
*/ */
public addSection(options: SectionPropertiesOptions): void { public addSection(options: SectionPropertiesOptions): void {
const currentSection = this.sections.pop() as SectionProperties;
this.root.push(this.createSectionParagraph(currentSection));
this.sections.push(new SectionProperties(options)); this.sections.push(new SectionProperties(options));
} }
@ -39,12 +36,4 @@ export class Body extends XmlComponent {
public getTablesOfContents(): TableOfContents[] { public getTablesOfContents(): TableOfContents[] {
return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[]; return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[];
} }
private createSectionParagraph(section: SectionProperties): Paragraph {
const paragraph = new Paragraph({});
const properties = new ParagraphProperties({});
properties.addChildElement(section);
paragraph.addChildElement(properties);
return paragraph;
}
} }

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", () => {
@ -20,8 +20,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
}); });
it("should create with correct headers and footers", () => { it("should create with correct headers and footers", () => {
@ -39,8 +39,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
}); });
it("should create with first headers and footers", () => { it("should create with first headers and footers", () => {
@ -58,8 +58,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
}); });
it("should create with correct headers", () => { it("should create with correct headers", () => {
@ -81,13 +81,13 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even"); expect(tree["w:body"][0]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even"); expect(tree["w:body"][0]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
}); });
}); });
@ -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,7 +2,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { NumberOfPages, Page } from "./page-number"; import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
describe("Page", () => { describe("Page", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
@ -21,3 +21,12 @@ describe("NumberOfPages", () => {
}); });
}); });
}); });
describe("NumberOfPagesSection", () => {
describe("#constructor()", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new NumberOfPagesSection());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] });
});
});
});

View File

@ -20,3 +20,11 @@ export class NumberOfPages extends XmlComponent {
this.root.push("NUMPAGES"); this.root.push("NUMPAGES");
} }
} }
export class NumberOfPagesSection extends XmlComponent {
constructor() {
super("w:instrText");
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
this.root.push("SECTIONPAGES");
}
}

View File

@ -284,6 +284,22 @@ describe("Run", () => {
}); });
}); });
describe("#numberOfTotalPagesSection", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});
run.numberOfTotalPagesSection();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:fldChar": { _attr: { "w:fldCharType": "begin" } } },
{ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] },
{ "w:fldChar": { _attr: { "w:fldCharType": "separate" } } },
{ "w:fldChar": { _attr: { "w:fldCharType": "end" } } },
],
});
});
});
describe("#pageNumber", () => { describe("#pageNumber", () => {
it("should set the run to the RTL mode", () => { it("should set the run to the RTL mode", () => {
const run = new Run({}); const run = new Run({});

View File

@ -21,7 +21,7 @@ import {
SizeComplexScript, SizeComplexScript,
Strike, Strike,
} from "./formatting"; } from "./formatting";
import { NumberOfPages, Page } from "./page-number"; import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { RunProperties } from "./properties"; import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts"; import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script"; import { SubScript, SuperScript } from "./script";
@ -161,4 +161,12 @@ export class Run extends XmlComponent {
this.root.push(new End()); this.root.push(new End());
return this; return this;
} }
public numberOfTotalPagesSection(): Run {
this.root.push(new Begin());
this.root.push(new NumberOfPagesSection());
this.root.push(new Separate());
this.root.push(new End());
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);
for (const child of options.children) {
this.root.push(child);
} }
public add(item: Paragraph | Table): TableCell { if (options.verticalAlign) {
this.root.push(item); this.properties.setVerticalAlign(options.verticalAlign);
}
return this; 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", () => {
], ],
}); });
}); });
it("should create with cant split", () => {
const tableRow = new TableRow({
children: [],
cantSplit: true,
}); });
const tree = new Formatter().format(tableRow);
describe("#getCell", () => { expect(tree).to.deep.equal({
it("should get the cell", () => { "w:tr": [
const cell = new TableCell(); {
const tableRow = new TableRow([cell]); "w:trPr": [
{
expect(tableRow.getCell(0)).to.equal(cell); "w:cantSplit": {
}); _attr: {
"w:val": true,
it("should throw an error if index is out of bounds", () => { },
const cell = new TableCell(); },
const tableRow = new TableRow([cell]); },
],
expect(() => tableRow.getCell(1)).to.throw(); },
],
}); });
}); });
describe("#addGridSpan", () => { it("should create with table header", () => {
it("should merge the cell", () => { const tableRow = new TableRow({
const tableRow = new TableRow([new TableCell(), new TableCell()]); children: [],
tableHeader: true,
tableRow.addGridSpan(0, 2); });
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("#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));
for (const child of options.children) {
this.root.push(child);
} }
public getCell(index: number): TableCell { if (options.cantSplit) {
const cell = this.cells[index];
if (!cell) {
throw Error("Index out of bounds when trying to get cell on row");
}
return cell;
}
public addGridSpan(index: number, cellSpan: number): TableCell {
const remainCell = this.cells[index];
remainCell.addGridSpan(cellSpan);
this.cells.splice(index + 1, cellSpan - 1);
this.root.splice(index + 2, cellSpan - 1);
return remainCell;
}
public mergeCells(startIndex: number, endIndex: number): TableCell {
const cellSpan = endIndex - startIndex + 1;
return this.addGridSpan(startIndex, cellSpan);
}
public setCantSplit(): TableRow {
this.properties.setCantSplit(); this.properties.setCantSplit();
return this;
} }
public setTableHeader(): TableRow { if (options.tableHeader) {
this.properties.setTableHeader(); this.properties.setTableHeader();
return this;
} }
public setHeight(height: number, rule: HeightRule): TableRow { if (options.height) {
this.properties.setHeight(height, rule); this.properties.setHeight(options.height.height, options.height.rule);
}
}
return this; public get CellCount(): number {
return this.options.children.length;
}
public get Children(): TableCell[] {
return this.options.children;
}
public addCellToIndex(cell: TableCell, index: number): void {
// Offset because properties is also in root.
this.root.splice(index + 1, 0, cell);
} }
} }

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({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
layout: TableLayoutType.FIXED,
}); });
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 tree = new Formatter().format(table);
const cell = (c) => ({ expect(tree)
"w:tc": [ .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:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, c] }] }], "w:tblW": {
_attr: {
"w:type": "pct",
"w:w": "100%",
}, },
],
});
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({
rows: 2,
columns: 2,
});
table.getCell(0, 0).add(new Paragraph("A1"));
table.getCell(0, 1).add(new Paragraph("B1"));
table.getCell(1, 0).add(new Paragraph("A2"));
table.getCell(1, 1).add(new Paragraph("B2"));
const tree = new Formatter().format(table);
const cell = (c) => ({
"w:tc": [
{
"w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, c] }] }],
}, },
], { "w:tblLayout": { _attr: { "w:type": "fixed" } } },
});
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")] },
], ],
}); });
}); });
}); });
// 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);
}
} }