浏览代码

Minor ViewConversionDispatcher refactoring.

Oskar Wróbel 7 年之前
父节点
当前提交
b2a5207ee0
共有 1 个文件被更改,包括 69 次插入40 次删除
  1. 69 40
      packages/ckeditor5-engine/src/conversion/viewconversiondispatcher.js

+ 69 - 40
packages/ckeditor5-engine/src/conversion/viewconversiondispatcher.js

@@ -19,8 +19,8 @@ import log from '@ckeditor/ckeditor5-utils/src/log';
 /**
  * `ViewConversionDispatcher` is a central point of {@link module:engine/view/view view} conversion, which is a process of
  * converting given {@link module:engine/view/documentfragment~DocumentFragment view document fragment} or
- * {@link module:engine/view/element~Element}
- * into another structure. In default application, {@link module:engine/view/view view} is converted to {@link module:engine/model/model}.
+ * {@link module:engine/view/element~Element} into another structure.
+ * In default application, {@link module:engine/view/view view} is converted to {@link module:engine/model/model}.
  *
  * During conversion process, for all {@link module:engine/view/node~Node view nodes} from the converted view document fragment,
  * `ViewConversionDispatcher` fires corresponding events. Special callbacks called "converters" should listen to
@@ -115,14 +115,23 @@ export default class ViewConversionDispatcher {
 		this._model = model;
 
 		/**
+		 * Store of split elements that was created as a result of conversion using {@link #_splitToAllowedParent}.
+		 * We need to remember these elements because at the end of conversion we want to remove all empty elements.
+		 *
+		 * @protected
+		 * @type {Set<module:engine/model/element~Element>}
+		 */
+		this._splitElements = new Set();
+
+		/**
 		 * Interface passed by dispatcher to the events callbacks.
 		 *
 		 * @member {module:engine/conversion/viewconversiondispatcher~ViewConversionApi}
 		 */
 		this.conversionApi = Object.assign( {}, conversionApi );
 
-		// `convertItem` and `convertChildren` are bound to this `ViewConversionDispatcher` instance and
-		// set on `conversionApi`. This way only a part of `ViewConversionDispatcher` API is exposed.
+		// `convertItem`, `convertChildren` and `splitToAllowedParent` are bound to this `ViewConversionDispatcher`
+		// instance and set on `conversionApi`. This way only a part of `ViewConversionDispatcher` API is exposed.
 		this.conversionApi.convertItem = this._convertItem.bind( this );
 		this.conversionApi.convertChildren = this._convertChildren.bind( this );
 		this.conversionApi.splitToAllowedParent = this._splitToAllowedParent.bind( this );
