瀏覽代碼

Sorted methods in classes in document namespace.

Szymon Cofalik 10 年之前
父節點
當前提交
95d71c441a

+ 20 - 20
packages/ckeditor5-engine/src/document/document.js

@@ -57,6 +57,17 @@ CKEDITOR.define( [
 			this.version = 0;
 		}
 
+		/**
+		 * Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
+		 *
+		 * @protected
+		 * @readonly
+		 * @property {document.RootElement} _graveyard
+		 */
+		get _graveyard() {
+			return this.getRoot( graveyardSymbol );
+		}
+
 		/**
 		 * This is the entry point for all document changes. All changes on the document are done using
 		 * {@link document.operation.Operation operations}. To create operations in the simple way use the
@@ -109,6 +120,15 @@ CKEDITOR.define( [
 			return root;
 		}
 
+		/**
+		 * Creates a {@link document.Transaction} instance which allows to change the document.
+		 *
+		 * @returns {document.Transaction} Transaction instance.
+		 */
+		createTransaction() {
+			return new Transaction( this );
+		}
+
 		/**
 		 * Returns top-level root by it's name.
 		 *
@@ -131,26 +151,6 @@ CKEDITOR.define( [
 
 			return this.roots.get( name );
 		}
-
-		/**
-		 * Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
-		 *
-		 * @protected
-		 * @readonly
-		 * @property {document.RootElement} _graveyard
-		 */
-		get _graveyard() {
-			return this.getRoot( graveyardSymbol );
-		}
-
-		/**
-		 * Creates a {@link document.Transaction} instance which allows to change the document.
-		 *
-		 * @returns {document.Transaction} Transaction instance.
-		 */
-		createTransaction() {
-			return new Transaction( this );
-		}
 	}
 
 	utils.extend( Document.prototype, EmitterMixin );

+ 29 - 29
packages/ckeditor5-engine/src/document/element.js

@@ -50,6 +50,35 @@ CKEDITOR.define( [
 			}
 		}
 
+		/**
+		 * Gets child at the given index.
+		 *
+		 * @param {Number} index Index of child.
+		 * @returns {document.Node} Child node.
+		 */
+		getChild( index ) {
+			return this._children.get( index );
+		}
+
+		/**
+		 * Gets the number of element's children.
+		 *
+		 * @returns {Number} The number of element's children.
+		 */
+		getChildCount() {
+			return this._children.length;
+		}
+
+		/**
+		 * Gets index of the given child node.
+		 *
+		 * @param {document.Node} node Child node.
+		 * @returns {Number} Index of the child node.
+		 */
+		getChildIndex( node ) {
+			return this._children.indexOf( node );
+		}
+
 		/**
 		 * Inserts a list of child nodes on the given index and sets the parent of these nodes to this element.
 		 *
@@ -86,35 +115,6 @@ CKEDITOR.define( [
 
 			return this._children.remove( index, number );
 		}
-
-		/**
-		 * Gets child at the given index.
-		 *
-		 * @param {Number} index Index of child.
-		 * @returns {document.Node} Child node.
-		 */
-		getChild( index ) {
-			return this._children.get( index );
-		}
-
-		/**
-		 * Gets index of the given child node.
-		 *
-		 * @param {document.Node} node Child node.
-		 * @returns {Number} Index of the child node.
-		 */
-		getChildIndex( node ) {
-			return this._children.indexOf( node );
-		}
-
-		/**
-		 * Gets the number of element's children.
-		 *
-		 * @returns {Number} The number of element's children.
-		 */
-		getChildCount() {
-			return this._children.length;
-		}
 	}
 
 	return Element;

+ 85 - 85
packages/ckeditor5-engine/src/document/node.js

@@ -41,33 +41,6 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 			this._attrs = new Set( attrs );
 		}
 
-		/**
-		 * Index of the node in the parent element or null if the node has no parent.
-		 *
-		 * Throws error if the parent element does not contain this node.
-		 *
-		 * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
-		 */
-		getIndex() {
-			let pos;
-
-			if ( !this.parent ) {
-				return null;
-			}
-
-			// No parent or child doesn't exist in parent's children.
-			if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
-				/**
-				 * The node's parent does not contain this node. It means that the document tree is corrupted.
-				 *
-				 * @error node-not-found-in-parent
-				 */
-				throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
-			}
-
-			return pos;
-		}
-
 		/**
 		 * Depth of the node, which equals to total number of its parents.
 		 *
@@ -87,6 +60,30 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 			return depth;
 		}
 
+		/**
+		 * Nodes next sibling or `null` if it is the last child.
+		 *
+		 * @readonly
+		 * @property {document.Node|null} nextSibling
+		 */
+		get nextSibling() {
+			const index = this.getIndex();
+
+			return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
+		}
+
+		/**
+		 * Nodes previous sibling or null if it is the last child.
+		 *
+		 * @readonly
+		 * @property {document.Node|null} previousSibling
+		 */
+		get previousSibling() {
+			const index = this.getIndex();
+
+			return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
+		}
+
 		/**
 		 * The top parent for the node. If node has no parent it is the root itself.
 		 *
@@ -104,34 +101,82 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 		}
 
 		/**
-		 * Nodes next sibling or `null` if it is the last child.
+		 * Finds an attribute by a key.
 		 *
-		 * @readonly
-		 * @property {document.Node|null} nextSibling
+		 * @param {String} attr The attribute key.
+		 * @returns {document.Attribute} The found attribute.
 		 */
