8
0
Эх сурвалжийг харах

Other: Refactor commands.

Maciej Gołaszewski 7 жил өмнө
parent
commit
f42e0ab143

+ 14 - 18
packages/ckeditor5-table/src/commands/removecolumncommand.js

@@ -11,6 +11,7 @@ import Command from '@ckeditor/ckeditor5-core/src/command';
 
 
 import TableWalker from '../tablewalker';
 import TableWalker from '../tablewalker';
 import TableUtils from '../tableutils';
 import TableUtils from '../tableutils';
+import { updateNumericAttribute } from './utils';
 
 
 /**
 /**
  * The split cell command.
  * The split cell command.
@@ -44,31 +45,26 @@ export default class RemoveColumnCommand extends Command {
 		const tableRow = tableCell.parent;
 		const tableRow = tableCell.parent;
 		const table = tableRow.parent;
 		const table = tableRow.parent;
 
 
-		model.change( writer => {
-			const headingColumns = parseInt( table.getAttribute( 'headingColumns' ) || 0 );
-			const rowIndex = table.getChildIndex( tableRow );
+		const headingColumns = parseInt( table.getAttribute( 'headingColumns' ) || 0 );
+		const row = table.getChildIndex( tableRow );
 
 
-			if ( headingColumns && rowIndex <= headingColumns ) {
-				writer.setAttribute( 'headingColumns', headingColumns - 1, table );
-			}
+		// Cache the table before removing or updating colspans.
+		const tableMap = [ ...new TableWalker( table ) ];
 
 
-			// Cache the table before removing or updating colspans.
-			const tableMap = [ ...new TableWalker( table ) ];
+		// Get column index of removed column.
+		const cellData = tableMap.find( value => value.cell === tableCell );
+		const removedColumn = cellData.column;
 
 
-			// Get column index of removed column.
-			const cellData = tableMap.find( value => value.cell === tableCell );
-			const removedColumn = cellData.column;
+		model.change( writer => {
+			// Update heading columns attribute if removing a row from head section.
+			if ( headingColumns && row <= headingColumns ) {
+				writer.setAttribute( 'headingColumns', headingColumns - 1, table );
+			}
 
 
 			for ( const { cell, column, colspan } of tableMap ) {
 			for ( const { cell, column, colspan } of tableMap ) {
 				// If colspaned cell overlaps removed column decrease it's span.
 				// If colspaned cell overlaps removed column decrease it's span.
 				if ( column <= removedColumn && colspan > 1 && column + colspan > removedColumn ) {
 				if ( column <= removedColumn && colspan > 1 && column + colspan > removedColumn ) {
-					const colspanToSet = colspan - 1;
-
-					if ( colspanToSet > 1 ) {
-						writer.setAttribute( 'colspan', colspanToSet, cell );
-					} else {
-						writer.removeAttribute( 'colspan', cell );
-					}
+					updateNumericAttribute( 'colspan', colspan - 1, cell, writer );
 				} else if ( column === removedColumn ) {
 				} else if ( column === removedColumn ) {
 					// The cell in removed column has colspan of 1.
 					// The cell in removed column has colspan of 1.
 					writer.remove( cell );
 					writer.remove( cell );

+ 30 - 56
packages/ckeditor5-table/src/commands/removerowcommand.js

@@ -4,14 +4,16 @@
  */
  */
 
 
 /**
 /**
- * @module table/commands/removerow
+ * @module table/commands/removerowcommand
  */
  */
 
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Command from '@ckeditor/ckeditor5-core/src/command';
-import TableWalker from '../tablewalker';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 
 