@@ -155,12 +164,8 @@ export default class ViewConversionDispatcher {
 			// Store writer in current conversion as a conversion API.
 			this.conversionApi.writer = writer;
 
-			// Create set for split elements. We need to remember this elements, because at the end of conversion
-			// we want to remove all empty elements that was created as a result of the split.
-			this.conversionApi.splitElements = new Set();
-
 			// Additional date available between conversions.
-			// Needed when one converter needs to leave some data for oder converters.
+			// Needed when one converter needs to leave some data for the oder converters.
 			this.conversionApi.data = {};
 
 			// Do the conversion.
@@ -171,10 +176,8 @@ export default class ViewConversionDispatcher {
 
 			// When there is a conversion result.
 			if ( range ) {
-				// Remove each empty element that was created as a result of split.
-				for ( const item of this.conversionApi.splitElements ) {
-					removeEmptySplitResult( item, this.conversionApi );
-				}
+				// Remove all empty elements that was created as a result of split.
+				this._removeEmptySplitElements();
 
 				// Move all items that was converted to context tree to document fragment.
 				for ( const item of Array.from( position.parent.getChildren() ) ) {
@@ -185,9 +188,11 @@ export default class ViewConversionDispatcher {
 				documentFragment.markers = extractMarkersFromModelFragment( documentFragment, writer );
 			}
 
+			// Clear split elements.
+			this._splitElements.clear();
+
 			// Clear conversion API.
 			this.conversionApi.writer = null;
-			this.conversionApi.splitElements = null;
 			this.conversionApi.data = null;
 
 			// Return fragment as conversion result.
@@ -247,30 +252,35 @@ export default class ViewConversionDispatcher {
 	}
 
 	/**
-	 * TODO
-	 *
 	 * @private
+	 * @see module:engine/conversion/viewconversiondispatcher~ViewConversionApi#splitToAllowedParent
 	 */
-	_splitToAllowedParent( element, position, conversionApi ) {
+	_splitToAllowedParent( element, position ) {
 		function checkLimit( node ) {
 			return node.hasAttribute( 'isContextTree' );
 		}
 
-		const allowedParent = conversionApi.schema.findAllowedParent( element, position, checkLimit );
+		// Try to find allowed parent.
+		const allowedParent = this.conversionApi.schema.findAllowedParent( element, position, checkLimit );
 
+		// When there is no parent that allows to insert element then return `null`.
 		if ( !allowedParent ) {
-			return;
+			return null;
 		}
 
+		// When current position parent allows to insert element then return this position.
 		if ( allowedParent === position.parent ) {
 			return { position };
 		}
 
-		const data = conversionApi.writer.split( position, allowedParent );
+		// Split element to allowed parent.
+		const data = this.conversionApi.writer.split( position, allowedParent );
 
+		// Remember all elements that are created as a result of split.
+		// This is important because at the end of conversion we want to remove all empty split elements.
 		for ( const pos of data.range.getPositions() ) {
 			if ( !pos.isEqual( data.position ) ) {
-				conversionApi.splitElements.add( pos.parent );
+				this._splitElements.add( pos.parent );
 			}
 		}
 
@@ -281,6 +291,30 @@ export default class ViewConversionDispatcher {
 	}
 
 	/**
+	 * Checks if {@link #_splitElements} contains empty elements and remove them.
+	 * We need to do it smart because there could be elements that are not empty because contains
+	 * other empty split elements and after removing its children they become available to remove.
+	 * We need to continue iterating over split elements as long as any element will be removed.
+	 *
+	 * @private
+	 */
+	_removeEmptySplitElements() {
+		let removed = false;
+
+		for ( const element of this._splitElements ) {
+			if ( element.isEmpty ) {
+				this.conversionApi.writer.remove( element );
+				this._splitElements.delete( element );
+				removed = true;
+			}
+		}
+
+		if ( removed ) {
+			this._removeEmptySplitElements();
+		}
+	}
+
+	/**
 	 * Fired before the first conversion event, at the beginning of view to model conversion process.
 	 *
 	 * @event viewCleanup
@@ -304,7 +338,6 @@ export default class ViewConversionDispatcher {
 	 * @param {module:engine/view/element~Element} data.input Converted element.
 	 * @param {*} data.output The current state of conversion result. Every change to converted element should
 	 * be reflected by setting or modifying this property.
-	 * @param {module:engine/model/schema~SchemaPath} data.context The conversion context.
 	 * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
 	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ViewConversionDispatcher` constructor.
 	 * Besides of properties passed in constructor, it also has `convertItem` and `convertChildren` methods which are references
@@ -394,19 +427,6 @@ function contextToPosition( contextDefinition, writer ) {
 	return position;
 }
 
-function removeEmptySplitResult( item, conversionApi ) {
-	if ( item.isEmpty ) {
-		const parent = item.parent;
-
-		conversionApi.writer.remove( item );
-		conversionApi.splitElements.delete( item );
-
-		if ( conversionApi.splitElements.has( parent ) ) {
-			removeEmptySplitResult( parent, conversionApi );
-		}
-	}
-}
-
 /**
  * Conversion interface that is registered for given {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher}
  * and is passed as one of parameters when {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher dispatcher}
@@ -423,8 +443,7 @@ function removeEmptySplitResult( item, conversionApi ) {
  *
  * Every fired event is passed (as first parameter) an object with `output` property. Every event may set and/or
  * modify that property. When all callbacks are done, the final value of `output` property is returned by this method.
- * The `output` must be either {@link module:engine/model/node~Node model node} or
- * {@link module:engine/model/documentfragment~DocumentFragment model document fragment} or `null` (as set by default).
+ * The `output` must be {@link module:engine/model/range~Range model range} or `null` (as set by default).
  *
  * @method #convertItem
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element
@@ -436,7 +455,7 @@ function removeEmptySplitResult( item, conversionApi ) {
  * @param {module:engine/model/position~Position} position Position of conversion.
  * @param {Object} [additionalData] Additional data to be passed in `data` argument when firing `ViewConversionDispatcher`
  * events. See also {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element element event}.
- * @returns {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment|null} The result of item conversion,
+ * @returns {module:engine/model/range~Range|null} Model range containing result of item conversion,
  * created and modified by callbacks attached to fired event, or `null` if the conversion result was incorrect.
  */
 
@@ -453,8 +472,18 @@ function removeEmptySplitResult( item, conversionApi ) {
  * @param {module:engine/model/position~Position} position Position of conversion.
  * @param {Object} [additionalData] Additional data to be passed in `data` argument when firing `ViewConversionDispatcher`
  * events. See also {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element element event}.
- * @returns {module:engine/model/documentfragment~DocumentFragment} Model document fragment containing results of conversion
- * of all children of given item.
+ * @returns {module:engine/model/range~Range} Model range containing results of conversion of all children of given item.
+ * When none of children was converted
+ */
+
+/**
+ * Find allowed parent for element that we are going to insert starting from given position.
+ * If current parent does not allow to insert element but one of the ancestors does then split nodes to allowed parent.
+ *
+ * @method #splitToAllowedParent
+ * @param {module:engine/model/position~Position} position Position on which element is going to be inserted.
+ * @param {module:engine/model/element~Node} element Element to insert.
+ * @returns TODO
  */
 
 /**