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; };