浏览代码

Corrected typos and documentation for Tree Model classes.
Prepared Tree Model classes for OT algorithms.
Introduced ReinsertOperation class.
Introduced RootElement class.

Szymon Cofalik 10 年之前
父节点
当前提交
80a43a4f00

+ 11 - 11
packages/ckeditor5-utils/src/document/attribute.js

@@ -9,14 +9,14 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 	/**
 	/**
 	 * Attributes can store any additional information for nodes in the data model.
 	 * Attributes can store any additional information for nodes in the data model.
 	 *
 	 *
-	 * @class Attribute
+	 * @class document.Attribute
 	 */
 	 */
 	class Attribute {
 	class Attribute {
 		/**
 		/**
 		 * Create a new attribute class. Once attribute is created it should not be modified.
 		 * Create a new attribute class. Once attribute is created it should not be modified.
 		 *
 		 *
-		 * @param {String} key Attribute key
-		 * @param {Mixed} value Attribute value
+		 * @param {String} key Attribute key.
+		 * @param {Mixed} value Attribute value.
 		 * @constructor
 		 * @constructor
 		 */
 		 */
 		constructor( key, value ) {
 		constructor( key, value ) {
@@ -50,7 +50,7 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 				return Attribute._register[ this._hash ];
 				return Attribute._register[ this._hash ];
 			}
 			}
 
 
-			// We do no care about the order so collections with the same elements should return the same hash.
+			// We do not care about the order, so collections with the same elements should return the same hash.
 			function sort( key, value ) {
 			function sort( key, value ) {
 				if ( !utils.isArray( value ) && utils.isObject( value ) ) {
 				if ( !utils.isArray( value ) && utils.isObject( value ) ) {
 					var sorted = {};
 					var sorted = {};
@@ -68,14 +68,14 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 		}
 		}
 
 
 		/**
 		/**
-		 * Compare two attributes. Returns true is two attributes have the same key and value even if the order of value
+		 * Compare two attributes. Returns true if two attributes have the same key and value even if the order of value
 		 * elements is different.
 		 * elements is different.
 		 *
 		 *
 		 *	var attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
 		 *	var attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
 		 *	var attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
 		 *	var attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
 		 *	attr1.isEqual( attr2 ); // true
 		 *	attr1.isEqual( attr2 ); // true
 		 *
 		 *
-		 * @param {document.Attribute} otherAttr other attribute
+		 * @param {document.Attribute} otherAttr Attribute to compare with.
 		 * @returns {Boolean} True if attributes equals.
 		 * @returns {Boolean} True if attributes equals.
 		 */
 		 */
 		isEqual( otherAttr ) {
 		isEqual( otherAttr ) {
@@ -83,7 +83,7 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 		}
 		}
 
 
 		/**
 		/**
-		 * To save memory common used attributes may be registered. If an attribute is registered the constructor will
+		 * To save memory, commonly used attributes may be registered. If an attribute is registered the constructor will
 		 * always return the same instance of this attribute.
 		 * always return the same instance of this attribute.
 		 *
 		 *
 		 * Note that attributes are registered globally.
 		 * Note that attributes are registered globally.
@@ -95,8 +95,8 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 		 *	attr1 === attr3 // true
 		 *	attr1 === attr3 // true
 		 *
 		 *
 		 * @static
 		 * @static
-		 * @param {String} key Attribute key
-		 * @param {Mixed} value Attribute value
+		 * @param {String} key Attribute key.
+		 * @param {Mixed} value Attribute value.
 		 * @returns {document.Attribute} Registered attribute.
 		 * @returns {document.Attribute} Registered attribute.
 		 */
 		 */
 		static register( key, value ) {
 		static register( key, value ) {
@@ -113,7 +113,7 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 	}
 	}
 
 
 	/**
 	/**
-	 * Attribute register where all registered attributes are stored.
+	 * Register of attributes in which all registered attributes are stored.
 	 *
 	 *
 	 * @static
 	 * @static
 	 * @private
 	 * @private
@@ -122,4 +122,4 @@ CKEDITOR.define( [ 'utils' ], function( utils ) {
 	Attribute._register = {};
 	Attribute._register = {};
 
 
 	return Attribute;
 	return Attribute;
-} );
+} );

+ 61 - 84
packages/ckeditor5-utils/src/document/changeoperation.js

@@ -5,11 +5,11 @@
 
 
 'use strict';
 'use strict';
 
 
-CKEDITOR.define( [ 'document/operation', 'ckeditorerror', 'utils' ], function( Operation, CKEditorError, utils ) {
+CKEDITOR.define( [ 'document/operation', 'ckeditorerror' ], function( Operation, CKEditorError ) {
 	/**
 	/**
 	 * Operation to change nodes attribute. Using this class you can add, remove or change value of the attribute.
 	 * Operation to change nodes attribute. Using this class you can add, remove or change value of the attribute.
 	 *
 	 *
-	 * @class document.Operation
+	 * @class document.ChangeOperation
 	 */
 	 */
 	class ChangeOperation extends Operation {
 	class ChangeOperation extends Operation {
 		/**
 		/**
@@ -24,22 +24,22 @@ CKEDITOR.define( [ 'document/operation', 'ckeditorerror', 'utils' ], function( O
 		 * If both new and old attributes are set the operation will change the attribute value. Node that both new and
 		 * If both new and old attributes are set the operation will change the attribute value. Node that both new and
 		 * old attributes have to have the same key and the old attribute must be present in all nodes in ranges.
 		 * old attributes have to have the same key and the old attribute must be present in all nodes in ranges.
 		 *
 		 *
-		 * @param {Array|document.Range} ranges Range or array of ranges on which operation should be applied.
-		 * @param {document.Attribute|null} oldAttr Old attribute to change. Null if operation inserts new attribute.
-		 * @param {document.Attribute|null} newAttr New attribute. Null if operation removes attribute.
+		 * @param {document.Range} range Range on which operation should be applied.
+		 * @param {document.Attribute|null} oldAttr Attribute to be removed. Null if operation just inserts a new attribute.
+		 * @param {document.Attribute|null} newAttr Attribute to be added. Null if operation just removes an attribute.
 		 * @param {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @param {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @constructor
 		 * @constructor
 		 */
 		 */
-		constructor( ranges, oldAttr, newAttr, baseVersion ) {
+		constructor( range, oldAttr, newAttr, baseVersion ) {
 			super( baseVersion );
 			super( baseVersion );
 
 
 			/**
 			/**
-			 * Array of ranges on which operation should be applied.
+			 * Range on which operation should be applied.
 			 *
 			 *
 			 * @readonly
 			 * @readonly
-			 * @type {Array}
+			 * @type {document.Range}
 			 */
 			 */
-			this.ranges = utils.isArray( ranges ) ? ranges : [ ranges ];
+			this.range = range;
 
 
 			/**
 			/**
 			 * Old attribute to change. Null if operation inserts new attribute.
 			 * Old attribute to change. Null if operation inserts new attribute.
@@ -64,91 +64,68 @@ CKEDITOR.define( [ 'document/operation', 'ckeditorerror', 'utils' ], function( O
 		 * @protected
 		 * @protected
 		 */
 		 */
 		_execute() {
 		_execute() {
-			var ranges = this.ranges;
 			var oldAttr = this.oldAttr;
 			var oldAttr = this.oldAttr;
 			var newAttr = this.newAttr;
 			var newAttr = this.newAttr;
+			var value;
 
 
-			var value, range;
+			if ( oldAttr !== null && newAttr !== null && oldAttr.key != newAttr.key ) {
+				/**
+				 * Old and new attributes should have the same keys.
+				 *
+				 * @error operation-change-different-keys
+				 * @param {document.ChangeOperation} changeOperation
+				 * @param {document.Attribute} oldAttr
+				 * @param {document.Attribute} newAttr
+				 */
+				throw new CKEditorError(
+					'operation-change-different-keys: Old and new attributes should have the same keys.',
+					{ changeOperation: this, oldAttr: oldAttr, newAttr: newAttr } );
+			}
 
 
-			// Remove.
-			if ( newAttr === null ) {
-				for ( range of ranges ) {
-					for ( value of range ) {
-						if ( !value.node.hasAttr( oldAttr ) ) {
-							/**
-							 * The attribute which should be removed does not exists.
-							 *
-							 * @error operation-change-no-attr-to-remove
-							 * @param {document.ChangeOperation} changeOperation
-							 * @param {document.Node} node
-							 * @param {document.Attribute} attr
-							 */
-							throw new CKEditorError(
-								'operation-change-no-attr-to-remove: The attribute which should be removed does not exists.',
-								{ changeOperation: this, node: value.node, attr: oldAttr } );
-						}
+			// Remove or change.
+			if ( oldAttr !== null ) {
+				for ( value of this.range ) {
+					if ( !value.node.hasAttr( oldAttr ) ) {
+						/**
+						 * The attribute which should be removed does not exists for given node.
+						 *
+						 * @error operation-change-no-attr-to-remove
+						 * @param {document.ChangeOperation} changeOperation
+						 * @param {document.Node} node
+						 * @param {document.Attribute} attr
+						 */
+						throw new CKEditorError(
+							'operation-change-no-attr-to-remove: The attribute which should be removed does not exists for given node.',
+							{ changeOperation: this, node: value.node, attr: oldAttr } );
+					}
 
 
+					// It looks like this condition should be in the if "above" but in that case we won't check if
+					// the whole range has attribute oldAttr. If we want to be super-clean, we should check it even
+					// if we don't apply any changes.
+					if ( newAttr === null ) {
 						value.node.removeAttr( oldAttr.key );
 						value.node.removeAttr( oldAttr.key );
 					}
 					}
 				}
 				}
 			}
 			}
-			// Insert.
-			else if ( oldAttr === null ) {
-				for ( range of ranges ) {
-					for ( value of range ) {
-						if ( value.node.hasAttr( newAttr.key ) ) {
-							/**
-							 * The attribute with given key already exists.
-							 *
-							 * @error operation-change-attr-exists
-							 * @param {document.ChangeOperation} changeOperation
-							 * @param {document.Node} node
-							 * @param {document.Attribute} attr
-							 */
-							throw new CKEditorError(
-								'operation-change-attr-exists: The attribute with given key already exists.',
-								{ changeOperation: this, node: value.node, attr: newAttr } );
-						}
 
 
-						value.node.setAttr( newAttr );
+			// Insert or change.
+			if ( newAttr !== null ) {
+				for ( value of this.range ) {
+					if ( value.node.hasAttr( newAttr.key ) ) {
+						/**
+						 * The attribute with given key already exists for given node.
+						 *
+						 * @error operation-change-attr-exists
+						 * @param {document.ChangeOperation} changeOperation
+						 * @param {document.Node} node
+						 * @param {document.Attribute} attr
+						 */
+						throw new CKEditorError(
+							'operation-change-attr-exists: The attribute with given key already exists.',
+							{ changeOperation: this, node: value.node, attr: newAttr } );
 					}
 					}
-				}
-			}
-			// Change.
-			else {
-				if ( oldAttr.key != newAttr.key ) {
-					/**
-					 * Old and new attributes should have the same keys.
-					 *
-					 * @error operation-change-different-keys
-					 * @param {document.ChangeOperation} changeOperation
-					 * @param {document.Attribute} oldAttr
-					 * @param {document.Attribute} newAttr
-					 */
-					throw new CKEditorError(
-						'operation-change-different-keys: Old and new attributes should have the same keys.',
-						{ changeOperation: this, oldAttr: oldAttr, newAttr: newAttr } );
-				}
-
-				for ( range of ranges ) {
-					for ( value of range ) {
-						if ( !value.node.hasAttr( oldAttr ) ) {
-							/**
-							 * The attribute which should be changed does not exists.
-							 *
-							 * @error operation-change-no-attr-to-change
-							 * @param {document.ChangeOperation} changeOperation
-							 * @param {document.Node} node
-							 * @param {document.Attribute} oldAttr
-							 * @param {document.Attribute} newAttr
-							 */
-							throw new CKEditorError(
-								'operation-change-no-attr-to-change: The attribute which should be changed does not exists.',
-								{ changeOperation: this, node: value.node, oldAttr: oldAttr, newAttr: newAttr } );
-						}
 
 
-						value.node.setAttr( newAttr );
-					}
+					value.node.setAttr( newAttr );
 				}
 				}
 			}
 			}
 		}
 		}
@@ -159,9 +136,9 @@ CKEDITOR.define( [ 'document/operation', 'ckeditorerror', 'utils' ], function( O
 		 * @returns {document.ChangeOperation} Reverse operation.
 		 * @returns {document.ChangeOperation} Reverse operation.
 		 */
 		 */
 		reverseOperation() {
 		reverseOperation() {
-			return new ChangeOperation( this.ranges, this.newAttr, this.oldAttr, this.baseVersion + 1 );
+			return new ChangeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
 		}
 		}
 	}
 	}
 
 
 	return ChangeOperation;
 	return ChangeOperation;
-} );
+} );

+ 2 - 2
packages/ckeditor5-utils/src/document/character.js

@@ -7,7 +7,7 @@
 
 
 CKEDITOR.define( [ 'document/node' ], function( Node ) {
 CKEDITOR.define( [ 'document/node' ], function( Node ) {
 	/**
 	/**
-	 * Data structure for character stored in the linear data.
+	 * Data structure for character stored in the tree data model.
 	 *
 	 *
 	 * @class Character
 	 * @class Character
 	 */
 	 */
@@ -33,4 +33,4 @@ CKEDITOR.define( [ 'document/node' ], function( Node ) {
 	}
 	}
 
 
 	return Character;
 	return Character;
-} );
+} );

+ 61 - 5
packages/ckeditor5-utils/src/document/document.js

@@ -7,10 +7,13 @@
 
 
 CKEDITOR.define( [
 CKEDITOR.define( [
 	'document/element',
 	'document/element',
+	'document/rootelement',
 	'emittermixin',
 	'emittermixin',
 	'utils',
 	'utils',
 	'ckeditorerror'
 	'ckeditorerror'
-], function( Element, EmitterMixin, utils, CKEditorError ) {
+], function( Element, RootElement, EmitterMixin, utils, CKEditorError ) {
+	var graveyardSymbol = Symbol( 'graveyard' );
+
 	/**
 	/**
 	 * Document model.
 	 * Document model.
 	 *
 	 *
@@ -24,12 +27,17 @@ CKEDITOR.define( [
 		 */
 		 */
 		constructor() {
 		constructor() {
 			/**
 			/**
-			 * Document tree root. Document always have an root document.
+			 * List of roots that are owned and managed by this document.
 			 *
 			 *
 			 * @readonly
 			 * @readonly
-			 * @property {String} root
+			 * @property {Map} roots
+			 */
+			this.roots = new Map();
+
+			/**
+			 * Graveyard tree root. Document always have a graveyard root, which is storing removed nodes.
 			 */
 			 */
-			this.root = new Element( 'root' );
+			this.createRoot( graveyardSymbol );
 
 
 			/**
 			/**
 			 * Document version. It starts from 0 and every operation increase the version. It is used to ensure that
 			 * Document version. It starts from 0 and every operation increase the version. It is used to ensure that
@@ -67,9 +75,57 @@ CKEDITOR.define( [
 			this.version++;
 			this.version++;
 			this.fire( 'operationApplied', operation );
 			this.fire( 'operationApplied', operation );
 		}
 		}
+
+		/**
+		 * Creates a new top-level root.
+		 *
+		 * @param {String|Symbol} name Unique root name.
+		 * @returns {document.RootElement} Created root.
+		 */
+		createRoot( name ) {
+			if ( this.roots.has( name ) ) {
+				/**
+				 * Root with specified name already exists.
+				 *
+				 * @error document-createRoot-name-exists
+				 * @param {document.Document} doc
+				 * @param {String} name
+				 */
+				throw new CKEditorError(
+					'document-createRoot-name-exists: Root with specified name already exists.',
+					{ doc: this, name: name }
+				);
+			}
+
+			var root = new RootElement( this );
+			this.roots.set( root, name );
+
+			return root;
+		}
+
+		/**
+		 * Returns top-level root by it's name.
+		 *
+		 * @param {String|Symbol} name Name of the root to get.
+		 * @returns (document.RootElement} Root registered under given name.
+		 */
+		getRoot( name ) {
+			return this.roots.get( name );
+		}
+
+		/**
+		 * Graveyard tree root. Document always have a graveyard root, which is storing removed nodes.
+		 *
+		 * @protected
+		 * @readonly
+		 * @property {document.RootElement} _graveyard
+		 */
+		get _graveyard() {
+			return this.getRoot( graveyardSymbol );
+		}
 	}
 	}
 
 
 	utils.extend( Document.prototype, EmitterMixin );
 	utils.extend( Document.prototype, EmitterMixin );
 
 
 	return Document;
 	return Document;
-} );
+} );

+ 11 - 10
packages/ckeditor5-utils/src/document/element.js

@@ -7,20 +7,20 @@
 
 
 CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeList ) {
 CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeList ) {
 	/**
 	/**
-	 * Linear data element.
+	 * Tree data model element.
 	 *
 	 *
 	 * @class document.Element
 	 * @class document.Element
 	 */
 	 */
 	class Element extends Node {
 	class Element extends Node {
 		/**
 		/**
-		 * Creates linear data element.
+		 * Creates tree data model element.
 		 *
 		 *
 		 * This constructor should be used only internally by the document.
 		 * This constructor should be used only internally by the document.
 		 *
 		 *
 		 * @param {String} name Node name.
 		 * @param {String} name Node name.
 		 * @param {Iterable} attrs Iterable collection of {@link document.Attribute attributes}.
 		 * @param {Iterable} attrs Iterable collection of {@link document.Attribute attributes}.
-		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} nodes List of nodes to be inserted.
-		 * List of nodes can be any type accepted by the {@link document.NodeList} constructor.
+		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} children List of nodes to be inserted
+		 * into created element. List of nodes can be of any type accepted by the {@link document.NodeList} constructor.
 		 * @constructor
 		 * @constructor
 		 */
 		 */
 		constructor( name, attrs, children ) {
 		constructor( name, attrs, children ) {
@@ -53,9 +53,9 @@ CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeL
 		 * Note that list of children can be modified only in elements not attached yet to the document.
 		 * Note that list of children can be modified only in elements not attached yet to the document.
 		 * All attached nodes should be modified using the {@link document.InsertOperation}.
 		 * All attached nodes should be modified using the {@link document.InsertOperation}.
 		 *
 		 *
-		 * @param {Nuber} index Position where nodes should be inserted.
+		 * @param {Number} index Position where nodes should be inserted.
 		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} nodes List of nodes to be inserted.
 		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} nodes List of nodes to be inserted.
-		 * List of nodes can be any type accepted by the {@link document.NodeList} constructor.
+		 * List of nodes can be of any type accepted by the {@link document.NodeList} constructor.
 		 */
 		 */
 		insertChildren( index, nodes ) {
 		insertChildren( index, nodes ) {
 			this._children.insert( index, new NodeList( nodes ) );
 			this._children.insert( index, new NodeList( nodes ) );
@@ -73,6 +73,7 @@ CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeL
 		 *
 		 *
 		 * @param {Number} index Position of the first node to remove.
 		 * @param {Number} index Position of the first node to remove.
 		 * @param {Number} number Number of nodes to remove.
 		 * @param {Number} number Number of nodes to remove.
+		 * @returns {document.NodeList} List of removed nodes.
 		 */
 		 */
 
 
 		removeChildren( index, number ) {
 		removeChildren( index, number ) {
@@ -80,11 +81,11 @@ CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeL
 				this._children.get( i ).parent = null;
 				this._children.get( i ).parent = null;
 			}
 			}
 
 
-			this._children.remove( index, number );
+			return this._children.remove( index, number );
 		}
 		}
 
 
 		/**
 		/**
-		 * Get child at given index.
+		 * Gets child at given index.
 		 *
 		 *
 		 * @param {Number} index Index of child.
 		 * @param {Number} index Index of child.
 		 * @returns {document.Node} Child node.
 		 * @returns {document.Node} Child node.
@@ -94,7 +95,7 @@ CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeL
 		}
 		}
 
 
 		/**
 		/**
-		 * Get index of child node.
+		 * Gets index of child node.
 		 *
 		 *
 		 * @param {document.Node} node Child node.
 		 * @param {document.Node} node Child node.
 		 * @returns {Number} Index of child.
 		 * @returns {Number} Index of child.
@@ -114,4 +115,4 @@ CKEDITOR.define( [ 'document/node', 'document/nodelist' ], function( Node, NodeL
 	}
 	}
 
 
 	return Element;
 	return Element;
-} );
+} );

+ 3 - 3
packages/ckeditor5-utils/src/document/insertoperation.js

@@ -51,7 +51,7 @@ CKEDITOR.define( [ 'document/operation', 'document/nodelist', 'document/removeop
 		}
 		}
 
 
 		/**
 		/**
-		 * Creates an reverse remove operation.
+		 * Creates a reverse operation.
 		 *
 		 *
 		 * @returns {document.RemoveOperation} Reverse operation.
 		 * @returns {document.RemoveOperation} Reverse operation.
 		 */
 		 */
@@ -59,9 +59,9 @@ CKEDITOR.define( [ 'document/operation', 'document/nodelist', 'document/removeop
 			// Because of circular dependencies we need to re-require remove operation here.
 			// Because of circular dependencies we need to re-require remove operation here.
 			var RemoveOperation = CKEDITOR.require( 'document/removeoperation' );
 			var RemoveOperation = CKEDITOR.require( 'document/removeoperation' );
 
 
-			return new RemoveOperation( this.position, this.nodeList, this.baseVersion + 1 );
+			return new RemoveOperation( this.position, this.nodeList.length, this.baseVersion + 1 );
 		}
 		}
 	}
 	}
 
 
 	return InsertOperation;
 	return InsertOperation;
-} );
+} );

