UNPKG

@amaui/diff

Version:
324 lines (265 loc) 9.78 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import is from '@amaui/utils/is'; import merge from '@amaui/utils/merge'; import stringify from '@amaui/utils/stringify'; const OPTIONS = { default: { init: { method: value => String(value) }, itemize: { method: value => value.split('').filter(Boolean) }, join: { method: value => value.join('') }, equal: { method: (value, value1) => value === value1 } }, word: { init: { method: value => String(value) }, itemize: { method: value => value.split(/\s|\b/).filter(Boolean) }, join: { method: value => value.join(' ') } }, line: { init: { method: value => String(value) }, itemize: { method: value => { const values = value.split(/(\n|\r\n)/); const newValues = []; for (let i = 0; i < values.length; i++) { if (i % 2) newValues[newValues.length - 1] += values[i];else newValues.push(values[i]); } return newValues; } }, join: { method: value => value.join('') } }, sentence: { init: { method: value => String(value) }, itemize: { method: value => value.split(/(\S.*?[.!?])(?=\s +|$)/).filter(Boolean) }, join: { method: value => value.join('') } } }; export const optionsDefault = _objectSpread(_objectSpread({}, OPTIONS.default), {}, { output: { compressed: true } }); class AmauiDiff { static get amauidiff() { return new AmauiDiff(); } static get word() { return new AmauiDiff(AmauiDiff.OPTIONS.word); } static get line() { return new AmauiDiff(AmauiDiff.OPTIONS.line); } static get sentence() { return new AmauiDiff(AmauiDiff.OPTIONS.sentence); } static get json() { const options = AmauiDiff.OPTIONS.line; options.init.method = value => stringify(value); return new AmauiDiff(options); } static get OPTIONS() { return OPTIONS; } static updateGroups(diff) { const updateGroups = []; let previousGroup = []; let lastGroupIsRemove = false; const items = []; for (let i = 0; i < diff.items.length;) { if (['r', 'remove'].indexOf(diff.items[i]) > -1) { items.push(diff.items.slice(i, i + 2)); i += 2; } else { items.push(diff.items.slice(i, i + 3)); i += 3; } } diff.items = items; diff.items.forEach((item, index) => { const itemFollowing = diff.items[index + 1]; const action = item[0]; const actionFollow = itemFollowing && itemFollowing[0]; if (['r', 'remove'].indexOf(action) > -1 && ['r', 'remove'].indexOf(actionFollow) > -1 && !lastGroupIsRemove) { if (previousGroup.length) updateGroups.push(previousGroup); lastGroupIsRemove = true; previousGroup = []; } if (['a', 'add'].indexOf(action) > -1 && lastGroupIsRemove) { if (previousGroup.length) updateGroups.push(previousGroup); lastGroupIsRemove = false; previousGroup = []; } previousGroup.push(item); }); if (previousGroup.length) updateGroups.push(previousGroup); return updateGroups; } constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : optionsDefault; _defineProperty(this, "options", void 0); this.options = merge(options, optionsDefault); } diff(value_, value1_) { let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const options = merge(options_, this.options); const value = options.init.method(value_); const value1 = options.init.method(value1_); if (is('string', value) && is('string', value1)) { const valueArrayi = options.itemize.method(value1); const valueArrayj = options.itemize.method(value); const lengthi = valueArrayi.length; const lengthj = valueArrayj.length; const lengthMax = Math.max(lengthi, lengthj); const matrix = []; const lcs = []; const findItem = item => { if (item) { let [i, j] = item.slice(1); let left = matrix[i] && matrix[i][j - 1]; let top = matrix[i - 1] && matrix[i - 1][j]; let topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && top && left[0] !== item[0] && top[0] !== item[0]) { // Enter the new upper left matrix part [i, j] = topLeft.slice(1); } let item_; while (!item_) { left = matrix[i] && matrix[i][j - 1]; top = matrix[i - 1] && matrix[i - 1][j]; topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && left[0] === matrix[i][j][0]) { [i, j] = left.slice(1); } else if (top && top[0] === matrix[i][j][0]) { [i, j] = top.slice(1); } else { item_ = matrix[i][j]; break; } } return item_; } }; const addItem = (i, j) => { const left = matrix[i] && matrix[i][j - 1]; const top = matrix[i - 1] && matrix[i - 1][j]; const topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; const match = options.equal.method(valueArrayi[i], valueArrayj[j]); let item = 0; if (match) item = (topLeft ? topLeft[0] : 0) + 1;else item = Math.max(top ? top[0] : 0, left ? left[0] : 0); const item_ = [item, i, j]; return item_; }; // Make a matrix for (let i = 0; i < lengthi; i++) { matrix.push([]); for (let j = 0; j < lengthj; j++) { matrix[i][j] = addItem(i, j); } } // Make a lcs const matrixItem = matrix[lengthi - 1][lengthj - 1]; if (matrixItem && matrixItem[0]) { let item = matrixItem; let i = lengthi - 1; let j = lengthj - 1; const left = matrix[i] && matrix[i][j - 1]; const top = matrix[i - 1] && matrix[i - 1][j]; const topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && top && left[0] !== item[0] && top[0] !== item[0]) { lcs.push(item); // Enter the new upper left matrix part [i, j] = topLeft.slice(1); } while (item && item[1] !== 0 && item[2] !== 0) { item = findItem(item); lcs.push(item); } } // Sort based on value lcs lcs.sort((a, b) => a[0] - b[0]); // Make a diff items const diff = { items: [] }; for (let i = 0; i < lengthMax; i++) { const iItem = lcs.find(item => item[1] === i); const jItem = lcs.find(item => item[2] === i); if (!jItem && i < lengthj) diff.items.push(this.action('remove', i, undefined, options)); if (!iItem && i < lengthi) diff.items.push(this.action('add', i, valueArrayi[i], options)); } function sortMethod(a, b) { if (a[0] < b[0]) return 1; if (a[0] > b[0]) return -1; return 0; } diff.items.sort(sortMethod); diff.items = diff.items.flat(); return diff; } } update(value__, diff) { let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const options = merge(options_, this.options); const value_ = options.init.method(value__); if (is('string', value_) && diff !== undefined) { var _diff$items; const value = options.itemize.method(value_); if ((_diff$items = diff.items) !== null && _diff$items !== void 0 && _diff$items.length) { const updateGroups = AmauiDiff.updateGroups(diff); updateGroups.forEach(group => { const removeGroup = group.every(item => ['r', 'remove'].indexOf(item[0]) > -1); if (removeGroup) { group.sort((a, b) => b[1] - a[1]); group.forEach(item => value.splice(item[1], 1)); } else { group.forEach(item => { const action = item[0]; const index = item[1]; const valueItem = item[2]; switch (action) { case 'a': case 'add': value.splice(index, 0, valueItem); break; case 'r': case 'remove': value.splice(index, 1); break; default: break; } }); } }); } return options.join.method(value); } } action(action, index, value, options) { var _options$output; const compressed = (_options$output = options.output) === null || _options$output === void 0 ? void 0 : _options$output.compressed; return [compressed ? action.charAt(0) : action, index, value].filter(item => item !== undefined); } } export default AmauiDiff;