Przeglądaj źródła

Merge pull request #27 from ckeditor/t/26

Implemented batchify().
Szymon Cofalik 9 lat temu
rodzic
commit
35f7b295a3

+ 10 - 10
packages/ckeditor5-utils/src/diff.js

@@ -9,18 +9,18 @@
 // by Sun Wu, Udi Manber, Gene Myers, Webb Miller.
 
 /**
- * Calculates the difference between two arrays producing an object containing a list of actions
- * necessary to transform one array into another.
+ * Calculates the difference between two arrays or strings producing an array containing a list of changes
+ * necessary to transform input into output.
  *
- *		diff( 'aba', 'acca' ); // [ EQUAL, INSERT, INSERT, DELETE, EQUAL ]
+ *		diff( 'aba', 'acca' ); // [ 'EQUAL', 'INSERT', 'INSERT', 'DELETE', 'EQUAL' ]
  *
  * @method utils.diff
- * @param {Array} a Input array.
- * @param {Array} b Output array.
+ * @param {Array|String} a Input array or string.
+ * @param {Array|String} b Output array or string.
  * @param {Function} [cmp] Optional function used to compare array values, by default === is used.
- * @return {Array} Array of actions.
+ * @returns {Array} Array of changes.
  */
- export default function diff( a, b, cmp ) {
+export default function diff( a, b, cmp ) {
 	// Set the comparator function.
 	cmp = cmp || function( a, b ) {
 			return a === b;
@@ -62,12 +62,12 @@
 		// The way we should go to get further.
 		const dir = y1 > y2 ? -1 : 1;
 
-		// Clone previous actions array (if any).
+		// Clone previous changes array (if any).
 		if ( es[ k + dir ] ) {
 			es[ k ] = es[ k + dir ].slice( 0 );
 		}
 
-		// Create actions array.
+		// Create changes array.
 		if ( !es[ k ] ) {
 			es[ k ] = [];
 		}
@@ -112,7 +112,7 @@
 		p++;
 	} while ( fp[ delta ] !== n );
 
-	// Return the final list of edit actions.
+	// Return the final list of edit changes.
 	// We remove the first item that represents the action for the injected nulls.
 	return es[ delta ].slice( 1 );
 }

+ 85 - 0
packages/ckeditor5-utils/src/difftochanges.js

@@ -0,0 +1,85 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Creates a set of changes which need to be applied to the input in order to transform
+ * it into the output. This function can be used with strings or arrays.
+ *
+ *		const input = Array.from( 'abc' );
+ *		const output = Array.from( 'xaby' );
+ *		const changes = diffToChanges( diff( input, output ), output );
+ *
+ *		changes.forEach( change => {
+ *			if ( change.type == 'INSERT' ) {
+ *				input.splice( change.index, 0, ...change.values );
+ *			} else if ( change.type == 'DELETE' ) {
+ *				input.splice( change.index, change.howMany );
+ *			}
+ *		} );
+ *
+ *		input.join( '' ) == output.join( '' ); // -> true
+ *
+ * @method utils.diffToChanges
+ * @param {Array.<'EQUAL'|'INSERT'|'DELETE'>} diff Result of {@link utils.diff}.
+ * @param {String|Array} output The string or array which was passed as diff's output.
+ * @returns {Array.<Object>} Set of changes (insert or delete) which need to be applied to the input
+ * in order to transform it into the output.
+ */
+export default function diffToChanges( diff, output ) {
+	const changes = [];
+	let index = 0;
+	let lastOperation;
+
+	diff.forEach( change => {
+		if ( change == 'EQUAL' ) {
+			pushLast();
+
+			index++;
+		} else if ( change == 'INSERT' ) {
+			if ( isContinuationOf( 'INSERT' ) ) {
+				lastOperation.values.push( output[ index ] );
+			} else {
+				pushLast();
+
+				lastOperation = {
+					type: 'INSERT',
+					index: index,
+					values: [ output[ index ] ]
+				};
+			}
+
+			index++;
+		} else /* if ( change == 'DELETE' ) */ {
+			if ( isContinuationOf( 'DELETE' ) ) {
+				lastOperation.howMany++;
+			} else {
+				pushLast();
+
+				lastOperation = {
+					type: 'DELETE',
+					index: index,
+					howMany: 1
+				};
+			}
+		}
+	} );
+
+	pushLast();
+
+	return changes;
+
+	function pushLast() {
+		if ( lastOperation ) {
+			changes.push( lastOperation );
+			lastOperation = null;
+		}
+	}
+
+	function isContinuationOf( expected ) {
+		return lastOperation && lastOperation.type == expected;
+	}
+}

+ 8 - 4
packages/ckeditor5-utils/tests/diff.js

@@ -8,19 +8,23 @@
 import diff from '/ckeditor5/utils/diff.js';
 
 describe( 'diff', () => {
-	it( 'should diff arrays', () => {
+	it( 'should diff strings', () => {
 		expect( diff( 'aba', 'acca' ) ).to.deep.equals( [ 'EQUAL', 'INSERT', 'INSERT', 'DELETE', 'EQUAL' ] );
 	} );
 
-	it( 'should reverse result if the second array is shorter', () => {
+	it( 'should diff arrays', () => {
+		expect( diff( Array.from( 'aba' ), Array.from( 'acca' ) ) ).to.deep.equals( [ 'EQUAL', 'INSERT', 'INSERT', 'DELETE', 'EQUAL' ] );
+	} );
+
+	it( 'should reverse result if the second string is shorter', () => {
 		expect( diff( 'acca', 'aba' ) ).to.deep.equals( [ 'EQUAL', 'DELETE', 'DELETE', 'INSERT', 'EQUAL' ] );
 	} );
 
-	it( 'should diff if arrays are same', () => {
+	it( 'should diff if strings are same', () => {
 		expect( diff( 'abc', 'abc' ) ).to.deep.equals( [ 'EQUAL', 'EQUAL', 'EQUAL' ] );
 	} );
 
-	it( 'should diff if one array is empty', () => {
+	it( 'should diff if one string is empty', () => {
 		expect( diff( '', 'abc' ) ).to.deep.equals( [ 'INSERT', 'INSERT', 'INSERT' ] );
 	} );
 

+ 84 - 0
packages/ckeditor5-utils/tests/difftochanges.js

@@ -0,0 +1,84 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import diff from '/ckeditor5/utils/diff.js';
+import diffToChanges from '/ckeditor5/utils/difftochanges.js';
+
+describe( 'diffToChanges', () => {
+	describe( 'equal patterns', () => {
+		test( 0,		'',				'' );
+		test( 0,		'abc',			'abc' );
+	} );
+
+	describe( 'insertion', () => {
+		test( 1,		'',				'abc' );
+		test( 1,		'abc',			'abcd' );
+		test( 1,		'abc',			'abcdef' );
+		test( 2,		'abc',			'xxabcyy' );
+		test( 2,		'abc',			'axxbyyc' );
+	} );
+
+	describe( 'deletion', () => {
+		test( 1,		'abc',			'' );
+		test( 1,		'abc',			'ac' );
+		test( 1,		'abc',			'bc' );
+		test( 1,		'abc',			'ab' );
+		test( 1,		'abc',			'c' );
+		test( 2,		'abc',			'b' );
+	} );
+
+	describe( 'replacement', () => {
+		test( 2,		'abc',			'def' );
+		test( 2,		'abc',			'axc' );
+		test( 2,		'abc',			'axyc' );
+		test( 2,		'abc',			'xybc' );
+		test( 2,		'abc',			'abxy' );
+	} );
+
+	describe( 'various', () => {
+		test( 3,		'abc',			'xbccy' );
+		test( 2,		'abcdef',		'defabc' );
+		test( 4,		'abcdef',		'axxdeyyfz' );
+		test( 4,		'abcdef',		'xybzc' );
+		test( 5,		'abcdef',		'bdxfy' );
+	} );
+
+	it( 'works with arrays', () => {
+		const input = Array.from( 'abc' );
+		const output = Array.from( 'xaby' );
+		const changes = diffToChanges( diff( input, output ), output );
+
+		changes.forEach( change => {
+			if ( change.type == 'INSERT' ) {
+				input.splice( change.index, 0, ...change.values );
+			} else if ( change.type == 'DELETE' ) {
+				input.splice( change.index, change.howMany );
+			}
+		} );
+
+		expect( input ).to.deep.equal( output );
+		expect( changes ).to.have.lengthOf( 3 );
+	} );
+
+	function test( expectedChangeNumber, oldStr, newStr ) {
+		it( `${ oldStr } => ${ newStr }`, () => {
+			const changes = diffToChanges( diff( oldStr, newStr ), newStr );
+			const oldStrChars = Array.from( oldStr );
+
+			changes.forEach( change => {
+				if ( change.type == 'INSERT' ) {
+					oldStrChars.splice( change.index, 0, ...change.values );
+				} else if ( change.type == 'DELETE' ) {
+					oldStrChars.splice( change.index, change.howMany );
+				}
+			} );
+
+			expect( oldStrChars.join( '' ) ).to.equal( newStr );
+			expect( changes ).to.have.lengthOf( expectedChangeNumber );
+		} );
+	}
+} );