+import TableWalker from '../tablewalker';
+import { updateNumericAttribute } from './utils';
+
 /**
 /**
  * The remove row command.
  * The remove row command.
  *
  *
@@ -35,80 +37,52 @@ export default class RemoveRowCommand extends Command {
 	 */
 	 */
 	execute() {
 	execute() {
 		const model = this.editor.model;
 		const model = this.editor.model;
-		const document = model.document;
-		const selection = document.selection;
+		const selection = model.document.selection;
 
 
 		const firstPosition = selection.getFirstPosition();
 		const firstPosition = selection.getFirstPosition();
 		const tableCell = firstPosition.parent;
 		const tableCell = firstPosition.parent;
 		const tableRow = tableCell.parent;
 		const tableRow = tableCell.parent;
-
 		const table = tableRow.parent;
 		const table = tableRow.parent;
 
 
-		const rowIndex = tableRow.index;
+		const currentRow = table.getChildIndex( tableRow );
+		const headingRows = parseInt( table.getAttribute( 'headingRows' ) || 0 );
 
 
 		model.change( writer => {
 		model.change( writer => {
-			const headingRows = ( table.getAttribute( 'headingRows' ) || 0 );
-
-			if ( headingRows && rowIndex <= headingRows ) {
-				writer.setAttribute( 'headingRows', headingRows - 1, table );
-			}
-
-			const cellsToMove = {};
-
-			// Cache cells from current row that have rowspan
-			for ( const tableWalkerValue of new TableWalker( table, { startRow: rowIndex, endRow: rowIndex } ) ) {
-				if ( tableWalkerValue.rowspan > 1 ) {
-					cellsToMove[ tableWalkerValue.column ] = {
-						cell: tableWalkerValue.cell,
-						updatedRowspan: tableWalkerValue.rowspan - 1
-					};
-				}
+			if ( headingRows && currentRow <= headingRows ) {
+				updateNumericAttribute( 'headingRows', headingRows - 1, table, writer, 0 );
 			}
 			}
 
 
-			// Update rowspans on cells abover removed row
-			for ( const tableWalkerValue of new TableWalker( table, { endRow: rowIndex - 1 } ) ) {
-				const row = tableWalkerValue.row;
-				const rowspan = tableWalkerValue.rowspan;
-				const cell = tableWalkerValue.cell;
+			const tableMap = [ ...new TableWalker( table, { endRow: currentRow } ) ];
 
 
-				if ( row + rowspan > rowIndex ) {
-					const rowspanToSet = rowspan - 1;
+			const cellsToMove = new Map();
 
 
-					if ( rowspanToSet > 1 ) {
-						writer.setAttribute( 'rowspan', rowspanToSet, cell );
-					} else {
-						writer.removeAttribute( 'rowspan', cell );
-					}
-				}
-			}
+			// Get cells from removed row that are spanned over multiple rows.
+			tableMap
+				.filter( ( { row, rowspan } ) => row === currentRow && rowspan > 1 )
+				.map( ( { column, cell, rowspan } ) => cellsToMove.set( column, { cell, rowspanToSet: rowspan - 1 } ) );
 
 
-			let previousCell;
+			// Reduce rowspan on cells that are above removed row and overlaps removed row.
+			tableMap
+				.filter( ( { row, rowspan } ) => row <= currentRow - 1 && row + rowspan > currentRow )
+				.map( ( { cell, rowspan } ) => updateNumericAttribute( 'rowspan', rowspan - 1, cell, writer ) );
 
 
 			// Move cells to another row
 			// Move cells to another row
-			for ( const tableWalkerValue of new TableWalker( table, {
-				includeSpanned: true,
-				startRow: rowIndex + 1,
-				endRow: rowIndex + 1
-			} ) ) {
-				const cellToMoveData = cellsToMove[ tableWalkerValue.column ];
-
-				if ( cellToMoveData ) {
-					const targetPosition = previousCell ? Position.createAfter( previousCell ) :
-						Position.createAt( table.getChild( tableWalkerValue.row ) );
+			const targetRow = currentRow + 1;
+			const tableWalker = new TableWalker( table, { includeSpanned: true, startRow: targetRow, endRow: targetRow } );
 
 
-					writer.move( Range.createOn( cellToMoveData.cell ), targetPosition );
+			let previousCell;
 
 
-					const rowspanToSet = cellToMoveData.updatedRowspan;
+			for ( const { row, column, cell } of [ ...tableWalker ] ) {
+				if ( cellsToMove.has( column ) ) {
+					const { cell: cellToMove, rowspanToSet } = cellsToMove.get( column );
+					const targetPosition = previousCell ? Position.createAfter( previousCell ) : Position.createAt( table.getChild( row ) );
 
 
-					if ( rowspanToSet > 1 ) {
-						writer.setAttribute( 'rowspan', rowspanToSet, cellToMoveData.cell );
-					} else {
-						writer.removeAttribute( 'rowspan', cellToMoveData.cell );
-					}
+					writer.move( Range.createOn( cellToMove ), targetPosition );
+					updateNumericAttribute( 'rowspan', rowspanToSet, cellToMove, writer );
 
 
-					previousCell = cellToMoveData.cell;
+					previousCell = cellToMove;
 				} else {
 				} else {
-					previousCell = tableWalkerValue.cell;
+					previousCell = cell;
 				}
 				}
 			}
 			}
 
 

+ 54 - 61
packages/ckeditor5-table/src/commands/settableheaderscommand.js

@@ -10,7 +10,7 @@
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 
 
-import { getParentTable } from './utils';
+import { getParentTable, updateNumericAttribute } from './utils';
 import TableWalker from '../tablewalker';
 import TableWalker from '../tablewalker';
 
 
 /**
 /**
@@ -40,59 +40,66 @@ export default class SetTableHeadersCommand extends Command {
 		const doc = model.document;
 		const doc = model.document;
 		const selection = doc.selection;
 		const selection = doc.selection;
 
 
-		const rows = parseInt( options.rows ) || 0;
-		const columns = parseInt( options.columns ) || 0;
+		const rowsToSet = parseInt( options.rows ) || 0;
 
 
 		const table = getParentTable( selection.getFirstPosition() );
 		const table = getParentTable( selection.getFirstPosition() );
 
 
 		model.change( writer => {
 		model.change( writer => {
-			const oldValue = parseInt( table.getAttribute( 'headingRows' ) || 0 );
+			const currentHeadingRows = parseInt( table.getAttribute( 'headingRows' ) || 0 );
 
 
-			if ( oldValue !== rows && rows > 0 ) {
-				const cellsToSplit = [];
+			if ( currentHeadingRows !== rowsToSet && rowsToSet > 0 ) {
+				// Changing heading rows requires to check if any of a heading cell is overlaping vertically the table head.
+				// Any table cell that has a rowspan attribute > 1 will not exceed the table head so we need to fix it in rows below.
+				const cellsToSplit = getOverlappingCells( table, rowsToSet, currentHeadingRows );
 
 
-				const startAnalysisRow = rows > oldValue ? oldValue : 0;
-
-				for ( const tableWalkerValue of new TableWalker( table, { startRow: startAnalysisRow, endRow: rows } ) ) {
-					const rowspan = tableWalkerValue.rowspan;
-					const row = tableWalkerValue.row;
-
-					if ( rowspan > 1 && row + rowspan > rows ) {
-						cellsToSplit.push( tableWalkerValue );
-					}
-				}
-
-				for ( const tableWalkerValue of cellsToSplit ) {
-					splitVertically( tableWalkerValue.cell, rows, writer );
+				for ( const cell of cellsToSplit ) {
+					splitVertically( cell, rowsToSet, writer );
 				}
 				}
 			}
 			}
 
 
-			updateTableAttribute( table, 'headingRows', rows, writer );
-			updateTableAttribute( table, 'headingColumns', columns, writer );
+			const columnsToSet = parseInt( options.columns ) || 0;
+			updateTableAttribute( table, 'headingColumns', columnsToSet, writer );
+			updateTableAttribute( table, 'headingRows', rowsToSet, writer );
 		} );
 		} );
 	}
 	}
 }
 }
 
 
+// Returns cells that span beyond new heading section.
+//
+// @param {module:engine/model/element~Element} table Table to check
+// @param {Number} headingRowsToSet New heading rows attribute.
+// @param {Number} currentHeadingRows Current heading rows attribute.
+// @returns {Array.<module:engine/model/element~Element>}
+function getOverlappingCells( table, headingRowsToSet, currentHeadingRows ) {
+	const cellsToSplit = [];
+
+	const startAnalysisRow = headingRowsToSet > currentHeadingRows ? currentHeadingRows : 0;
+
+	const tableWalker = new TableWalker( table, { startRow: startAnalysisRow, endRow: headingRowsToSet } );
+
+	for ( const { row, rowspan, cell } of tableWalker ) {
+		if ( rowspan > 1 && row + rowspan > headingRowsToSet ) {
+			cellsToSplit.push( cell );
+		}
+	}
+
+	return cellsToSplit;
+}
+
 // @private
 // @private
 function updateTableAttribute( table, attributeName, newValue, writer ) {
 function updateTableAttribute( table, attributeName, newValue, writer ) {
 	const currentValue = parseInt( table.getAttribute( attributeName ) || 0 );
 	const currentValue = parseInt( table.getAttribute( attributeName ) || 0 );
 
 
 	if ( newValue !== currentValue ) {
 	if ( newValue !== currentValue ) {
-		if ( newValue > 0 ) {
-			writer.setAttribute( attributeName, newValue, table );
-		} else {
-			writer.removeAttribute( attributeName, table );
-		}
+		updateNumericAttribute( attributeName, newValue, table, writer, 0 );
 	}
 	}
 }
 }
 
 
-/**
- * Splits table cell vertically.
- *
- * @param {module:engine/model/element} tableCell
- * @param {Number} headingRows
- * @param writer
- */
+// Splits table cell vertically.
+//
+// @param {module:engine/model/element~Element} tableCell
+// @param {Number} headingRows
+// @param {module:engine/model/writer~Writer} writer
 function splitVertically( tableCell, headingRows, writer ) {
 function splitVertically( tableCell, headingRows, writer ) {
 	const tableRow = tableCell.parent;
 	const tableRow = tableCell.parent;
 	const table = tableRow.parent;
 	const table = tableRow.parent;
@@ -101,14 +108,6 @@ function splitVertically( tableCell, headingRows, writer ) {
 	const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) );
 	const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) );
 	const newRowspan = headingRows - rowIndex;
 	const newRowspan = headingRows - rowIndex;
 
 
-	const startRow = table.getChildIndex( tableRow );
-	const endRow = startRow + newRowspan;
-
-	const tableWalker = new TableWalker( table, { startRow, endRow, includeSpanned: true } );
-
-	let columnIndex;
-	let previousCell;
-
 	const attributes = {};
 	const attributes = {};
 
 
 	const spanToSet = rowspan - newRowspan;
 	const spanToSet = rowspan - newRowspan;
@@ -117,34 +116,28 @@ function splitVertically( tableCell, headingRows, writer ) {
 		attributes.rowspan = spanToSet;
 		attributes.rowspan = spanToSet;
 	}
 	}
 
 
-	const values = [ ...tableWalker ];
+	const startRow = table.getChildIndex( tableRow );
+	const endRow = startRow + newRowspan;
+	const tableMap = [ ...new TableWalker( table, { startRow, endRow, includeSpanned: true } ) ];
 
 
-	for ( const tableWalkerInfo of values ) {
-		if ( tableWalkerInfo.cell ) {
-			previousCell = tableWalkerInfo.cell;
-		}
+	let columnIndex;
 
 
-		if ( tableWalkerInfo.cell === tableCell ) {
-			columnIndex = tableWalkerInfo.column;
+	for ( const { row, column, cell, colspan, cellIndex } of tableMap ) {
+		if ( cell === tableCell ) {
+			columnIndex = column;
 
 
-			if ( tableWalkerInfo.colspan > 1 ) {
-				attributes.colspan = tableWalkerInfo.colspan;
+			if ( colspan > 1 ) {
+				attributes.colspan = colspan;
 			}
 			}
 		}
 		}
 
 
-		if ( columnIndex !== undefined && columnIndex === tableWalkerInfo.column && tableWalkerInfo.row === endRow ) {
-			const insertRow = table.getChild( tableWalkerInfo.row );
-
-			const position = previousCell.parent === insertRow ? Position.createAfter( previousCell ) : Position.createAt( insertRow );
+		if ( columnIndex !== undefined && columnIndex === column && row === endRow ) {
+			const tableRow = table.getChild( row );
 
 
-			writer.insertElement( 'tableCell', attributes, position );
+			writer.insertElement( 'tableCell', attributes, Position.createFromParentAndOffset( tableRow, cellIndex ) );
 		}
 		}
 	}
 	}
 
 
-	// Update rowspan attribute after iterating over current table.
-	if ( newRowspan > 1 ) {
-		writer.setAttribute( 'rowspan', newRowspan, tableCell );
-	} else {
-		writer.removeAttribute( 'rowspan', tableCell );
-	}
+	// Update the rowspan attribute after updating table.
+	updateNumericAttribute( 'rowspan', newRowspan, tableCell, writer );
 }
 }

+ 17 - 0
packages/ckeditor5-table/src/commands/utils.js

@@ -24,3 +24,20 @@ export function getParentTable( position ) {
 		parent = parent.parent;
 		parent = parent.parent;
 	}
 	}
 }
 }