+ 65 - 33
packages/ckeditor5-utils/src/document/moveoperation.js

@@ -9,30 +9,24 @@ CKEDITOR.define( [
 	'document/operation',
 	'document/operation',
 	'document/nodelist',
 	'document/nodelist',
 	'ckeditorerror',
 	'ckeditorerror',
-	'utils'
+	'utilis'
 ], function( Operation, NodeList, CKEditorError, utils ) {
 ], function( Operation, NodeList, CKEditorError, utils ) {
 	/**
 	/**
 	 * Operation to move list of following nodes from the one position in the document to another.
 	 * Operation to move list of following nodes from the one position in the document to another.
 	 *
 	 *
-	 * @class document.Operation
+	 * @class document.MoveOperation
 	 */
 	 */
 	class MoveOperation extends Operation {
 	class MoveOperation extends Operation {
 		/**
 		/**
 		 * Creates a move operation.
 		 * Creates a move operation.
 		 *
 		 *
-		 * Note that this constructor is used not only to create an operation on the current state of the document,
-		 * but also to create reverse operation or the result of the operational transformation. The operation also
-		 * needs to keep data needed to transform it (creates an insert operation from the move & remove combination).
-		 * This is why this constructor contains list of nodes instead of length.
-		 *
-		 * @param {document.Position} sourcePosition Source move position.
-		 * @param {document.Position} targetPosition Target move position.
-		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} nodes List of nodes to be moved.
-		 * List of nodes can be any type accepted by the {@link document.NodeList} constructor.
+		 * @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 {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @param {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @constructor
 		 * @constructor
 		 */
 		 */
-		constructor( sourcePosition, targetPosition, nodes, baseVersion ) {
+		constructor( sourcePosition, targetPosition, howMany, baseVersion ) {
 			super( baseVersion );
 			super( baseVersion );
 
 
 			/**
 			/**
@@ -50,11 +44,11 @@ CKEDITOR.define( [
 			this.targetPosition = targetPosition;
 			this.targetPosition = targetPosition;
 
 
 			/**
 			/**
-			 * List of nodes to move.
+			 * How many nodes to move.
 			 *
 			 *
-			 * @type {document.NodeList}
+			 * @type {Number}
 			 */
 			 */
-			this.nodeList = new NodeList( nodes );
+			this.howMany = howMany;
 		}
 		}
 
 
 		/**
 		/**
@@ -67,48 +61,86 @@ CKEDITOR.define( [
 			var targetElement = this.targetPosition.parent;
 			var targetElement = this.targetPosition.parent;
 			var sourceOffset = this.sourcePosition.offset;
 			var sourceOffset = this.sourcePosition.offset;
 			var targetOffset = this.targetPosition.offset;
 			var targetOffset = this.targetPosition.offset;
-			var nodeList = this.nodeList;
 
 
-			if ( CKEDITOR.isDebug ) {
-				var i = 0;
+			// Validate whether move operation has correct parameters.
+			// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
+			// We expect that many errors might be connected with one of scenarios described below.
+			if ( !sourceElement || !targetElement ) {
+				/**
+				 * Source position or target position is invalid.
+				 *
+				 * @error operation-move-position-invalid
+				 * @param {document.MoveOperation} moveOperation
+				 */
+				throw new CKEditorError(
+					'operation-move-source-position-invalid: Source position or target position is invalid.',
+					{ moveOperation: this }
+				);
+			} else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
+				/**
+				 * The nodes which should be moved do not exist.
+				 *
+				 * @error operation-move-nodes-do-not-exist
+				 * @param {document.MoveOperation} moveOperation
+				 */
+				throw new CKEditorError(
+					'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.',
+					{ moveOperation: this }
+				);
+			} else if ( sourceElement === targetElement && sourceOffset + this.howMany >= targetOffset ) {
+				/**
+				 * Trying to move a range of nodes into the middle of that range.
+				 *
+				 * @error operation-move-range-into-itself
+				 * @param {document.MoveOperation} moveOperation
+				 */
+				throw new CKEditorError(
+					'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.',
+					{ moveOperation: this }
+				);
+			} else {
+				var sourcePath = this.sourcePosition.path.slice( 0, -1 );
+				var targetPath = this.targetPosition.path.slice( 0, -1 );
+
+				if ( utils.compareArrays( sourcePath, targetPath ) == utils.compareArrays.PREFIX ) {
+					var i = sourcePath.length;
 
 
-				for ( var node of this.nodeList ) {
-					if ( !utils.isEqual( sourceElement.getChild( sourceOffset + i ), node ) ) {
+					if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
 						/**
 						/**
-						 * The node which should be removed does not exists.
+						 * Trying to move a range of nodes into one of nodes from that range.
 						 *
 						 *
-						 * @error operation-move-node-does-not-exists:
+						 * @error operation-move-node-into-itself
 						 * @param {document.MoveOperation} moveOperation
 						 * @param {document.MoveOperation} moveOperation
-						 * @param {document.Node} node
 						 */
 						 */
 						throw new CKEditorError(
 						throw new CKEditorError(
-							'operation-move-node-does-not-exists: The node which should be moved does not exists.',
-							{ moveOperation: this, node: this.node } );
+							'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.',
+							{ moveOperation: this }
+						);
 					}
 					}
-					i++;
 				}
 				}
 			}
 			}
