Ver código fonte

Changed: Refactor TableIterator to a TableWalker.

Maciej Gołaszewski 7 anos atrás
pai
commit
74c3ba14da

+ 9 - 9
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -9,7 +9,7 @@
 
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
-import TableIterator from './../tableiterator';
+import TableWalker from './../tablewalker';
 
 /**
  * Model table element to view table element conversion helper.
@@ -35,9 +35,9 @@ export default function downcastTable() {
 
 		const tableElement = conversionApi.writer.createContainerElement( 'table' );
 
-		const tableIterator = new TableIterator( table );
+		const tableIterator = new TableWalker( table );
 
-		for ( const tableCellInfo of tableIterator.iterateOver() ) {
+		for ( const tableCellInfo of tableIterator ) {
 			const { row, table: { headingRows } } = tableCellInfo;
 
 			const isHead = headingRows && row < headingRows;
@@ -84,9 +84,9 @@ export function downcastInsertRow() {
 
 		const tableSection = getOrCreateTableSection( isHeadingRow ? 'thead' : 'tbody', tableElement, conversionApi );
 
-		const tableIterator = new TableIterator( table );
+		const tableIterator = new TableWalker( table, { startRow: row, endRow: row } );
 
-		for ( const tableCellInfo of tableIterator.iterateOverRows( row ) ) {
+		for ( const tableCellInfo of tableIterator ) {
 			const trElement = getOrCreateTr( tableRow, row, tableSection, conversionApi );
 
 			createViewTableCellElement( tableCellInfo, trElement, conversionApi );
@@ -112,9 +112,9 @@ export function downcastInsertCell() {
 		const tableRow = tableCell.parent;
 		const table = tableRow.parent;
 
-		const tableIterator = new TableIterator( table );
+		const tableIterator = new TableWalker( table );
 
-		for ( const tableCellInfo of tableIterator.iterateOver() ) {
+		for ( const tableCellInfo of tableIterator ) {
 			if ( tableCellInfo.cell === tableCell ) {
 				const trElement = conversionApi.mapper.toViewElement( tableRow );
 
@@ -149,9 +149,9 @@ export function downcastAttributeChange( attribute ) {
 
 		const cachedTableSections = {};
 
-		const tableIterator = new TableIterator( table );
+		const tableIterator = new TableWalker( table );
 
-		for ( const tableCellInfo of tableIterator.iterateOver() ) {
+		for ( const tableCellInfo of tableIterator ) {
 			const { row, cell } = tableCellInfo;
 			const tableRow = table.getChild( row );
 

+ 3 - 3
packages/ckeditor5-table/src/insertcolumncommand.js

@@ -8,7 +8,7 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
-import TableIterator from './tableiterator';
+import TableWalker from './tablewalker';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 
 function createCells( columns, writer, insertPosition ) {
@@ -74,12 +74,12 @@ export default class InsertColumnCommand extends Command {
 				writer.setAttribute( 'headingColumns', headingColumns + columns, table );
 			}
 
-			const tableIterator = new TableIterator( table );
+			const tableIterator = new TableWalker( table );
 
 			let currentRow = -1;
 			let currentRowInserted = false;
 
-			for ( const tableCellInfo of tableIterator.iterateOver() ) {
+			for ( const tableCellInfo of tableIterator ) {
 				const { row, column, cell: tableCell, colspan } = tableCellInfo;
 
 				if ( currentRow !== row ) {

+ 3 - 3
packages/ckeditor5-table/src/insertrowcommand.js

@@ -8,7 +8,7 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
-import TableIterator from './tableiterator';
+import TableWalker from './tablewalker';
 
 /**
  * The insert row command.
@@ -56,11 +56,11 @@ export default class InsertRowCommand extends Command {
 				writer.setAttribute( 'headingRows', headingRows + rows, table );
 			}
 
-			const tableIterator = new TableIterator( table );
+			const tableIterator = new TableWalker( table, { endRow: insertAt + 1 } );
 
 			let tableCellToInsert = 0;
 
-			for ( const tableCellInfo of tableIterator.iterateOverRows( 0, insertAt + 1 ) ) {
+			for ( const tableCellInfo of tableIterator ) {
 				const { row, rowspan, colspan, cell } = tableCellInfo;
 
 				if ( row < insertAt ) {

+ 0 - 170
packages/ckeditor5-table/src/tableiterator.js

@@ -1,170 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module table/tableiterator
- */
-
-export default class TableIterator {
-	constructor( table ) {
-		this.table = table;
-	}
-
-	* iterateOver() {
-		let rowIndex = 0;
-
-		const cellSpans = new CellSpans();
-
-		const table = {
-			headingRows: this.table.getAttribute( 'headingRows' ) || 0,
-			headingColumns: this.table.getAttribute( 'headingColumns' ) || 0
-		};
-
-		for ( const tableRow of Array.from( this.table.getChildren() ) ) {
-			let columnIndex = 0;
-
-			let i = 0;
-			let tableCell = tableRow.getChild( i );
-
-			while ( tableCell ) {
-				// for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
-				columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
-
-				const colspan = tableCell.getAttribute( 'colspan' ) || 1;
-				const rowspan = tableCell.getAttribute( 'rowspan' ) || 1;
-
-				yield {
-					column: columnIndex,
-					row: rowIndex,
-					cell: tableCell,
-					rowspan,
-					colspan,
-					table
-				};
-
-				// Skip to next "free" column index.
-				const colspanAfter = tableCell.getAttribute( 'colspan' ) || 1;
-
-				cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspanAfter );
-
-				columnIndex += colspanAfter;
-
-				i++;
-				tableCell = tableRow.getChild( i );
-			}
-
-			rowIndex++;
-		}
-	}
-
-	* iterateOverRows( fromRow, toRow ) {
-		const startRow = fromRow || 0;
-
-		const endRow = toRow || fromRow;
-
-		for ( const tableCellInfo of this.iterateOver() ) {
-			const { row } = tableCellInfo;
-
-			if ( row > endRow ) {
-				return;
-			}
-
-			if ( row >= startRow && row <= endRow ) {
-				yield tableCellInfo;
-			}
-		}
-	}
-}
-
-// Holds information about spanned table cells.
-class CellSpans {
-	// Creates CellSpans instance.
-	constructor() {
-		// Holds table cell spans mapping.
-		//
-		// @type {Map<Number, Number>}
-		// @private
-		this._spans = new Map();
-	}
-
-	// Returns proper column index if a current cell index is overlapped by other (has a span defined).
-	//
-	// @param {Number} row
-	// @param {Number} column
-	// @return {Number} Returns current column or updated column index.
-	getAdjustedColumnIndex( row, column ) {
-		let span = this._check( row, column ) || 0;
-
-		// Offset current table cell columnIndex by spanning cells from rows above.
-		while ( span ) {
-			column += span;
-			span = this._check( row, column );
-		}
-
-		return column;
-	}
-
-	// Updates spans based on current table cell height & width. Spans with height <= 1 will not be recorded.
-	//
-	// For instance if a table cell at row 0 and column 0 has height of 3 and width of 2 we're setting spans:
-	//
-	//        0 1 2 3 4 5
-	//     0:
-	//     1: 2
-	//     2: 2
-	//     3:
-	//
-	// Adding another spans for a table cell at row 2 and column 1 that has height of 2 and width of 4 will update above to:
-	//
-	//        0 1 2 3 4 5
-	//     0:
-	//     1: 2
-	//     2: 2
-	//     3:   4
-	//
-	// The above span mapping was calculated from a table below (cells 03 & 12 were not added as their height is 1):
-	//
-	//     +----+----+----+----+----+----+
-	//     | 00      | 02 | 03      | 05 |
-	//     |         +--- +----+----+----+
-	//     |         | 12      | 24 | 25 |
-	//     |         +----+----+----+----+
-	//     |         | 22                |
-	//     |----+----+                   +
-	//     | 31 | 32 |                   |
-	//     +----+----+----+----+----+----+
-	//
-	// @param {Number} rowIndex
-	// @param {Number} columnIndex
-	// @param {Number} height
-	// @param {Number} width
-	recordSpans( rowIndex, columnIndex, height, width ) {
-		// This will update all rows below up to row height with value of span width.
-		for ( let rowToUpdate = rowIndex + 1; rowToUpdate < rowIndex + height; rowToUpdate++ ) {
-			if ( !this._spans.has( rowToUpdate ) ) {
-				this._spans.set( rowToUpdate, new Map() );
-			}
-
-			const rowSpans = this._spans.get( rowToUpdate );
-
-			rowSpans.set( columnIndex, width );
-		}
-	}
-
-	// Checks if given table cell is spanned by other.
-	//
-	// @param {Number} rowIndex
-	// @param {Number} columnIndex
-	// @return {Boolean|Number} Returns false or width of a span.
-	_check( rowIndex, columnIndex ) {
-		if ( !this._spans.has( rowIndex ) ) {
-			return false;
-		}
-
-		const rowSpans = this._spans.get( rowIndex );
-
-		return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
-	}
-}

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