+
+/**
+ * Common method to update numeric value. If value is default one it will be unset.
+ *
+ * @param {String} key Attribute key.
+ * @param {*} value Attribute new value.
+ * @param {module:engine/model/item~Item} item Model item on which the attribute will be set.
+ * @param {module:engine/model/writer~Writer} writer
+ * @param {*} defaultValue Default attribute value if value is lower or equal then it will be unset.
+ */
+export function updateNumericAttribute( key, value, item, writer, defaultValue = 1 ) {
+	if ( value > defaultValue ) {
+		writer.setAttribute( key, value, item );
+	} else {
+		writer.removeAttribute( key, item );
+	}
+}

+ 2 - 6
packages/ckeditor5-table/src/tableutils.js

@@ -11,7 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 
 
 import TableWalker from './tablewalker';
 import TableWalker from './tablewalker';
-import { getParentTable } from './commands/utils';
+import { getParentTable, updateNumericAttribute } from './commands/utils';
 
 
 /**
 /**
  * The table utils plugin.
  * The table utils plugin.
@@ -164,11 +164,7 @@ export default class TableUtils extends Plugin {
 				const colspanOfInsertedCells = Math.floor( cellColspan / cellNumber );
 				const colspanOfInsertedCells = Math.floor( cellColspan / cellNumber );
 				const newColspan = ( cellColspan - colspanOfInsertedCells * cellNumber ) + colspanOfInsertedCells;
 				const newColspan = ( cellColspan - colspanOfInsertedCells * cellNumber ) + colspanOfInsertedCells;
 
 
-				if ( newColspan > 1 ) {
-					writer.setAttribute( 'colspan', newColspan, tableCell );
-				} else {
-					writer.removeAttribute( 'colspan', tableCell );
-				}
+				updateNumericAttribute( 'colspan', newColspan, tableCell, writer );
 
 
 				const attributes = colspanOfInsertedCells > 1 ? { colspan: colspanOfInsertedCells } : {};
 				const attributes = colspanOfInsertedCells > 1 ? { colspan: colspanOfInsertedCells } : {};
 
 

+ 4 - 0
packages/ckeditor5-table/src/tablewalker.js

@@ -183,6 +183,7 @@ export default class TableWalker {
 				column: this.column,
 				column: this.column,
 				rowspan: 1,
 				rowspan: 1,
 				colspan: 1,
 				colspan: 1,
+				cellIndex: this.cell,
 				cell: undefined,
 				cell: undefined,
 				table: this._tableData
 				table: this._tableData
 			};
 			};
@@ -219,6 +220,7 @@ export default class TableWalker {
 			column: this.column,
 			column: this.column,
 			rowspan,
 			rowspan,
 			colspan,
 			colspan,
+			cellIndex: this.cell,
 			table: this._tableData
 			table: this._tableData
 		};
 		};
 
 
@@ -282,6 +284,8 @@ export default class TableWalker {
  * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
  * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
  * @property {Number} [rowspan] The rowspan attribute of a cell - always defined even if model attribute is not present. Not set if
  * @property {Number} [rowspan] The rowspan attribute of a cell - always defined even if model attribute is not present. Not set if
  * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
  * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
+ * @property {Number} cellIndex The index of a current cell in parent row. When using `includeSpanned` option it will indicate next child
+ * index if #cell is empty (spanned cell).
  * @property {Object} table Table attributes
  * @property {Object} table Table attributes
  * @property {Object} table.headingRows The heading rows attribute of a table - always defined even if model attribute is not present.
  * @property {Object} table.headingRows The heading rows attribute of a table - always defined even if model attribute is not present.
  * @property {Object} table.headingColumns The heading columns attribute of a table - always defined even if model attribute is not present.
  * @property {Object} table.headingColumns The heading columns attribute of a table - always defined even if model attribute is not present.

+ 79 - 71
packages/ckeditor5-table/tests/tablewalker.js

@@ -57,26 +57,34 @@ describe( 'TableWalker', () => {
 			result.push( tableInfo );
 			result.push( tableInfo );
 		}
 		}
 
 
-		const formattedResult = result.map( ( { row, column, cell } ) => ( { row, column, data: cell && cell.getChild( 0 ).data } ) );
+		const formattedResult = result.map( ( { row, column, cell, cellIndex } ) => ( {
+			row,
+			column,
+			data: cell && cell.getChild( 0 ).data,
+			index: cellIndex
+		} ) );
 
 
 		expect( formattedResult ).to.deep.equal( expected );
 		expect( formattedResult ).to.deep.equal( expected );
 	}
 	}
 
 
 	it( 'should iterate over a table', () => {
 	it( 'should iterate over a table', () => {
 		testWalker( [
 		testWalker( [
-			[ '11', '12' ]
+			[ '00', '01' ],
+			[ '10', '11' ]
 		], [
 		], [
-			{ row: 0, column: 0, data: '11' },
-			{ row: 0, column: 1, data: '12' }
+			{ row: 0, column: 0, index: 0, data: '00' },
+			{ row: 0, column: 1, index: 1, data: '01' },
+			{ row: 1, column: 0, index: 0, data: '10' },
+			{ row: 1, column: 1, index: 1, data: '11' }
 		] );
 		] );
 	} );
 	} );
 
 
 	it( 'should properly output column indexes of a table that has colspans', () => {
 	it( 'should properly output column indexes of a table that has colspans', () => {
 		testWalker( [
 		testWalker( [
-			[ { colspan: 2, contents: '11' }, '13' ]
+			[ { colspan: 2, contents: '00' }, '13' ]
 		], [
 		], [
-			{ row: 0, column: 0, data: '11' },
-			{ row: 0, column: 2, data: '13' }
+			{ row: 0, column: 0, index: 0, data: '00' },
+			{ row: 0, column: 2, index: 1, data: '13' }
 		] );
 		] );
 	} );
 	} );
 
 
@@ -87,13 +95,13 @@ describe( 'TableWalker', () => {
 			[ '22' ],
 			[ '22' ],
 			[ '30', '31', '32' ]
 			[ '30', '31', '32' ]
 		], [
 		], [
-			{ row: 0, column: 0, data: '00' },
-			{ row: 0, column: 2, data: '02' },
-			{ row: 1, column: 2, data: '12' },
-			{ row: 2, column: 2, data: '22' },
-			{ row: 3, column: 0, data: '30' },
-			{ row: 3, column: 1, data: '31' },
-			{ row: 3, column: 2, data: '32' }
+			{ row: 0, column: 0, index: 0, data: '00' },
+			{ row: 0, column: 2, index: 1, data: '02' },
+			{ row: 1, column: 2, index: 0, data: '12' },
+			{ row: 2, column: 2, index: 0, data: '22' },
+			{ row: 3, column: 0, index: 0, data: '30' },
+			{ row: 3, column: 1, index: 1, data: '31' },
+			{ row: 3, column: 2, index: 2, data: '32' }
 		] );
 		] );
 	} );
 	} );
 
 
@@ -104,30 +112,18 @@ describe( 'TableWalker', () => {
 			[ '33' ],
 			[ '33' ],
 			[ '41', '42', '43' ]
 			[ '41', '42', '43' ]
 		], [
 		], [
-			{ row: 0, column: 0, data: '11' },
-			{ row: 0, column: 1, data: '12' },
-			{ row: 0, column: 2, data: '13' },
-			{ row: 1, column: 1, data: '22' },
-			{ row: 1, column: 2, data: '23' },
-			{ row: 2, column: 2, data: '33' },
-			{ row: 3, column: 0, data: '41' },
-			{ row: 3, column: 1, data: '42' },
-			{ row: 3, column: 2, data: '43' }
+			{ row: 0, column: 0, index: 0, data: '11' },
+			{ row: 0, column: 1, index: 1, data: '12' },
+			{ row: 0, column: 2, index: 2, data: '13' },
+			{ row: 1, column: 1, index: 0, data: '22' },
+			{ row: 1, column: 2, index: 1, data: '23' },
+			{ row: 2, column: 2, index: 0, data: '33' },
+			{ row: 3, column: 0, index: 0, data: '41' },
+			{ row: 3, column: 1, index: 1, data: '42' },
+			{ row: 3, column: 2, index: 2, data: '43' }
 		] );
 		] );
 	} );
 	} );
 
 
-	it( 'should output spanned cells at the end of a table', () => {
-		testWalker( [
-			[ '00', { rowspan: 2, contents: '01' } ],
-			[ '10' ]
-		], [
-			{ row: 0, column: 0, data: '00' },
-			{ row: 0, column: 1, data: '01' },
-			{ row: 1, column: 0, data: '10' },
-			{ row: 1, column: 1, data: undefined }
-		], { includeSpanned: true } );
-	} );
-
 	describe( 'option.startRow', () => {
 	describe( 'option.startRow', () => {
 		it( 'should start iterating from given row but with cell spans properly calculated', () => {
 		it( 'should start iterating from given row but with cell spans properly calculated', () => {
 			testWalker( [
 			testWalker( [
@@ -136,10 +132,10 @@ describe( 'TableWalker', () => {
 				[ '33' ],
 				[ '33' ],
 				[ '41', '42', '43' ]
 				[ '41', '42', '43' ]
 			], [
 			], [
-				{ row: 2, column: 2, data: '33' },
-				{ row: 3, column: 0, data: '41' },
-				{ row: 3, column: 1, data: '42' },
-				{ row: 3, column: 2, data: '43' }
+				{ row: 2, column: 2, index: 0, data: '33' },
+				{ row: 3, column: 0, index: 0, data: '41' },
+				{ row: 3, column: 1, index: 1, data: '42' },
+				{ row: 3, column: 2, index: 2, data: '43' }
 			], { startRow: 2 } );
 			], { startRow: 2 } );
 		} );
 		} );
 	} );
 	} );
@@ -152,15 +148,27 @@ describe( 'TableWalker', () => {
 				[ '33' ],
 				[ '33' ],
 				[ '41', '42', '43' ]
 				[ '41', '42', '43' ]
 			], [
 			], [
-				{ row: 0, column: 0, data: '11' },
-				{ row: 0, column: 2, data: '13' },
-				{ row: 1, column: 2, data: '23' },
-				{ row: 2, column: 2, data: '33' }
+				{ row: 0, column: 0, index: 0, data: '11' },
+				{ row: 0, column: 2, index: 1, data: '13' },
+				{ row: 1, column: 2, index: 0, data: '23' },
+				{ row: 2, column: 2, index: 0, data: '33' }
 			], { endRow: 2 } );
 			], { endRow: 2 } );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'option.includeSpanned', () => {
 	describe( 'option.includeSpanned', () => {
+		it( 'should output spanned cells at the end of a table', () => {
+			testWalker( [
+				[ '00', { rowspan: 2, contents: '01' } ],
+				[ '10' ]
+			], [
+				{ row: 0, column: 0, index: 0, data: '00' },
+				{ row: 0, column: 1, index: 1, data: '01' },
+				{ row: 1, column: 0, index: 0, data: '10' },
+				{ row: 1, column: 1, index: 1, data: undefined }
+			], { includeSpanned: true } );
+		} );
+
 		it( 'should output spanned cells as empty cell', () => {
 		it( 'should output spanned cells as empty cell', () => {
 			testWalker( [
 			testWalker( [
 				[ { colspan: 2, rowspan: 3, contents: '00' }, '02' ],
 				[ { colspan: 2, rowspan: 3, contents: '00' }, '02' ],
@@ -168,18 +176,18 @@ describe( 'TableWalker', () => {
 				[ '22' ],
 				[ '22' ],
 				[ '30', { colspan: 2, contents: '31' } ]
 				[ '30', { colspan: 2, contents: '31' } ]
 			], [
 			], [
-				{ row: 0, column: 0, data: '00' },
-				{ row: 0, column: 1, data: undefined },
-				{ row: 0, column: 2, data: '02' },
-				{ row: 1, column: 0, data: undefined },
-				{ row: 1, column: 1, data: undefined },
-				{ row: 1, column: 2, data: '12' },
-				{ row: 2, column: 0, data: undefined },
-				{ row: 2, column: 1, data: undefined },
-				{ row: 2, column: 2, data: '22' },
-				{ row: 3, column: 0, data: '30' },
-				{ row: 3, column: 1, data: '31' },
-				{ row: 3, column: 2, data: undefined }
+				{ row: 0, column: 0, index: 0, data: '00' },
+				{ row: 0, column: 1, index: 1, data: undefined },
+				{ row: 0, column: 2, index: 1, data: '02' },
+				{ row: 1, column: 0, index: 0, data: undefined },
+				{ row: 1, column: 1, index: 0, data: undefined },
+				{ row: 1, column: 2, index: 0, data: '12' },
+				{ row: 2, column: 0, index: 0, data: undefined },
+				{ row: 2, column: 1, index: 0, data: undefined },
+				{ row: 2, column: 2, index: 0, data: '22' },
+				{ row: 3, column: 0, index: 0, data: '30' },
+				{ row: 3, column: 1, index: 1, data: '31' },
+				{ row: 3, column: 2, index: 2, data: undefined }
 			], { includeSpanned: true } );
 			], { includeSpanned: true } );
 		} );
 		} );
 
 
@@ -188,10 +196,10 @@ describe( 'TableWalker', () => {
 				[ '00', { rowspan: 2, contents: '01' } ],
 				[ '00', { rowspan: 2, contents: '01' } ],
 				[ '10' ]
 				[ '10' ]
 			], [
 			], [
-				{ row: 0, column: 0, data: '00' },
-				{ row: 0, column: 1, data: '01' },
-				{ row: 1, column: 0, data: '10' },
-				{ row: 1, column: 1, data: undefined }
+				{ row: 0, column: 0, index: 0, data: '00' },
+				{ row: 0, column: 1, index: 1, data: '01' },
+				{ row: 1, column: 0, index: 0, data: '10' },
+				{ row: 1, column: 1, index: 1, data: undefined }
 			], { includeSpanned: true } );
 			], { includeSpanned: true } );
 		} );
 		} );
 
 
@@ -202,12 +210,12 @@ describe( 'TableWalker', () => {
 				[ '22' ],
 				[ '22' ],
 				[ '30', '31', '32' ]
 				[ '30', '31', '32' ]
 			], [
 			], [
-				{ row: 1, column: 0, data: undefined },
-				{ row: 1, column: 1, data: undefined },
-				{ row: 1, column: 2, data: '12' },
-				{ row: 2, column: 0, data: undefined },
-				{ row: 2, column: 1, data: undefined },
-				{ row: 2, column: 2, data: '22' }
+				{ row: 1, column: 0, index: 0, data: undefined },
+				{ row: 1, column: 1, index: 0, data: undefined },
+				{ row: 1, column: 2, index: 0, data: '12' },
+				{ row: 2, column: 0, index: 0, data: undefined },
+				{ row: 2, column: 1, index: 0, data: undefined },
+				{ row: 2, column: 2, index: 0, data: '22' }
 			], { includeSpanned: true, startRow: 1, endRow: 2 } );
 			], { includeSpanned: true, startRow: 1, endRow: 2 } );
 		} );
 		} );
 	} );
 	} );
@@ -220,8 +228,8 @@ describe( 'TableWalker', () => {
 				[ '33' ],
 				[ '33' ],
 				[ '41', '42', '43' ]
 				[ '41', '42', '43' ]
 			], [
 			], [
-				{ row: 0, column: 0, data: '11' },
-				{ row: 0, column: 2, data: '13' }
+				{ row: 0, column: 0, index: 0, data: '11' },
+				{ row: 0, column: 2, index: 1, data: '13' }
 			], { endRow: 0 } );
 			], { endRow: 0 } );
 		} );
 		} );
 
 
@@ -231,10 +239,10 @@ describe( 'TableWalker', () => {
 				[ '10' ],
 				[ '10' ],
 				[ '20', '21' ]
 				[ '20', '21' ]
 			], [
 			], [
-				{ row: 0, column: 0, data: '00' },
-				{ row: 0, column: 1, data: '01' },
-				{ row: 1, column: 0, data: '10' },
-				{ row: 1, column: 1, data: undefined }
+				{ row: 0, column: 0, index: 0, data: '00' },
+				{ row: 0, column: 1, index: 1, data: '01' },
+				{ row: 1, column: 0, index: 0, data: '10' },
+				{ row: 1, column: 1, index: 1, data: undefined }
 			], { startRow: 0, endRow: 1, includeSpanned: true } );
 			], { startRow: 0, endRow: 1, includeSpanned: true } );
 		} );
 		} );
 	} );
 	} );