-
-			sourceElement.removeChildren( sourceOffset, nodeList.length );
+			// End of validation.
 
 
 			// If we move children in the same element and we remove elements on the position before the target we
 			// If we move children in the same element and we remove elements on the position before the target we
 			// need to update a target offset.
 			// need to update a target offset.
 			if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
 			if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
-				targetOffset -= nodeList.length;
+				targetOffset -= this.howMany;
 			}
 			}
 
 
-			targetElement.insertChildren( targetOffset, this.nodeList );
+			var removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
+
+			targetElement.insertChildren( targetOffset, removedNodes );
 		}
 		}
 
 
 		/**
 		/**
-		 * Creates an reverse move operation.
+		 * Creates a reverse operation.
 		 *
 		 *
 		 * @returns {document.MoveOperation} Reverse operation.
 		 * @returns {document.MoveOperation} Reverse operation.
 		 */
 		 */
 		reverseOperation() {
 		reverseOperation() {
-			return new MoveOperation( this.targetPosition, this.sourcePosition, this.nodeList, this.baseVersion + 1 );
+			return new MoveOperation( this.targetPosition, this.sourcePosition, this.howMany, this.baseVersion + 1 );
 		}
 		}
 	}
 	}
 
 
 	return MoveOperation;
 	return MoveOperation;