-		get nextSibling() {
-			const index = this.getIndex();
+		getAttr( key ) {
+			for ( let attr of this._attrs ) {
+				if ( attr.key == key ) {
+					return attr.value;
+				}
+			}
 
-			return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
+			return null;
 		}
 
 		/**
-		 * Nodes previous sibling or null if it is the last child.
+		 * Returns attribute iterator. It can be use to create a new element with the same attributes:
 		 *
-		 * @readonly
-		 * @property {document.Node|null} previousSibling
+		 *		const copy = new Element( element.name, element.getAttrs() );
+		 *
+		 * @returns {Iterable.<document.Attribute>} Attribute iterator.
 		 */
-		get previousSibling() {
-			const index = this.getIndex();
+		getAttrs() {
+			return this._attrs[ Symbol.iterator ]();
+		}
 
-			return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
+		/**
+		 * Index of the node in the parent element or null if the node has no parent.
+		 *
+		 * Throws error if the parent element does not contain this node.
+		 *
+		 * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
+		 */
+		getIndex() {
+			let pos;
+
+			if ( !this.parent ) {
+				return null;
+			}
+
+			// No parent or child doesn't exist in parent's children.
+			if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
+				/**
+				 * The node's parent does not contain this node. It means that the document tree is corrupted.
+				 *
+				 * @error node-not-found-in-parent
+				 */
+				throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
+			}
+
+			return pos;
+		}
+
+		/**
+		 * Gets path to the node. For example if the node is the second child of the first child of the root then the path
+		 * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link document.Position}.
+		 *
+		 * @returns {Number[]} The path.
+		 */
+		getPath() {
+			const path = [];
+			let node = this; // jscs:ignore safeContextKeyword
+
+			while ( node.parent ) {
+				path.unshift( node.getIndex() );
+				node = node.parent;
+			}
+
+			return path;
 		}
 
 		/**
 		 * Returns `true` if the node contains an attribute with the same key and value as given or the same key if the
 		 * given parameter is a string.
 		 *
-		 * @param {document.Attribute|String} attr An attribute or a key to compare.
+		 * @param {document.Attribute|String} key An attribute or a key to compare.
 		 * @returns {Boolean} True if node contains given attribute or an attribute with the given key.
 		 */
 		hasAttr( key ) {
@@ -157,22 +202,6 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 			return false;
 		}
 
-		/**
-		 * Finds an attribute by a key.
-		 *
-		 * @param {String} attr The attribute key.
-		 * @returns {document.Attribute} The found attribute.
-		 */
-		getAttr( key ) {
-			for ( let attr of this._attrs ) {
-				if ( attr.key == key ) {
-					return attr.value;
-				}
-			}
-
-			return null;
-		}
-
 		/**
 		 * Removes attribute from the list of attributes.
 		 *
@@ -199,24 +228,6 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 			this._attrs.add( attr );
 		}
 
-		/**
-		 * Gets path to the node. For example if the node is the second child of the first child of the root then the path
-		 * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link document.Position}.
-		 *
-		 * @returns {Number[]} The path.
-		 */
-		getPath() {
-			const path = [];
-			let node = this; // jscs:ignore safeContextKeyword
-
-			while ( node.parent ) {
-				path.unshift( node.getIndex() );
-				node = node.parent;
-			}
-
-			return path;
-		}
-
 		/**
 		 * Custom toJSON method to solve child-parent circular dependencies.
 		 *
@@ -230,17 +241,6 @@ CKEDITOR.define( [ 'document/attribute', 'utils', 'ckeditorerror' ], ( Attribute
 
 			return json;
 		}
-
-		/**
-		 * Returns attribute iterator. It can be use to create a new element with the same attributes:
-		 *
-		 *		const copy = new Element( element.name, element.getAttrs() );
-		 *
-		 * @returns {Iterable.<document.Attribute>} Attribute iterator.
-		 */
-		getAttrs() {
-			return this._attrs[ Symbol.iterator ]();
-		}
 	}
 
 	return Node;

+ 25 - 25
packages/ckeditor5-engine/src/document/nodelist.js

