ng-diff-match-patch
Version:
A Diff-Match-Patch component for your Angular 2 + applications
1 lines • 175 kB
Source Map (JSON)
{"version":3,"file":"ng-diff-match-patch.umd.js.map","sources":["ng://ng-diff-match-patch/lib/diffMatchPatch.ts","ng://ng-diff-match-patch/lib/diffMatchPatch.service.ts",null,"ng://ng-diff-match-patch/lib/lineCompare.component.ts","ng://ng-diff-match-patch/lib/diff.directive.ts","ng://ng-diff-match-patch/lib/lineDiff.directive.ts","ng://ng-diff-match-patch/lib/processingDiff.directive.ts","ng://ng-diff-match-patch/lib/semanticDiff.directive.ts","ng://ng-diff-match-patch/lib/diffMatchPatch.module.ts"],"sourcesContent":["export const enum DiffOp {\n Delete = -1,\n Equal = 0,\n Insert = 1\n}\n\nexport type Diff = [DiffOp, string];\n\n/**\n * Class containing the diff, match and patch methods.\n\n */\nclass DiffMatchPatch {\n\n constructor() { }\n\n // Defaults.\n // Redefine these in your program to override the defaults.\n\n // Number of seconds to map a diff before giving up (0 for infinity).\n Diff_Timeout = 1.0;\n // Cost of an empty edit operation in terms of edit characters.\n Diff_EditCost = 4;\n // At what point is no match declared (0.0 = perfection, 1.0 = very loose).\n Match_Threshold = 0.5;\n // How far to search for a match (0 = exact location, 1000+ = broad match).\n // A match this many characters away from the expected location will add\n // 1.0 to the score (0.0 is a perfect match).\n Match_Distance = 1000;\n // When deleting a large block of text (over ~64 characters), how close do\n // the contents have to be to match the expected contents. (0.0 = perfection,\n // 1.0 = very loose). Note that Match_Threshold controls how closely the\n // end points of a delete need to match.\n Patch_DeleteThreshold = 0.5;\n // Chunk size for context length.\n Patch_Margin = 4;\n\n // The number of bits in an int.\n Match_MaxBits = 32;\n /**\n * The data structure representing a diff is an array of tuples:\n * [[DiffOp.Delete, 'Hello'], [DiffOp.Insert, 'Goodbye'], [DiffOp.Equal, ' world.']]\n * which means: delete 'Hello', add 'Goodbye' and keep ' world.'\n */\n\n // Define some regex patterns for matching boundaries.\n whitespaceRegex_ = new RegExp('/\\s/');\n linebreakRegex_ = new RegExp('/[\\r\\n]/');\n blanklineEndRegex_ = new RegExp('/\\n\\r?\\n$/');\n blanklineStartRegex_ = new RegExp('/^\\r?\\n\\r?\\n/');\n\n /**\n * Find the differences between two texts. Simplifies the problem by stripping\n * any common prefix or suffix off the texts before diffing.\n * @param text1 Old string to be diffed.\n * @param text2 New string to be diffed.\n * @param opt_checklines Optional speedup flag. If present and false,\n * then don't run a line-level diff first to identify the changed areas.\n * Defaults to true, which does a faster, slightly less optimal diff.\n * @param opt_deadline Optional time when the diff should be complete\n * by. Used internally for recursive calls. Users should set DiffTimeout\n * instead.\n * @return Array of diff tuples.\n */\n diff_main (text1: string, text2: string, opt_checklines?: boolean, opt_deadline?: number): Array<Diff> {\n // Set a deadline by which time the diff must be complete.\n if (typeof opt_deadline == 'undefined') {\n if (this.Diff_Timeout <= 0) {\n opt_deadline = Number.MAX_VALUE;\n } else {\n opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000;\n }\n }\n const deadline = opt_deadline;\n\n // Check for null inputs.\n if (text1 == null || text2 == null) {\n throw new Error('Null input. (diff_main)');\n }\n\n // Check for equality (speedup).\n if (text1 == text2) {\n if (text1) {\n return [[DiffOp.Equal, text1]];\n }\n return [];\n }\n\n if (typeof opt_checklines == 'undefined') {\n opt_checklines = true;\n }\n const checklines = opt_checklines;\n\n // Trim off common prefix (speedup).\n let commonlength = this.diff_commonPrefix(text1, text2);\n const commonprefix = text1.substring(0, commonlength);\n text1 = text1.substring(commonlength);\n text2 = text2.substring(commonlength);\n\n // Trim off common suffix (speedup).\n commonlength = this.diff_commonSuffix(text1, text2);\n const commonsuffix = text1.substring(text1.length - commonlength);\n text1 = text1.substring(0, text1.length - commonlength);\n text2 = text2.substring(0, text2.length - commonlength);\n\n // Compute the diff on the middle block.\n const diffs = this.diff_compute_(text1, text2, checklines, deadline);\n\n // Restore the prefix and suffix.\n if (commonprefix) {\n diffs.unshift([DiffOp.Equal, commonprefix]);\n }\n if (commonsuffix) {\n diffs.push([DiffOp.Equal, commonsuffix]);\n }\n this.diff_cleanupMerge(diffs);\n return diffs;\n };\n\n\n /**\n * Find the differences between two texts. Assumes that the texts do not\n * have any common prefix or suffix.\n * @param text1 Old string to be diffed.\n * @param text2 New string to be diffed.\n * @param checklines Speedup flag. If false, then don't run a\n * line-level diff first to identify the changed areas.\n * If true, then run a faster, slightly less optimal diff.\n * @param deadline Time when the diff should be complete by.\n * @return Array of diff tuples.\n\n */\n diff_compute_ (text1: string, text2: string, checklines: boolean,\n deadline: number): Array<Diff> {\n let diffs: Array<Diff>;\n\n if (!text1) {\n // Just add some text (speedup).\n return [[DiffOp.Insert, text2]];\n }\n\n if (!text2) {\n // Just delete some text (speedup).\n return [[DiffOp.Delete, text1]];\n }\n\n const longtext = text1.length > text2.length ? text1 : text2;\n const shorttext = text1.length > text2.length ? text2 : text1;\n const i = longtext.indexOf(shorttext);\n if (i != -1) {\n // Shorter text is inside the longer text (speedup).\n diffs = [[DiffOp.Insert, longtext.substring(0, i)],\n [DiffOp.Equal, shorttext],\n [DiffOp.Insert, longtext.substring(i + shorttext.length)]];\n // Swap insertions for deletions if diff is reversed.\n if (text1.length > text2.length) {\n diffs[0][0] = diffs[2][0] = DiffOp.Delete;\n }\n return diffs;\n }\n\n if (shorttext.length == 1) {\n // Single character string.\n // After the previous speedup, the character can't be an equality.\n return [[DiffOp.Delete, text1], [DiffOp.Insert, text2]];\n }\n\n // Check to see if the problem can be split in two.\n const hm = this.diff_halfMatch_(text1, text2);\n if (hm) {\n // A half-match was found, sort out the return data.\n const text1_a = hm[0];\n const text1_b = hm[1];\n const text2_a = hm[2];\n const text2_b = hm[3];\n const mid_common = hm[4];\n // Send both pairs off for separate processing.\n const diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);\n const diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);\n // Merge the results.\n return diffs_a.concat([[DiffOp.Equal, mid_common]], diffs_b);\n }\n\n if (checklines && text1.length > 100 && text2.length > 100) {\n return this.diff_lineMode_(text1, text2, deadline);\n }\n\n return this.diff_bisect_(text1, text2, deadline);\n };\n\n\n /**\n * Do a quick line-level diff on both strings, then rediff the parts for\n * greater accuracy.\n * This speedup can produce non-minimal diffs.\n * @param text1 Old string to be diffed.\n * @param text2 New string to be diffed.\n * @param deadline Time when the diff should be complete by.\n * @return Array of diff tuples.\n\n */\n diff_lineMode_ (text1: string, text2: string, deadline: number) {\n // Scan the text on a line-by-line basis first.\n const a = this.diff_linesToChars_(text1, text2);\n text1 = a.chars1;\n text2 = a.chars2;\n const linearray = a.lineArray;\n\n const diffs = this.diff_main(text1, text2, false, deadline);\n\n // Convert the diff back to original text.\n this.diff_charsToLines_(diffs, linearray);\n // Eliminate freak matches (e.g. blank lines)\n this.diff_cleanupSemantic(diffs);\n\n // Rediff any replacement blocks, this time character-by-character.\n // Add a dummy entry at the end.\n diffs.push([DiffOp.Equal, '']);\n let pointer = 0;\n let count_delete = 0;\n let count_insert = 0;\n let text_delete = '';\n let text_insert = '';\n while (pointer < diffs.length) {\n switch (diffs[pointer][0]) {\n case DiffOp.Insert:\n count_insert++;\n text_insert += diffs[pointer][1];\n break;\n case DiffOp.Delete:\n count_delete++;\n text_delete += diffs[pointer][1];\n break;\n case DiffOp.Equal:\n // Upon reaching an equality, check for prior redundancies.\n if (count_delete >= 1 && count_insert >= 1) {\n // Delete the offending records and add the merged ones.\n diffs.splice(pointer - count_delete - count_insert,\n count_delete + count_insert);\n pointer = pointer - count_delete - count_insert;\n const b = this.diff_main(text_delete, text_insert, false, deadline);\n for (let j = b.length - 1; j >= 0; j--) {\n diffs.splice(pointer, 0, b[j]);\n }\n pointer = pointer + b.length;\n }\n count_insert = 0;\n count_delete = 0;\n text_delete = '';\n text_insert = '';\n break;\n }\n pointer++;\n }\n diffs.pop(); // Remove the dummy entry at the end.\n\n return diffs;\n };\n\n\n /**\n * Find the 'middle snake' of a diff, split the problem in two\n * and return the recursively constructed diff.\n * See Myers 1986 paper: An O(ND) Difference Algorithm and Its constiations.\n * @param text1 Old string to be diffed.\n * @param text2 New string to be diffed.\n * @param deadline Time at which to bail if not yet complete.\n * @return Array of diff tuples.\n\n */\n diff_bisect_ (text1: string, text2: string, deadline: number): Array<Diff> {\n // Cache the text lengths to prevent multiple calls.\n const text1_length = text1.length;\n const text2_length = text2.length;\n const max_d = Math.ceil((text1_length + text2_length) / 2);\n const v_offset = max_d;\n const v_length = 2 * max_d;\n const v1 = new Array(v_length);\n const v2 = new Array(v_length);\n // Setting all elements to -1 is faster in Chrome & Firefox than mixing\n // integers and undefined.\n for (let x = 0; x < v_length; x++) {\n v1[x] = -1;\n v2[x] = -1;\n }\n v1[v_offset + 1] = 0;\n v2[v_offset + 1] = 0;\n const delta = text1_length - text2_length;\n // If the total number of characters is odd, then the front path will collide\n // with the reverse path.\n const front = (delta % 2 != 0);\n // Offsets for start and end of k loop.\n // Prevents mapping of space beyond the grid.\n let k1start = 0;\n let k1end = 0;\n let k2start = 0;\n let k2end = 0;\n for (let d = 0; d < max_d; d++) {\n // Bail out if deadline is reached.\n if ((new Date()).getTime() > deadline) {\n break;\n }\n\n // Walk the front path one step.\n for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {\n const k1_offset = v_offset + k1;\n let x1;\n if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {\n x1 = v1[k1_offset + 1];\n } else {\n x1 = v1[k1_offset - 1] + 1;\n }\n let y1 = x1 - k1;\n while (x1 < text1_length && y1 < text2_length &&\n text1.charAt(x1) == text2.charAt(y1)) {\n x1++;\n y1++;\n }\n v1[k1_offset] = x1;\n if (x1 > text1_length) {\n // Ran off the right of the graph.\n k1end += 2;\n } else if (y1 > text2_length) {\n // Ran off the bottom of the graph.\n k1start += 2;\n } else if (front) {\n const k2_offset = v_offset + delta - k1;\n if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {\n // Mirror x2 onto top-left coordinate system.\n const x2 = text1_length - v2[k2_offset];\n if (x1 >= x2) {\n // Overlap detected.\n return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);\n }\n }\n }\n }\n\n // Walk the reverse path one step.\n for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {\n const k2_offset = v_offset + k2;\n let x2: number;\n if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {\n x2 = v2[k2_offset + 1];\n } else {\n x2 = v2[k2_offset - 1] + 1;\n }\n let y2 = x2 - k2;\n while (x2 < text1_length && y2 < text2_length &&\n text1.charAt(text1_length - x2 - 1) ==\n text2.charAt(text2_length - y2 - 1)) {\n x2++;\n y2++;\n }\n v2[k2_offset] = x2;\n if (x2 > text1_length) {\n // Ran off the left of the graph.\n k2end += 2;\n } else if (y2 > text2_length) {\n // Ran off the top of the graph.\n k2start += 2;\n } else if (!front) {\n const k1_offset = v_offset + delta - k2;\n if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {\n const x1 = v1[k1_offset];\n const y1 = v_offset + x1 - k1_offset;\n // Mirror x2 onto top-left coordinate system.\n x2 = text1_length - x2;\n if (x1 >= x2) {\n // Overlap detected.\n return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);\n }\n }\n }\n }\n }\n // Diff took too long and hit the deadline or\n // number of diffs equals number of characters, no commonality at all.\n return [[DiffOp.Delete, text1], [DiffOp.Insert, text2]];\n };\n\n\n /**\n * Given the location of the 'middle snake', split the diff in two parts\n * and recurse.\n * @param text1 Old string to be diffed.\n * @param text2 New string to be diffed.\n * @param x Index of split point in text1.\n * @param y Index of split point in text2.\n * @param deadline Time at which to bail if not yet complete.\n * @return Array of diff tuples.\n\n */\n diff_bisectSplit_ (text1: string, text2: string, x: number, y: number, deadline: number) {\n const text1a = text1.substring(0, x);\n const text2a = text2.substring(0, y);\n const text1b = text1.substring(x);\n const text2b = text2.substring(y);\n\n // Compute both diffs serially.\n const diffs = this.diff_main(text1a, text2a, false, deadline);\n const diffsb = this.diff_main(text1b, text2b, false, deadline);\n\n return diffs.concat(diffsb);\n };\n\n\n /**\n * Split two texts into an array of strings. Reduce the texts to a string of\n * hashes where each Unicode character represents one line.\n * @param text1 First string.\n * @param text2 Second string.\n * @return }\n * An object containing the encoded text1, the encoded text2 and\n * the array of unique strings.\n * The zeroth element of the array of unique strings is intentionally blank.\n\n */\n diff_linesToChars_ (text1: string, text2: string) {\n const lineArray = []; // e.g. lineArray[4] == 'Hello\\n'\n const lineHash = {}; // e.g. lineHash['Hello\\n'] == 4\n\n // '\\x00' is a valid character, but constious debuggers don't like it.\n // So we'll insert a junk entry to avoid generating a null character.\n lineArray[0] = '';\n\n\n const chars1 = this.diff_linesToCharsMunge_(text1, lineArray, lineHash);\n const chars2 = this.diff_linesToCharsMunge_(text2, lineArray, lineHash);\n return {chars1: chars1, chars2: chars2, lineArray: lineArray};\n };\n\n /**\n * Split a text into an array of strings. Reduce the texts to a string of\n * hashes where each Unicode character represents one line.\n * Modifies linearray and linehash through being a closure.\n * @param text String to encode.\n * @return Encoded string.\n\n */\n diff_linesToCharsMunge_(text: string, lineArray: Array<string>, lineHash: any): string {\n let chars = '';\n // Walk the text, pulling out a substring for each line.\n // text.split('\\n') would would temporarily double our memory footprint.\n // Modifying text would create many large strings to garbage collect.\n let lineStart = 0;\n let lineEnd = -1;\n // Keeping our own length constiable is faster than looking it up.\n let lineArrayLength = lineArray.length;\n while (lineEnd < text.length - 1) {\n lineEnd = text.indexOf('\\n', lineStart);\n if (lineEnd == -1) {\n lineEnd = text.length - 1;\n }\n const line = text.substring(lineStart, lineEnd + 1);\n lineStart = lineEnd + 1;\n\n if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :\n (lineHash[line] !== undefined)) {\n chars += String.fromCharCode(lineHash[line]);\n } else {\n chars += String.fromCharCode(lineArrayLength);\n lineHash[line] = lineArrayLength;\n lineArray[lineArrayLength++] = line;\n }\n }\n return chars;\n }\n\n /**\n * Rehydrate the text in a diff from a string of line hashes to real lines of\n * text.\n * @param diffs Array of diff tuples.\n * @param lineArray Array of unique strings.\n\n */\n diff_charsToLines_ (diffs: Array<Diff>, lineArray: Array<string>): void {\n for (let x = 0; x < diffs.length; x++) {\n const chars = diffs[x][1];\n const text = [];\n for (let y = 0; y < chars.length; y++) {\n text[y] = lineArray[chars.charCodeAt(y)];\n }\n diffs[x][1] = text.join('');\n }\n };\n\n\n /**\n * Determine the common prefix of two strings.\n * @param text1 First string.\n * @param text2 Second string.\n * @return The number of characters common to the start of each\n * string.\n */\n diff_commonPrefix (text1: string, text2: string): number {\n // Quick check for common null cases.\n if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {\n return 0;\n }\n // Binary search.\n // Performance analysis: http://neil.fraser.name/news/2007/10/09/\n let pointermin = 0;\n let pointermax = Math.min(text1.length, text2.length);\n let pointermid = pointermax;\n let pointerstart = 0;\n while (pointermin < pointermid) {\n if (text1.substring(pointerstart, pointermid) ==\n text2.substring(pointerstart, pointermid)) {\n pointermin = pointermid;\n pointerstart = pointermin;\n } else {\n pointermax = pointermid;\n }\n pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);\n }\n return pointermid;\n };\n\n\n /**\n * Determine the common suffix of two strings.\n * @param text1 First string.\n * @param text2 Second string.\n * @return The number of characters common to the end of each string.\n */\n diff_commonSuffix (text1: string, text2: string): number {\n // Quick check for common null cases.\n if (!text1 || !text2 ||\n text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {\n return 0;\n }\n // Binary search.\n // Performance analysis: http://neil.fraser.name/news/2007/10/09/\n let pointermin = 0;\n let pointermax = Math.min(text1.length, text2.length);\n let pointermid = pointermax;\n let pointerend = 0;\n while (pointermin < pointermid) {\n if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==\n text2.substring(text2.length - pointermid, text2.length - pointerend)) {\n pointermin = pointermid;\n pointerend = pointermin;\n } else {\n pointermax = pointermid;\n }\n pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);\n }\n return pointermid;\n };\n\n\n /**\n * Determine if the suffix of one string is the prefix of another.\n * @param text1 First string.\n * @param text2 Second string.\n * @return The number of characters common to the end of the first\n * string and the start of the second string.\n\n */\n diff_commonOverlap_ (text1: string, text2: string): number {\n // Cache the text lengths to prevent multiple calls.\n const text1_length = text1.length;\n const text2_length = text2.length;\n // Eliminate the null case.\n if (text1_length == 0 || text2_length == 0) {\n return 0;\n }\n // Truncate the longer string.\n if (text1_length > text2_length) {\n text1 = text1.substring(text1_length - text2_length);\n } else if (text1_length < text2_length) {\n text2 = text2.substring(0, text1_length);\n }\n const text_length = Math.min(text1_length, text2_length);\n // Quick check for the worst case.\n if (text1 == text2) {\n return text_length;\n }\n\n // Start by looking for a single character match\n // and increase length until no match is found.\n // Performance analysis: http://neil.fraser.name/news/2010/11/04/\n let best = 0;\n let length = 1;\n while (true) {\n const pattern = text1.substring(text_length - length);\n const found = text2.indexOf(pattern);\n if (found == -1) {\n return best;\n }\n length += found;\n if (found == 0 || text1.substring(text_length - length) ==\n text2.substring(0, length)) {\n best = length;\n length++;\n }\n }\n };\n\n\n /**\n * Do the two texts share a substring which is at least half the length of the\n * longer text?\n * This speedup can produce non-minimal diffs.\n * @param text1 First string.\n * @param text2 Second string.\n * @return Five element Array, containing the prefix of\n * text1, the suffix of text1, the prefix of text2, the suffix of\n * text2 and the common middle. Or null if there was no match.\n\n */\n diff_halfMatch_ (text1: string, text2: string) {\n if (this.Diff_Timeout <= 0) {\n // Don't risk returning a non-optimal diff if we have unlimited time.\n return null;\n }\n const longtext = text1.length > text2.length ? text1 : text2;\n const shorttext = text1.length > text2.length ? text2 : text1;\n if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {\n return null; // Pointless.\n }\n const dmp = this; // 'this' becomes 'window' in a closure.\n\n\n // First check if the second quarter is the seed for a half-match.\n const hm1 = this.diff_halfMatchI_(longtext, shorttext,\n Math.ceil(longtext.length / 4), dmp);\n // Check again based on the third quarter.\n const hm2 = this.diff_halfMatchI_(longtext, shorttext,\n Math.ceil(longtext.length / 2), dmp);\n let hm;\n if (!hm1 && !hm2) {\n return null;\n } else if (!hm2) {\n hm = hm1;\n } else if (!hm1) {\n hm = hm2;\n } else {\n // Both matched. Select the longest.\n hm = hm1[4].length > hm2[4].length ? hm1 : hm2;\n }\n\n // A half-match was found, sort out the return data.\n let text1_a, text1_b, text2_a, text2_b;\n if (text1.length > text2.length) {\n text1_a = hm[0];\n text1_b = hm[1];\n text2_a = hm[2];\n text2_b = hm[3];\n } else {\n text2_a = hm[0];\n text2_b = hm[1];\n text1_a = hm[2];\n text1_b = hm[3];\n }\n const mid_common = hm[4];\n return [text1_a, text1_b, text2_a, text2_b, mid_common];\n };\n\n /**\n * Does a substring of shorttext exist within longtext such that the substring\n * is at least half the length of longtext?\n * Closure, but does not reference any external constiables.\n * @param longtext Longer string.\n * @param shorttext Shorter string.\n * @param i Start index of quarter length substring within longtext.\n * @return Five element Array, containing the prefix of\n * longtext, the suffix of longtext, the prefix of shorttext, the suffix\n * of shorttext and the common middle. Or null if there was no match.\n\n */\n diff_halfMatchI_(longtext: string, shorttext: string, i: number, dmp: DiffMatchPatch): Array<string> {\n // Start with a 1/4 length substring at position i as a seed.\n const seed = longtext.substring(i, i + Math.floor(longtext.length / 4));\n let j = -1;\n let best_common = '';\n let best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;\n while ((j = shorttext.indexOf(seed, j + 1)) != -1) {\n const prefixLength = dmp.diff_commonPrefix(longtext.substring(i),\n shorttext.substring(j));\n const suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i),\n shorttext.substring(0, j));\n if (best_common.length < suffixLength + prefixLength) {\n best_common = shorttext.substring(j - suffixLength, j) +\n shorttext.substring(j, j + prefixLength);\n best_longtext_a = longtext.substring(0, i - suffixLength);\n best_longtext_b = longtext.substring(i + prefixLength);\n best_shorttext_a = shorttext.substring(0, j - suffixLength);\n best_shorttext_b = shorttext.substring(j + prefixLength);\n }\n }\n if (best_common.length * 2 >= longtext.length) {\n return [best_longtext_a, best_longtext_b,\n best_shorttext_a, best_shorttext_b, best_common];\n } else {\n return null;\n }\n }\n\n /**\n * Reduce the number of edits by eliminating semantically trivial equalities.\n * @param diffs Array of diff tuples.\n */\n diff_cleanupSemantic (diffs: Array<Diff>) {\n let changes = false;\n const equalities = []; // Stack of indices where equalities are found.\n let equalitiesLength = 0; // Keeping our own length const is faster in JS.\n\n let lastequality = null;\n // Always equal to diffs[equalities[equalitiesLength - 1]][1]\n let pointer = 0; // Index of current position.\n // Number of characters that changed prior to the equality.\n let length_insertions1 = 0;\n let length_deletions1 = 0;\n // Number of characters that changed after the equality.\n let length_insertions2 = 0;\n let length_deletions2 = 0;\n while (pointer < diffs.length) {\n if (diffs[pointer][0] == DiffOp.Equal) { // Equality found.\n equalities[equalitiesLength++] = pointer;\n length_insertions1 = length_insertions2;\n length_deletions1 = length_deletions2;\n length_insertions2 = 0;\n length_deletions2 = 0;\n lastequality = diffs[pointer][1];\n } else { // An insertion or deletion.\n if (diffs[pointer][0] == DiffOp.Insert) {\n length_insertions2 += diffs[pointer][1].length;\n } else {\n length_deletions2 += diffs[pointer][1].length;\n }\n // Eliminate an equality that is smaller or equal to the edits on both\n // sides of it.\n if (lastequality && (lastequality.length <=\n Math.max(length_insertions1, length_deletions1)) &&\n (lastequality.length <= Math.max(length_insertions2,\n length_deletions2))) {\n // Duplicate record.\n diffs.splice(equalities[equalitiesLength - 1], 0,\n [DiffOp.Delete, lastequality]);\n // Change second copy to insert.\n diffs[equalities[equalitiesLength - 1] + 1][0] = DiffOp.Insert;\n // Throw away the equality we just deleted.\n equalitiesLength--;\n // Throw away the previous equality (it needs to be reevaluated).\n equalitiesLength--;\n pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;\n length_insertions1 = 0; // Reset the counters.\n length_deletions1 = 0;\n length_insertions2 = 0;\n length_deletions2 = 0;\n lastequality = null;\n changes = true;\n }\n }\n pointer++;\n }\n\n // Normalize the diff.\n if (changes) {\n this.diff_cleanupMerge(diffs);\n }\n this.diff_cleanupSemanticLossless(diffs);\n\n // Find any overlaps between deletions and insertions.\n // e.g: <del>abcxxx</del><ins>xxxdef</ins>\n // -> <del>abc</del>xxx<ins>def</ins>\n // e.g: <del>xxxabc</del><ins>defxxx</ins>\n // -> <ins>def</ins>xxx<del>abc</del>\n // Only extract an overlap if it is as big as the edit ahead or behind it.\n pointer = 1;\n while (pointer < diffs.length) {\n if (diffs[pointer - 1][0] == DiffOp.Delete &&\n diffs[pointer][0] == DiffOp.Insert) {\n const deletion = diffs[pointer - 1][1];\n const insertion = diffs[pointer][1];\n const overlap_length1 = this.diff_commonOverlap_(deletion, insertion);\n const overlap_length2 = this.diff_commonOverlap_(insertion, deletion);\n if (overlap_length1 >= overlap_length2) {\n if (overlap_length1 >= deletion.length / 2 ||\n overlap_length1 >= insertion.length / 2) {\n // Overlap found. Insert an equality and trim the surrounding edits.\n diffs.splice(pointer, 0,\n [DiffOp.Equal, insertion.substring(0, overlap_length1)]);\n diffs[pointer - 1][1] =\n deletion.substring(0, deletion.length - overlap_length1);\n diffs[pointer + 1][1] = insertion.substring(overlap_length1);\n pointer++;\n }\n } else {\n if (overlap_length2 >= deletion.length / 2 ||\n overlap_length2 >= insertion.length / 2) {\n // Reverse overlap found.\n // Insert an equality and swap and trim the surrounding edits.\n diffs.splice(pointer, 0,\n [DiffOp.Equal, deletion.substring(0, overlap_length2)]);\n diffs[pointer - 1][0] = DiffOp.Insert;\n diffs[pointer - 1][1] =\n insertion.substring(0, insertion.length - overlap_length2);\n diffs[pointer + 1][0] = DiffOp.Delete;\n diffs[pointer + 1][1] =\n deletion.substring(overlap_length2);\n pointer++;\n }\n }\n pointer++;\n }\n pointer++;\n }\n };\n\n\n /**\n * Look for single edits surrounded on both sides by equalities\n * which can be shifted sideways to align the edit to a word boundary.\n * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.\n * @param diffs Array of diff tuples.\n */\n diff_cleanupSemanticLossless (diffs: Array<Diff>) {\n /**\n * Given two strings, compute a score representing whether the internal\n * boundary falls on logical boundaries.\n * Scores range from 6 (best) to 0 (worst).\n * Closure, but does not reference any external constiables.\n * @param one First string.\n * @param two Second string.\n * @return The score.\n\n */\n function diff_cleanupSemanticScore_(one: string, two: string): number {\n if (!one || !two) {\n // Edges are the best.\n return 6;\n }\n\n\n const nonAlphaNumericRegex_ = new RegExp('/[^a-zA-Z0-9]/');\n\n // Each port of this function behaves slightly differently due to\n // subtle differences in each language's definition of things like\n // 'whitespace'. Since this function's purpose is largely cosmetic,\n // the choice has been made to use each language's native features\n // rather than force total conformity.\n const char1 = one.charAt(one.length - 1);\n const char2 = two.charAt(0);\n const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);\n const nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);\n const whitespace1 = nonAlphaNumeric1 &&\n char1.match(this.whitespaceRegex_);\n const whitespace2 = nonAlphaNumeric2 &&\n char2.match(this.whitespaceRegex_);\n const lineBreak1 = whitespace1 &&\n char1.match(this.linebreakRegex_);\n const lineBreak2 = whitespace2 &&\n char2.match(this.linebreakRegex_);\n const blankLine1 = lineBreak1 &&\n one.match(this.blanklineEndRegex_);\n const blankLine2 = lineBreak2 &&\n two.match(this.blanklineStartRegex_);\n\n if (blankLine1 || blankLine2) {\n // Five points for blank lines.\n return 5;\n } else if (lineBreak1 || lineBreak2) {\n // Four points for line breaks.\n return 4;\n } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {\n // Three points for end of sentences.\n return 3;\n } else if (whitespace1 || whitespace2) {\n // Two points for whitespace.\n return 2;\n } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {\n // One point for non-alphanumeric.\n return 1;\n }\n return 0;\n }\n\n let pointer = 1;\n // Intentionally ignore the first and last element (don't need checking).\n while (pointer < diffs.length - 1) {\n if (diffs[pointer - 1][0] == DiffOp.Equal &&\n diffs[pointer + 1][0] == DiffOp.Equal) {\n // This is a single edit surrounded by equalities.\n let equality1 = diffs[pointer - 1][1];\n let edit = diffs[pointer][1];\n let equality2 = diffs[pointer + 1][1];\n\n // First, shift the edit as far left as possible.\n const commonOffset = this.diff_commonSuffix(equality1, edit);\n if (commonOffset) {\n const commonString = edit.substring(edit.length - commonOffset);\n equality1 = equality1.substring(0, equality1.length - commonOffset);\n edit = commonString + edit.substring(0, edit.length - commonOffset);\n equality2 = commonString + equality2;\n }\n\n // Second, step character by character right, looking for the best fit.\n let bestEquality1 = equality1;\n let bestEdit = edit;\n let bestEquality2 = equality2;\n let bestScore = diff_cleanupSemanticScore_(equality1, edit) +\n diff_cleanupSemanticScore_(edit, equality2);\n while (edit.charAt(0) === equality2.charAt(0)) {\n equality1 += edit.charAt(0);\n edit = edit.substring(1) + equality2.charAt(0);\n equality2 = equality2.substring(1);\n const score = diff_cleanupSemanticScore_(equality1, edit) +\n diff_cleanupSemanticScore_(edit, equality2);\n // The >= encourages trailing rather than leading whitespace on edits.\n if (score >= bestScore) {\n bestScore = score;\n bestEquality1 = equality1;\n bestEdit = edit;\n bestEquality2 = equality2;\n }\n }\n\n if (diffs[pointer - 1][1] != bestEquality1) {\n // We have an improvement, save it back to the diff.\n if (bestEquality1) {\n diffs[pointer - 1][1] = bestEquality1;\n } else {\n diffs.splice(pointer - 1, 1);\n pointer--;\n }\n diffs[pointer][1] = bestEdit;\n if (bestEquality2) {\n diffs[pointer + 1][1] = bestEquality2;\n } else {\n diffs.splice(pointer + 1, 1);\n pointer--;\n }\n }\n }\n pointer++;\n }\n };\n\n\n /**\n * Reduce the number of edits by eliminating operationally trivial equalities.\n * @param diffs Array of diff tuples.\n */\n diff_cleanupEfficiency (diffs: Array<Diff>) {\n let changes = false;\n const equalities = []; // Stack of indices where equalities are found.\n let equalitiesLength = 0; // Keeping our own length const is faster in JS.\n\n let lastequality = null;\n // Always equal to diffs[equalities[equalitiesLength - 1]][1]\n let pointer = 0; // Index of current position.\n // Is there an insertion operation before the last equality.\n let pre_ins = false;\n // Is there a deletion operation before the last equality.\n let pre_del = false;\n // Is there an insertion operation after the last equality.\n let post_ins = false;\n // Is there a deletion operation after the last equality.\n let post_del = false;\n while (pointer < diffs.length) {\n if (diffs[pointer][0] == DiffOp.Equal) { // Equality found.\n if (diffs[pointer][1].length < this.Diff_EditCost &&\n (post_ins || post_del)) {\n // Candidate found.\n equalities[equalitiesLength++] = pointer;\n pre_ins = post_ins;\n pre_del = post_del;\n lastequality = diffs[pointer][1];\n } else {\n // Not a candidate, and can never become one.\n equalitiesLength = 0;\n lastequality = null;\n }\n post_ins = post_del = false;\n } else { // An insertion or deletion.\n if (diffs[pointer][0] == DiffOp.Delete) {\n post_del = true;\n } else {\n post_ins = true;\n }\n /*\n * Five types to be split:\n * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>\n * <ins>A</ins>X<ins>C</ins><del>D</del>\n * <ins>A</ins><del>B</del>X<ins>C</ins>\n * <ins>A</del>X<ins>C</ins><del>D</del>\n * <ins>A</ins><del>B</del>X<del>C</del>\n */\n if (lastequality && ((pre_ins && pre_del && post_ins && post_del) ||\n ((lastequality.length < this.Diff_EditCost / 2) &&\n ((pre_ins?1:0) + (pre_del?1:0) + (post_ins?1:0) + (post_del?1:0) == 3)))) {\n // Duplicate record.\n diffs.splice(equalities[equalitiesLength - 1], 0,\n [DiffOp.Delete, lastequality]);\n // Change second copy to insert.\n diffs[equalities[equalitiesLength - 1] + 1][0] = DiffOp.Insert;\n equalitiesLength--; // Throw away the equality we just deleted;\n lastequality = null;\n if (pre_ins && pre_del) {\n // No changes made which could affect previous entry, keep going.\n post_ins = post_del = true;\n equalitiesLength = 0;\n } else {\n equalitiesLength--; // Throw away the previous equality.\n pointer = equalitiesLength > 0 ?\n equalities[equalitiesLength - 1] : -1;\n post_ins = post_del = false;\n }\n changes = true;\n }\n }\n pointer++;\n }\n\n if (changes) {\n this.diff_cleanupMerge(diffs);\n }\n };\n\n\n /**\n * Reorder and merge like edit sections. Merge equalities.\n * Any edit section can move as long as it doesn't cross an equality.\n * @param diffs Array of diff tuples.\n */\n diff_cleanupMerge (diffs: Array<Diff>) {\n diffs.push([DiffOp.Equal, '']); // Add a dummy entry at the end.\n let pointer = 0;\n let count_delete = 0;\n let count_insert = 0;\n let text_delete = '';\n let text_insert = '';\n let commonlength;\n while (pointer < diffs.length) {\n switch (diffs[pointer][0]) {\n case DiffOp.Insert:\n count_insert++;\n text_insert += diffs[pointer][1];\n pointer++;\n break;\n case DiffOp.Delete:\n count_delete++;\n text_delete += diffs[pointer][1];\n pointer++;\n break;\n case DiffOp.Equal:\n // Upon reaching an equality, check for prior redundancies.\n if (count_delete + count_insert > 1) {\n if (count_delete !== 0 && count_insert !== 0) {\n // Factor out any common prefixies.\n commonlength = this.diff_commonPrefix(text_insert, text_delete);\n if (commonlength !== 0) {\n if ((pointer - count_delete - count_insert) > 0 &&\n diffs[pointer - count_delete - count_insert - 1][0] ==\n DiffOp.Equal) {\n diffs[pointer - count_delete - count_insert - 1][1] +=\n text_insert.substring(0, commonlength);\n } else {\n diffs.splice(0, 0, [DiffOp.Equal,\n text_insert.substring(0, commonlength)]);\n pointer++;\n }\n text_insert = text_insert.substring(commonlength);\n text_delete = text_delete.substring(commonlength);\n }\n // Factor out any common suffixies.\n commonlength = this.diff_commonSuffix(text_insert, text_delete);\n if (commonlength !== 0) {\n diffs[pointer][1] = text_insert.substring(text_insert.length -\n commonlength) + diffs[pointer][1];\n text_insert = text_insert.substring(0, text_insert.length -\n commonlength);\n text_delete = text_delete.substring(0, text_delete.length -\n commonlength);\n }\n }\n // Delete the offending records and add the merged ones.\n if (count_delete === 0) {\n diffs.splice(pointer - count_insert,\n count_delete + count_insert, [DiffOp.Insert, text_insert]);\n } else if (count_insert === 0) {\n diffs.splice(pointer - count_delete,\n count_delete + count_insert, [DiffOp.Delete, text_delete]);\n } else {\n diffs.splice(pointer - count_delete - count_insert,\n count_delete + count_insert, [DiffOp.Delete, text_delete],\n [DiffOp.Insert, text_insert]);\n }\n pointer = pointer - count_delete - count_insert +\n (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;\n } else if (pointer !== 0 && diffs[pointer - 1][0] == DiffOp.Equal) {\n // Merge this equality with the previous one.\n diffs[pointer - 1][1] += diffs[pointer][1];\n diffs.splice(pointer, 1);\n } else {\n pointer++;\n }\n count_insert = 0;\n count_delete = 0;\n text_delete = '';\n text_insert = '';\n break;\n }\n }\n if (diffs[diffs.length - 1][1] === '') {\n diffs.pop(); // Remove the dummy entry at the end.\n }\n\n // Second pass: look for single edits surrounded on both sides by equalities\n // which can be shifted sideways to eliminate an equality.\n // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC\n let changes = false;\n pointer = 1;\n // Intentionally ignore the first and last element (don't need checking).\n while (pointer < diffs.length - 1) {\n if (diffs[pointer - 1][0] == DiffOp.Equal &&\n diffs[pointer + 1][0] == DiffOp.Equal) {\n // This is a single edit surrounded by equalities.\n if (diffs[pointer][1].substring(diffs[pointer][1].length -\n diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) {\n // Shift the edit over the previous equality.\n diffs[pointer][1] = diffs[pointer - 1][1] +\n diffs[pointer][1].substring(0, diffs[pointer][1].length -\n diffs[pointer - 1][1].length);\n diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];\n diffs.splice(pointer - 1, 1);\n changes = true;\n } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==\n diffs[pointer + 1][1]) {\n // Shift the edit over the next equality.\n diffs[pointer - 1][1] += diffs[pointer + 1][1];\n diffs[pointer][1] =\n diffs[pointer][1].substring(diffs[pointer + 1][1].length) +\n diffs[pointer + 1][1];\n diffs.splice(pointer + 1, 1);\n changes = true;\n }\n }\n pointer++;\n }\n // If shifts were made, the diff needs reordering and another shift sweep.\n if (changes) {\n this.diff_cleanupMerge(diffs);\n }\n };\n\n\n /**\n * loc is a location in text1, compute and return the equivalent location in\n * text2.\n * e.g. 'The cat' vs 'The big cat', 1->1, 5->8\n * @param diffs Array of diff tuples.\n * @param loc Location within text1.\n * @return Location within text2.\n */\n diff_xIndex (diffs: Array<Diff>, loc: number): number {\n let chars1 = 0;\n let chars2 = 0;\n let last_chars1 = 0;\n let last_chars2 = 0;\n let x;\n for (x = 0; x < diffs.length; x++) {\n if (diffs[x][0] !== DiffOp.Insert) { // Equality or deletion.\n chars1 += diffs[x][1].length;\n }\n if (diffs[x][0] !== DiffOp.Delete) { // Equality or insertion.\n chars2 += diffs[x][1].length;\n }\n if (chars1 > loc) { // Overshot the location.\n break;\n }\n last_chars1 = chars1;\n last_chars2 = chars2;\n }\n // Was the location was deleted?\n if (diffs.length != x && diffs[x][0] === DiffOp.Delete) {\n return last_chars2;\n }\n // Add the remaining character length.\n return last_chars2 + (loc - last_chars1);\n };\n\n\n /**\n * Convert a diff array into a pretty HTML report.\n * @param diffs Array of diff tuples.\n * @return HTML representation.\n */\n diff_prettyHtml = function(diffs: Array<Diff>): string {\n const html = [];\n const pattern_amp = /&/g;\n const pattern_lt = /</g;\n const pattern_gt = />/g;\n const pattern_para = /\\n/g;\n for (let x = 0; x < diffs.length; x++) {\n const op = diffs[x][0]; // Operation (insert, delete, equal)\n const data = diffs[x][1]; // Text of change.\n const text = data.replace(pattern_amp, '&').replace(pattern_lt, '<')\n .replace(pattern_gt, '>').replace(pattern_para, '¶<br>');\n switch (op) {\n case DiffOp.Insert:\n html[x] = '<ins style=\"background:#e6ffe6;\">' + text + '</ins>';\n break;\n case DiffOp.Delete:\n html[x] = '<del style=\"background:#ffe6e6;\">' + text + '</del>';\n break;\n case DiffOp.Equal:\n html[x] = '<span>' + text + '</span>';\n break;\n }\n }\n return html.join('');\n };\n\n\n /**\n * Compute and return the source text (all equalities and deletions).\n * @param diffs Array of diff tuples.\n * @return Source text.\n */\n diff_text1 (diffs: Array<Diff>): string {\n const text = [];\n for (let x = 0; x < diffs.length; x++) {\n if (diffs[x][0] !== DiffOp.Insert) {\n text[x] = diffs[x][1];\n }\n }\n return text.join('');\n };\n\n\n /**\n * Compute and return the destination text (all equalities and insertions).\n * @param diffs Array of diff tuples.\n * @return Destination text.\n */\n diff_text2 (diffs: Array<Diff>): string {\n const text = [];\n for (let x = 0; x < diffs.length; x++) {\n if (diffs[x][0] !== DiffOp.Delete) {\n text[x] = diffs[x][1];\n }\n }\n return text.join('');\n };\n\n\n /**\n * Compute the Levenshtein distance; the number of inserted, deleted or\n * substituted characters.\n * @param diffs Array of diff tuples.\n * @return Number of changes.\n */\n diff_levenshtein (diffs: Array<Diff>): number {\n let levenshtein = 0;\n let insertions = 0;\n let deletions = 0;\n for (let x = 0; x < diffs.length; x++) {\n const op = diffs[x][0];\n const data = diffs[x][1];\n switch (op) {\n case DiffOp.Insert:\n insertions += data.length;\n break;\n case DiffOp.Delete:\n deletions += data.length;\n break;\n case DiffOp.Equal:\n // A deletion and an insertion is one substitution.\n levenshtein += Math.max(insertions, deletions);\n insertions = 0;\n deletions = 0;\n break;\n }\n }\n levenshtein += Math.max(insertions, deletions);\n return levenshtein;\n };\n\n\n /**\n * Crush the diff into an encoded string which describes the operations\n * required to transform text1 into text2.\n * E.g. =3\\t-2\\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.\n * Operations are tab-separated. Inserted text is escaped using %xx notation.\n * @param diffs Array of diff tuples.\n * @return Delta text.\n */\n diff_toDelta (diffs: Array<Diff>): string {\n const text = [];\n for (let x = 0; x < diffs.length; x++) {\n switch (diffs[x][0]) {\n case DiffOp.Insert:\n text[x] = '+' + encodeURI(diffs[x][1]);\n break;\n case DiffOp.Delete:\n text[x] = '-' + diffs[x][1].length;\n break;\n case DiffOp.Equal:\n text[x] = '=' + diffs[x][1].length;\n break;\n }\n }\n return text.join('\\t').replace(/%20/g, ' ');\n };\n\n\n /**\n * Given the original text1, and an encoded string which describes the\n * operations required to transform text1 into text2, compute the full diff.\n * @param text1 Source string for the diff.\n * @param delta Delta text.\n * @return Array of diff tuples.\n * @throws {!Error} If invalid input.\n */\n diff_fromDelta (text1: string, delta: string) {\n const diffs = [];\n let diffsLength = 0; // Keeping our own length const is faster in JS.\n let pointer = 0; // Cursor in text1\n const tokens = delta.split(/\\t/g);\n for (let x = 0; x