Explorar el Código

Added treeModel.NodeListText. Changes in treeModel.NodeList: using CharacterProxy and NodeListText.

Szymon Cofalik hace 10 años
padre
commit
e9702cd43b

+ 62 - 15
packages/ckeditor5-engine/src/treemodel/nodelist.js

@@ -5,9 +5,54 @@
 
 'use strict';
 
-import TextNode from './textnode.js';
+import CharacterProxy from './characterproxy.js';
 import Text from './text.js';
 import utils from '../utils.js';
+import langUtils from '../lib/lodash/lang.js';
+
+/**
+ * This is a private helper-class for {@link treeModel.NodeList} text compression utility.
+ *
+ * @private
+ * @class treeModel.NodeListText
+ */
+class NodeListText extends Text {
+	/**
+	 * @see {@link treeModel.Text#constructor}
+	 * @protected
+	 * @constructor
+	 */
+	constructor( text, attrs ) {
+		super( text, attrs );
+
+		this.parent = null;
+	}
+
+	/**
+	 * Gets a character at given index and creates a {@link treeModel.CharacterProxy} out of it.
+	 *
+	 * @param {Number} index Character index.
+	 * @returns {treeModel.CharacterProxy}
+	 */
+	getCharAt( index ) {
+		index = index && index >= 0 ? index : 0;
+
+		return new CharacterProxy( this, index );
+	}
+
+	/**
+	 * Custom toJSON method to solve child-parent circular dependencies.
+	 *
+	 * @returns {Object} Clone of this object with the parent property replaced with its name.
+	 */
+	toJSON() {
+		const json = langUtils.clone( this );
+
+		json.parent = json.parent ? this.parent.name : null;
+
+		return json;
+	}
+}
 
 /**
  * List of nodes. It is used to represent multiple nodes with a given order, for example children of
@@ -47,7 +92,7 @@ export default class NodeList {
 	 *		nodeListA === nodeListB // true
 	 *		nodeListB.length // 3
 	 *
-	 * @param {treeModel.Node|treeModel.Text|treeModel.TextNode|String|treeModel.NodeList|Iterable} nodes List of nodes.
+	 * @param {treeModel.Node|treeModel.Text|String|treeModel.NodeList|Iterable} nodes List of nodes.
 	 * @constructor
 	 */
 	constructor( nodes ) {
@@ -84,18 +129,20 @@ export default class NodeList {
 				let mergedWithPrev = false;
 				let length = 1;
 
-				if ( node instanceof TextNode ) {
-					node = new Text( node.text, node.attrs );
+				if ( node instanceof CharacterProxy ) {
+					node = new NodeListText( node.character, node.attrs );
+				} else if ( node instanceof Text ) {
+					node = new NodeListText( node.text, node.attrs );
 				} else if ( typeof node == 'string' ) {
-					node = new Text( node, [] );
+					node = new NodeListText( node, [] );
 				}
 
-				if ( node instanceof Text ) {
+				if ( node instanceof NodeListText ) {
 					length = node.text.length;
 
 					let prev = this._nodes[ this._nodes.length - 1 ];
 
-					if ( prev instanceof Text && prev.attrs.isEqual( node.attrs ) ) {
+					if ( prev instanceof NodeListText && prev.attrs.isEqual( node.attrs ) ) {
 						// If previously added text has same attributes, merge this text with it.
 						prev.text += node.text;
 						mergedWithPrev = true;
@@ -151,8 +198,8 @@ export default class NodeList {
 		let realIndex = this._indexMap[ index ];
 		let node = this._nodes[ realIndex ];
 
-		if ( node instanceof Text ) {
-			return node.getTextNode( this._getCharIndex( index ), 1 );
+		if ( node instanceof NodeListText ) {
+			return node.getCharAt( this._getCharIndex( index ) );
 		} else {
 			return node;
 		}
@@ -165,10 +212,10 @@ export default class NodeList {
 	 * @returns {Number} Position of the element in the list or -1 if not found.
 	 */
 	indexOf( node ) {
-		if ( node instanceof TextNode ) {
-			let baseIndex = this.indexOf( node._textItem );
+		if ( node instanceof CharacterProxy ) {
+			let baseIndex = this.indexOf( node._nodeListText );
 
-			return baseIndex == -1 ? -1 : baseIndex + node._start;
+			return baseIndex == -1 ? -1 : baseIndex + node._index;
 		}
 
 		let realIndex = this._nodes.indexOf( node );
@@ -227,7 +274,7 @@ export default class NodeList {
 			return new NodeList();
 		}
 
-		// Removed "range" may start in text node or end in text node. Some splitting may be needed.
+		// Removed "range" may start in NodeListText or end in NodeListText. Some splitting may be needed.
 		this._splitNodeAt( index );
 		this._splitNodeAt( index + number );
 
@@ -248,7 +295,7 @@ export default class NodeList {
 	}
 
 	/**
-	 * Checks whether given index is inside a text object and if so, splits that text node. This method should be used
+	 * Checks whether given index is inside a text and if so, splits that text node. This method should be used
 	 * to split text objects whenever there are some changes made on a part of text object (i.e. removing part of text,
 	 * inserting between text object, changing attributes of part of a text object).
 	 *
@@ -277,7 +324,7 @@ export default class NodeList {
 		node.text = textBefore;
 
 		// Create a new text node with the "removed" part and splice it after original node.
-		let newText = new Text( textAfter, node.attrs );
+		let newText = new NodeListText( textAfter, node.attrs );
 		newText.parent = node.parent;
 		this._nodes.splice.call( this._nodes, realIndex + 1, 0, newText );
 

+ 88 - 71
packages/ckeditor5-engine/tests/treemodel/nodelist.js

@@ -14,56 +14,29 @@ import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
 describe( 'NodeList', () => {
 	describe( 'constructor', () => {
-		it( 'should change array of characters into a set of nodes', () => {
-			let textFoo = new Text( 'foo' );
-			let nodeList = new NodeList( [ 'foo', new Text( 'x' ), 'bar', textFoo.getTextNode() ] );
-
-			expect( nodeList.length ).to.equal( 10 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 3 ).text ).to.equal( 'x' );
-			expect( nodeList.get( 4 ).text ).to.equal( 'b' );
-			expect( nodeList.get( 5 ).text ).to.equal( 'a' );
-			expect( nodeList.get( 6 ).text ).to.equal( 'r' );
-			expect( nodeList.get( 7 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 8 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 9 ).text ).to.equal( 'o' );
-		} );
-
-		it( 'should omit empty strings / texts', () => {
-			let nodeList = new NodeList( [ 'fo', '', 'ob', new Text( '', [ new Attribute( 'foo', true ) ] ), 'ar' ] );
+		it( 'should add elements to the node list', () => {
+			let p = new Element( 'p' );
+			let nodeList = new NodeList( p );
 
-			expect( nodeList.length ).to.equal( 6 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 3 ).text ).to.equal( 'b' );
-			expect( nodeList.get( 4 ).text ).to.equal( 'a' );
-			expect( nodeList.get( 5 ).text ).to.equal( 'r' );
-
-			expect( nodeList.get( 0 ).attrs.size ).to.equal( 0 );
-			expect( nodeList.get( 1 ).attrs.size ).to.equal( 0 );
-			expect( nodeList.get( 2 ).attrs.size ).to.equal( 0 );
-			expect( nodeList.get( 3 ).attrs.size ).to.equal( 0 );
-			expect( nodeList.get( 4 ).attrs.size ).to.equal( 0 );
-			expect( nodeList.get( 5 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.length ).to.equal( 1 );
+			expect( nodeList.get( 0 ) ).to.equal( p );
 		} );
 
 		it( 'should change string into a set of nodes', () => {
 			let nodeList = new NodeList( 'foo' );
 
 			expect( nodeList.length ).to.equal( 3 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).text ).to.equal( 'o' );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
 		} );
 
 		it( 'should change node into a set of nodes', () => {
-			let nodeList = new NodeList( new Text( 'x' ) );
+			let nodeList = new NodeList( new Text( 'xy' ) );
 
-			expect( nodeList.length ).to.equal( 1 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'x' );
+			expect( nodeList.length ).to.equal( 2 );
+			expect( nodeList.get( 0 ).character ).to.equal( 'x' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'y' );
 		} );
 
 		it( 'should change text with attribute into a set of nodes', () => {
@@ -71,27 +44,61 @@ describe( 'NodeList', () => {
 			let nodeList = new NodeList( new Text( 'foo', [ attr ] ) );
 
 			expect( nodeList.length ).to.equal( 3 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
 			expect( nodeList.get( 0 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
 			expect( nodeList.get( 1 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
-			expect( nodeList.get( 2 ).text ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
 			expect( nodeList.get( 2 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
 		} );
 
+		it( 'should change array of characters into a set of nodes', () => {
+			let char = new Element( 'p', [], 'y' ).getChild( 0 );
+			let nodeList = new NodeList( [ 'foo', new Text( 'x' ), char, 'bar' ] );
+
+			expect( nodeList.length ).to.equal( 8 );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 3 ).character ).to.equal( 'x' );
+			expect( nodeList.get( 4 ).character ).to.equal( 'y' );
+			expect( nodeList.get( 5 ).character ).to.equal( 'b' );
+			expect( nodeList.get( 6 ).character ).to.equal( 'a' );
+			expect( nodeList.get( 7 ).character ).to.equal( 'r' );
+		} );
+
+		it( 'should omit empty strings / texts', () => {
+			let nodeList = new NodeList( [ 'fo', '', 'ob', new Text( '', [ new Attribute( 'foo', true ) ] ), 'ar' ] );
+
+			expect( nodeList.length ).to.equal( 6 );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 3 ).character ).to.equal( 'b' );
+			expect( nodeList.get( 4 ).character ).to.equal( 'a' );
+			expect( nodeList.get( 5 ).character ).to.equal( 'r' );
+
+			expect( nodeList.get( 0 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.get( 1 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.get( 2 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.get( 3 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.get( 4 ).attrs.size ).to.equal( 0 );
+			expect( nodeList.get( 5 ).attrs.size ).to.equal( 0 );
+		} );
+
 		it( 'should merge strings and text objects if possible', () => {
 			let attr = new Attribute( 'foo', 'bar' );
 			let nodeList = new NodeList( [ 'fo', new Text( 'o' ), new Text( 'x', [ attr ] ), new Text( 'y', [ attr ] ), 'bar' ] );
 
 			expect( nodeList.length ).to.equal( 8 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 3 ).text ).to.equal( 'x' );
-			expect( nodeList.get( 4 ).text ).to.equal( 'y' );
-			expect( nodeList.get( 5 ).text ).to.equal( 'b' );
-			expect( nodeList.get( 6 ).text ).to.equal( 'a' );
-			expect( nodeList.get( 7 ).text ).to.equal( 'r' );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 3 ).character ).to.equal( 'x' );
+			expect( nodeList.get( 4 ).character ).to.equal( 'y' );
+			expect( nodeList.get( 5 ).character ).to.equal( 'b' );
+			expect( nodeList.get( 6 ).character ).to.equal( 'a' );
+			expect( nodeList.get( 7 ).character ).to.equal( 'r' );
 
 			expect( nodeList._nodes.length ).to.equal( 3 );
 			expect( nodeList._nodes[ 0 ].text ).to.equal( 'foo' );
@@ -108,12 +115,12 @@ describe( 'NodeList', () => {
 			outerList.insert( 2, innerList );
 
 			expect( outerList.length ).to.equal( 6 );
-			expect( outerList.get( 0 ).text ).to.equal( 'f' );
-			expect( outerList.get( 1 ).text ).to.equal( 'o' );
-			expect( outerList.get( 2 ).text ).to.equal( 'x' );
-			expect( outerList.get( 3 ).text ).to.equal( 'x' );
-			expect( outerList.get( 4 ).text ).to.equal( 'x' );
-			expect( outerList.get( 5 ).text ).to.equal( 'o' );
+			expect( outerList.get( 0 ).character ).to.equal( 'f' );
+			expect( outerList.get( 1 ).character ).to.equal( 'o' );
+			expect( outerList.get( 2 ).character ).to.equal( 'x' );
+			expect( outerList.get( 3 ).character ).to.equal( 'x' );
+			expect( outerList.get( 4 ).character ).to.equal( 'x' );
+			expect( outerList.get( 5 ).character ).to.equal( 'o' );
 		} );
 
 		it( 'should merge inserted text objects if possible', () => {
@@ -136,15 +143,15 @@ describe( 'NodeList', () => {
 			let removed = nodeList.remove( 2, 3 );
 
 			expect( nodeList.length ).to.equal( 3 );
-			expect( nodeList.get( 0 ).text ).to.equal( 'f' );
-			expect( nodeList.get( 1 ).text ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).text ).to.equal( 'r' );
+			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
+			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
+			expect( nodeList.get( 2 ).character ).to.equal( 'r' );
 
 			expect( removed ).to.be.instanceof( NodeList );
 			expect( removed.length ).to.equal( 3 );
-			expect( removed.get( 0 ).text ).to.equal( 'o' );
-			expect( removed.get( 1 ).text ).to.equal( 'b' );
-			expect( removed.get( 2 ).text ).to.equal( 'a' );
+			expect( removed.get( 0 ).character ).to.equal( 'o' );
+			expect( removed.get( 1 ).character ).to.equal( 'b' );
+			expect( removed.get( 2 ).character ).to.equal( 'a' );
 		} );
 
 		it( 'should merge text objects left in node list possible', () => {
@@ -179,9 +186,9 @@ describe( 'NodeList', () => {
 			expect( index ).to.equal( 3 );
 		} );
 
-		it( 'should return index of specified text node', () => {
-			let textNode = nodeList.get( 5 );
-			let index = nodeList.indexOf( textNode );
+		it( 'should return index of specified character', () => {
+			let char = nodeList.get( 5 );
+			let index = nodeList.indexOf( char );
 
 			expect( index ).to.equal( 5 );
 		} );
@@ -190,11 +197,11 @@ describe( 'NodeList', () => {
 			expect( nodeList.indexOf( new Element( 'p' ) ) ).to.equal( -1 );
 		} );
 
-		it( 'should return -1 if specified text node is not a part of a node list', () => {
-			let text = new Text( 'foobar' );
-			let textNode = text.getTextNode( 2, 2 );
+		it( 'should return -1 if specified character is not a part of a node list', () => {
+			let div = new Element( 'div', [], 'a' );
+			let char = div.getChild( 0 );
 
-			expect( nodeList.indexOf( textNode ) ).to.equal( -1 );
+			expect( nodeList.indexOf( char ) ).to.equal( -1 );
 		} );
 	} );
 
@@ -205,7 +212,7 @@ describe( 'NodeList', () => {
 			let i = 0;
 
 			for ( let node of nodeList ) {
-				expect( node.text ).to.equal( characters[ i ] );
+				expect( node.character ).to.equal( characters[ i ] );
 				i++;
 			}
 
@@ -258,7 +265,7 @@ describe( 'NodeList', () => {
 	} );
 
 	describe( '_getCharIndex', () => {
-		it( 'should return offset of given index from the beginning of the text node', () => {
+		it( 'should return offset of character at given index from the beginning of the NodeListText containing that character', () => {
 			let nodeList = new NodeList( [ new Text( 'ab', [ new Attribute( 'foo', true ) ] ), 'cd' ] );
 			let charIndexC = nodeList._getCharIndex( 2 );
 			let charIndexD = nodeList._getCharIndex( 3 );
@@ -267,4 +274,14 @@ describe( 'NodeList', () => {
 			expect( charIndexD ).to.equal( 1 );
 		} );
 	} );
+
+	// Additional test for code coverage.
+	describe( 'NodeListText', () => {
+		it( 'should create proper JSON string using toJSON method', () => {
+			let nodeList = new NodeList( 'foo' );
+			let parsed = JSON.parse( JSON.stringify( nodeList ) );
+
+			expect( parsed._nodes[ 0 ].parent ).to.equal( null );
+		} );
+	} );
 } );