NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// ==UserLibrary==
// @name libTextDiff
// @namespace https://openuserjs.org/users/Anakunda
// @version 1.02
// @license GPL-3.0-or-later
// @copyright 2023, Anakunda (https://openuserjs.org/users/Anakunda)
// @description Text diff implementation
// @exclude *
// ==/UserScript==
// ==/UserLibrary==
const DIFF_DELETE = -1;
const DIFF_INSERT = 1;
const DIFF_EQUAL = 0;
function TextDiff(options) {
if (!options) options = { };
this.Timeout = options.timeout || 1000;
this.EditCost = options.editCost || 4;
}
TextDiff.prototype.main = function(text1, text2, opt_checklines, opt_deadline) {
let deadline = Date.now() + (opt_deadline > 0 ? opt_deadline : this.Timeout);
if (text1 == null || text2 == null) throw new Error('Null input. (diff_main)');
if (text1 == text2) {
if (text1) return [[DIFF_EQUAL, text1]];
return [ ];
}
if (typeof opt_checklines == 'undefined') opt_checklines = true;
let checklines = opt_checklines;
let commonlength = this.commonPrefix(text1, text2);
let commonprefix = text1.substring(0, commonlength);
text1 = text1.substring(commonlength);
text2 = text2.substring(commonlength);
commonlength = this.commonSuffix(text1, text2);
let commonsuffix = text1.substring(text1.length - commonlength);
text1 = text1.substring(0, text1.length - commonlength);
text2 = text2.substring(0, text2.length - commonlength);
let diffs = this.compute_(text1, text2, checklines, deadline);
if (commonprefix) diffs.unshift([DIFF_EQUAL, commonprefix]);
if (commonsuffix) diffs.push([DIFF_EQUAL, commonsuffix]);
this.cleanupMerge(diffs);
return diffs;
};
TextDiff.prototype.compute_ = function(text1, text2, checklines, deadline) {
if (!text1) return [[DIFF_INSERT, text2]];
if (!text2) return [[DIFF_DELETE, text1]];
let longtext = text1.length > text2.length ? text1 : text2;
let shorttext = text1.length > text2.length ? text2 : text1;
let i = longtext.indexOf(shorttext), diffs;
if (i != -1) {
diffs = [[DIFF_INSERT, longtext.substring(0, i)],
[DIFF_EQUAL, shorttext],
[DIFF_INSERT, longtext.substring(i + shorttext.length)]];
if (text1.length > text2.length) diffs[0][0] = diffs[2][0] = DIFF_DELETE;
return diffs;
}
if (shorttext.length == 1) return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
let hm = this.halfMatch_(text1, text2);
if (hm) {
let text1_a = hm[0], text1_b = hm[1], text2_a = hm[2], text2_b = hm[3];
let mid_common = hm[4];
let diffs_a = this.main(text1_a, text2_a, checklines, deadline);
let diffs_b = this.main(text1_b, text2_b, checklines, deadline);
return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b);
}
if (checklines && text1.length > 100 && text2.length > 100)
return this.lineMode_(text1, text2, deadline);
return this.bisect_(text1, text2, deadline);
};
TextDiff.prototype.lineMode_ = function(text1, text2, deadline) {
let a = this.linesToChars_(text1, text2);
text1 = a.chars1; text2 = a.chars2;
let linearray = a.lineArray;
let diffs = this.main(text1, text2, false, deadline);
this.charsToLines_(diffs, linearray);
this.cleanupSemantic(diffs);
diffs.push([DIFF_EQUAL, '']);
let pointer = 0;
let count_delete = 0, count_insert = 0;
let text_delete = '', text_insert = '';
while (pointer < diffs.length) {
switch (diffs[pointer][0]) {
case DIFF_INSERT:
++count_insert;
text_insert += diffs[pointer][1];
break;
case DIFF_DELETE:
++count_delete;
text_delete += diffs[pointer][1];
break;
case DIFF_EQUAL:
if (count_delete >= 1 && count_insert >= 1) {
diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert);
pointer = pointer - count_delete - count_insert;
a = this.main(text_delete, text_insert, false, deadline);
for (var j = a.length - 1; j >= 0; j--) diffs.splice(pointer, 0, a[j]);
pointer = pointer + a.length;
}
count_insert = 0; count_delete = 0;
text_delete = ''; text_insert = '';
break;
}
++pointer;
}
diffs.pop();
return diffs;
};
TextDiff.prototype.bisect_ = function(text1, text2, deadline) {
let text1_length = text1.length, text2_length = text2.length;
let max_d = Math.ceil((text1_length + text2_length) / 2);
let v_offset = max_d, v_length = 2 * max_d;
let v1 = new Array(v_length), v2 = new Array(v_length);
for (var x = 0; x < v_length; x++) { v1[x] = -1; v2[x] = -1; }
v1[v_offset + 1] = 0; v2[v_offset + 1] = 0;
let delta = text1_length - text2_length, front = (delta % 2 != 0);
let k1start = 0, k1end = 0;
let k2start = 0, k2end = 0;
for (let d = 0; d < max_d; ++d) {
if (deadline > 0 && Date.now() > deadline) break;
for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
let k1_offset = v_offset + k1;
let x1 = k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1]) ?
v1[k1_offset + 1] : v1[k1_offset - 1] + 1;
let y1 = x1 - k1;
while (x1 < text1_length && y1 < text2_length && text1.charAt(x1) == text2.charAt(y1)) {
++x1;
++y1;
}
v1[k1_offset] = x1;
if (x1 > text1_length) k1end += 2;
else if (y1 > text2_length) k1start += 2;
else if (front) {
let k2_offset = v_offset + delta - k1;
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
let x2 = text1_length - v2[k2_offset];
if (x1 >= x2) return this.bisectSplit_(text1, text2, x1, y1, deadline);
}
}
}
for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
let k2_offset = v_offset + k2;
let x2 = k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1]) ?
v2[k2_offset + 1] : v2[k2_offset - 1] + 1;
let y2 = x2 - k2;
while (x2 < text1_length && y2 < text2_length &&
text1.charAt(text1_length - x2 - 1) ==
text2.charAt(text2_length - y2 - 1)) {
++x2;
++y2;
}
v2[k2_offset] = x2;
if (x2 > text1_length) k2end += 2;
else if (y2 > text2_length) k2start += 2;
else if (!front) {
let k1_offset = v_offset + delta - k2;
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
x1 = v1[k1_offset];
y1 = v_offset + x1 - k1_offset;
x2 = text1_length - x2;
if (x1 >= x2) return this.bisectSplit_(text1, text2, x1, y1, deadline);
}
}
}
}
return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
};
TextDiff.prototype.bisectSplit_ = function(text1, text2, x, y, deadline) {
let text1a = text1.substring(0, x), text2a = text2.substring(0, y);
let text1b = text1.substring(x), text2b = text2.substring(y);
let diffs = this.main(text1a, text2a, false, deadline);
let diffsb = this.main(text1b, text2b, false, deadline);
return diffs.concat(diffsb);
};
TextDiff.prototype.linesToChars_ = function(text1, text2) {
function diff_linesToCharsMunge_(text) {
let chars = '', lineStart = 0, lineEnd = -1;
letlineArrayLength = lineArray.length;
while (lineEnd < text.length - 1) {
lineEnd = text.indexOf('\n', lineStart);
if (lineEnd == -1) lineEnd = text.length - 1;
var line = text.substring(lineStart, lineEnd + 1);
lineStart = lineEnd + 1;
if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
chars += String.fromCharCode(lineHash[line]);
} else {
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
}
return chars;
}
let lineArray = [ ], lineHash = { };
lineArray[0] = '';
return {
chars1: diff_linesToCharsMunge_(text1),
chars2: diff_linesToCharsMunge_(text2),
lineArray: lineArray,
};
};
TextDiff.prototype.charsToLines_ = function(diffs, lineArray) {
for (let x = 0; x < diffs.length; ++x) {
let chars = diffs[x][1], text = [ ];
for (let y = 0; y < chars.length; ++y) text[y] = lineArray[chars.charCodeAt(y)];
diffs[x][1] = text.join('');
}
};
TextDiff.prototype.commonPrefix = function(text1, text2) {
if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) return 0;
let pointermin = 0, pointermax = Math.min(text1.length, text2.length);
let pointermid = pointermax, pointerstart = 0;
while (pointermin < pointermid) {
if (text1.substring(pointerstart, pointermid) == text2.substring(pointerstart, pointermid)) {
pointermin = pointermid;
pointerstart = pointermin;
} else pointermax = pointermid;
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
}
return pointermid;
};
TextDiff.prototype.commonSuffix = function(text1, text2) {
if (!text1 || !text2 || text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1))
return 0;
let pointermin = 0, pointermax = Math.min(text1.length, text2.length);
let pointermid = pointermax, pointerend = 0;
while (pointermin < pointermid) {
if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==
text2.substring(text2.length - pointermid, text2.length - pointerend)) {
pointermin = pointermid;
pointerend = pointermin;
} else pointermax = pointermid;
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
}
return pointermid;
};
TextDiff.prototype.commonOverlap_ = function(text1, text2) {
let text1_length = text1.length, text2_length = text2.length;
if (text1_length == 0 || text2_length == 0) return 0;
if (text1_length > text2_length)
text1 = text1.substring(text1_length - text2_length);
else if (text1_length < text2_length)
text2 = text2.substring(0, text1_length);
let text_length = Math.min(text1_length, text2_length);
if (text1 == text2) return text_length;
let best = 0, length = 1;
while (true) {
let pattern = text1.substring(text_length - length);
let found = text2.indexOf(pattern);
if (found == -1) return best;
length += found;
if (found == 0 || text1.substring(text_length - length) ==
text2.substring(0, length)) {
best = length;
length++;
}
}
};
TextDiff.prototype.halfMatch_ = function(text1, text2) {
function diff_halfMatchI_(longtext, shorttext, i) {
let seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
let j = -1, best_common = '';
let best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;
while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
let prefixLength = dmp.commonPrefix(longtext.substring(i), shorttext.substring(j));
let suffixLength = dmp.commonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
if (best_common.length < suffixLength + prefixLength) {
best_common = shorttext.substring(j - suffixLength, j) +
shorttext.substring(j, j + prefixLength);
best_longtext_a = longtext.substring(0, i - suffixLength);
best_longtext_b = longtext.substring(i + prefixLength);
best_shorttext_a = shorttext.substring(0, j - suffixLength);
best_shorttext_b = shorttext.substring(j + prefixLength);
}
}
return best_common.length * 2 >= longtext.length ? [
best_longtext_a,
best_longtext_b,
best_shorttext_a,
best_shorttext_b,
best_common,
] : null;
}
if (this.Timeout <= 0) return null;
let longtext = text1.length > text2.length ? text1 : text2;
let shorttext = text1.length > text2.length ? text2 : text1;
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) return null;
let dmp = this, hm;
let hm1 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 4));
let hm2 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 2));
if (!hm1 && !hm2) return null;
if (!hm2) hm = hm1; else if (!hm1) hm = hm2;
else hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
let text1_a, text1_b, text2_a, text2_b;
if (text1.length > text2.length) {
text1_a = hm[0]; text1_b = hm[1];
text2_a = hm[2]; text2_b = hm[3];
} else {
text2_a = hm[0]; text2_b = hm[1];
text1_a = hm[2]; text1_b = hm[3];
}
return [text1_a, text1_b, text2_a, text2_b, hm[4]];
};
TextDiff.prototype.cleanupSemantic = function(diffs) {
let changes = false, equalities = [ ], equalitiesLength = 0;
let lastequality = null, pointer = 0;
let length_insertions1 = 0, length_deletions1 = 0;
let length_insertions2 = 0, length_deletions2 = 0;
while (pointer < diffs.length) {
if (diffs[pointer][0] == DIFF_EQUAL) {
equalities[equalitiesLength++] = pointer;
length_insertions1 = length_insertions2;
length_deletions1 = length_deletions2;
length_insertions2 = 0;
length_deletions2 = 0;
lastequality = diffs[pointer][1];
} else {
if (diffs[pointer][0] == DIFF_INSERT) {
length_insertions2 += diffs[pointer][1].length;
} else length_deletions2 += diffs[pointer][1].length;
if (lastequality && (lastequality.length <=
Math.max(length_insertions1, length_deletions1)) &&
(lastequality.length <= Math.max(length_insertions2, length_deletions2))) {
diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
--equalitiesLength;
--equalitiesLength;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
length_insertions1 = 0;
length_deletions1 = 0;
length_insertions2 = 0;
length_deletions2 = 0;
lastequality = null;
changes = true;
}
}
++pointer;
}
if (changes) this.cleanupMerge(diffs);
this.cleanupSemanticLossless(diffs);
pointer = 1;
while (pointer < diffs.length) {
if (diffs[pointer - 1][0] == DIFF_DELETE && diffs[pointer][0] == DIFF_INSERT) {
let deletion = diffs[pointer - 1][1], insertion = diffs[pointer][1];
let overlap_length1 = this.commonOverlap_(deletion, insertion);
let overlap_length2 = this.commonOverlap_(insertion, deletion);
if (overlap_length1 >= overlap_length2) {
if (overlap_length1 >= deletion.length / 2 ||
overlap_length1 >= insertion.length / 2) {
diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlap_length1)]);
diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlap_length1);
diffs[pointer + 1][1] = insertion.substring(overlap_length1);
++pointer;
}
} else {
if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) {
diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlap_length2)]);
diffs[pointer - 1][0] = DIFF_INSERT;
diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2);
diffs[pointer + 1][0] = DIFF_DELETE;
diffs[pointer + 1][1] = deletion.substring(overlap_length2);
++pointer;
}
}
++pointer;
}
++pointer;
}
};
TextDiff.prototype.cleanupSemanticLossless = function(diffs) {
function diff_cleanupSemanticScore_(one, two) {
if (!one || !two) return 6;
let char1 = one.charAt(one.length - 1), char2 = two.charAt(0);
let nonAlphaNumeric1 = char1.match(TextDiff.nonAlphaNumericRegex_);
let nonAlphaNumeric2 = char2.match(TextDiff.nonAlphaNumericRegex_);
let whitespace1 = nonAlphaNumeric1 && char1.match(TextDiff.whitespaceRegex_);
let whitespace2 = nonAlphaNumeric2 && char2.match(TextDiff.whitespaceRegex_);
let lineBreak1 = whitespace1 && char1.match(TextDiff.linebreakRegex_);
let lineBreak2 = whitespace2 && char2.match(TextDiff.linebreakRegex_);
let blankLine1 = lineBreak1 && one.match(TextDiff.blanklineEndRegex_);
let blankLine2 = lineBreak2 && two.match(TextDiff.blanklineStartRegex_);
if (blankLine1 || blankLine2) return 5;
if (lineBreak1 || lineBreak2) return 4;
if (nonAlphaNumeric1 && !whitespace1 && whitespace2) return 3;
if (whitespace1 || whitespace2) return 2;
if (nonAlphaNumeric1 || nonAlphaNumeric2) return 1;
return 0;
}
let pointer = 1;
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] == DIFF_EQUAL && diffs[pointer + 1][0] == DIFF_EQUAL) {
let equality1 = diffs[pointer - 1][1], equality2 = diffs[pointer + 1][1];
let edit = diffs[pointer][1];
let commonOffset = this.commonSuffix(equality1, edit);
if (commonOffset) {
var commonString = edit.substring(edit.length - commonOffset);
equality1 = equality1.substring(0, equality1.length - commonOffset);
edit = commonString + edit.substring(0, edit.length - commonOffset);
equality2 = commonString + equality2;
}
let bestEquality1 = equality1, bestEquality2 = equality2;
let bestEdit = edit;
let bestScore = diff_cleanupSemanticScore_(equality1, edit) +
diff_cleanupSemanticScore_(edit, equality2);
while (edit.charAt(0) === equality2.charAt(0)) {
equality1 += edit.charAt(0);
edit = edit.substring(1) + equality2.charAt(0);
equality2 = equality2.substring(1);
let score = diff_cleanupSemanticScore_(equality1, edit) +
diff_cleanupSemanticScore_(edit, equality2);
if (score >= bestScore) {
bestScore = score;
bestEquality1 = equality1;
bestEdit = edit;
bestEquality2 = equality2;
}
}
if (diffs[pointer - 1][1] != bestEquality1) {
if (bestEquality1) diffs[pointer - 1][1] = bestEquality1; else {
diffs.splice(pointer - 1, 1);
--pointer;
}
diffs[pointer][1] = bestEdit;
if (bestEquality2) diffs[pointer + 1][1] = bestEquality2; else {
diffs.splice(pointer + 1, 1);
--pointer;
}
}
}
++pointer;
}
};
TextDiff.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
TextDiff.whitespaceRegex_ = /\s/;
TextDiff.linebreakRegex_ = /[\r\n]/;
TextDiff.blanklineEndRegex_ = /\n\r?\n$/;
TextDiff.blanklineStartRegex_ = /^\r?\n\r?\n/;
TextDiff.prototype.cleanupEfficiency = function(diffs) {
let changes = false, equalities = [ ], equalitiesLength = 0;
let lastequality = null, pointer = 0, pre_ins = false, pre_del = false;
let post_ins = false, post_del = false;
while (pointer < diffs.length) {
if (diffs[pointer][0] == DIFF_EQUAL) {
if (diffs[pointer][1].length < this.EditCost && (post_ins || post_del)) {
equalities[equalitiesLength++] = pointer;
pre_ins = post_ins; pre_del = post_del;
lastequality = diffs[pointer][1];
} else {
equalitiesLength = 0;
lastequality = null;
}
post_ins = post_del = false;
} else {
if (diffs[pointer][0] == DIFF_DELETE) post_del = true; else post_ins = true;
if (lastequality && ((pre_ins && pre_del && post_ins && post_del)
|| ((lastequality.length < this.EditCost / 2)
&& (pre_ins + pre_del + post_ins + post_del) == 3))) {
diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
--equalitiesLength;
lastequality = null;
if (pre_ins && pre_del) {
post_ins = post_del = true;
equalitiesLength = 0;
} else {
--equalitiesLength;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
post_ins = post_del = false;
}
changes = true;
}
}
++pointer;
}
if (changes) this.cleanupMerge(diffs);
};
TextDiff.prototype.cleanupMerge = function(diffs) {
diffs.push([DIFF_EQUAL, '']);
let pointer = 0, count_delete = 0, count_insert = 0;
let text_delete = '', text_insert = '', commonlength;
while (pointer < diffs.length) {
switch (diffs[pointer][0]) {
case DIFF_INSERT:
++count_insert;
text_insert += diffs[pointer++][1];
break;
case DIFF_DELETE:
++count_delete;
text_delete += diffs[pointer++][1];
break;
case DIFF_EQUAL:
if (count_delete + count_insert > 1) {
if (count_delete !== 0 && count_insert !== 0) {
commonlength = this.commonPrefix(text_insert, text_delete);
if (commonlength !== 0) {
if ((pointer - count_delete - count_insert) > 0 &&
diffs[pointer - count_delete - count_insert - 1][0] ==
DIFF_EQUAL) {
diffs[pointer - count_delete - count_insert - 1][1] +=
text_insert.substring(0, commonlength);
} else {
diffs.splice(0, 0, [DIFF_EQUAL, text_insert.substring(0, commonlength)]);
++pointer;
}
text_insert = text_insert.substring(commonlength);
text_delete = text_delete.substring(commonlength);
}
commonlength = this.commonSuffix(text_insert, text_delete);
if (commonlength !== 0) {
diffs[pointer][1] = text_insert.substring(text_insert.length -
commonlength) + diffs[pointer][1];
text_insert = text_insert.substring(0, text_insert.length -
commonlength);
text_delete = text_delete.substring(0, text_delete.length -
commonlength);
}
}
if (count_delete === 0) {
diffs.splice(pointer - count_insert,
count_delete + count_insert, [DIFF_INSERT, text_insert]);
} else if (count_insert === 0) {
diffs.splice(pointer - count_delete,
count_delete + count_insert, [DIFF_DELETE, text_delete]);
} else {
diffs.splice(pointer - count_delete - count_insert,
count_delete + count_insert, [DIFF_DELETE, text_delete],
[DIFF_INSERT, text_insert]);
}
pointer = pointer - count_delete - count_insert +
(count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;
} else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {
diffs[pointer - 1][1] += diffs[pointer][1];
diffs.splice(pointer, 1);
} else ++pointer;
count_insert = 0; count_delete = 0;
text_delete = ''; text_insert = '';
break;
}
}
if (diffs[diffs.length - 1][1] === '') diffs.pop();
let changes = false;
pointer = 1;
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] == DIFF_EQUAL && diffs[pointer + 1][0] == DIFF_EQUAL) {
if (diffs[pointer][1].substring(diffs[pointer][1].length -
diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) {
diffs[pointer][1] = diffs[pointer - 1][1] +
diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
diffs.splice(pointer - 1, 1);
changes = true;
} else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == diffs[pointer + 1][1]) {
diffs[pointer - 1][1] += diffs[pointer + 1][1];
diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
diffs[pointer + 1][1];
diffs.splice(pointer + 1, 1);
changes = true;
}
}
++pointer;
}
if (changes) this.cleanupMerge(diffs);
};
TextDiff.prototype.xIndex = function(diffs, loc) {
let chars1 = 0, chars2 = 0, last_chars1 = 0, last_chars2 = 0;
for (var x = 0; x < diffs.length; ++x) {
if (diffs[x][0] !== DIFF_INSERT) chars1 += diffs[x][1].length;
if (diffs[x][0] !== DIFF_DELETE) chars2 += diffs[x][1].length;
if (chars1 > loc) break;
last_chars1 = chars1; last_chars2 = chars2;
}
if (diffs.length != x && diffs[x][0] === DIFF_DELETE) return last_chars2;
return last_chars2 + (loc - last_chars1);
};
TextDiff.prototype.prettyHtml = function(diffs) {
let html = [ ];
let pattern_amp = /&/g, pattern_br = /\n/g;
let pattern_lt = /</g, pattern_gt = />/g;
for (let x = 0; x < diffs.length; x++) {
let op = diffs[x][0], data = diffs[x][1];
let text = data.replace(pattern_amp, '&').replace(pattern_lt, '<')
.replace(pattern_gt, '>').replace(pattern_br, '<br/>');
switch (op) {
case DIFF_INSERT: html[x] = '<ins>' + text + '</ins>'; break;
case DIFF_DELETE: html[x] = '<del>' + text + '</del>'; break;
case DIFF_EQUAL: html[x] = '<span>' + text + '</span>'; break;
}
}
return html.join('');
};
TextDiff.prototype.text1 = function(diffs) {
let text = [ ];
for (let x = 0; x < diffs.length; ++x)
if (diffs[x][0] !== DIFF_INSERT) text[x] = diffs[x][1];
return text.join('');
};
TextDiff.prototype.text2 = function(diffs) {
let text = [ ];
for (let x = 0; x < diffs.length; ++x)
if (diffs[x][0] !== DIFF_DELETE) text[x] = diffs[x][1];
return text.join('');
};
TextDiff.prototype.levenshtein = function(diffs) {
let levenshtein = 0, insertions = 0, deletions = 0;
for (let x = 0; x < diffs.length; ++x) {
let op = diffs[x][0], data = diffs[x][1];
switch (op) {
case DIFF_INSERT: insertions += data.length; break;
case DIFF_DELETE: deletions += data.length; break;
case DIFF_EQUAL:
levenshtein += Math.max(insertions, deletions);
insertions = 0; deletions = 0;
break;
}
}
return (levenshtein += Math.max(insertions, deletions));
};
TextDiff.prototype.toDelta = function(diffs) {
let text = [ ];
for (let x = 0; x < diffs.length; ++x) {
switch (diffs[x][0]) {
case DIFF_INSERT: text[x] = '+' + encodeURI(diffs[x][1]); break;
case DIFF_DELETE: text[x] = '-' + diffs[x][1].length; break;
case DIFF_EQUAL: text[x] = '=' + diffs[x][1].length; break;
}
}
return text.join('\t').replace(/%20/g, ' ');
};
TextDiff.prototype.fromDelta = function(text1, delta) {
let diffs = [ ], diffsLength = 0, pointer = 0, tokens = delta.split(/\t/g);
for (let x = 0; x < tokens.length; ++x) {
let param = tokens[x].substring(1);
switch (tokens[x].charAt(0)) {
case '+':
try {
diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)];
} catch (ex) {
throw new Error('Illegal escape in diff_fromDelta: ' + param);
}
break;
case '-':
case '=': {
let n = parseInt(param, 10);
if (isNaN(n) || n < 0) throw new Error('Invalid number in diff_fromDelta: ' + param);
let text = text1.substring(pointer, pointer += n);
if (tokens[x].charAt(0) == '=') diffs[diffsLength++] = [DIFF_EQUAL, text];
else diffs[diffsLength++] = [DIFF_DELETE, text];
break;
}
default:
if (tokens[x]) throw new Error('Invalid TextDiff operation in diff_fromDelta: ' + tokens[x]);
}
}
if (pointer != text1.length) throw new Error('Delta length (' + pointer +
') does not equal source text length (' + text1.length + ').');
return diffs;
};