@@ -0,0 +1,247 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module table/tablewalker
+ */
+
+/**
+ * Table iterator class. It allows to iterate over a table cells. For each cell the iterator yields
+ * {@link module:table/tablewalker~TableWalkerValue} with proper table cell attributes.
+ */
+export default class TableWalker {
+	/**
+	 * Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
+	 *
+	 * The most important values of iterator values are column & row of a cell.
+	 *
+	 * To iterate over given row:
+	 *
+	 *		const tableWalker = new TableWalker( table, { startRow: 1, endRow: 2 } );
+	 *
+	 *		for( const cellInfo of tableWalker ) {
+	 *			console.log( 'A cell at row ' + cellInfo.row + ' and column ' + cellInfo.column );
+	 *		}
+	 *
+	 * For instance the above code for a table:
+	 *
+	 *		+----+----+----+----+----+----+
+	 *		| 00      | 02 | 03      | 05 |
+	 *		|         +--- +----+----+----+
+	 *		|         | 12      | 24 | 25 |
+	 *		|         +----+----+----+----+
+	 *		|         | 22                |
+	 *		|----+----+                   +
+	 *		| 31 | 32 |                   |
+	 *		+----+----+----+----+----+----+
+	 *
+	 *	will log in the console:
+	 *
+	 *		'A cell at row 1 and column 2'
+	 *		'A cell at row 1 and column 4'
+	 *		'A cell at row 1 and column 5'
+	 *		'A cell at row 2 and column 2'
+	 *
+	 * @constructor
+	 * @param {module:engine/model/element~Element} table A table over which iterate.
+	 * @param {Object} [options={}] Object with configuration.
+	 * @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.
+	 */
+	constructor( table, options = {} ) {
+		this.table = table;
+
+		this.startRow = options.startRow || 0;
+		this.endRow = options.endRow;
+
+		this.previousCell = undefined;
+
+		this.row = 0;
+		this.cell = 0;
+		this.column = 0;
+
+		this.cellSpans = new CellSpans();
+	}
+
+	/**
+	 * Iterable interface.
+	 *
+	 * @returns {Iterable.<module:table/tablewalker~TableWalkerValue>}
+	 */
+	[ Symbol.iterator ]() {
+		return this;
+	}
+
+	/**
+	 * Gets the next table walker's value.
+	 *
+	 * @returns {module:table/tablewalker~TableWalkerValue} Next table walker's value.
+	 */
+	next() {
+		const row = this.table.getChild( this.row );
+
+		if ( !row ) {
+			return { done: true };
+		}
+
+		if ( this.previousCell ) {
+			const colspan = this.previousCell.getAttribute( 'colspan' ) || 1;
+			const rowspan = this.previousCell.getAttribute( 'rowspan' ) || 1;
+
+			this.cellSpans.recordSpans( this.row, this.column, rowspan, colspan );
+
+			this.column += colspan;
+		}
+
+		const cell = row.getChild( this.cell );
+
+		if ( !cell ) {
+			const colspan = this.previousCell.getAttribute( 'colspan' ) || 1;
+			const rowspan = this.previousCell.getAttribute( 'rowspan' ) || 1;
+
+			this.cellSpans.recordSpans( this.row, this.column, rowspan, colspan );
+
+			this.cell = 0;
+			this.column = 0;
+			this.row++;
+			this.previousCell = undefined;
+
+			return this.next();
+		}
+
+		this.column = this.cellSpans.getAdjustedColumnIndex( this.row, this.column );
+
+		const colspan = cell.getAttribute( 'colspan' ) || 1;
+		const rowspan = cell.getAttribute( 'rowspan' ) || 1;
+
+		this.previousCell = cell;
+
+		this.cell++;
+
+		if ( this.startRow > this.row || ( this.endRow && this.row > this.endRow ) ) {
+			return this.next();
+		}
+
+		return {
+			done: false,
+			value: {
+				column: this.column,
+				row: this.row,
+				cell,
+				rowspan,
+				colspan,
+				table: {
+					headingRows: this.table.getAttribute( 'headingRows' ) || 0,
+					headingColumns: this.table.getAttribute( 'headingColumns' ) || 0
+				}
+			}
+		};
+	}
+}
+
+// Holds information about spanned table cells.
+class CellSpans {
+	// Creates CellSpans instance.
+	constructor() {
+		// Holds table cell spans mapping.
+		//
+		// @type {Map<Number, Number>}
+		// @private
+		this._spans = new Map();
+	}
+
+	// Returns proper column index if a current cell index is overlapped by other (has a span defined).
+	//
+	// @param {Number} row
+	// @param {Number} column
+	// @return {Number} Returns current column or updated column index.
+	getAdjustedColumnIndex( row, column ) {
+		let span = this._check( row, column ) || 0;
+
+		// Offset current table cell columnIndex by spanning cells from rows above.
+		while ( span ) {
+			column += span;
+			span = this._check( row, column );
+		}
+
+		return column;
+	}
+
+	// Updates spans based on current table cell height & width. Spans with height <= 1 will not be recorded.
+	//
+	// For instance if a table cell at row 0 and column 0 has height of 3 and width of 2 we're setting spans:
+	//
+	//		   0 1 2 3 4 5
+	//		0:
+	//		1: 2
+	//		2: 2
+	//		3:
+	//
+	// Adding another spans for a table cell at row 2 and column 1 that has height of 2 and width of 4 will update above to:
+	//
+	//		   0 1 2 3 4 5
+	//		0:
+	//		1: 2
+	//		2: 2
+	//		3:   4
+	//
+	// The above span mapping was calculated from a table below (cells 03 & 12 were not added as their height is 1):
+	//
+	//		+----+----+----+----+----+----+
+	//		| 00      | 02 | 03      | 05 |
+	//		|         +--- +----+----+----+
+	//		|         | 12      | 24 | 25 |
+	//		|         +----+----+----+----+
+	//		|         | 22                |
+	//		|----+----+                   +
+	//		| 31 | 32 |                   |
+	//		+----+----+----+----+----+----+
+	//
+	// @param {Number} rowIndex
+	// @param {Number} columnIndex
+	// @param {Number} height
+	// @param {Number} width
+	recordSpans( rowIndex, columnIndex, height, width ) {
+		// This will update all rows below up to row height with value of span width.
+		for ( let rowToUpdate = rowIndex + 1; rowToUpdate < rowIndex + height; rowToUpdate++ ) {
+			if ( !this._spans.has( rowToUpdate ) ) {
+				this._spans.set( rowToUpdate, new Map() );
+			}
+
+			const rowSpans = this._spans.get( rowToUpdate );
+
+			rowSpans.set( columnIndex, width );
+		}
+	}
+
+	// Checks if given table cell is spanned by other.
+	//
+	// @param {Number} rowIndex
+	// @param {Number} columnIndex
+	// @return {Boolean|Number} Returns false or width of a span.
+	_check( rowIndex, columnIndex ) {
+		if ( !this._spans.has( rowIndex ) ) {
+			return false;
+		}
+
+		const rowSpans = this._spans.get( rowIndex );
+
+		return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
+	}
+}
+
+/**
+ * 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 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 & heights of previous cells.
+ * @property {Number} colspan The colspan attribute of a cell - always defined even if model attribute is not present.
+ * @property {Number} rowspan The rowspan attribute of a cell - always defined even if model attribute is not present.
+ * @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.headingColumns The heading columns attribute of a table - always defined even if model attribute is not present.
+ */

