浏览代码

Docs: Update `downcastTable()` documentation.

Maciej Gołaszewski 7 年之前
父节点
当前提交
25db5daa0b
共有 1 个文件被更改,包括 106 次插入54 次删除
  1. 106 54
      packages/ckeditor5-table/src/converters/downcasttable.js

+ 106 - 54
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -7,8 +7,15 @@
  * @module table/converters/downcasttable
  * @module table/converters/downcasttable
  */
  */
 
 
-import Position from '@ckeditor/ckeditor5-engine/src/view/position';
+import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 
 
+/**
+ * Model table element to view table element conversion helper.
+ *
+ * This conversion helper creates whole table element with child elements.
+ *
+ * @returns {Function} Conversion helper.
+ */
 export default function downcastTable() {
 export default function downcastTable() {
 	return dispatcher => dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => {
 	return dispatcher => dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => {
 		const table = data.item;
 		const table = data.item;
@@ -17,18 +24,19 @@ export default function downcastTable() {
 			return;
 			return;
 		}
 		}
 
 
+		// The <thead> and <tbody> elements are created on the fly when needed by inner `getTableSection()` function.
 		let tHead, tBody;
 		let tHead, tBody;
 
 
 		const tableElement = conversionApi.writer.createContainerElement( 'table' );
 		const tableElement = conversionApi.writer.createContainerElement( 'table' );
-		const headingRowsCount = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
+		const headingRows = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
 		const tableRows = Array.from( table.getChildren() );
 		const tableRows = Array.from( table.getChildren() );
 		const cellSpans = new CellSpans();
 		const cellSpans = new CellSpans();
 
 
-		for ( const row of tableRows ) {
-			const rowIndex = tableRows.indexOf( row );
-			const parent = getParent( rowIndex, headingRowsCount, tableElement, conversionApi );
+		for ( const tableRow of tableRows ) {
+			const rowIndex = tableRows.indexOf( tableRow );
+			const tableSectionElement = getTableSection( rowIndex, headingRows, tableElement, conversionApi );
 
 
-			_downcastTableRow( row, rowIndex, cellSpans, parent, conversionApi );
+			downcastTableRow( tableRow, rowIndex, cellSpans, tableSectionElement, conversionApi );
 
 
 			// Drop table cell spans information for downcasted row.
 			// Drop table cell spans information for downcasted row.
 			cellSpans.drop( rowIndex );
 			cellSpans.drop( rowIndex );
@@ -40,17 +48,17 @@ export default function downcastTable() {
 		conversionApi.writer.insert( viewPosition, tableElement );
 		conversionApi.writer.insert( viewPosition, tableElement );
 
 
 		// Creates if not existing and returns <tbody> or <thead> element for given rowIndex.
 		// Creates if not existing and returns <tbody> or <thead> element for given rowIndex.
-		function getParent( rowIndex, headingRowsCount, tableElement, conversionApi ) {
-			if ( headingRowsCount && rowIndex < headingRowsCount ) {
+		function getTableSection( rowIndex, headingRows, tableElement, conversionApi ) {
+			if ( headingRows && rowIndex < headingRows ) {
 				if ( !tHead ) {
 				if ( !tHead ) {
-					tHead = _createTableSection( 'thead', tableElement, conversionApi );
+					tHead = createTableSection( 'thead', tableElement, conversionApi );
 				}
 				}
 
 
 				return tHead;
 				return tHead;
 			}
 			}
 
 
 			if ( !tBody ) {
 			if ( !tBody ) {
-				tBody = _createTableSection( 'tbody', tableElement, conversionApi );
+				tBody = createTableSection( 'tbody', tableElement, conversionApi );
 			}
 			}
 
 
 			return tBody;
 			return tBody;
@@ -58,53 +66,87 @@ export default function downcastTable() {
 	}, { priority: 'normal' } );
 	}, { priority: 'normal' } );
 }
 }
 
 
-function _downcastTableRow( tableRow, rowIndex, cellSpans, parent, conversionApi ) {
+// Downcast converter for tableRow model element. Converts tableCells as well.
+//
+// @param {module:engine/model/element~Element} tableRow
+// @param {Number} rowIndex
+// @param {CellSpans} cellSpans
+// @param {module:engine/view/containerelement~ContainerElement} tableSection
+// @param {Object} conversionApi
+function downcastTableRow( tableRow, rowIndex, cellSpans, tableSection, conversionApi ) {
 	// Will always consume since we're converting <tableRow> element from a parent <table>.
 	// Will always consume since we're converting <tableRow> element from a parent <table>.
 	conversionApi.consumable.consume( tableRow, 'insert' );
 	conversionApi.consumable.consume( tableRow, 'insert' );
 	const trElement = conversionApi.writer.createContainerElement( 'tr' );
 	const trElement = conversionApi.writer.createContainerElement( 'tr' );
 
 
 	conversionApi.mapper.bindElements( tableRow, trElement );
 	conversionApi.mapper.bindElements( tableRow, trElement );
-	conversionApi.writer.insert( Position.createAt( parent, 'end' ), trElement );
+	conversionApi.writer.insert( ViewPosition.createAt( tableSection, 'end' ), trElement );
 
 
-	let cellIndex = 0;
+	// Defines tableCell horizontal position in table.
+	// Might be different then position of tableCell in parent tableRow
+	// as tableCells from previous rows might overlaps current row's cells.
+	let columnIndex = 0;
 
 
+	const headingRows = tableRow.parent.getAttribute( 'headingRows' ) || 0;
 	const headingColumns = tableRow.parent.getAttribute( 'headingColumns' ) || 0;
 	const headingColumns = tableRow.parent.getAttribute( 'headingColumns' ) || 0;
 
 
 	for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
 	for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
-		cellIndex = cellSpans.getColumnWithSpan( rowIndex, cellIndex );
+		// Check whether current columnIndex is overlapped by table cells from previous rows.
+		columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
 
 
-		const cellWidth = tableCell.hasAttribute( 'colspan' ) ? parseInt( tableCell.getAttribute( 'colspan' ) ) : 1;
-		const cellHeight = tableCell.hasAttribute( 'rowspan' ) ? parseInt( tableCell.getAttribute( 'rowspan' ) ) : 1;
+		const colspan = tableCell.hasAttribute( 'colspan' ) ? parseInt( tableCell.getAttribute( 'colspan' ) ) : 1;
+		const rowspan = tableCell.hasAttribute( 'rowspan' ) ? parseInt( tableCell.getAttribute( 'rowspan' ) ) : 1;
 
 
-		cellSpans.updateSpans( rowIndex, cellIndex, cellHeight, cellWidth );
+		cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
 
 
 		// Will always consume since we're converting <tableRow> element from a parent <table>.
 		// Will always consume since we're converting <tableRow> element from a parent <table>.
 		conversionApi.consumable.consume( tableCell, 'insert' );
 		conversionApi.consumable.consume( tableCell, 'insert' );
 
 
-		const isHead = _isHead( tableCell, rowIndex, cellIndex, headingColumns );
-		const cellElement = conversionApi.writer.createContainerElement( isHead ? 'th' : 'td' );
+		const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
+		const cellElement = conversionApi.writer.createContainerElement( cellElementName );
 
 
 		conversionApi.mapper.bindElements( tableCell, cellElement );
 		conversionApi.mapper.bindElements( tableCell, cellElement );
-		conversionApi.writer.insert( Position.createAt( trElement, 'end' ), cellElement );
+		conversionApi.writer.insert( ViewPosition.createAt( trElement, 'end' ), cellElement );
 
 
-		cellIndex += cellWidth;
+		// Skip to next "free" column index.
+		columnIndex += colspan;
 	}
 	}
 }
 }
 
 
-function _createTableSection( elementName, tableElement, conversionApi ) {
+// Creates table section at the end of a table.
+//
+// @param {String} elementName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+// @return {module:engine/view/containerelement~ContainerElement}
+function createTableSection( elementName, tableElement, conversionApi ) {
 	const tableChildElement = conversionApi.writer.createContainerElement( elementName );
 	const tableChildElement = conversionApi.writer.createContainerElement( elementName );
 
 
-	conversionApi.writer.insert( Position.createAt( tableElement, 'end' ), tableChildElement );
+	conversionApi.writer.insert( ViewPosition.createAt( tableElement, 'end' ), tableChildElement );
 
 
 	return tableChildElement;
 	return tableChildElement;
 }
 }
 
 
-function _isHead( tableCell, rowIndex, cellIndex, columnHeadings ) {
-	const row = tableCell.parent;
-	const table = row.parent;
-	const headingRows = table.getAttribute( 'headingRows' ) || 0;
+// Returns `th` for heading cells and `td` for other cells.
+// It is based on tableCell location (rowIndex x columnIndex) and the sizes of column & row headings sizes.
+//
+// @param {Number} rowIndex
+// @param {Number} columnIndex
+// @param {Number} headingRows
+// @param {Number} headingColumns
+// @returns {String}
+function getCellElementName( rowIndex, columnIndex, headingRows, headingColumns ) {
+	// Column heading are all tableCells in the first `columnHeading` rows.
+	const isHeadingForAColumn = headingRows && headingRows > rowIndex;
+
+	// So a whole row gets <th> element.
+	if ( isHeadingForAColumn ) {
+		return 'th';
+	}
+
+	// Row heading are tableCells which columnIndex is lower then headingColumns.
+	const isHeadingForARow = headingColumns && headingColumns > columnIndex;
 
 
-	return ( !!headingRows && headingRows > rowIndex ) || ( !!columnHeadings && columnHeadings > cellIndex );
+	return isHeadingForARow ? 'th' : 'td';
 }
 }
 
 
 /**
 /**
@@ -127,13 +169,13 @@ class CellSpans {
 	}
 	}
 
 
 	/**
 	/**
-	 * Returns proper column index if current cell have span.
+	 * Returns proper column index if a current cell index is overlapped by other (has a span defined).
 	 *
 	 *
 	 * @param {Number} row
 	 * @param {Number} row
 	 * @param {Number} column
 	 * @param {Number} column
 	 * @return {Number} Returns current column or updated column index.
 	 * @return {Number} Returns current column or updated column index.
 	 */
 	 */
-	getColumnWithSpan( row, column ) {
+	getNextFreeColumnIndex( row, column ) {
 		let span = this._check( row, column ) || 0;
 		let span = this._check( row, column ) || 0;
 
 
 		// Offset current table cell columnIndex by spanning cells from rows above.
 		// Offset current table cell columnIndex by spanning cells from rows above.
@@ -146,11 +188,11 @@ class CellSpans {
 	}
 	}
 
 
 	/**
 	/**
-	 * Updates spans based on current table cell height & width.
+	 * 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:
 	 * 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
+	 *        0 1 2 3 4 5
 	 *     0:
 	 *     0:
 	 *     1: 2
 	 *     1: 2
 	 *     2: 2
 	 *     2: 2
@@ -158,58 +200,68 @@ class CellSpans {
 	 *
 	 *
 	 * 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:
 	 * 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
+	 *        0 1 2 3 4 5
 	 *     0:
 	 *     0:
 	 *     1: 2
 	 *     1: 2
 	 *     2: 2
 	 *     2: 2
 	 *     3:   4
 	 *     3:   4
 	 *
 	 *
-	 * @param {Number} row
-	 * @param {Number} column
+	 * 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} height
 	 * @param {Number} width
 	 * @param {Number} width
 	 */
 	 */
-	updateSpans( row, column, height, width ) {
-		// Omit horizontal spans as those are handled during current row conversion.
-
+	recordSpans( rowIndex, columnIndex, height, width ) {
 		// This will update all rows below up to row height with value of span width.
 		// This will update all rows below up to row height with value of span width.
-		for ( let nextRow = row + 1; nextRow < row + height; nextRow++ ) {
-			if ( !this._spans.has( nextRow ) ) {
-				this._spans.set( nextRow, new Map() );
+		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( nextRow );
+			const rowSpans = this._spans.get( rowToUpdate );
 
 
-			rowSpans.set( column, width );
+			rowSpans.set( columnIndex, width );
 		}
 		}
 	}
 	}
 
 
 	/**
 	/**
-	 * Drops already downcasted row.
+	 * Removes row from mapping.
 	 *
 	 *
-	 * @param {Number} row
+	 * @param {Number} rowIndex
 	 */
 	 */
-	drop( row ) {
-		if ( this._spans.has( row ) ) {
-			this._spans.delete( row );
+	drop( rowIndex ) {
+		if ( this._spans.has( rowIndex ) ) {
+			this._spans.delete( rowIndex );
 		}
 		}
 	}
 	}
 
 
 	/**
 	/**
 	 * Checks if given table cell is spanned by other.
 	 * Checks if given table cell is spanned by other.
 	 *
 	 *
-	 * @param {Number} row
-	 * @param {Number} column
+	 * @param {Number} rowIndex
+	 * @param {Number} columnIndex
 	 * @return {Boolean|Number} Returns false or width of a span.
 	 * @return {Boolean|Number} Returns false or width of a span.
 	 * @private
 	 * @private
 	 */
 	 */
-	_check( row, column ) {
-		if ( !this._spans.has( row ) ) {
+	_check( rowIndex, columnIndex ) {
+		if ( !this._spans.has( rowIndex ) ) {
 			return false;
 			return false;
 		}
 		}
 
 
-		const rowSpans = this._spans.get( row );
+		const rowSpans = this._spans.get( rowIndex );
 
 
-		return rowSpans.has( column ) ? rowSpans.get( column ) : false;
+		return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
 	}
 	}
 }
 }