浏览代码

Moved diff from the engine.

Piotrek Koszuliński 9 年之前
父节点
当前提交
aeb3df04f9
共有 2 个文件被更改,包括 149 次插入0 次删除
  1. 118 0
      packages/ckeditor5-utils/src/diff.js
  2. 31 0
      packages/ckeditor5-utils/tests/diff.js

+ 118 - 0
packages/ckeditor5-utils/src/diff.js

@@ -0,0 +1,118 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+// The following code is based on the "O(NP) Sequence Comparison Algorithm"
+// 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.
+ *
+ *		diff( 'aba', 'acca' ); // [ EQUAL, INSERT, INSERT, DELETE, EQUAL ]
+ *
+ * @method utils.diff
+ * @param {Array} a Input array.
+ * @param {Array} b Output array.
+ * @param {Function} [cmp] Optional function used to compare array values, by default === is used.
+ * @return {Array} Array of actions.
+ */
+ export default function diff( a, b, cmp ) {
+	// Set the comparator function.
+	cmp = cmp || function( a, b ) {
+			return a === b;
+		};
+
+	// Temporary action type statics.
+	let _INSERT, _DELETE;
+
+	// Swapped the arrays to use the shorter one as the first one.
+	if ( b.length < a.length ) {
+		let tmp = a;
+
+		a = b;
+		b = tmp;
+
+		// We swap the action types as well.
+		_INSERT = 'DELETE';
+		_DELETE = 'INSERT';
+	} else {
+		_INSERT = 'INSERT';
+		_DELETE = 'DELETE';
+	}
+
+	const m = a.length;
+	const n = b.length;
+	const delta = n - m;
+
+	// Edit scripts, for each diagonal.
+	const es = {};
+	// Furthest points, the furthest y we can get on each diagonal.
+	const fp = {};
+
+	function snake( k ) {
+		// We use -1 as an alternative below to handle initial values ( instead of filling the fp with -1 first ).
+		// Furthest points (y) on the diagonal below k.
+		const y1 = ( fp[ k - 1 ] !== undefined ? fp[ k - 1 ] : -1 ) + 1;
+		// Furthest points (y) on the diagonal above k.
+		const y2 = fp[ k + 1 ] !== undefined ? fp[ k + 1 ] : -1;
+		// The way we should go to get further.
+		const dir = y1 > y2 ? -1 : 1;
+
+		// Clone previous actions array (if any).
+		if ( es[ k + dir ] ) {
+			es[ k ] = es[ k + dir ].slice( 0 );
+		}
+
+		// Create actions array.
+		if ( !es[ k ] ) {
+			es[ k ] = [];
+		}
+
+		// Push the action.
+		es[ k ].push( y1 > y2 ? _INSERT : _DELETE );
+
+		// Set the beginning coordinates.
+		let y = Math.max( y1, y2 );
+		let x = y - k;
+
+		// Traverse the diagonal as long as the values match.
+		while ( x < m && y < n && cmp( a[ x ], b[ y ] ) ) {
+			x++;
+			y++;
+			// Push no change action.
+			es[ k ].push( 'EQUAL' );
+		}
+
+		return y;
+	}
+
+	let p = 0;
+	let k;
+
+	// Traverse the graph until we reach the end of the longer string.
+	do {
+		// Updates furthest points and edit scripts for diagonals below delta.
+		for ( k = -p; k < delta; k++ ) {
+			fp[ k ] = snake( k );
+		}
+
+		// Updates furthest points and edit scripts for diagonals above delta.
+		for ( k = delta + p; k > delta; k-- ) {
+			fp[ k ] = snake( k );
+		}
+
+		// Updates furthest point and edit script for the delta diagonal.
+		// note that the delta diagonal is the one which goes through the sink (m, n).
+		fp[ delta ] = snake( delta );
+
+		p++;
+	} while ( fp[ delta ] !== n );
+
+	// Return the final list of edit actions.
+	// We remove the first item that represents the action for the injected nulls.
+	return es[ delta ].slice( 1 );
+}

+ 31 - 0
packages/ckeditor5-utils/tests/diff.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-20'INSERT'6, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import diff from '/ckeditor5/utils/diff.js';
+
+describe( 'diff', () => {
+	it( 'should diff arrays', () => {
+		expect( diff( 'aba', 'acca' ) ).to.deep.equals( [ 'EQUAL', 'INSERT', 'INSERT', 'DELETE', 'EQUAL' ] );
+	} );
+
+	it( 'should reverse result if the second array is shorter', () => {
+		expect( diff( 'acca', 'aba' ) ).to.deep.equals( [ 'EQUAL', 'DELETE', 'DELETE', 'INSERT', 'EQUAL' ] );
+	} );
+
+	it( 'should diff if arrays are same', () => {
+		expect( diff( 'abc', 'abc' ) ).to.deep.equals( [ 'EQUAL', 'EQUAL', 'EQUAL' ] );
+	} );
+
+	it( 'should diff if one array is empty', () => {
+		expect( diff( '', 'abc' ) ).to.deep.equals( [ 'INSERT', 'INSERT', 'INSERT' ] );
+	} );
+
+	it( 'should use custom comparator', () => {
+		expect( diff( 'aBc', 'abc' ) ).to.deep.equals( [ 'EQUAL', 'INSERT', 'DELETE', 'EQUAL' ] );
+		expect( diff( 'aBc', 'abc', ( a, b ) => a.toLowerCase() == b.toLowerCase() ) ).to.deep.equals( [ 'EQUAL', 'EQUAL', 'EQUAL' ] );
+	} );
+} );