-} );
+} );

+ 4 - 4
packages/ckeditor5-utils/src/document/node.js

@@ -51,7 +51,7 @@ CKEDITOR.define( [ 'document/attribute', 'utils' ], function( Attribute, utils )
 		hasAttr( key ) {
 		hasAttr( key ) {
 			var attr;
 			var attr;
 
 
-			// Attribute
+			// Attribute.
 			if ( key instanceof Attribute ) {
 			if ( key instanceof Attribute ) {
 				for ( attr of this._attrs ) {
 				for ( attr of this._attrs ) {
 					if ( attr.isEqual( key ) ) {
 					if ( attr.isEqual( key ) ) {
@@ -59,7 +59,7 @@ CKEDITOR.define( [ 'document/attribute', 'utils' ], function( Attribute, utils )
 					}
 					}
 				}
 				}
 			}
 			}
-			// Key
+			// Key.
 			else {
 			else {
 				for ( attr of this._attrs ) {
 				for ( attr of this._attrs ) {
 					if ( attr.key == key ) {
 					if ( attr.key == key ) {
@@ -149,7 +149,7 @@ CKEDITOR.define( [ 'document/attribute', 'utils' ], function( Attribute, utils )
 		}
 		}
 
 
 		/**
 		/**
-		 * Dept of the node, which equals total number of its parents.
+		 * Depth of the node, which equals to total number of its parents.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
 		 * @property {Number} depth
 		 * @property {Number} depth
@@ -241,4 +241,4 @@ CKEDITOR.define( [ 'document/attribute', 'utils' ], function( Attribute, utils )
 	}
 	}
 
 
 	return Node;
 	return Node;
-} );
+} );

+ 5 - 6
packages/ckeditor5-utils/src/document/nodelist.js

@@ -13,11 +13,9 @@ CKEDITOR.define( [
 ], function( Character, Text, Node, utils ) {
 ], function( Character, Text, Node, utils ) {
 	/**
 	/**
 	 * List of nodes. It is used to represent multiple nodes with a given order, for example children of
 	 * List of nodes. It is used to represent multiple nodes with a given order, for example children of
-	 * elements {@link document.Element}.
+	 * {@link document.Element} object or nodes inserted using {@link document.InsertOperation}.
 	 *
 	 *
-	 * This class let you modify list of nodes, for example nodes to insert and pass the reference for such list.
-	 *
-	 * Thanks to the constructor which accept various structure, this class let you easily create list of text node.
+	 * Thanks to the constructor, which accepts various arguments, this class lets you easily create desired list of nodes.
 	 *
 	 *
 	 * It also may internally compress nodes.
 	 * It also may internally compress nodes.
 	 *
 	 *
@@ -120,9 +118,10 @@ CKEDITOR.define( [
 		 *
 		 *
 		 * @param {Number} index Position of the first node to remove.
 		 * @param {Number} index Position of the first node to remove.
 		 * @param {Number} number Number of nodes to remove.
 		 * @param {Number} number Number of nodes to remove.
+		 * @returns {document.NodeList} List of removed nodes.
 		 */
 		 */
 		remove( index, number ) {
 		remove( index, number ) {
-			this._nodes.splice( index, number );
+			return new NodeList( this._nodes.splice( index, number ) );
 		}
 		}
 
 
 		/**
 		/**
@@ -155,4 +154,4 @@ CKEDITOR.define( [
 	}
 	}
 
 
 	return NodeList;
 	return NodeList;
-} );
+} );

+ 3 - 3
packages/ckeditor5-utils/src/document/operation.js

@@ -21,15 +21,15 @@ CKEDITOR.define( [], function() {
 		constructor( baseVersion ) {
 		constructor( baseVersion ) {
 			/**
 			/**
 			 * {@link document.Document#version} on which operation can be applied. If you try to
 			 * {@link document.Document#version} on which operation can be applied. If you try to
-			 * {@link document.Document#applyOperation apply} operation with different base version then
+			 * {@link document.Document#applyOperation apply} operation with different base version than
 			 * {@link document.Document#version document version} the {@link document-applyOperation-wrong-version}
 			 * {@link document.Document#version document version} the {@link document-applyOperation-wrong-version}
 			 * error is thrown.
 			 * error is thrown.
 			 *
 			 *
-			 * @type {Number}
+			 * @property {Number} baseVersion
 			 */
 			 */
 			this.baseVersion = baseVersion;
 			this.baseVersion = baseVersion;
 		}
 		}
 	}
 	}
 
 
 	return Operation;
 	return Operation;
-} );
+} );

+ 5 - 5
packages/ckeditor5-utils/src/document/position.js

@@ -7,7 +7,7 @@
 
 
 CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError ) {
 CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError ) {
 	/**
 	/**
-	 * Position is always before of after a node.
+	 * Position is always before or after a node.
 	 * See {@link #path} property for more information.
 	 * See {@link #path} property for more information.
 	 *
 	 *
 	 * @class document.Position
 	 * @class document.Position
@@ -84,7 +84,7 @@ CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError )
 		/**
 		/**
 		 * Set the position after given node.
 		 * Set the position after given node.
 		 *
 		 *
-		 * @param {document.node} node Node the position should be directly after.
+		 * @param {document.Node} node Node the position should be directly after.
 		 */
 		 */
 		static createAfter( node ) {
 		static createAfter( node ) {
 			if ( !node.parent ) {
 			if ( !node.parent ) {
@@ -132,7 +132,7 @@ CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError )
 		 * Node directly before the position.
 		 * Node directly before the position.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @type {Node}
+		 * @type {document.Node}
 		 */
 		 */
 		get nodeBefore() {
 		get nodeBefore() {
 			return this.parent.getChild( this.offset - 1 ) || null;
 			return this.parent.getChild( this.offset - 1 ) || null;
@@ -142,7 +142,7 @@ CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError )
 		 * Node directly after the position.
 		 * Node directly after the position.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @type {Node}
+		 * @type {document.Node}
 		 */
 		 */
 		get nodeAfter() {
 		get nodeAfter() {
 			return this.parent.getChild( this.offset ) || null;
 			return this.parent.getChild( this.offset ) || null;
@@ -160,4 +160,4 @@ CKEDITOR.define( [ 'utils', 'ckeditorerror' ], function( utils, CKEditorError )
 	}
 	}
 
 
 	return Position;
 	return Position;
-} );
+} );

+ 16 - 16
packages/ckeditor5-utils/src/document/positioniterator.js

@@ -10,8 +10,8 @@ CKEDITOR.define( [
 	'document/element',
 	'document/element',
 	'document/position'
 	'document/position'
 ], function( Character, Element, Position ) {
 ], function( Character, Element, Position ) {
-	var OPENING_TAG = 0;
-	var CLOSING_TAG = 1;
+	var ELEMENT_ENTER = 0;
+	var ELEMENT_LEAVE = 1;
 	var CHARACTER = 2;
 	var CHARACTER = 2;
 
 
 	/**
 	/**
@@ -57,8 +57,8 @@ CKEDITOR.define( [
 		 * @returns {Object} Value between last and new {@link #position}.
 		 * @returns {Object} Value between last and new {@link #position}.
 		 * @returns {Boolean} return.done True if iterator is done.
 		 * @returns {Boolean} return.done True if iterator is done.
 		 * @returns {Object} return.value
 		 * @returns {Object} return.value
-		 * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#OPENING_TAG},
-		 * {@link PositionIterator#CLOSING_TAG} or {@link PositionIterator#CHARACTER}.
+		 * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
+		 * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#CHARACTER}.
 		 * @returns {Node} return.value.node Skipped node.
 		 * @returns {Node} return.value.node Skipped node.
 		 */
 		 */
 		next() {
 		next() {
@@ -79,7 +79,7 @@ CKEDITOR.define( [
 			if ( nodeAfter instanceof Element ) {
 			if ( nodeAfter instanceof Element ) {
 				this.position = Position.createFromParentAndOffset( nodeAfter, 0 );
 				this.position = Position.createFromParentAndOffset( nodeAfter, 0 );
 
 
-				return formatReturnValue( OPENING_TAG, nodeAfter );
+				return formatReturnValue( ELEMENT_ENTER, nodeAfter );
 			} else if ( nodeAfter instanceof Character ) {
 			} else if ( nodeAfter instanceof Character ) {
 				this.position = Position.createFromParentAndOffset( parent, position.offset + 1 );
 				this.position = Position.createFromParentAndOffset( parent, position.offset + 1 );
 
 
@@ -87,7 +87,7 @@ CKEDITOR.define( [
 			} else {
 			} else {
 				this.position = Position.createFromParentAndOffset( parent.parent, parent.positionInParent + 1 );
 				this.position = Position.createFromParentAndOffset( parent.parent, parent.positionInParent + 1 );
 
 
-				return formatReturnValue( CLOSING_TAG, this.position.nodeBefore );
+				return formatReturnValue( ELEMENT_LEAVE, this.position.nodeBefore );
 			}
 			}
 		}
 		}
 
 
@@ -97,15 +97,15 @@ CKEDITOR.define( [
 		 * @returns {Object} Value between last and new {@link #position}.
 		 * @returns {Object} Value between last and new {@link #position}.
 		 * @returns {Boolean} return.done True if iterator is done.
 		 * @returns {Boolean} return.done True if iterator is done.
 		 * @returns {Object} return.value
 		 * @returns {Object} return.value
-		 * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#OPENING_TAG},
-		 * {@link PositionIterator#CLOSING_TAG} or {@link PositionIterator#CHARACTER}.
+		 * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
+		 * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#CHARACTER}.
 		 * @returns {Node} return.value.node Skipped node.
 		 * @returns {Node} return.value.node Skipped node.
 		 */
 		 */
 		previous() {
 		previous() {
 			var position = this.position;
 			var position = this.position;
 			var parent = position.parent;
 			var parent = position.parent;
 
 
-			// We are at the begging of the root.
+			// We are at the beginning of the root.
 			if ( parent.parent === null && position.offset === 0 ) {
 			if ( parent.parent === null && position.offset === 0 ) {
 				return { done: true };
 				return { done: true };
 			}
 			}
@@ -119,7 +119,7 @@ CKEDITOR.define( [
 			if ( nodeBefore instanceof Element ) {
 			if ( nodeBefore instanceof Element ) {
 				this.position = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
 				this.position = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
 
 
-				return formatReturnValue( CLOSING_TAG, nodeBefore );
+				return formatReturnValue( ELEMENT_LEAVE, nodeBefore );
 			} else if ( nodeBefore instanceof Character ) {
 			} else if ( nodeBefore instanceof Character ) {
 				this.position = Position.createFromParentAndOffset( parent, position.offset - 1 );
 				this.position = Position.createFromParentAndOffset( parent, position.offset - 1 );
 
 
@@ -127,7 +127,7 @@ CKEDITOR.define( [
 			} else {
 			} else {
 				this.position = Position.createFromParentAndOffset( parent.parent, parent.positionInParent );
 				this.position = Position.createFromParentAndOffset( parent.parent, parent.positionInParent );
 
 
-				return formatReturnValue( OPENING_TAG, this.position.nodeAfter );
+				return formatReturnValue( ELEMENT_ENTER, this.position.nodeAfter );
 			}
 			}
 		}
 		}
 	}
 	}
@@ -143,18 +143,18 @@ CKEDITOR.define( [
 	}
 	}
 
 
 	/**
 	/**
-	 * Flag for opening tag.
+	 * Flag for element entering.
 	 *
 	 *
 	 * @readonly
 	 * @readonly
 	 */
 	 */
-	PositionIterator.OPENING_TAG = OPENING_TAG;
+	PositionIterator.ELEMENT_ENTER = ELEMENT_ENTER;
 
 
 	/**
 	/**
-	 * Flag for closing tag.
+	 * Flag for element leaving.
 	 *
 	 *
 	 * @readonly
 	 * @readonly
 	 */
 	 */
-	PositionIterator.CLOSING_TAG = CLOSING_TAG;
+	PositionIterator.ELEMENT_LEAVE = ELEMENT_LEAVE;
 
 
 	/**
 	/**
 	 * Flag for character.
 	 * Flag for character.
@@ -164,4 +164,4 @@ CKEDITOR.define( [
 	PositionIterator.CHARACTER = CHARACTER;
 	PositionIterator.CHARACTER = CHARACTER;
 
 
 	return PositionIterator;
 	return PositionIterator;
-} );
+} );

+ 4 - 4
packages/ckeditor5-utils/src/document/range.js

@@ -36,10 +36,10 @@ CKEDITOR.define( [ 'document/positioniterator' ], function( PositionIterator ) {
 		}
 		}
 
 
 		/**
 		/**
-		 * Two ranges equals if first and last positions equal.
+		 * Two ranges equal if their start and end positions equal.
 		 *
 		 *
-		 * @param {document.Range} otherRange Range to compare.
-		 * @returns {Boolean} true if ranges equal.
+		 * @param {document.Range} otherRange Range to compare with.
+		 * @returns {Boolean} True if ranges equal.
 		 */
 		 */
 		isEqual( otherRange ) {
 		isEqual( otherRange ) {
 			return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
 			return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
@@ -56,4 +56,4 @@ CKEDITOR.define( [ 'document/positioniterator' ], function( PositionIterator ) {
 	}
 	}
 
 
 	return Range;
 	return Range;
-} );
+} );

+ 36 - 0
packages/ckeditor5-utils/src/document/reinsertoperation.js

@@ -0,0 +1,36 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+CKEDITOR.define( [
+	'document/moveoperation',
+	'document/removeoperation'
+], function( MoveOperation ) {
+	/**
+	 * Operation to reinsert previously removed nodes back to the non-graveyard root.
+	 * This is basically MoveOperation but it returns RemoveOperation when reversed.
+	 * We achieve two goals: by having separate classes it's easier to distinguish whether move
+	 * operation is actually a remove/reinsert operation and fire proper events. Also it
+	 * will be easier to expand if we need to change operation's behavior if it is remove/reinsert.
+	 *
+	 * @class document.ReinsertOperation
+	 */
+	class ReinsertOperation extends MoveOperation {
+		/**
+		 * Creates a reverse operation.
+		 *
+		 * @returns {document.RemoveOperation} Reverse operation.
+		 */
+		reverseOperation() {
+			// Because of circular dependencies we need to re-require reinsert operation here.
+			var RemoveOperation = CKEDITOR.require( 'document/removeoperation' );
+
+			return new RemoveOperation( this.targetPosition, this.howMany, this.baseVersion + 1 );
+		}
+	}
+
+	return ReinsertOperation;
+} );

+ 18 - 65
packages/ckeditor5-utils/src/document/removeoperation.js

@@ -6,94 +6,47 @@
 'use strict';
 'use strict';
 
 
 CKEDITOR.define( [
 CKEDITOR.define( [
-	'document/operation',
-	'document/nodelist',
-	'ckeditorerror',
-	'utils',
-	'document/insertoperation'
-], function( Operation, NodeList, CKEditorError, utils ) {
+	'document/moveoperation',
+	'document/position',
+	'document/reinsertoperation'
+], function( MoveOperation, Position ) {
 	/**
 	/**
-	 * Operation to remove list of nodes.
+	 * Operation to remove a range of nodes.
 	 *
 	 *
 	 * @class document.RemoveOperation
 	 * @class document.RemoveOperation
 	 */
 	 */
-	class RemoveOperation extends Operation {
+	class RemoveOperation extends MoveOperation {
 		/**
 		/**
 		 * Creates a remove operation.
 		 * Creates a remove operation.
 		 *
 		 *
-		 * Note that this constructor is used not only to create an operation on the current state of the document,
-		 * but also to create reverse operation or the result of the operational transformation. The operation also
-		 * needs to keep data needed to transform it (creates an insert operation as a reverse of the remove).
-		 * This is why this constructor contains list of nodes instead of length.
-		 *
 		 * @param {document.Position} position Position before the first node to remove.
 		 * @param {document.Position} position Position before the first node to remove.
-		 * @param {document.Node|document.Text|document.NodeList|String|Iterable} nodes List of nodes to be remove.
-		 * List of nodes can be any type accepted by the {@link document.NodeList} constructor.
+		 * @param {Number} howMany How many nodes to remove.
 		 * @param {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @param {Number} baseVersion {@link document.Document#version} on which operation can be applied.
 		 * @constructor
 		 * @constructor
 		 */
 		 */
-		constructor( position, nodes, baseVersion ) {
-			super( baseVersion );
-
-			/**
-			 * Position of insertion.
-			 *
-			 * @type {document.Position}
-			 */
-			this.position = position;
+		constructor( position, howMany, baseVersion ) {
+			var graveyard = position.root.document._graveyard;
 
 
 			/**
 			/**
-			 * List of nodes to insert.
-			 *
-			 * @type {document.NodeList}
+			 * Position in a graveyard where nodes were moved.
 			 */
 			 */
-			this.nodeList = new NodeList( nodes );
-		}
-
-		/**
-		 * Execute operation.
-		 *
-		 * @protected
-		 */
-		_execute() {
-			var parent = this.position.parent;
-			var offset = this.position.offset;
-
-			if ( CKEDITOR.isDebug ) {
-				var i = 0;
-
-				for ( var node of this.nodeList ) {
-					if ( !utils.isEqual( parent.getChild( offset + i ), node ) ) {
-						/**
-						 * The node which should be removed does not exists.
-						 *
-						 * @error operation-remove-node-does-not-exists:
-						 * @param {document.RemoveOperation} removeOperation
-						 * @param {document.Node} node
-						 */
-						throw new CKEditorError(
-							'operation-remove-node-does-not-exists: The node which should be removed does not exists.',
-							{ removeOperation: this, node: this.node } );
-					}
-					i++;
-				}
-			}
+			var graveyardPosition = Position.createFromParentAndOffset( graveyard, 0 );
 
 
-			parent.removeChildren( offset, this.nodeList.length );
+			super( position, graveyardPosition, howMany, baseVersion );
 		}
 		}
 
 
 		/**
 		/**
-		 * Creates an reverse insert operation.
+		 * Creates a reverse operation.
 		 *
 		 *
-		 * @returns {document.InsertOperation} Reverse operation.
+		 * @returns {document.ReinsertOperation} Reverse operation.
 		 */
 		 */
 		reverseOperation() {
 		reverseOperation() {
-			// Because of circular dependencies we need to re-require insert operation here.
-			var InsertOperation = CKEDITOR.require( 'document/insertoperation' );
+			// Because of circular dependencies we need to re-require reinsert operation here.
+			var ReinsertOperation = CKEDITOR.require( 'document/reinsertoperation' );
 
 
-			return new InsertOperation( this.position, this.nodeList, this.baseVersion + 1 );
+			return new ReinsertOperation( this.targetPosition, this.sourcePosition, this.howMany, this.baseVersion + 1 );
 		}
 		}
 	}
 	}
 
 
 	return RemoveOperation;
 	return RemoveOperation;
-} );
+} );

+ 46 - 0
packages/ckeditor5-utils/src/document/rootelement.js

@@ -0,0 +1,46 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+CKEDITOR.define( [], function() {
+	/**
+	 * Class for nodes that are roots of trees in tree data model.
+	 *
+	 * @class document.RootElement
+	 */
+	class RootElement extends Element {
+		/**
+		 * Creates tree root node.
+		 *
+		 * @param {document.Document} document {@link document.Document} that is an owner of the root.
+		 * @constructor
+		 */
+		constructor( document ) {
+			super( 'root' );
+
+			/**
+			 * {@link document.Document} that is an owner of this root.
+			 *
+			 * @readonly
+			 * @protected
+			 * @property {document.Document}
+			 */
+			this._document = document;
+		}
+
+		/**
+		 * Document that is an owner of this root.
+		 *
+		 * @readonly
+		 * @property {Document} document
+		 */
+		get document() {
+			return this._document;
+		}
+	}
+
+	return RootElement;
+} );

+ 57 - 0
packages/ckeditor5-utils/src/utils.js

@@ -53,9 +53,66 @@ CKEDITOR.define( [ 'utils-lodash', 'lib/lodash/lodash-ckeditor' ], function( lod
 		 */
 		 */
 		isIterable( value ) {
 		isIterable( value ) {
 			return !!( value && value[ Symbol.iterator ] );
 			return !!( value && value[ Symbol.iterator ] );
+		},
+
+		/**
+		 * Compares how given arrays relate to each other. One array can be: same as another array, prefix of another array
+		 * or completely different.
+		 *
+		 *   compareArrays( [ 0, 2 ], [ 0, 2 ] ); // SAME
+		 *   compareArrays( [ 0, 2 ], [ 0 ] ); // PREFIX
+		 *   compareArrays( [ 0, 2 ], [ 0, 2, 1 ] ); // DIFFERENT
+		 *   compareArrays( [ 0, 2 ], [ 1, 3 ] ); // DIFFERENT
+		 *
+		 * @param {Array} a Array that is compared.
+		 * @param {Array} b Array to compare with.
+		 * @returns {Number} How array `a` is related to array `b`. Represented by one of flags:
+		 * `a` is {@link utils.compareArrays#SAME same}, `a` is a {@link utils.compareArrays#PREFIX prefix}
+		 * or `a` is {@link utils.compareArrays#DIFFERENT different}.
+		 */
+		compareArrays( a, b ) {
+			var minLen = Math.min( a.length, b.length );
+
+			for ( var i = 0; i < minLen; i++ ) {
+				if ( a[ i ] != b[ i ] ) {
+					// The arrays are different.
+					return this.DIFFERENT;
+				}
+			}
+
+			// Both arrays were same at all points.
+			if ( a.length == b.length ) {
+				// If their length is also same, they are the same.
+				return this.SAME;
+			} else if ( a.length < b.length ) {
+				// Compared array is shorter so it is a prefix of the other array.
+				return this.PREFIX;
+			}
+
+			// In other case, the arrays are different.
+			return this.DIFFERENT;
 		}
 		}
 	};
 	};
 
 
+	/**
+	 * Flag for "is same as" relation between arrays.
+	 *
+	 * @type {number}
+	 */
+	utils.compareArrays.SAME = 0;
+	/**
+	 * Flag for "is a prefix of" relation between arrays.
+	 *
+	 * @type {number}
+	 */
+	utils.compareArrays.PREFIX = 1;
+	/**
+	 * Flag for "is different than" relation between arrays.
+	 *
+	 * @type {number}
+	 */
+	utils.compareArrays.DIFFERENT = 2;
+
 	// Extend "utils" with Lo-Dash methods.
 	// Extend "utils" with Lo-Dash methods.
 	for ( var i = 0; i < lodashIncludes.length; i++ ) {
 	for ( var i = 0; i < lodashIncludes.length; i++ ) {
 		utils[ lodashIncludes[ i ] ] = lodash[ lodashIncludes[ i ] ];
 		utils[ lodashIncludes[ i ] ] = lodash[ lodashIncludes[ i ] ];

+ 2 - 2
packages/ckeditor5-utils/tests/document/positioniterator.js

@@ -76,7 +76,7 @@ describe( 'range iterator', function() {
 		var PositionIterator = modules[ 'document/positioniterator' ];
 		var PositionIterator = modules[ 'document/positioniterator' ];
 		var Position = modules[ 'document/position' ];
 		var Position = modules[ 'document/position' ];
 
 
-		var iterator = new PositionIterator( new Position( [ 0 ], doc.root ) ); // begging of root
+		var iterator = new PositionIterator( new Position( [ 0 ], doc.root ) ); // beginning of root
 		var i, len;
 		var i, len;
 
 
 		for ( i = 0, len = expectedItems.length; i < len; i++ ) {
 		for ( i = 0, len = expectedItems.length; i < len; i++ ) {
@@ -150,4 +150,4 @@ describe( 'range iterator', function() {
 		}
 		}
 		expect( i ).to.equal( expectedItems.length );
 		expect( i ).to.equal( expectedItems.length );
 	} );
 	} );
-} );
+} );