@amaui/diff
Version:
Diff algorithm
255 lines (254 loc) • 10.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.optionsDefault = void 0;
const is_1 = __importDefault(require("@amaui/utils/is"));
const merge_1 = __importDefault(require("@amaui/utils/merge"));
const stringify_1 = __importDefault(require("@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('') },
},
};
exports.optionsDefault = Object.assign(Object.assign({}, OPTIONS.default), { output: {
compressed: true
} });
class AmauiDiff {
constructor(options = exports.optionsDefault) {
this.options = (0, merge_1.default)(options, exports.optionsDefault);
}
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) => (0, stringify_1.default)(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;
}
diff(value_, value1_, options_ = {}) {
const options = (0, merge_1.default)(options_, this.options);
const value = options.init.method(value_);
const value1 = options.init.method(value1_);
if ((0, is_1.default)('string', value) && (0, is_1.default)('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, options_ = {}) {
var _a;
const options = (0, merge_1.default)(options_, this.options);
const value_ = options.init.method(value__);
if ((0, is_1.default)('string', value_) && diff !== undefined) {
const value = options.itemize.method(value_);
if ((_a = diff.items) === null || _a === void 0 ? void 0 : _a.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 _a;
const compressed = (_a = options.output) === null || _a === void 0 ? void 0 : _a.compressed;
return [compressed ? action.charAt(0) : action, index, value].filter(item => item !== undefined);
}
}
exports.default = AmauiDiff;