@@ -94,34 +94,30 @@ CKEDITOR.define( [
 		}
 
 		/**
-		 * Returns node at the given index.
+		 * Number of nodes in the node list.
 		 *
-		 * @param {Number} index Node index.
-		 * @returns {document.Node} Node at given index.
+		 * @readonly
+		 * @property {Number} length
 		 */
-		get( index ) {
-			return this._nodes[ index ];
+		get length() {
+			return this._nodes.length;
 		}
 
 		/**
-		 * Inserts nodes from the given node list into this node list at the given index.
-		 *
-		 * @param {Number} index Position where nodes should be inserted.
-		 * @param {document.NodeList} nodeList List of nodes to insert.
+		 * Node list iterator.
 		 */
-		insert( index, nodeList ) {
-			this._nodes.splice.apply( this._nodes, [ index, 0 ].concat( nodeList._nodes ) );
+		[ Symbol.iterator ]() {
+			return this._nodes[ Symbol.iterator ]();
 		}
 
 		/**
-		 * Removes number of nodes starting at the given index.
+		 * Returns node at the given index.
 		 *
-		 * @param {Number} index Position of the first node to remove.
-		 * @param {Number} number Number of nodes to remove.
-		 * @returns {document.NodeList} List of removed nodes.
+		 * @param {Number} index Node index.
+		 * @returns {document.Node} Node at given index.
 		 */
-		remove( index, number ) {
-			return new NodeList( this._nodes.splice( index, number ) );
+		get( index ) {
+			return this._nodes[ index ];
 		}
 
 		/**
@@ -135,20 +131,24 @@ CKEDITOR.define( [
 		}
 
 		/**
-		 * Number of nodes in the node list.
+		 * Inserts nodes from the given node list into this node list at the given index.
 		 *
-		 * @readonly
-		 * @property {Number} length
+		 * @param {Number} index Position where nodes should be inserted.
+		 * @param {document.NodeList} nodeList List of nodes to insert.
 		 */
-		get length() {
-			return this._nodes.length;
+		insert( index, nodeList ) {
+			this._nodes.splice.apply( this._nodes, [ index, 0 ].concat( nodeList._nodes ) );
 		}
 
 		/**
-		 * Node list iterator.
+		 * Removes number of nodes starting at the given index.
+		 *
+		 * @param {Number} index Position of the first node to remove.
+		 * @param {Number} number Number of nodes to remove.
+		 * @returns {document.NodeList} List of removed nodes.
 		 */
-		[ Symbol.iterator ]() {
-			return this._nodes[ Symbol.iterator ]();
+		remove( index, number ) {
+			return new NodeList( this._nodes.splice( index, number ) );
 		}
 	}
 

+ 8 - 8
packages/ckeditor5-engine/src/document/operation/changeoperation.js

@@ -61,6 +61,14 @@ CKEDITOR.define( [
 			this.newAttr = newAttr;
 		}
 
+		clone() {
+			return new ChangeOperation( this.range.clone(), this.oldAttr, this.newAttr, this.baseVersion );
+		}
+
+		getReversed() {
+			return new ChangeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
+		}
+
 		_execute() {
 			const oldAttr = this.oldAttr;
 			const newAttr = this.newAttr;
@@ -123,14 +131,6 @@ CKEDITOR.define( [
 				}
 			}
 		}
-
-		getReversed() {
-			return new ChangeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
-		}
-
-		clone() {
-			return new ChangeOperation( this.range.clone(), this.oldAttr, this.newAttr, this.baseVersion );
-		}
 	}
 
 	return ChangeOperation;

+ 4 - 4
packages/ckeditor5-engine/src/document/operation/insertoperation.js

@@ -45,8 +45,8 @@ CKEDITOR.define( [
 			this.nodeList = new NodeList( nodes );
 		}
 
-		_execute() {
-			this.position.parent.insertChildren( this.position.offset, this.nodeList );
+		clone() {
+			return new InsertOperation( this.position, this.nodeList, this.baseVersion );
 		}
 
 		getReversed() {
@@ -56,8 +56,8 @@ CKEDITOR.define( [
 			return new RemoveOperation( this.position, this.nodeList.length, this.baseVersion + 1 );
 		}
 
-		clone() {
-			return new InsertOperation( this.position, this.nodeList, this.baseVersion );
+		_execute() {
+			this.position.parent.insertChildren( this.position.offset, this.nodeList );
 		}
 	}
 

+ 8 - 8
packages/ckeditor5-engine/src/document/operation/moveoperation.js

@@ -50,6 +50,14 @@ CKEDITOR.define( [
 			this.howMany = howMany;
 		}
 
+		clone() {
+			return new MoveOperation( this.sourcePosition.clone(), this.targetPosition.clone(), this.howMany, this.baseVersion );
+		}
+
+		getReversed() {
+			return new MoveOperation( this.targetPosition.clone(), this.sourcePosition.clone(), this.howMany, this.baseVersion + 1 );
+		}
+
 		_execute() {
 			let sourceElement = this.sourcePosition.parent;
 			let targetElement = this.targetPosition.parent;
@@ -114,14 +122,6 @@ CKEDITOR.define( [
 
 			targetElement.insertChildren( targetOffset, removedNodes );
 		}
-
-		getReversed() {
-			return new MoveOperation( this.targetPosition.clone(), this.sourcePosition.clone(), this.howMany, this.baseVersion + 1 );
-		}
-
-		clone() {
-			return new MoveOperation( this.sourcePosition.clone(), this.targetPosition.clone(), this.howMany, this.baseVersion );
-		}
 	}
 
 	return MoveOperation;

+ 4 - 4
packages/ckeditor5-engine/src/document/operation/nooperation.js

@@ -20,10 +20,6 @@ CKEDITOR.define( [
 	 * @class document.operation.NoOperation
 	 */
 	class NoOperation extends Operation {
-		_execute() {
-			// Do nothing.
-		}
-
 		clone() {
 			return new NoOperation( this.baseVersion );
 		}
@@ -31,6 +27,10 @@ CKEDITOR.define( [
 		getReversed() {
 			return new NoOperation( this.baseVersion + 1 );
 		}
+
+		_execute() {
+			// Do nothing.
+		}
 	}
 
 	return NoOperation;

+ 8 - 8
packages/ckeditor5-engine/src/document/operation/operation.js

@@ -39,11 +39,10 @@ CKEDITOR.define( [], () => {
 			 */
 
 			/**
-			 * Executes the operation - modifications described by the operation attributes
-			 * will be applied to the tree model.
+			 * Creates and returns an operation that has the same parameters as this operation.
 			 *
-			 * @method _execute
-			 * @protected
+			 * @method clone
+			 * @returns {document.operation.Operation} Clone of this operation.
 			 */
 
 			/**
@@ -53,17 +52,18 @@ CKEDITOR.define( [], () => {
 			 *
 			 * Keep in mind that tree model state may change since executing the original operation,
 			 * so reverse operation will be "outdated". In that case you will need to
-			 * {@link #getTransformedBy transform} it by all operations that were executed after the original operation.
+			 * {@link document.operation.transform} it by all operations that were executed after the original operation.
 			 *
 			 * @method getReversed
 			 * @returns {document.operation.Operation} Reversed operation.
 			 */
 
 			/**
-			 * Creates and returns an operation that has the same parameters as this operation.
+			 * Executes the operation - modifications described by the operation attributes
+			 * will be applied to the tree model.
 			 *
-			 * @method clone
-			 * @returns {document.operation.Operation} Clone of this operation.
+			 * @method _execute
+			 * @protected
 			 */
 		}
 	}

+ 62 - 62
packages/ckeditor5-engine/src/document/operation/transform.js

@@ -51,68 +51,6 @@ CKEDITOR.define( [
 	'document/range',
 	'utils'
 ], ( InsertOperation, ChangeOperation, MoveOperation, NoOperation, Range, utils ) => {
-	// When we don't want to update an operation, we create and return a clone of it.
-	// Returns the operation in "unified format" - wrapped in an Array.
-	function doNotUpdate( operation ) {
-		return [ operation.clone() ];
-	}
-
-	// Takes an Array of operations and sets consecutive base versions for them, starting from given base version.
-	// Returns the passed array.
-	function updateBaseVersions( baseVersion, operations ) {
-		for ( let i = 0; i < operations.length; i++ ) {
-			operations[ i ].baseVersion = baseVersion + i + 1;
-		}
-
-		return operations;
-	}
-
-	// Checks whether MoveOperation targetPosition is inside a node from the moved range of the other MoveOperation.
-	function moveTargetIntoMovedRange( a, b ) {
-		return a.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
-	}
-
-	// Takes two ChangeOperations and checks whether their attributes are in conflict.
-	// This happens when both operations changes an attribute with the same key and they either set different
-	// values for this attribute or one of them removes it while the other one sets it.
-	// Returns true if attributes are in conflict.
-	function haveConflictingAttributes( a, b ) {
-		// Keeping in mind that newAttr or oldAttr might be null.
-		// We will retrieve the key from whichever parameter is set.
-		const keyA = ( a.newAttr || a.oldAttr ).key;
-		const keyB = ( b.newAttr || b.oldAttr ).key;
-
-		if ( keyA != keyB ) {
-			// Different keys - not conflicting.
-			return false;
-		}
-
-		if ( a.newAttr === null && b.newAttr === null ) {
-			// Both remove the attribute - not conflicting.
-			return false;
-		}
-
-		// Check if they set different value or one of them removes the attribute.
-		return ( a.newAttr === null && b.newAttr !== null ) ||
-			( a.newAttr !== null && b.newAttr === null ) ||
-			( !a.newAttr.isEqual( b.newAttr ) );
-	}
-
-	// Gets an array of Ranges and produces one Range out of it. The root of a new range will be same as
-	// the root of the first range in the array. If any of given ranges has different root than the first range,
-	// it will be discarded.
-	function joinRanges( ranges ) {
-		if ( ranges.length === 0 ) {
-			return null;
-		} else if ( ranges.length == 1 ) {
-			return ranges[ 0 ];
-		} else {
-			ranges[ 0 ].end = ranges[ ranges.length - 1 ].end;
-
-			return ranges[ 0 ];
-		}
-	}
-
 	const ot = {
 		InsertOperation: {
 			// Transforms InsertOperation `a` by InsertOperation `b`. Accepts a flag stating whether `a` is more important
@@ -403,4 +341,66 @@ CKEDITOR.define( [
 
 		return updateBaseVersions( a.baseVersion, transformed );
 	};
+
+	// When we don't want to update an operation, we create and return a clone of it.
+	// Returns the operation in "unified format" - wrapped in an Array.
+	function doNotUpdate( operation ) {
+		return [ operation.clone() ];
+	}
+
+	// Takes an Array of operations and sets consecutive base versions for them, starting from given base version.
+	// Returns the passed array.
+	function updateBaseVersions( baseVersion, operations ) {
+		for ( let i = 0; i < operations.length; i++ ) {
+			operations[ i ].baseVersion = baseVersion + i + 1;
+		}
+
+		return operations;
+	}
+
+	// Checks whether MoveOperation targetPosition is inside a node from the moved range of the other MoveOperation.
+	function moveTargetIntoMovedRange( a, b ) {
+		return a.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
+	}
+
+	// Takes two ChangeOperations and checks whether their attributes are in conflict.
+	// This happens when both operations changes an attribute with the same key and they either set different
+	// values for this attribute or one of them removes it while the other one sets it.
+	// Returns true if attributes are in conflict.
+	function haveConflictingAttributes( a, b ) {
+		// Keeping in mind that newAttr or oldAttr might be null.
+		// We will retrieve the key from whichever parameter is set.
+		const keyA = ( a.newAttr || a.oldAttr ).key;
+		const keyB = ( b.newAttr || b.oldAttr ).key;
+
+		if ( keyA != keyB ) {
+			// Different keys - not conflicting.
+			return false;
+		}
+
+		if ( a.newAttr === null && b.newAttr === null ) {
+			// Both remove the attribute - not conflicting.
+			return false;
+		}
+
+		// Check if they set different value or one of them removes the attribute.
+		return ( a.newAttr === null && b.newAttr !== null ) ||
+			( a.newAttr !== null && b.newAttr === null ) ||
+			( !a.newAttr.isEqual( b.newAttr ) );
+	}
+
+	// Gets an array of Ranges and produces one Range out of it. The root of a new range will be same as
+	// the root of the first range in the array. If any of given ranges has different root than the first range,
+	// it will be discarded.
+	function joinRanges( ranges ) {
+		if ( ranges.length === 0 ) {
+			return null;
+		} else if ( ranges.length == 1 ) {
+			return ranges[ 0 ];
+		} else {
+			ranges[ 0 ].end = ranges[ ranges.length - 1 ].end;
+
+			return ranges[ 0 ];
+		}
+	}
 } );

+ 223 - 223
packages/ckeditor5-engine/src/document/position.js

@@ -63,6 +63,43 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 			this.root = root;
 		}
 
+		/**
+		 * Node directly after the position.
+		 *
+		 * @readonly
+		 * @property {document.Node}
+		 */
+		get nodeAfter() {
+			return this.parent.getChild( this.offset ) || null;
+		}
+
+		/**
+		 * Node directly before the position.
+		 *
+		 * @readonly
+		 * @type {document.Node}
+		 */
+		get nodeBefore() {
+			return this.parent.getChild( this.offset - 1 ) || null;
+		}
+
+		/**
+		 * Offset at which the position is located in the {@link #parent}.
+		 *
+		 * @readonly
+		 * @property {Number} offset
+		 */
+		get offset() {
+			return utils.last( this.path );
+		}
+
+		/**
+		 * Sets offset in the parent, which is the last element of the path.
+		 */
+		set offset( newOffset ) {
+			this.path[ this.path.length - 1 ] = newOffset;
+		}
+
 		/**
 		 * Parent element of the position. The position is located at {@link #offset} in this element.
 		 *
@@ -82,50 +119,184 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 		}
 
 		/**
-		 * Offset at which the position is located in the {@link #parent}.
+		 * Creates and returns a new instance of {@link document.Position}
+		 * which is equal to this {@link document.Position position}.
 		 *
-		 * @readonly
-		 * @property {Number} offset
+		 * @returns {document.Position} Cloned {@link document.Position position}.
 		 */
-		get offset() {
-			return utils.last( this.path );
+		clone() {
+			return new Position( this.path.slice(), this.root );
 		}
 
 		/**
-		 * Sets offset in the parent, which is the last element of the path.
+		 * Checks whether this position is before or after given position.
+		 *
+		 * @param {document.Position} otherPosition Position to compare with.
+		 * @returns {Number} A flag indicating whether this position is {@link #BEFORE} or
+		 * {@link #AFTER} or {@link #SAME} as given position. If positions are in different roots,
+		 * {@link #DIFFERENT} flag is returned.
 		 */
-		set offset( newOffset ) {
-			this.path[ this.path.length - 1 ] = newOffset;
+		compareWith( otherPosition ) {
+			if ( this.root != otherPosition.root ) {
+				return DIFFERENT;
+			}
+
+			const result = utils.compareArrays( this.path, otherPosition.path );
+
+			switch ( result ) {
+				case utils.compareArrays.SAME:
+					return SAME;
+
+				case utils.compareArrays.PREFIX:
+					return BEFORE;
+
+				case utils.compareArrays.EXTENSION:
+					return AFTER;
+
+				default:
+					if ( this.path[ result ] < otherPosition.path[ result ] ) {
+						return BEFORE;
+					} else {
+						return AFTER;
+					}
+			}
 		}
 
 		/**
-		 * Node directly before the position.
+		 * Returns the path to the parent, which is the {@link document.Position#path} without the last element.
 		 *
-		 * @readonly
-		 * @type {document.Node}
+		 * This method returns the parent path even if the parent does not exists.
+		 *
+		 * @returns {Number[]} Path to the parent.
 		 */
-		get nodeBefore() {
-			return this.parent.getChild( this.offset - 1 ) || null;
+		getParentPath() {
+			return this.path.slice( 0, -1 );
 		}
 
 		/**
-		 * Node directly after the position.
+		 * Returns this position after being updated by removing `howMany` nodes starting from `deletePosition`.
+		 * It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
 		 *
-		 * @readonly
-		 * @property {document.Node}
+		 * @param {document.Position} deletePosition Position before the first removed node.
+		 * @param {Number} howMany How many nodes are removed.
+		 * @returns {document.Position|null} Transformed position or `null`.
 		 */
-		get nodeAfter() {
-			return this.parent.getChild( this.offset ) || null;
+		getTransformedByDeletion( deletePosition, howMany ) {
+			let transformed = this.clone();
+
+			// This position can't be affected if deletion was in a different root.
+			if ( this.root != deletePosition.root ) {
+				return transformed;
+			}
+
+			if ( utils.compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.SAME ) {
+				// If nodes are removed from the node that is pointed by this position...
+				if ( deletePosition.offset < this.offset ) {
+					// And are removed from before an offset of that position...
+					// Decrement the offset accordingly.
+					if ( deletePosition.offset + howMany > this.offset ) {
+						transformed.offset = deletePosition.offset;
+					} else {
+						transformed.offset -= howMany;
+					}
+				}
+			} else if ( utils.compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.PREFIX ) {
+				// If nodes are removed from a node that is on a path to this position...
+				const i = deletePosition.path.length - 1;
+
+				if ( deletePosition.offset < this.path[ i ] ) {
+					// And are removed from before next node of that path...
+					if ( deletePosition.offset + howMany > this.path[ i ] ) {
+						// If the next node of that path is removed return null
+						// because the node containing this position got removed.
+						return null;
+					} else {
+						// Otherwise, decrement index on that path.
+						transformed.path[ i ] -= howMany;
+					}
+				}
+			}
+
+			return transformed;
 		}
 
 		/**
-		 * Checks whether this position equals given position.
+		 * Returns this position after being updated by inserting `howMany` nodes at `insertPosition`.
+		 *
+		 * @param {document.Position} insertPosition Position where nodes are inserted.
+		 * @param {Number} howMany How many nodes are inserted.
+		 * @param {Boolean} insertBefore Flag indicating whether nodes are inserted before or after `insertPosition`.
+		 * This is important only when `insertPosition` and this position are same. If that is the case and the flag is
+		 * set to `true`, this position will get transformed. If the flag is set to `false`, it won't.
+		 * @returns {document.Position} Transformed position.
+		 */
+		getTransformedByInsertion( insertPosition, howMany, insertBefore ) {
+			let transformed = this.clone();
+
+			// This position can't be affected if insertion was in a different root.
+			if ( this.root != insertPosition.root ) {
+				return transformed;
+			}
+
+			if ( utils.compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.SAME ) {
+				// If nodes are inserted in the node that is pointed by this position...
+				if ( insertPosition.offset < this.offset || ( insertPosition.offset == this.offset && insertBefore ) ) {
+					// And are inserted before an offset of that position...
+					// "Push" this positions offset.
+					transformed.offset += howMany;
+				}
+			} else if ( utils.compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.PREFIX ) {
+				// If nodes are inserted in a node that is on a path to this position...
+				const i = insertPosition.path.length - 1;
+
+				if ( insertPosition.offset <= this.path[ i ] ) {
+					// And are inserted before next node of that path...
+					// "Push" the index on that path.
+					transformed.path[ i ] += howMany;
+				}
+			}
+
+			return transformed;
+		}
+
+		/**
+		 * Returns this position after being updated by moving `howMany` attributes from `sourcePosition` to `targetPosition`.
+		 *
+		 * @param {document.Position} sourcePosition Position before the first element to move.
+		 * @param {document.Position} targetPosition Position where moved elements will be inserted.
+		 * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
+		 * @param {Boolean} insertBefore Flag indicating whether moved nodes are pasted before or after `insertPosition`.
+		 * This is important only when `targetPosition` and this position are same. If that is the case and the flag is
+		 * set to `true`, this position will get transformed by range insertion. If the flag is set to `false`, it won't.
+		 * @returns {document.Position} Transformed position.
+		 */
+		getTransformedByMove( sourcePosition, targetPosition, howMany, insertBefore ) {
+			// Moving a range removes nodes from their original position. We acknowledge this by proper transformation.
+			let transformed = this.getTransformedByDeletion( sourcePosition, howMany );
+
+			if ( transformed !== null ) {
+				// This position is not inside a removed node.
+				// Next step is to reflect pasting nodes, which might further affect the position.
+				transformed = transformed.getTransformedByInsertion( targetPosition, howMany, insertBefore );
+			} else {
+				// This position is inside a removed node. In this case, we are unable to simply transform it by range insertion.
+				// Instead, we calculate a combination of this position, move source position and target position.
+				transformed = this._getCombined( sourcePosition, targetPosition );
+			}
+
+			return transformed;
+		}
+
+		/**
+		 * Checks whether this position is after given position.
+		 *
+		 * **Note:** see {document.Position#isBefore}.
 		 *
 		 * @param {document.Position} otherPosition Position to compare with.
-		 * @returns {Boolean} True if positions are same.
+		 * @returns {Boolean} True if this position is after given position.
 		 */
-		isEqual( otherPosition ) {
-			return this.compareWith( otherPosition ) == SAME;
+		isAfter( otherPosition ) {
+			return this.compareWith( otherPosition ) == AFTER;
 		}
 
 		/**
@@ -164,51 +335,33 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 		}
 
 		/**
-		 * Checks whether this position is after given position.
-		 *
-		 * **Note:** see {document.Position#isBefore}.
+		 * Checks whether this position equals given position.
 		 *
 		 * @param {document.Position} otherPosition Position to compare with.
-		 * @returns {Boolean} True if this position is after given position.
-		 */
-		isAfter( otherPosition ) {
-			return this.compareWith( otherPosition ) == AFTER;
-		}
-
-		/**
-		 * Returns the path to the parent, which is the {@link document.Position#path} without the last element.
-		 *
-		 * This method returns the parent path even if the parent does not exists.
-		 *
-		 * @returns {Number[]} Path to the parent.
-		 */
-		getParentPath() {
-			return this.path.slice( 0, -1 );
-		}
-
-		/**
-		 * Creates and returns a new instance of {@link document.Position}
-		 * which is equal to this {@link document.Position position}.
-		 *
-		 * @returns {document.Position} Cloned {@link document.Position position}.
+		 * @returns {Boolean} True if positions are same.
 		 */
-		clone() {
-			return new Position( this.path.slice(), this.root );
+		isEqual( otherPosition ) {
+			return this.compareWith( otherPosition ) == SAME;
 		}
 
 		/**
-		 * Creates a new position from the parent element and the offset in that element.
+		 * Creates a new position after given node.
 		 *
-		 * @param {document.Element} parent Position parent element.
-		 * @param {Number} offset Position offset.
+		 * @param {document.Node} node Node the position should be directly after.
 		 * @returns {document.Position}
 		 */
-		static createFromParentAndOffset( parent, offset ) {
-			const path = parent.getPath();
-
-			path.push( offset );
+		static createAfter( node ) {
+			if ( !node.parent ) {
+				/**
+				 * You can not make position after root.
+				 *
+				 * @error position-after-root
+				 * @param {document.Node} root
+				 */
+				throw new CKEditorError( 'position-after-root: You can not make position after root.', { root: node } );
+			}
 
-			return new Position( path, parent.root );
+			return Position.createFromParentAndOffset( node.parent, node.getIndex() + 1 );
 		}
 
 		/**
@@ -232,85 +385,18 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 		}
 
 		/**
-		 * Creates a new position after given node.
+		 * Creates a new position from the parent element and the offset in that element.
 		 *
-		 * @param {document.Node} node Node the position should be directly after.
+		 * @param {document.Element} parent Position parent element.
+		 * @param {Number} offset Position offset.
 		 * @returns {document.Position}
 		 */
-		static createAfter( node ) {
-			if ( !node.parent ) {
-				/**
-				 * You can not make position after root.
-				 *
-				 * @error position-after-root
-				 * @param {document.Node} root
-				 */
-				throw new CKEditorError( 'position-after-root: You can not make position after root.', { root: node } );
-			}
-
-			return Position.createFromParentAndOffset( node.parent, node.getIndex() + 1 );
-		}
-
-		/**
-		 * Checks whether this position is before or after given position.
-		 *
-		 * @param {document.Position} otherPosition Position to compare with.
-		 * @returns {Number} A flag indicating whether this position is {@link #BEFORE} or
-		 * {@link #AFTER} or {@link #SAME} as given position. If positions are in different roots,
-		 * {@link #DIFFERENT} flag is returned.
-		 */
-		compareWith( otherPosition ) {
-			if ( this.root != otherPosition.root ) {
-				return DIFFERENT;
-			}
-
-			const result = utils.compareArrays( this.path, otherPosition.path );
-
-			switch ( result ) {
-				case utils.compareArrays.SAME:
-					return SAME;
-
-				case utils.compareArrays.PREFIX:
-					return BEFORE;
-
-				case utils.compareArrays.EXTENSION:
-					return AFTER;
-
-				default:
-					if ( this.path[ result ] < otherPosition.path[ result ] ) {
-						return BEFORE;
-					} else {
-						return AFTER;
-					}
-			}
-		}
-
-		/**
-		 * Returns this position after being updated by moving `howMany` attributes from `sourcePosition` to `targetPosition`.
-		 *
-		 * @param {document.Position} sourcePosition Position before the first element to move.
-		 * @param {document.Position} targetPosition Position where moved elements will be inserted.
-		 * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
-		 * @param {Boolean} insertBefore Flag indicating whether moved nodes are pasted before or after `insertPosition`.
-		 * This is important only when `targetPosition` and this position are same. If that is the case and the flag is
-		 * set to `true`, this position will get transformed by range insertion. If the flag is set to `false`, it won't.
-		 * @returns {document.Position} Transformed position.
-		 */
-		getTransformedByMove( sourcePosition, targetPosition, howMany, insertBefore ) {
-			// Moving a range removes nodes from their original position. We acknowledge this by proper transformation.
-			let transformed = this.getTransformedByDeletion( sourcePosition, howMany );
+		static createFromParentAndOffset( parent, offset ) {
+			const path = parent.getPath();
 
-			if ( transformed !== null ) {
-				// This position is not inside a removed node.
-				// Next step is to reflect pasting nodes, which might further affect the position.
-				transformed = transformed.getTransformedByInsertion( targetPosition, howMany, insertBefore );
-			} else {
-				// This position is inside a removed node. In this case, we are unable to simply transform it by range insertion.
-				// Instead, we calculate a combination of this position, move source position and target position.
-				transformed = this._getCombined( sourcePosition, targetPosition );
-			}
+			path.push( offset );
 
-			return transformed;
+			return new Position( path, parent.root );
 		}
 
 		/**
@@ -360,100 +446,14 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 
 			return combined;
 		}
-
-		/**
-		 * Returns this position after being updated by inserting `howMany` nodes at `insertPosition`.
-		 *
-		 * @param {document.Position} insertPosition Position where nodes are inserted.
-		 * @param {Number} howMany How many nodes are inserted.
-		 * @param {Boolean} insertBefore Flag indicating whether nodes are inserted before or after `insertPosition`.
-		 * This is important only when `insertPosition` and this position are same. If that is the case and the flag is
-		 * set to `true`, this position will get transformed. If the flag is set to `false`, it won't.
-		 * @returns {document.Position} Transformed position.
-		 */
-		getTransformedByInsertion( insertPosition, howMany, insertBefore ) {
-			let transformed = this.clone();
-
-			// This position can't be affected if insertion was in a different root.
-			if ( this.root != insertPosition.root ) {
-				return transformed;
-			}
-
-			if ( utils.compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.SAME ) {
-				// If nodes are inserted in the node that is pointed by this position...
-				if ( insertPosition.offset < this.offset || ( insertPosition.offset == this.offset && insertBefore ) ) {
-					// And are inserted before an offset of that position...
-					// "Push" this positions offset.
-					transformed.offset += howMany;
-				}
-			} else if ( utils.compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.PREFIX ) {
-				// If nodes are inserted in a node that is on a path to this position...
-				const i = insertPosition.path.length - 1;
-
-				if ( insertPosition.offset <= this.path[ i ] ) {
-					// And are inserted before next node of that path...
-					// "Push" the index on that path.
-					transformed.path[ i ] += howMany;
-				}
-			}
-
-			return transformed;
-		}
-
-		/**
-		 * Returns this position after being updated by removing `howMany` nodes starting from `deletePosition`.
-		 * It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
-		 *
-		 * @param {document.Position} deletePosition Position before the first removed node.
-		 * @param {Number} howMany How many nodes are removed.
-		 * @returns {document.Position|null} Transformed position or `null`.
-		 */
-		getTransformedByDeletion( deletePosition, howMany ) {
-			let transformed = this.clone();
-
-			// This position can't be affected if deletion was in a different root.
-			if ( this.root != deletePosition.root ) {
-				return transformed;
-			}
-
-			if ( utils.compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.SAME ) {
-				// If nodes are removed from the node that is pointed by this position...
-				if ( deletePosition.offset < this.offset ) {
-					// And are removed from before an offset of that position...
-					// Decrement the offset accordingly.
-					if ( deletePosition.offset + howMany > this.offset ) {
-						transformed.offset = deletePosition.offset;
-					} else {
-						transformed.offset -= howMany;
-					}
-				}
-			} else if ( utils.compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == utils.compareArrays.PREFIX ) {
-				// If nodes are removed from a node that is on a path to this position...
-				const i = deletePosition.path.length - 1;
-
-				if ( deletePosition.offset < this.path[ i ] ) {
-					// And are removed from before next node of that path...
-					if ( deletePosition.offset + howMany > this.path[ i ] ) {
-						// If the next node of that path is removed return null
-						// because the node containing this position got removed.
-						return null;
-					} else {
-						// Otherwise, decrement index on that path.
-						transformed.path[ i ] -= howMany;
-					}
-				}
-			}
-
-			return transformed;
-		}
 	}
 
 	/**
-	 * Flag for "are same" relation between Positions.
+	 * Flag for "is after" relation between Positions.
 	 *
 	 * @type {Number}
 	 */
-	Position.SAME = SAME;
+	Position.AFTER = AFTER;
 
 	/**
 	 * Flag for "is before" relation between Positions.
@@ -463,18 +463,18 @@ CKEDITOR.define( [ 'document/rootelement', 'utils', 'ckeditorerror' ], ( RootEle
 	Position.BEFORE = BEFORE;
 
 	/**
-	 * Flag for "is after" relation between Positions.
+	 * Flag for "are in different roots" relation between Positions.
 	 *
 	 * @type {Number}
 	 */
-	Position.AFTER = AFTER;
+	Position.DIFFERENT = DIFFERENT;
 
 	/**
-	 * Flag for "are in different roots" relation between Positions.
+	 * Flag for "are same" relation between Positions.
 	 *
 	 * @type {Number}
 	 */
-	Position.DIFFERENT = DIFFERENT;
+	Position.SAME = SAME;
 
 	return Position;
 } );

+ 6 - 6
packages/ckeditor5-engine/src/document/positioniterator.js

@@ -148,31 +148,31 @@ CKEDITOR.define( [
 	}
 
 	/**
-	 * Flag for entering element.
+	 * Flag for character.
 	 *
 	 * @static
 	 * @readonly
 	 * @property {Number}
 	 */
-	PositionIterator.ELEMENT_ENTER = ELEMENT_ENTER;
+	PositionIterator.CHARACTER = CHARACTER;
 
 	/**
-	 * Flag for leaving element.
+	 * Flag for entering element.
 	 *
 	 * @static
 	 * @readonly
 	 * @property {Number}
 	 */
-	PositionIterator.ELEMENT_LEAVE = ELEMENT_LEAVE;
+	PositionIterator.ELEMENT_ENTER = ELEMENT_ENTER;
 
 	/**
-	 * Flag for character.
+	 * Flag for leaving element.
 	 *
 	 * @static
 	 * @readonly
 	 * @property {Number}
 	 */
-	PositionIterator.CHARACTER = CHARACTER;
+	PositionIterator.ELEMENT_LEAVE = ELEMENT_LEAVE;
 
 	return PositionIterator;
 } );

+ 91 - 91
packages/ckeditor5-engine/src/document/range.js

@@ -36,39 +36,12 @@ CKEDITOR.define( [ 'document/position', 'document/positioniterator', 'utils' ],
 		}
 
 		/**
-		 * Creates a range inside an element which starts before the first child and ends after the last child.
-		 *
-		 * @param {document.Element} element Element which is a parent for the range.
-		 * @returns {document.Range} Created range.
-		 */
-		static createFromElement( element ) {
-			return Range.createFromParentsAndOffsets( element, 0, element, element.getChildCount() );
-		}
-
-		/**
-		 * Creates a range from given parents and offsets.
-		 *
-		 * @param {document.Element} startElement Start position parent element.
-		 * @param {Number} startOffset Start position offset.
-		 * @param {document.Element} endElement End position parent element.
-		 * @param {Number} endOffset End position offset.
-		 * @returns {document.Range} Created range.
-		 */
-		static createFromParentsAndOffsets( startElement, startOffset, endElement, endOffset ) {
-			return new Range(
-					Position.createFromParentAndOffset( startElement, startOffset ),
-					Position.createFromParentAndOffset( endElement, endOffset )
-				);
-		}
-
-		/**
-		 * Two ranges equal if their start and end positions equal.
+		 * Range iterator.
 		 *
-		 * @param {document.Range} otherRange Range to compare with.
-		 * @returns {Boolean} True if ranges equal.
+		 * @see document.PositionIterator
 		 */
-		isEqual( otherRange ) {
-			return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
+		[ Symbol.iterator ]() {
+			return new PositionIterator( this );
 		}
 
 		/**
@@ -91,62 +64,6 @@ CKEDITOR.define( [ 'document/position', 'document/positioniterator', 'utils' ],
 			return position.isAfter( this.start ) && position.isBefore( this.end );
 		}
 
-		/**
-		 * Returns an array containing one or two {document.Range ranges} that are a result of transforming this
-		 * {@link document.Range range} by inserting `howMany` nodes at `insertPosition`. Two {@link document.Range ranges} are
-		 * returned if the insertion was inside this {@link document.Range range}.
-		 *
-		 * Examples:
-		 *
-		 * 	let range = new Range( new Position( [ 2, 7 ], root ), new Position( [ 4, 0, 1 ], root ) );
-		 * 	let transformed = range.getTransformedByInsertion( new Position( [ 1 ], root ), 2 );
-		 * 	// transformed array has one range from [ 4, 7 ] to [ 6, 0, 1 ]
-		 *
-		 * 	transformed = range.getTransformedByInsertion( new Position( [ 3, 2 ], root ), 4 );
-		 * 	// transformed array has two ranges: from [ 2, 7 ] to [ 3, 2 ] and from [ 3, 6 ] to [ 4, 0, 1 ]
-		 *
-		 * 	transformed = range.getTransformedByInsertion( new Position( [ 3, 2 ], root ), 4, true );
-		 * 	// transformed array has one range which is equal to `range`. This is because of spreadOnlyOnSameLevel flag.
-		 *
-		 * @param {document.Position} insertPosition Position where nodes are inserted.
-		 * @param {Number} howMany How many nodes are inserted.
-		 * @param {Boolean} spreadOnlyOnSameLevel Flag indicating whether this {document.Range range} should be spread
-		 * if insertion was inside a node from this {document.Range range} but not in the range itself.
-		 * @returns {Array.<document.Range>} Result of the transformation.
-		 */
-		getTransformedByInsertion( insertPosition, howMany, spreadOnlyOnSameLevel ) {
-			// Flag indicating whether this whole range and given insertPosition is on the same tree level.
-			const areOnSameLevel = utils.compareArrays( this.start.getParentPath(), this.end.getParentPath() ) == utils.compareArrays.SAME &&
-				utils.compareArrays( this.start.getParentPath(), insertPosition.getParentPath() ) == utils.compareArrays.SAME;
-
-			if ( this.containsPosition( insertPosition ) && ( !spreadOnlyOnSameLevel || areOnSameLevel ) ) {
-				// Range has to be spread. The first part is from original start to the spread point.
-				// The other part is from spread point to the original end, but transformed by
-				// insertion to reflect insertion changes.
-
-				return [
-					new Range(
-						insertPosition.getTransformedByInsertion( insertPosition, howMany, true ),
-						this.end.getTransformedByInsertion( insertPosition, howMany, true )
-					),
-					new Range(
-						this.start.clone(),
-						insertPosition.clone()
-					)
-				];
-			} else {
-				// If insertion is not inside the range, simply transform range boundaries (positions) by the insertion.
-				// Both, one or none of them might be affected by the insertion.
-
-				const range = this.clone();
-
-				range.start = range.start.getTransformedByInsertion( insertPosition, howMany, true );
-				range.end = range.end.getTransformedByInsertion( insertPosition, howMany, false );
-
-				return [ range ];
-			}
-		}
-
 		/**
 		 * Gets a part of this {@link document.Range range} which is not a part of given {@link document.Range range}. Returned
 		 * array contains zero, one or two {@link document.Range ranges}.
@@ -246,6 +163,82 @@ CKEDITOR.define( [ 'document/position', 'document/positioniterator', 'utils' ],
 			return null;
 		}
 
+		/**
+		 * Returns an array containing one or two {document.Range ranges} that are a result of transforming this
+		 * {@link document.Range range} by inserting `howMany` nodes at `insertPosition`. Two {@link document.Range ranges} are
+		 * returned if the insertion was inside this {@link document.Range range}.
+		 *
+		 * Examples:
+		 *
+		 * 	let range = new Range( new Position( [ 2, 7 ], root ), new Position( [ 4, 0, 1 ], root ) );
+		 * 	let transformed = range.getTransformedByInsertion( new Position( [ 1 ], root ), 2 );
+		 * 	// transformed array has one range from [ 4, 7 ] to [ 6, 0, 1 ]
+		 *
+		 * 	transformed = range.getTransformedByInsertion( new Position( [ 3, 2 ], root ), 4 );
+		 * 	// transformed array has two ranges: from [ 2, 7 ] to [ 3, 2 ] and from [ 3, 6 ] to [ 4, 0, 1 ]
+		 *
+		 * 	transformed = range.getTransformedByInsertion( new Position( [ 3, 2 ], root ), 4, true );
+		 * 	// transformed array has one range which is equal to `range`. This is because of spreadOnlyOnSameLevel flag.
+		 *
+		 * @param {document.Position} insertPosition Position where nodes are inserted.
+		 * @param {Number} howMany How many nodes are inserted.
+		 * @param {Boolean} spreadOnlyOnSameLevel Flag indicating whether this {document.Range range} should be spread
+		 * if insertion was inside a node from this {document.Range range} but not in the range itself.
+		 * @returns {Array.<document.Range>} Result of the transformation.
+		 */
+		getTransformedByInsertion( insertPosition, howMany, spreadOnlyOnSameLevel ) {
+			// Flag indicating whether this whole range and given insertPosition is on the same tree level.
+			const areOnSameLevel = utils.compareArrays( this.start.getParentPath(), this.end.getParentPath() ) == utils.compareArrays.SAME &&
+				utils.compareArrays( this.start.getParentPath(), insertPosition.getParentPath() ) == utils.compareArrays.SAME;
+
+			if ( this.containsPosition( insertPosition ) && ( !spreadOnlyOnSameLevel || areOnSameLevel ) ) {
+				// Range has to be spread. The first part is from original start to the spread point.
+				// The other part is from spread point to the original end, but transformed by
+				// insertion to reflect insertion changes.
+
+				return [
+					new Range(
+						insertPosition.getTransformedByInsertion( insertPosition, howMany, true ),
+						this.end.getTransformedByInsertion( insertPosition, howMany, true )
+					),
+					new Range(
+						this.start.clone(),
+						insertPosition.clone()
+					)
+				];
+			} else {
+				// If insertion is not inside the range, simply transform range boundaries (positions) by the insertion.
+				// Both, one or none of them might be affected by the insertion.
+
+				const range = this.clone();
+
+				range.start = range.start.getTransformedByInsertion( insertPosition, howMany, true );
+				range.end = range.end.getTransformedByInsertion( insertPosition, howMany, false );
+
+				return [ range ];
+			}
+		}
+
+		/**
+		 * Two ranges equal if their start and end positions equal.
+		 *
+		 * @param {document.Range} otherRange Range to compare with.
+		 * @returns {Boolean} True if ranges equal.
+		 */
+		isEqual( otherRange ) {
+			return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
+		}
+
+		/**
+		 * Creates a range inside an element which starts before the first child and ends after the last child.
+		 *
+		 * @param {document.Element} element Element which is a parent for the range.
+		 * @returns {document.Range} Created range.
+		 */
+		static createFromElement( element ) {
+			return Range.createFromParentsAndOffsets( element, 0, element, element.getChildCount() );
+		}
+
 		/**
 		 * Creates a new range spreading from specified position to the same position moved by given offset.
 		 *
@@ -261,12 +254,19 @@ CKEDITOR.define( [ 'document/position', 'document/positioniterator', 'utils' ],
 		}
 
 		/**
-		 * Range iterator.
+		 * Creates a range from given parents and offsets.
 		 *
-		 * @see document.PositionIterator
+		 * @param {document.Element} startElement Start position parent element.
+		 * @param {Number} startOffset Start position offset.
+		 * @param {document.Element} endElement End position parent element.
+		 * @param {Number} endOffset End position offset.
+		 * @returns {document.Range} Created range.
 		 */
-		[ Symbol.iterator ]() {
-			return new PositionIterator( this );
+		static createFromParentsAndOffsets( startElement, startOffset, endElement, endOffset ) {
+			return new Range(
+				Position.createFromParentAndOffset( startElement, startOffset ),
+				Position.createFromParentAndOffset( endElement, endOffset )
+			);
 		}
 	}