+ 1 - 1
packages/ckeditor5-table/tests/converters/downcasttable.js

@@ -395,7 +395,7 @@ describe( 'downcastTable()', () => {
 				writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
 			} );
 
-			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+			expect( formatModelTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
 				[ { rowspan: 3, contents: '11', isHeading: true }, '12' ],
 				[ '22' ],
 				[ '' ],

+ 8 - 8
packages/ckeditor5-table/tests/tableiterator.js

@@ -7,9 +7,9 @@ import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
 import { modelTable } from './_utils/utils';
 
-import TableIterator from '../src/tableiterator';
+import TableWalker from '../src/tablewalker';
 
-describe( 'TableIterator', () => {
+describe( 'TableWalker', () => {
 	let editor, model, doc, root;
 
 	beforeEach( () => {
@@ -46,14 +46,14 @@ describe( 'TableIterator', () => {
 			} );
 	} );
 
-	function testIterator( tableData, expected ) {
+	function testWalker( tableData, expected, options = {} ) {
 		setData( model, modelTable( tableData ) );
 
-		const iterator = new TableIterator( root.getChild( 0 ) );
+		const iterator = new TableWalker( root.getChild( 0 ), options );
 
 		const result = [];
 
-		for ( const tableInfo of iterator.iterateOver() ) {
+		for ( const tableInfo of iterator ) {
 			result.push( tableInfo );
 		}
 
@@ -62,7 +62,7 @@ describe( 'TableIterator', () => {
 	}
 
 	it( 'should iterate over a table', () => {
-		testIterator( [
+		testWalker( [
 			[ '11', '12' ]
 		], [
 			{ row: 0, column: 0, data: '11' },
@@ -71,7 +71,7 @@ describe( 'TableIterator', () => {
 	} );
 
 	it( 'should properly output column indexes of a table that has colspans', () => {
-		testIterator( [
+		testWalker( [
 			[ { colspan: 2, contents: '11' }, '13' ]
 		], [
 			{ row: 0, column: 0, data: '11' },
@@ -80,7 +80,7 @@ describe( 'TableIterator', () => {
 	} );
 
 	it( 'should properly output column indexes of a table that has rowspans', () => {
-		testIterator( [
+		testWalker( [
 			[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
 			[ '23' ],
 			[ '33' ],