Merge pull request #553 from wangfengming/master
Fix: `rowSpan` does not work correctly
This commit is contained in:
@ -2,7 +2,7 @@
|
|||||||
// Also includes an example on how to center tables
|
// Also includes an example on how to center tables
|
||||||
// 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, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
import { AlignmentType, BorderStyle, Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
||||||
|
|
||||||
const doc = new Document();
|
const doc = new Document();
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ const table5 = new Table({
|
|||||||
new TableRow({
|
new TableRow({
|
||||||
children: [
|
children: [
|
||||||
new TableCell({
|
new TableCell({
|
||||||
children: [],
|
children: [new Paragraph("1,0")],
|
||||||
}),
|
}),
|
||||||
new TableCell({
|
new TableCell({
|
||||||
children: [new Paragraph("1,2")],
|
children: [new Paragraph("1,2")],
|
||||||
@ -195,10 +195,137 @@ const table5 = new Table({
|
|||||||
new TableRow({
|
new TableRow({
|
||||||
children: [
|
children: [
|
||||||
new TableCell({
|
new TableCell({
|
||||||
children: [],
|
children: [new Paragraph("2,0")],
|
||||||
}),
|
}),
|
||||||
new TableCell({
|
new TableCell({
|
||||||
children: [],
|
children: [new Paragraph("2,1")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const borders = {
|
||||||
|
top: {
|
||||||
|
style: BorderStyle.DASH_SMALL_GAP,
|
||||||
|
size: 1,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
style: BorderStyle.DASH_SMALL_GAP,
|
||||||
|
size: 1,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
style: BorderStyle.DASH_SMALL_GAP,
|
||||||
|
size: 1,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
style: BorderStyle.DASH_SMALL_GAP,
|
||||||
|
size: 1,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const table6 = new Table({
|
||||||
|
rows: [
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
borders,
|
||||||
|
children: [new Paragraph("0,0")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
borders,
|
||||||
|
children: [new Paragraph("0,1")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
borders,
|
||||||
|
children: [new Paragraph("1,1")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
borders,
|
||||||
|
children: [new Paragraph("2,0")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const table7 = new Table({
|
||||||
|
rows: [
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,1")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,2")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,3")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,0")],
|
||||||
|
columnSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,3")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,0")],
|
||||||
|
columnSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,2")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,3")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,1")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,3")],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -222,10 +349,14 @@ doc.addSection({
|
|||||||
heading: HeadingLevel.HEADING_2,
|
heading: HeadingLevel.HEADING_2,
|
||||||
}),
|
}),
|
||||||
table3,
|
table3,
|
||||||
new Paragraph("Merging columns"),
|
new Paragraph("Merging columns 1"),
|
||||||
table4,
|
table4,
|
||||||
new Paragraph("More Merging columns"),
|
new Paragraph("Merging columns 2"),
|
||||||
table5,
|
table5,
|
||||||
|
new Paragraph("Merging columns 3"),
|
||||||
|
table6,
|
||||||
|
new Paragraph("Merging columns 4"),
|
||||||
|
table7,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -182,4 +182,96 @@ describe("TableRow", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#rootIndexToColumnIndex", () => {
|
||||||
|
it("should get the correct virtual column index by root index", () => {
|
||||||
|
const tableRow = new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tableRow.rootIndexToColumnIndex(1)).to.equal(0);
|
||||||
|
expect(tableRow.rootIndexToColumnIndex(2)).to.equal(3);
|
||||||
|
expect(tableRow.rootIndexToColumnIndex(3)).to.equal(4);
|
||||||
|
expect(tableRow.rootIndexToColumnIndex(4)).to.equal(5);
|
||||||
|
|
||||||
|
expect(() => tableRow.rootIndexToColumnIndex(0)).to.throw(`cell 'rootIndex' should between 1 to 4`);
|
||||||
|
expect(() => tableRow.rootIndexToColumnIndex(5)).to.throw(`cell 'rootIndex' should between 1 to 4`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#columnIndexToRootIndex", () => {
|
||||||
|
it("should get the correct root index by virtual column index", () => {
|
||||||
|
const tableRow = new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tableRow.columnIndexToRootIndex(0)).to.equal(1);
|
||||||
|
expect(tableRow.columnIndexToRootIndex(1)).to.equal(1);
|
||||||
|
expect(tableRow.columnIndexToRootIndex(2)).to.equal(1);
|
||||||
|
|
||||||
|
expect(tableRow.columnIndexToRootIndex(3)).to.equal(2);
|
||||||
|
expect(tableRow.columnIndexToRootIndex(4)).to.equal(3);
|
||||||
|
|
||||||
|
expect(tableRow.columnIndexToRootIndex(5)).to.equal(4);
|
||||||
|
expect(tableRow.columnIndexToRootIndex(6)).to.equal(4);
|
||||||
|
expect(tableRow.columnIndexToRootIndex(7)).to.equal(4);
|
||||||
|
|
||||||
|
expect(() => tableRow.columnIndexToRootIndex(-1)).to.throw(`cell 'columnIndex' should not less than zero`);
|
||||||
|
expect(() => tableRow.columnIndexToRootIndex(8)).to.throw(`cell 'columnIndex' should not great than 7`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow end new cell index", () => {
|
||||||
|
const tableRow = new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("test")],
|
||||||
|
columnSpan: 3,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tableRow.columnIndexToRootIndex(8, true)).to.equal(5);
|
||||||
|
expect(() => tableRow.columnIndexToRootIndex(9, true)).to.throw(`cell 'columnIndex' should not great than 8`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -46,8 +46,52 @@ export class TableRow extends XmlComponent {
|
|||||||
return this.options.children;
|
return this.options.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get cells(): TableCell[] {
|
||||||
|
return this.root.filter((xmlComponent) => xmlComponent instanceof TableCell);
|
||||||
|
}
|
||||||
|
|
||||||
public addCellToIndex(cell: TableCell, index: number): void {
|
public addCellToIndex(cell: TableCell, index: number): void {
|
||||||
// Offset because properties is also in root.
|
// Offset because properties is also in root.
|
||||||
this.root.splice(index + 1, 0, cell);
|
this.root.splice(index + 1, 0, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addCellToColumnIndex(cell: TableCell, columnIndex: number): void {
|
||||||
|
const rootIndex = this.columnIndexToRootIndex(columnIndex, true);
|
||||||
|
this.addCellToIndex(cell, rootIndex - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public rootIndexToColumnIndex(rootIndex: number): number {
|
||||||
|
// convert the root index to the virtual column index
|
||||||
|
if (rootIndex < 1 || rootIndex >= this.root.length) {
|
||||||
|
throw new Error(`cell 'rootIndex' should between 1 to ${this.root.length - 1}`);
|
||||||
|
}
|
||||||
|
let colIdx = 0;
|
||||||
|
// Offset because properties is also in root.
|
||||||
|
for (let rootIdx = 1; rootIdx < rootIndex; rootIdx++) {
|
||||||
|
const cell = this.root[rootIdx] as TableCell;
|
||||||
|
colIdx += cell.options.columnSpan || 1;
|
||||||
|
}
|
||||||
|
return colIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public columnIndexToRootIndex(columnIndex: number, allowEndNewCell: boolean = false): number {
|
||||||
|
// convert the virtual column index to the root index
|
||||||
|
// `allowEndNewCell` for get index to inert new cell
|
||||||
|
if (columnIndex < 0) {
|
||||||
|
throw new Error(`cell 'columnIndex' should not less than zero`);
|
||||||
|
}
|
||||||
|
let colIdx = 0;
|
||||||
|
// Offset because properties is also in root.
|
||||||
|
let rootIdx = 1;
|
||||||
|
const endRootIndex = allowEndNewCell ? this.root.length : this.root.length - 1;
|
||||||
|
while (colIdx <= columnIndex) {
|
||||||
|
if (rootIdx > endRootIndex) {
|
||||||
|
throw new Error(`cell 'columnIndex' should not great than ${colIdx - 1}`);
|
||||||
|
}
|
||||||
|
const cell = this.root[rootIdx] as TableCell;
|
||||||
|
rootIdx += 1;
|
||||||
|
colIdx += (cell && cell.options.columnSpan) || 1;
|
||||||
|
}
|
||||||
|
return rootIdx - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,6 +188,72 @@ describe("Table", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("creates a table with the correct columnSpan and rowSpan", () => {
|
||||||
|
const table = new Table({
|
||||||
|
rows: [
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("hello")],
|
||||||
|
columnSpan: 2,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("hello")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("hello")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("hello")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const tree = new Formatter().format(table);
|
||||||
|
const cellP = { "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "hello"] }] }] };
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tbl": [
|
||||||
|
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] },
|
||||||
|
{
|
||||||
|
"w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:tr": [
|
||||||
|
{
|
||||||
|
"w:tc": [{ "w:tcPr": [{ "w:gridSpan": { _attr: { "w:val": 2 } } }] }, cellP],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:tr": [
|
||||||
|
{
|
||||||
|
"w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "restart" } } }] }, cellP],
|
||||||
|
},
|
||||||
|
{ "w:tc": [cellP] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:tr": [
|
||||||
|
{
|
||||||
|
"w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": {} }],
|
||||||
|
},
|
||||||
|
{ "w:tc": [cellP] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
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: [
|
rows: [
|
||||||
|
@ -78,27 +78,28 @@ export class Table extends XmlComponent {
|
|||||||
this.root.push(row);
|
this.root.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const row of rows) {
|
rows.forEach((row, rowIndex) => {
|
||||||
row.Children.forEach((cell, cellIndex) => {
|
row.cells.forEach((cell, cellIndex) => {
|
||||||
const column = rows.map((r) => r.Children[cellIndex]);
|
|
||||||
// Row Span has to be added in this method and not the constructor because it needs to know information about the column which happens after Table Cell construction
|
// Row Span 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
|
// 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) {
|
if (cell.options.rowSpan && cell.options.rowSpan > 1) {
|
||||||
const thisCellsColumnIndex = column.indexOf(cell);
|
const columnIndex = row.rootIndexToColumnIndex(cellIndex + 1);
|
||||||
const endColumnIndex = thisCellsColumnIndex + (cell.options.rowSpan - 1);
|
const startRowIndex = rowIndex + 1;
|
||||||
|
const endRowIndex = rowIndex + (cell.options.rowSpan - 1);
|
||||||
for (let i = thisCellsColumnIndex + 1; i <= endColumnIndex; i++) {
|
for (let i = startRowIndex; i <= endRowIndex; i++) {
|
||||||
rows[i].addCellToIndex(
|
rows[i].addCellToColumnIndex(
|
||||||
new TableCell({
|
new TableCell({
|
||||||
|
columnSpan: cell.options.columnSpan,
|
||||||
|
borders: cell.options.borders,
|
||||||
children: [],
|
children: [],
|
||||||
verticalMerge: VerticalMergeType.CONTINUE,
|
verticalMerge: VerticalMergeType.CONTINUE,
|
||||||
}),
|
}),
|
||||||
i,
|
columnIndex,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
if (float) {
|
if (float) {
|
||||||
this.properties.setTableFloatProperties(float);
|
this.properties.setTableFloatProperties(float);
|
||||||
|
Reference in New Issue
Block a user