|
|
@@ -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.
|
|
|
*/
|