jiff
Version:
JSON diff and patch based on rfc6902
149 lines (124 loc) • 3.14 kB
JavaScript
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
exports.compare = compare;
exports.reduce = reduce;
var REMOVE, RIGHT, ADD, DOWN, SKIP;
exports.REMOVE = REMOVE = RIGHT = -1;
exports.ADD = ADD = DOWN = 1;
exports.EQUAL = SKIP = 0;
/**
* Create an lcs comparison matrix describing the differences
* between two array-like sequences
* @param {array} a array-like
* @param {array} b array-like
* @returns {object} lcs descriptor, suitable for passing to reduce()
*/
function compare(a, b) {
var cols = a.length;
var rows = b.length;
var prefix = findPrefix(a, b);
var suffix = prefix < cols && prefix < rows
? findSuffix(a, b, prefix)
: 0;
var remove = suffix + prefix - 1;
cols -= remove;
rows -= remove;
var matrix = createMatrix(cols, rows);
for (var j = cols - 1; j >= 0; --j) {
for (var i = rows - 1; i >= 0; --i) {
matrix[i][j] = backtrack(matrix, a, b, prefix, j, i);
}
}
return {
prefix: prefix,
matrix: matrix,
suffix: suffix
};
}
/**
* Reduce a set of lcs changes previously created using compare
* @param {function(result:*, type:number, i:number, j:number)} f
* reducer function, where:
* - result is the current reduce value,
* - type is the type of change: ADD, REMOVE, or SKIP
* - i is the index of the change location in b
* - j is the index of the change location in a
* @param {*} r initial value
* @param {object} lcs results returned by compare()
* @returns {*} the final reduced value
*/
function reduce(f, r, lcs) {
var i, j, k, op;
var m = lcs.matrix;
// Reduce shared prefix
var l = lcs.prefix;
for(i = 0;i < l; ++i) {
r = f(r, SKIP, i, i);
}
// Reduce longest change span
k = i;
l = m.length;
i = 0;
j = 0;
while(i < l) {
op = m[i][j].type;
r = f(r, op, i+k, j+k);
switch(op) {
case SKIP: ++i; ++j; break;
case RIGHT: ++j; break;
case DOWN: ++i; break;
}
}
// Reduce shared suffix
i += k;
j += k;
l = lcs.suffix;
for(k = 0;k < l; ++k) {
r = f(r, SKIP, i+k, j+k);
}
return r;
}
function findPrefix(a, b) {
var i = 0;
var l = Math.min(a.length, b.length);
while(i < l && a[i] === b[i]) {
++i;
}
return i;
}
function findSuffix(a, b) {
var al = a.length - 1;
var bl = b.length - 1;
var l = Math.min(al, bl);
var i = 0;
while(i < l && a[al-i] === b[bl-i]) {
++i;
}
return i;
}
function backtrack(matrix, a, b, start, j, i) {
if (a[j+start] === b[i+start]) {
return { value: matrix[i + 1][j + 1].value, type: SKIP };
}
if (matrix[i][j + 1].value < matrix[i + 1][j].value) {
return { value: matrix[i][j + 1].value + 1, type: RIGHT };
}
return { value: matrix[i + 1][j].value + 1, type: DOWN };
}
function createMatrix (cols, rows) {
var m = [], i, j, lastrow;
// Fill the last row
lastrow = m[rows] = [];
for (j = 0; j<cols; ++j) {
lastrow[j] = { value: cols - j, type: RIGHT };
}
// Fill the last col
for (i = 0; i<rows; ++i) {
m[i] = [];
m[i][cols] = { value: rows - i, type: DOWN };
}
// Fill the last cell
m[rows][cols] = { value: 0, type: SKIP };
return m;
}