瀏覽代碼

Fixed previous and next position returned by TreeWalker.

Oskar Wrobel 9 年之前
父節點
當前提交
c5e826413d
共有 2 個文件被更改,包括 57 次插入75 次删除
  1. 54 30
      packages/ckeditor5-engine/src/view/treewalker.js
  2. 3 45
      packages/ckeditor5-engine/tests/view/treewalker.js

+ 54 - 30
packages/ckeditor5-engine/src/view/treewalker.js

@@ -130,6 +130,16 @@ export default class TreeWalker {
 		 * @member {engine.view.Element} engine.view.TreeWalker#_boundaryEndParent
 		 */
 		this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
+
+		/**
+		 * Not every step is returned by iterator e.g. `ELEMENT_START` or `ELEMENT_END` for Text. So if iterator
+		 * omit some step than last returned position needs to be cached, because nextPosition of previousStep must
+		 * be equal to previousPosition of nextStep.
+		 *
+		 * @private
+		 * @member {engine.view.Position} engine.view.TreeWalker#_lastPosition
+		 */
+		this._previousReturnedPosition = this.position;
 	}
 
 	/**
@@ -163,7 +173,6 @@ export default class TreeWalker {
 	 */
 	_next() {
 		let position = Position.createFromPosition( this.position );
-		const previousPosition = this.position;
 		const parent = position.parent;
 
 		// We are at the end of the root.
@@ -195,7 +204,7 @@ export default class TreeWalker {
 
 			this.position = position;
 
-			return formatReturnValue( 'ELEMENT_START', node, previousPosition, position, 1 );
+			return this._formatReturnValue( 'ELEMENT_START', node, this._previousReturnedPosition, position, 1 );
 		} else if ( node instanceof Text ) {
 			if ( this.singleCharacters ) {
 				position = new Position( node, 0 );
@@ -204,7 +213,7 @@ export default class TreeWalker {
 				return this._next();
 			} else {
 				let charactersCount = node._data.length;
-				let prevPosition = previousPosition;
+				let previousPosition = this._previousReturnedPosition;
 				let item = node;
 
 				// If text stick out of walker range, we need to cut it and wrap by TextProxy.
@@ -212,7 +221,7 @@ export default class TreeWalker {
 					charactersCount = this.boundaries.end.offset;
 					item = new TextProxy( node, 0, charactersCount );
 					position = Position.createAfter( item );
-					prevPosition = Position.createBefore( item );
+					previousPosition = Position.createBefore( item );
 				} else {
 					// If not just move forward.
 					position.offset++;
@@ -220,18 +229,18 @@ export default class TreeWalker {
 
 				this.position = position;
 
-				return formatReturnValue( 'TEXT', item, prevPosition, position, charactersCount );
+				return this._formatReturnValue( 'TEXT', item, previousPosition, position, charactersCount );
 			}
 		} else if ( typeof node == 'string' ) {
 			let textLength;
 
-			if ( !this.singleCharacters ) {
+			if ( this.singleCharacters ) {
+				textLength = 1;
+			} else {
 				// Check if text stick out of walker range.
 				const endOffset = parent === this._boundaryEndParent ? this.boundaries.end.offset : parent._data.length;
 
 				textLength = endOffset - position.offset;
-			} else {
-				textLength = 1;
 			}
 
 			const textProxy = new TextProxy( parent, position.offset, textLength );
@@ -239,7 +248,7 @@ export default class TreeWalker {
 			position.offset += textLength;
 			this.position = position;
 
-			return formatReturnValue( 'TEXT', textProxy, previousPosition, position, textLength );
+			return this._formatReturnValue( 'TEXT', textProxy, this._previousReturnedPosition, position, textLength );
 		} else {
 			// `node` is not set, we reached the end of current `parent`.
 			position = Position.createAfter( parent );
@@ -249,7 +258,7 @@ export default class TreeWalker {
 			if ( this.ignoreElementEnd || parent instanceof Text ) {
 				return this._next();
 			} else {
-				return formatReturnValue( 'ELEMENT_END', parent, previousPosition, position );
+				return this._formatReturnValue( 'ELEMENT_END', parent, this._previousReturnedPosition, position );
 			}
 		}
 	}
@@ -264,7 +273,6 @@ export default class TreeWalker {
 	 */
 	_previous() {
 		let position = Position.createFromPosition( this.position );
-		const previousPosition = this.position;
 		const parent = position.parent;
 
 		// We are at the beginning of the root.
@@ -295,13 +303,13 @@ export default class TreeWalker {
 				if ( this.ignoreElementEnd ) {
 					return this._previous();
 				} else {
-					return formatReturnValue( 'ELEMENT_END', node, previousPosition, position );
+					return this._formatReturnValue( 'ELEMENT_END', node, this._previousReturnedPosition, position );
 				}
 			} else {
 				position.offset--;
 				this.position = position;
 
-				return formatReturnValue( 'ELEMENT_START', node, previousPosition, position, 1 );
+				return this._formatReturnValue( 'ELEMENT_START', node, this._previousReturnedPosition, position, 1 );
 			}
 		} else if ( node instanceof Text ) {
 			if ( this.singleCharacters ) {
@@ -311,7 +319,7 @@ export default class TreeWalker {
 				return this._previous();
 			} else {
 				let charactersCount = node._data.length;
-				let prevPosition = previousPosition;
+				let previousPosition = this._previousReturnedPosition;
 				let item = node;
 
 				// If text stick out of walker range, we need to cut it and wrap by TextProxy.
@@ -321,7 +329,7 @@ export default class TreeWalker {
 					item = new TextProxy( node, offset );
 
 					position = Position.createBefore( item );
-					prevPosition = Position.createAfter( item );
+					previousPosition = Position.createAfter( item );
 					charactersCount = item._data.length;
 				} else {
 					// If not just move backward.
@@ -330,7 +338,7 @@ export default class TreeWalker {
 
 				this.position = position;
 
-				return formatReturnValue( 'TEXT', item, prevPosition, position, charactersCount );
+				return this._formatReturnValue( 'TEXT', item, previousPosition, position, charactersCount );
 			}
 		} else if ( typeof node == 'string' ) {
 			let textLength;
@@ -350,7 +358,7 @@ export default class TreeWalker {
 
 			this.position = position;
 
-			return formatReturnValue( 'TEXT', textProxy, previousPosition, position, textLength );
+			return this._formatReturnValue( 'TEXT', textProxy, this._previousReturnedPosition, position, textLength );
 		} else {
 			// `node` is not set, we reached the beginning of current `parent`.
 			position = Position.createBefore( parent );
@@ -358,25 +366,41 @@ export default class TreeWalker {
 
 			// We don't return `ELEMENT_START` for {@link engine.view.Text} element.
 			if ( parent instanceof Text ) {
+				position = Position.createBefore( parent );
+				this.position = position;
+
 				return this._previous();
 			}
 
-			return formatReturnValue( 'ELEMENT_START', parent, previousPosition, position, 1 );
+			return this._formatReturnValue( 'ELEMENT_START', parent, this._previousReturnedPosition, position, 1 );
 		}
 	}
-}
 
-function formatReturnValue( type, item, previousPosition, nextPosition, length ) {
-	return {
-		done: false,
-		value: {
-			type: type,
-			item: item,
-			previousPosition: previousPosition,
-			nextPosition: nextPosition,
-			length: length
-		}
-	};
+	/**
+	 * Format returned data and store information about previous position {#_previousReturnedPosition}.
+	 *
+	 * @private
+	 * @param {engine.view.TreeWalkerValueType} type Type of step.
+	 * @param {engine.view.Item} item Item between old and new position.
+	 * @param {engine.view.Position} previousPosition Previous position of iterator.
+	 * @param {engine.view.Position} nextPosition Next position of iterator.
+	 * @param {Number} [length] Length of the item.
+	 * @returns {engine.view.TreeWalkerValue}
+	 */
+	_formatReturnValue( type, item, previousPosition, nextPosition, length ) {
+		this._previousReturnedPosition = nextPosition;
+
+		return {
+			done: false,
+			value: {
+				type: type,
+				item: item,
+				previousPosition: previousPosition,
+				nextPosition: nextPosition,
+				length: length
+			}
+		};
+	}
 }
 
 /**

+ 3 - 45
packages/ckeditor5-engine/tests/view/treewalker.js

@@ -604,59 +604,17 @@ function expectValue( value, expected, options = {} ) {
 	}
 }
 
-function expectText( value, expected, options ) {
-	let previousPosition, nextPosition;
-
+function expectText( value, expected ) {
 	expect( value.item._data ).to.equal( expected.text );
 	expect( value.length ).to.equal( value.item._data.length );
-
-	if ( options.direction == 'BACKWARD' ) {
-		previousPosition = Position.createAfter( value.item );
-		nextPosition = Position.createBefore( value.item );
-	} else {
-		previousPosition = Position.createBefore( value.item );
-		nextPosition = Position.createAfter( value.item );
-	}
-
-	expect( value.previousPosition ).to.deep.equal( previousPosition );
-	expect( value.nextPosition ).to.deep.equal( nextPosition );
 }
 
-function expectStart( value, expected, options ) {
-	let previousPosition, nextPosition;
-
+function expectStart( value, expected ) {
 	expect( value.item ).to.equal( expected.item );
 	expect( value.length ).to.equal( 1 );
-
-	if ( options.direction == 'BACKWARD' ) {
-		previousPosition = Position.createAfter( value.item );
-		nextPosition = Position.createBefore( value.item );
-	} else {
-		previousPosition = Position.createBefore( value.item );
-		nextPosition = new Position( value.item, 0 );
-	}
-
-	if ( options.shallow ) {
-		expect( value.previousPosition ).to.deep.equal( previousPosition );
-	} else {
-		expect( value.nextPosition ).to.deep.equal( nextPosition );
-	}
 }
 
-function expectEnd( value, expected, options ) {
-	let previousPosition, nextPosition;
-
+function expectEnd( value, expected ) {
 	expect( value.item ).to.equal( expected.item );
 	expect( value.length ).to.be.undefined;
-
-	if ( options.direction == 'BACKWARD' ) {
-		previousPosition = Position.createAfter( value.item );
-		nextPosition = new Position( value.item, value.item.getChildCount() );
-	} else {
-		previousPosition = new Position( value.item, value.item.getChildCount() );
-		nextPosition = Position.createAfter( value.item );
-	}
-
-	expect( value.previousPosition ).to.deep.equal( previousPosition );
-	expect( value.nextPosition ).to.deep.equal( nextPosition );
 }