Browse Source

Merge pull request #202 from ckeditor/cf/tc-tables

Feature: `TableWalker` will now return `cell` value also for spanned cells when traversing a table with `includeSpanned` option set to `true`. Additionally, `isSpanned` property was introduced in returned values.
Internal: Improvements in table plugin allowing for integration with track changes.

BREAKING CHANGE: `TableWalker` will not return `undefined` as `cell` value for spanned cells anymore. Use `isSpanned` instead.
Maciej 6 years ago
parent
commit
1a106cccf4

+ 9 - 4
packages/ckeditor5-table/src/commands/setheadercolumncommand.js

@@ -61,22 +61,27 @@ export default class SetHeaderColumnCommand extends Command {
 	 * When the selection is already in a header column, it will set `headingColumns` so the heading section will end before that column.
 	 *
 	 * @fires execute
+	 * @params {Boolean} [forceValue] If set, the command will set (`true`) or unset (`false`) header columns according to `forceValue`
+	 * parameter instead of the current model state.
 	 */
-	execute() {
+	execute( forceValue = null ) {
 		const model = this.editor.model;
 		const doc = model.document;
 		const selection = doc.selection;
 		const tableUtils = this.editor.plugins.get( 'TableUtils' );
 
 		const position = selection.getFirstPosition();
-		const tableCell = findAncestor( 'tableCell', position.parent );
+		const tableCell = findAncestor( 'tableCell', position );
 		const tableRow = tableCell.parent;
 		const table = tableRow.parent;
 
-		const currentHeadingColumns = parseInt( table.getAttribute( 'headingColumns' ) || 0 );
 		const { column: selectionColumn } = tableUtils.getCellLocation( tableCell );
 
-		const headingColumnsToSet = currentHeadingColumns > selectionColumn ? selectionColumn : selectionColumn + 1;
+		if ( forceValue === this.value ) {
+			return;
+		}
+
+		const headingColumnsToSet = this.value ? selectionColumn : selectionColumn + 1;
 
 		model.change( writer => {
 			updateNumericAttribute( 'headingColumns', headingColumnsToSet, table, writer, 0 );

+ 16 - 8
packages/ckeditor5-table/src/commands/setheaderrowcommand.js

@@ -60,8 +60,10 @@ export default class SetHeaderRowCommand extends Command {
 	 * When the selection is already in a header row, it will set `headingRows` so the heading section will end before that row.
 	 *
 	 * @fires execute
+	 * @params {Boolean} [forceValue] If set, the command will set (`true`) or unset (`false`) header rows according to `forceValue`
+	 * parameter instead of the current model state.
 	 */
-	execute() {
+	execute( forceValue = null ) {
 		const model = this.editor.model;
 		const doc = model.document;
 		const selection = doc.selection;
@@ -74,7 +76,11 @@ export default class SetHeaderRowCommand extends Command {
 		const currentHeadingRows = table.getAttribute( 'headingRows' ) || 0;
 		const selectionRow = tableRow.index;
 
-		const headingRowsToSet = currentHeadingRows > selectionRow ? selectionRow : selectionRow + 1;
+		if ( forceValue === this.value ) {
+			return;
+		}
+
+		const headingRowsToSet = this.value ? selectionRow : selectionRow + 1;
 
 		model.change( writer => {
 			if ( headingRowsToSet ) {
@@ -151,19 +157,21 @@ function splitHorizontally( tableCell, headingRows, writer ) {
 		attributes.rowspan = spanToSet;
 	}
 
+	const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
+
+	if ( colspan > 1 ) {
+		attributes.colspan = colspan;
+	}
+
 	const startRow = table.getChildIndex( tableRow );
 	const endRow = startRow + newRowspan;
 	const tableMap = [ ...new TableWalker( table, { startRow, endRow, includeSpanned: true } ) ];
 
 	let columnIndex;
 
-	for ( const { row, column, cell, colspan, cellIndex } of tableMap ) {
-		if ( cell === tableCell ) {
+	for ( const { row, column, cell, cellIndex } of tableMap ) {
+		if ( cell === tableCell && columnIndex === undefined ) {
 			columnIndex = column;
-
-			if ( colspan > 1 ) {
-				attributes.colspan = colspan;
-			}
 		}
 
 		if ( columnIndex !== undefined && columnIndex === column && row === endRow ) {

+ 1 - 0
packages/ckeditor5-table/src/converters/downcast.js

@@ -345,6 +345,7 @@ function renameViewTableCell( tableCell, desiredCellElementName, conversionApi,
 		renamedCell = viewWriter.rename( desiredCellElementName, viewCell );
 	}
 
+	conversionApi.mapper.unbindViewElement( viewCell );
 	conversionApi.mapper.bindElements( tableCell, renamedCell );
 }
 

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

@@ -180,7 +180,7 @@ export default class TableUtils extends Plugin {
 	 *		|       | c |                   |               | c |
 	 *		+---+---+---+     will give:    +---+---+---+---+---+
 	 *		| d | e | f |                   | d |   |   | e | f |
-	 *		+---+   +---+                   +---+---+---+  +---+
+	 *		+---+   +---+                   +---+---+---+   +---+
 	 *		| g |   | h |                   | g |   |   |   | h |
 	 *		+---+---+---+                   +---+---+---+---+---+
 	 *		| i         |                   | i                 |
@@ -219,13 +219,16 @@ export default class TableUtils extends Plugin {
 
 			const tableWalker = new TableWalker( table, { column: insertAt, includeSpanned: true } );
 
-			for ( const { row, column, cell, colspan, rowspan, cellIndex } of tableWalker ) {
+			for ( const { row, cell, cellIndex } of tableWalker ) {
 				// When iterating over column the table walker outputs either:
 				// - cells at given column index (cell "e" from method docs),
 				// - spanned columns (spanned cell from row between cells "g" and "h" - spanned by "e", only if `includeSpanned: true`),
 				// - or a cell from the same row which spans over this column (cell "a").
 
-				if ( column !== insertAt ) {
+				const rowspan = parseInt( cell.getAttribute( 'rowspan' ) || 1 );
+				const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
+
+				if ( cell.index !== insertAt && colspan > 1 ) {
 					// If column is different than `insertAt`, it is a cell that spans over an inserted column (cell "a" & "i").
 					// For such cells expand them by a number of columns inserted.
 					writer.setAttribute( 'colspan', colspan + columnsToInsert, cell );

+ 80 - 80
packages/ckeditor5-table/src/tablewalker.js

@@ -15,7 +15,6 @@ export default class TableWalker {
 	/**
 	 * Creates an instance of the table walker.
 	 *
-	 *
 	 * The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
 	 * It walks row by row and column by column in order to output values defined in the constructor.
 	 * By default it will output only those locations that are occupied by a cell. To include also spanned rows and columns,
@@ -23,6 +22,8 @@ export default class TableWalker {
 	 *
 	 * The most important values of the iterator are column and row indexes of a cell.
 	 *
+	 * See {@link module:table/tablewalker~TableWalkerValue} what values are returned by the table walker.
+	 *
 	 * To iterate over a given row:
 	 *
 	 *		const tableWalker = new TableWalker( table, { startRow: 1, endRow: 2 } );
@@ -54,8 +55,8 @@ export default class TableWalker {
 	 *
 	 *		const tableWalker = new TableWalker( table, { startRow: 1, endRow: 1, includeSpanned: true } );
 	 *
-	 *		for ( const cellInfo of tableWalker ) {
-	 *			console.log( 'Cell at ' + cellInfo.row + ' x ' + cellInfo.column + ' : ' + ( cellInfo.cell ? 'has data' : 'is spanned' ) );
+	 *		for ( const value of tableWalker ) {
+	 *			console.log( 'Cell at ' + value.row + ' x ' + value.column + ' : ' + ( value.isSpanned ? 'is spanned' : 'has data' ) );
 	 *		}
 	 *
 	 * will log in the console for the table from previous example:
@@ -73,7 +74,7 @@ export default class TableWalker {
 	 * @param {Number} [options.column] A column index for which this iterator will output cells.
 	 * @param {Number} [options.startRow=0] A row index for which this iterator should start.
 	 * @param {Number} [options.endRow] A row index for which this iterator should end.
-	 * @param {Boolean} [options.includeSpanned] Also return values for spanned cells.
+	 * @param {Boolean} [options.includeSpanned=false] Also return values for spanned cells.
 	 */
 	constructor( table, options = {} ) {
 		/**
@@ -151,16 +152,18 @@ export default class TableWalker {
 		 * @member {Number}
 		 * @private
 		 */
-		this._cell = 0;
+		this._cellIndex = 0;
 
 		/**
 		 * Holds a map of spanned cells in a table.
 		 *
 		 * @readonly
-		 * @member {Map<Number, Map.<Number, Number>>}
+		 * @member {Map<Number, Map.<Number, module:engine/model/element~Element>>}
 		 * @private
 		 */
 		this._spannedCells = new Map();
+
+		this._nextCellAtColumn = -1;
 	}
 
 	/**
@@ -180,62 +183,53 @@ export default class TableWalker {
 	next() {
 		const row = this.table.getChild( this._row );
 
-		// Iterator is done when no row (table end) or the row is after #endRow.
+		// Iterator is done when there's no row (table ended) or the row is after `endRow` limit.
 		if ( !row || this._isOverEndRow() ) {
 			return { done: true };
 		}
 
-		// Spanned cell location handling.
-		if ( this._isSpanned( this._row, this._column ) ) {
-			// Current column must be kept as it will be updated before returning current value.
-			const currentColumn = this._column;
-			const outValue = this._formatOutValue( undefined, currentColumn );
+		let cell, skipCurrentValue, outValue;
 
-			// Advance to next column - always.
-			this._column++;
+		if ( this._isSpanned( this._row, this._column ) ) {
+			cell = this._getSpanned( this._row, this._column );
 
-			const skipCurrentValue = !this.includeSpanned || this._shouldSkipRow() || this._shouldSkipColumn( currentColumn, 1 );
+			skipCurrentValue = !this.includeSpanned || this._shouldSkipRow() || this._shouldSkipColumn();
+			outValue = this._formatOutValue( cell, this._column, true );
+		} else {
+			cell = row.getChild( this._cellIndex );
 
-			// The current value will be returned only if #includedSpanned=true and also current row and column are not skipped.
-			return skipCurrentValue ? this.next() : outValue;
-		}
+			if ( !cell ) {
+				// If there are no more cells left in row advance to the next row.
+				this._row++;
+				this._column = 0;
+				this._cellIndex = 0;
+				this._nextCellAtColumn = -1;
 
-		// The cell location is not spanned by other cells.
-		const cell = row.getChild( this._cell );
+				return this.next();
+			}
 
-		if ( !cell ) {
-			// If there are no more cells left in row advance to next row.
-			this._row++;
-			// And reset column & cell indexes.
-			this._column = 0;
-			this._cell = 0;
+			const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
+			const rowspan = parseInt( cell.getAttribute( 'rowspan' ) || 1 );
 
-			// Return next value.
-			return this.next();
-		}
+			// Record this cell spans if it's not 1x1 cell.
+			if ( colspan > 1 || rowspan > 1 ) {
+				this._recordSpans( this._row, this._column, rowspan, colspan, cell );
+			}
 
-		// Read table cell attributes.
-		const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
-		const rowspan = parseInt( cell.getAttribute( 'rowspan' ) || 1 );
+			this._nextCellAtColumn = this._column + colspan;
 
-		// Record this cell spans if it's not 1x1 cell.
-		if ( colspan > 1 || rowspan > 1 ) {
-			this._recordSpans( this._row, this._column, rowspan, colspan );
+			skipCurrentValue = this._shouldSkipRow() || this._shouldSkipColumn();
+			outValue = this._formatOutValue( cell, this._column, false, rowspan, colspan );
 		}
 
-		// Current column must be kept as it will be updated before returning current value.
-		const currentColumn = this._column;
-		const outValue = this._formatOutValue( cell, currentColumn, rowspan, colspan );
-
-		// Advance to next column before returning value.
+		// Advance to the next column before returning value.
 		this._column++;
 
-		// Advance to next cell in a parent row before returning value.
-		this._cell++;
-
-		const skipCurrentValue = this._shouldSkipRow() || this._shouldSkipColumn( currentColumn, colspan );
+		if ( this._column == this._nextCellAtColumn ) {
+			this._cellIndex++;
+		}
 
-		// The current value will be returned only if current row & column are not skipped.
+		// The current value will be returned only if current row and column are not skipped.
 		return skipCurrentValue ? this.next() : outValue;
 	}
 
@@ -252,8 +246,8 @@ export default class TableWalker {
 	/**
 	 * Checks if the current row is over {@link #endRow}.
 	 *
-	 * @returns {Boolean}
 	 * @private
+	 * @returns {Boolean}
 	 */
 	_isOverEndRow() {
 		// If {@link #endRow) is defined skip all rows above it.
@@ -263,24 +257,25 @@ export default class TableWalker {
 	/**
 	 * A common method for formatting the iterator's output value.
 	 *
-	 * @param {module:engine/model/element~Element|undefined} cell The table cell to output. It might be undefined for spanned cell
-	 * locations.
-	 * @param {Number} column Column index (use the cached value)
+	 * @private
+	 * @param {module:engine/model/element~Element} cell The table cell to output.
+	 * @param {Number} column Column index (use the cached value).
+	 * @param {Boolean} isSpanned Whether the value is returned for a spanned cell location or actual cell.
 	 * @param {Number} rowspan Rowspan of the current cell.
 	 * @param {Number} colspan Colspan of the current cell.
 	 * @returns {{done: boolean, value: {cell: *, row: Number, column: *, rowspan: *, colspan: *, cellIndex: Number}}}
-	 * @private
 	 */
-	_formatOutValue( cell, column, rowspan = 1, colspan = 1 ) {
+	_formatOutValue( cell, column, isSpanned, rowspan = 1, colspan = 1 ) {
 		return {
 			done: false,
 			value: {
 				cell,
 				row: this._row,
 				column,
+				isSpanned,
 				rowspan,
 				colspan,
-				cellIndex: this._cell
+				cellIndex: this._cellIndex
 			}
 		};
 	}
@@ -288,8 +283,8 @@ export default class TableWalker {
 	/**
 	 * Checks if the current row should be skipped.
 	 *
-	 * @returns {Boolean}
 	 * @private
+	 * @returns {Boolean}
 	 */
 	_shouldSkipRow() {
 		const rowIsBelowStartRow = this._row < this.startRow;
@@ -301,33 +296,25 @@ export default class TableWalker {
 	/**
 	 * Checks if the current column should be skipped.
 	 *
-	 * @param {Number} column
-	 * @param {Number} colspan
-	 * @returns {Boolean}
 	 * @private
+	 * @returns {Boolean}
 	 */
-	_shouldSkipColumn( column, colspan ) {
+	_shouldSkipColumn() {
 		if ( this.column === undefined ) {
 			// The {@link #column} is not defined so output all columns.
 			return false;
 		}
 
-		// When outputting cells from given column we skip:
-		// - Cells that are not on that column.
-		const isCurrentColumn = column === this.column;
-		// - CSells that are before given column and they overlaps given column.
-		const isPreviousThatOverlapsColumn = column < this.column && column + colspan > this.column;
-
-		return !isCurrentColumn && !isPreviousThatOverlapsColumn;
+		return this.column != this._column;
 	}
 
 	/**
 	 * Checks if the current cell location (row x column) is spanned by another cell.
 	 *
+	 * @private
 	 * @param {Number} row Row index of a cell location to check.
 	 * @param {Number} column Column index of a cell location to check.
 	 * @returns {Boolean}
-	 * @private
 	 */
 	_isSpanned( row, column ) {
 		if ( !this._spannedCells.has( row ) ) {
@@ -341,25 +328,38 @@ export default class TableWalker {
 		return rowSpans.has( column );
 	}
 
+	/**
+	 * Returns the cell element that is spanned over `row` x `column` location.
+	 *
+	 * @private
+	 * @param {Number} row Row index of the cell location.
+	 * @param {Number} column Column index of the cell location.
+	 * @returns {module:engine/model/element~Element}
+	 */
+	_getSpanned( row, column ) {
+		return this._spannedCells.get( row ).get( column );
+	}
+
 	/**
 	 * Updates spanned cells map relative to the current cell location and its span dimensions.
 	 *
+	 * @private
 	 * @param {Number} row Row index of a cell.
 	 * @param {Number} column Column index of a cell.
 	 * @param {Number} rowspan Cell height.
 	 * @param {Number} colspan Cell width.
-	 * @private
+	 * @param {module:engine/model/element~Element} cell Cell that is spanned.
 	 */
-	_recordSpans( row, column, rowspan, colspan ) {
+	_recordSpans( row, column, rowspan, colspan, cell ) {
 		// This will update all cell locations after current column - ie a cell has colspan set.
 		for ( let columnToUpdate = column + 1; columnToUpdate <= column + colspan - 1; columnToUpdate++ ) {
-			this._markSpannedCell( row, columnToUpdate );
+			this._markSpannedCell( row, columnToUpdate, cell );
 		}
 
 		// This will update all rows below current up to row's height.
 		for ( let rowToUpdate = row + 1; rowToUpdate < row + rowspan; rowToUpdate++ ) {
 			for ( let columnToUpdate = column; columnToUpdate <= column + colspan - 1; columnToUpdate++ ) {
-				this._markSpannedCell( rowToUpdate, columnToUpdate );
+				this._markSpannedCell( rowToUpdate, columnToUpdate, cell );
 			}
 		}
 	}
@@ -367,18 +367,19 @@ export default class TableWalker {
 	/**
 	 * Marks the cell location as spanned by another cell.
 	 *
+	 * @private
 	 * @param {Number} row Row index of the cell location.
 	 * @param {Number} column Column index of the cell location.
-	 * @private
+	 * @param {module:engine/model/element~Element} cell Cell that is spanned.
 	 */
-	_markSpannedCell( row, column ) {
+	_markSpannedCell( row, column, cell ) {
 		if ( !this._spannedCells.has( row ) ) {
 			this._spannedCells.set( row, new Map() );
 		}
 
 		const rowSpans = this._spannedCells.get( row );
 
-		rowSpans.set( column, true );
+		rowSpans.set( column, cell );
 	}
 }
 
@@ -386,14 +387,13 @@ export default class TableWalker {
  * An object returned by {@link module:table/tablewalker~TableWalker} when traversing table cells.
  *
  * @typedef {Object} module:table/tablewalker~TableWalkerValue
- * @property {module:engine/model/element~Element} [cell] The current table cell. Might be empty if
- * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
+ * @property {module:engine/model/element~Element} cell The current table cell.
  * @property {Number} row The row index of a cell.
  * @property {Number} column The column index of a cell. Column index is adjusted to widths and heights of previous cells.
- * @property {Number} [colspan] The `colspan` attribute of a cell. It is always defined even if the model attribute is not present. Not
- * set if {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
- * @property {Number} [rowspan] The `rowspan` attribute of a cell. It is always defined even if the model attribute is not present. Not
- * set if {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
- * @property {Number} cellIndex The index of the current cell in a parent row. When using the `includeSpanned` option it will indicate the
- * next child index if #cell is empty (which indicates that the cell is spanned by another cell).
+ * @param {Boolean} isSpanned Whether the value is returned for a spanned cell location or actual cell.
+ * @property {Number} colspan The `colspan` attribute of a cell. It the model attribute is not present, it is set to `1`. For spanned
+ * table locations, it is set to `1`.
+ * @property {Number} rowspan The `rowspan` attribute of a cell. It the model attribute is not present, it is set to `1`. For spanned
+ * table locations, it is set to `1`.
+ * @property {Number} cellIndex The index of the current cell in a parent row.
  */

+ 24 - 0
packages/ckeditor5-table/tests/commands/setheadercolumncommand.js

@@ -115,5 +115,29 @@ describe( 'SetHeaderColumnCommand', () => {
 				[ '00', '01[]', '02', '03' ]
 			], { headingColumns: 2 } ) );
 		} );
+
+		it( 'should respect forceValue parameter #1', () => {
+			setData( model, modelTable( [
+				[ '00', '01[]', '02', '03' ]
+			], { headingColumns: 3 } ) );
+
+			command.execute( true );
+
+			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
+				[ '00', '01[]', '02', '03' ]
+			], { headingColumns: 3 } ) );
+		} );
+
+		it( 'should respect forceValue parameter #2', () => {
+			setData( model, modelTable( [
+				[ '00', '01[]', '02', '03' ]
+			], { headingColumns: 1 } ) );
+
+			command.execute( false );
+
+			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
+				[ '00', '01[]', '02', '03' ]
+			], { headingColumns: 1 } ) );
+		} );
 	} );
 } );

+ 36 - 0
packages/ckeditor5-table/tests/commands/setheaderrowcommand.js

@@ -163,6 +163,42 @@ describe( 'SetHeaderRowCommand', () => {
 			] ) );
 		} );
 
+		it( 'should respect forceValue parameter #1', () => {
+			setData( model, modelTable( [
+				[ '00' ],
+				[ '[]10' ],
+				[ '20' ],
+				[ '30' ]
+			], { headingRows: 3 } ) );
+
+			command.execute( true );
+
+			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
+				[ '00' ],
+				[ '[]10' ],
+				[ '20' ],
+				[ '30' ]
+			], { headingRows: 3 } ) );
+		} );
+
+		it( 'should respect forceValue parameter #2', () => {
+			setData( model, modelTable( [
+				[ '00' ],
+				[ '[]10' ],
+				[ '20' ],
+				[ '30' ]
+			], { headingRows: 1 } ) );
+
+			command.execute( false );
+
+			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
+				[ '00' ],
+				[ '[]10' ],
+				[ '20' ],
+				[ '30' ]
+			], { headingRows: 1 } ) );
+		} );
+
 		it( 'should fix rowspaned cells on the edge of an table head section', () => {
 			setData( model, modelTable( [
 				[ '00', '01', '02' ],

+ 44 - 23
packages/ckeditor5-table/tests/tablewalker.js

@@ -37,12 +37,20 @@ describe( 'TableWalker', () => {
 			result.push( tableInfo );
 		}
 
-		const formattedResult = result.map( ( { row, column, cell, cellIndex } ) => ( {
-			row,
-			column,
-			data: cell && cell.getChild( 0 ).getChild( 0 ).data,
-			index: cellIndex
-		} ) );
+		const formattedResult = result.map( ( { row, column, isSpanned, cell, cellIndex } ) => {
+			const result = {
+				row,
+				column,
+				data: cell && cell.getChild( 0 ).getChild( 0 ).data,
+				index: cellIndex
+			};
+
+			if ( isSpanned ) {
+				result.isSpanned = true;
+			}
+
+			return result;
+		} );
 
 		expect( formattedResult ).to.deep.equal( expected );
 	}
@@ -135,7 +143,7 @@ describe( 'TableWalker', () => {
 			], { endRow: 2 } );
 		} );
 
-		it( 'should iterate over given row 0 only', () => {
+		it( 'should iterate over given row only', () => {
 			testWalker( [
 				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
 				[ '23' ],
@@ -157,11 +165,11 @@ describe( 'TableWalker', () => {
 				{ 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 }
+				{ row: 1, column: 1, index: 1, data: '01', isSpanned: true }
 			], { includeSpanned: true } );
 		} );
 
-		it( 'should output spanned cells as empty cell', () => {
+		it( 'should output spanned cells', () => {
 			testWalker( [
 				[ { colspan: 2, rowspan: 3, contents: '00' }, '02' ],
 				[ '12' ],
@@ -169,17 +177,17 @@ describe( 'TableWalker', () => {
 				[ '30', { colspan: 2, contents: '31' } ]
 			], [
 				{ row: 0, column: 0, index: 0, data: '00' },
-				{ row: 0, column: 1, index: 1, data: undefined },
+				{ row: 0, column: 1, index: 0, data: '00', isSpanned: true },
 				{ 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: 0, index: 0, data: '00', isSpanned: true },
+				{ row: 1, column: 1, index: 0, data: '00', isSpanned: true },
 				{ 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: 0, index: 0, data: '00', isSpanned: true },
+				{ row: 2, column: 1, index: 0, data: '00', isSpanned: true },
 				{ 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 }
+				{ row: 3, column: 2, index: 1, data: '31', isSpanned: true }
 			], { includeSpanned: true } );
 		} );
 
@@ -191,7 +199,7 @@ describe( 'TableWalker', () => {
 				{ 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 }
+				{ row: 1, column: 1, index: 1, data: '01', isSpanned: true }
 			], { includeSpanned: true } );
 		} );
 
@@ -202,16 +210,16 @@ describe( 'TableWalker', () => {
 				[ '22' ],
 				[ '30', '31', '32' ]
 			], [
-				{ row: 1, column: 0, index: 0, data: undefined },
-				{ row: 1, column: 1, index: 0, data: undefined },
+				{ row: 1, column: 0, index: 0, data: '00', isSpanned: true },
+				{ row: 1, column: 1, index: 0, data: '00', isSpanned: true },
 				{ 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: 0, index: 0, data: '00', isSpanned: true },
+				{ row: 2, column: 1, index: 0, data: '00', isSpanned: true },
 				{ row: 2, column: 2, index: 0, data: '22' }
 			], { includeSpanned: true, startRow: 1, endRow: 2 } );
 		} );
 
-		it( 'should output rowspanned cells at the end of a table row', () => {
+		it( 'should output rowspanned cells at the end of a table row with startRow & endRow options', () => {
 			testWalker( [
 				[ '00', { rowspan: 2, contents: '01' } ],
 				[ '10' ],
@@ -220,7 +228,7 @@ describe( 'TableWalker', () => {
 				{ 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 }
+				{ row: 1, column: 1, index: 1, data: '01', isSpanned: true }
 			], { startRow: 0, endRow: 1, includeSpanned: true } );
 		} );
 	} );
@@ -233,9 +241,22 @@ describe( 'TableWalker', () => {
 				[ '22' ],
 				[ '30', '31', '32' ]
 			], [
-				{ row: 0, column: 0, index: 0, data: '00' },
 				{ row: 3, column: 1, index: 1, data: '31' }
 			], { column: 1 } );
 		} );
+
+		it( 'should output only cells on given column, includeSpanned = true', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '00' }, '02' ],
+				[ '12' ],
+				[ '22' ],
+				[ '30', '31', '32' ]
+			], [
+				{ row: 0, column: 1, index: 0, data: '00', isSpanned: true },
+				{ row: 1, column: 1, index: 0, data: '00', isSpanned: true },
+				{ row: 2, column: 1, index: 0, data: '00', isSpanned: true },
+				{ row: 3, column: 1, index: 1, data: '31' }
+			], { column: 1, includeSpanned: true } );
+		} );
 	} );
 } );