UNPKG

@ckeditor/ckeditor5-utils

Version:

Miscellaneous utilities used by CKEditor 5.

116 lines (115 loc) 4.38 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module utils/diff */ import fastDiff from './fastdiff.js'; // 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 or strings producing an array containing a list of changes * necessary to transform input into output. * * ```ts * diff( 'aba', 'acca' ); // [ 'equal', 'insert', 'insert', 'delete', 'equal' ] * ``` * * This function is based on the "O(NP) Sequence Comparison Algorithm" by Sun Wu, Udi Manber, Gene Myers, Webb Miller. * Unfortunately, while it gives the most precise results, its to complex for longer strings/arrow (above 200 items). * Therefore, `diff()` automatically switches to {@link module:utils/fastdiff~fastDiff `fastDiff()`} when detecting * such a scenario. The return formats of both functions are identical. * * @param a Input array or string. * @param b Output array or string. * @param cmp Optional function used to compare array values, by default === is used. * @returns Array of changes. */ export default function diff(a, b, cmp) { // Set the comparator function. cmp = cmp || function (a, b) { return a === b; }; const aLength = a.length; const bLength = b.length; // Perform `fastDiff` for longer strings/arrays (see #269). if (aLength > 200 || bLength > 200 || aLength + bLength > 300) { return diff.fastDiff(a, b, cmp, true); } // Temporary action type statics. let _insert, _delete; // Swapped the arrays to use the shorter one as the first one. if (bLength < aLength) { const 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 changes array (if any). if (es[k + dir]) { es[k] = es[k + dir].slice(0); } // Create changes 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 changes. // We remove the first item that represents the action for the injected nulls. return es[delta].slice(1); } // Store the API in static property to easily overwrite it in tests. // Too bad dependency injection does not work in Webpack + ES 6 (const) + Babel. diff.fastDiff = fastDiff;