| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * This is part of jsdifflib v1.0. <http://snowtide.com/jsdifflib> | |
| 3 * | |
| 4 * Copyright (c) 2007, Snowtide Informatics Systems, Inc. | |
| 5 * All rights reserved. | |
| 6 * | |
| 7 * Redistribution and use in source and binary forms, with or without modificati
on, | |
| 8 * are permitted provided that the following conditions are met: | |
| 9 * | |
| 10 * * Redistributions of source code must retain the above copyright notice, t
his | |
| 11 * list of conditions and the following disclaimer. | |
| 12 * * Redistributions in binary form must reproduce the above copyright notice
, | |
| 13 * this list of conditions and the following disclaimer in the documentation | |
| 14 * and/or other materials provided with the distribution. | |
| 15 * * Neither the name of the Snowtide Informatics Systems nor the names of it
s | |
| 16 * contributors may be used to endorse or promote products derived from this | |
| 17 * software without specific prior written permission. | |
| 18 * | |
| 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" A
ND ANY | |
| 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAR
RANTIES | |
| 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT | |
| 22 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED | |
| 24 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFIT
S; OR | |
| 25 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN | |
| 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISI
NG IN | |
| 27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY O
F SUCH | |
| 28 * DAMAGE. | |
| 29 **/ | |
| 30 /* Author: Chas Emerick <cemerick@snowtide.com> */ | |
| 31 /* taken from https://github.com/cemerick/jsdifflib */ | |
| 32 | |
| 33 __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true}; | |
| 34 | |
| 35 difflib = { | |
| 36 defaultJunkFunction: function (c) { | |
| 37 return __whitespace.hasOwnProperty(c); | |
| 38 }, | |
| 39 | |
| 40 stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, "
"); }, | |
| 41 | |
| 42 stringAsLines: function (str) { | |
| 43 var lfpos = str.indexOf("\n"); | |
| 44 var crpos = str.indexOf("\r"); | |
| 45 var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r"; | |
| 46 | |
| 47 var lines = str.split(linebreak); | |
| 48 for (var i = 0; i < lines.length; i++) { | |
| 49 lines[i] = difflib.stripLinebreaks(lines[i]); | |
| 50 } | |
| 51 | |
| 52 return lines; | |
| 53 }, | |
| 54 | |
| 55 // iteration-based reduce implementation | |
| 56 __reduce: function (func, list, initial) { | |
| 57 if (initial != null) { | |
| 58 var value = initial; | |
| 59 var idx = 0; | |
| 60 } else if (list) { | |
| 61 var value = list[0]; | |
| 62 var idx = 1; | |
| 63 } else { | |
| 64 return null; | |
| 65 } | |
| 66 | |
| 67 for (; idx < list.length; idx++) { | |
| 68 value = func(value, list[idx]); | |
| 69 } | |
| 70 | |
| 71 return value; | |
| 72 }, | |
| 73 | |
| 74 // comparison function for sorting lists of numeric tuples | |
| 75 __ntuplecomp: function (a, b) { | |
| 76 var mlen = Math.max(a.length, b.length); | |
| 77 for (var i = 0; i < mlen; i++) { | |
| 78 if (a[i] < b[i]) return -1; | |
| 79 if (a[i] > b[i]) return 1; | |
| 80 } | |
| 81 | |
| 82 return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1); | |
| 83 }, | |
| 84 | |
| 85 __calculate_ratio: function (matches, length) { | |
| 86 return length ? 2.0 * matches / length : 1.0; | |
| 87 }, | |
| 88 | |
| 89 // returns a function that returns true if a key passed to the returned func
tion | |
| 90 // is in the dict (js object) provided to this function; replaces being able
to | |
| 91 // carry around dict.has_key in python... | |
| 92 __isindict: function (dict) { | |
| 93 return function (key) { return dict.hasOwnProperty(key); }; | |
| 94 }, | |
| 95 | |
| 96 // replacement for python's dict.get function -- need easy default values | |
| 97 __dictget: function (dict, key, defaultValue) { | |
| 98 return dict.hasOwnProperty(key) ? dict[key] : defaultValue; | |
| 99 }, | |
| 100 | |
| 101 SequenceMatcher: function (a, b, isjunk) { | |
| 102 this.set_seqs = function (a, b) { | |
| 103 this.set_seq1(a); | |
| 104 this.set_seq2(b); | |
| 105 } | |
| 106 | |
| 107 this.set_seq1 = function (a) { | |
| 108 if (a == this.a) return; | |
| 109 this.a = a; | |
| 110 this.matching_blocks = this.opcodes = null; | |
| 111 } | |
| 112 | |
| 113 this.set_seq2 = function (b) { | |
| 114 if (b == this.b) return; | |
| 115 this.b = b; | |
| 116 this.matching_blocks = this.opcodes = this.fullbcount = null; | |
| 117 this.__chain_b(); | |
| 118 } | |
| 119 | |
| 120 this.__chain_b = function () { | |
| 121 var b = this.b; | |
| 122 var n = b.length; | |
| 123 var b2j = this.b2j = {}; | |
| 124 var populardict = {}; | |
| 125 for (var i = 0; i < b.length; i++) { | |
| 126 var elt = b[i]; | |
| 127 if (b2j.hasOwnProperty(elt)) { | |
| 128 var indices = b2j[elt]; | |
| 129 if (n >= 200 && indices.length * 100 > n) { | |
| 130 populardict[elt] = 1; | |
| 131 delete b2j[elt]; | |
| 132 } else { | |
| 133 indices.push(i); | |
| 134 } | |
| 135 } else { | |
| 136 b2j[elt] = [i]; | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 for (var elt in populardict) { | |
| 141 if (populardict.hasOwnProperty(elt)) { | |
| 142 delete b2j[elt]; | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 var isjunk = this.isjunk; | |
| 147 var junkdict = {}; | |
| 148 if (isjunk) { | |
| 149 for (var elt in populardict) { | |
| 150 if (populardict.hasOwnProperty(elt) && isjunk(elt)) { | |
| 151 junkdict[elt] = 1; | |
| 152 delete populardict[elt]; | |
| 153 } | |
| 154 } | |
| 155 for (var elt in b2j) { | |
| 156 if (b2j.hasOwnProperty(elt) && isjunk(elt)) { | |
| 157 junkdict[elt] = 1; | |
| 158 delete b2j[elt]; | |
| 159 } | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 this.isbjunk = difflib.__isindict(junkdict); | |
| 164 this.isbpopular = difflib.__isindict(populardict); | |
| 165 } | |
| 166 | |
| 167 this.find_longest_match = function (alo, ahi, blo, bhi) { | |
| 168 var a = this.a; | |
| 169 var b = this.b; | |
| 170 var b2j = this.b2j; | |
| 171 var isbjunk = this.isbjunk; | |
| 172 var besti = alo; | |
| 173 var bestj = blo; | |
| 174 var bestsize = 0; | |
| 175 var j = null; | |
| 176 | |
| 177 var j2len = {}; | |
| 178 var nothing = []; | |
| 179 for (var i = alo; i < ahi; i++) { | |
| 180 var newj2len = {}; | |
| 181 var jdict = difflib.__dictget(b2j, a[i], nothing); | |
| 182 for (var jkey in jdict) { | |
| 183 if (jdict.hasOwnProperty(jkey)) { | |
| 184 j = jdict[jkey]; | |
| 185 if (j < blo) continue; | |
| 186 if (j >= bhi) break; | |
| 187 newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1
; | |
| 188 if (k > bestsize) { | |
| 189 besti = i - k + 1; | |
| 190 bestj = j - k + 1; | |
| 191 bestsize = k; | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 j2len = newj2len; | |
| 196 } | |
| 197 | |
| 198 while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[bes
ti - 1] == b[bestj - 1]) { | |
| 199 besti--; | |
| 200 bestj--; | |
| 201 bestsize++; | |
| 202 } | |
| 203 | |
| 204 while (besti + bestsize < ahi && bestj + bestsize < bhi && | |
| 205 !isbjunk(b[bestj + bestsize]) && | |
| 206 a[besti + bestsize] == b[bestj + bestsize]) { | |
| 207 bestsize++; | |
| 208 } | |
| 209 | |
| 210 while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[best
i - 1] == b[bestj - 1]) { | |
| 211 besti--; | |
| 212 bestj--; | |
| 213 bestsize++; | |
| 214 } | |
| 215 | |
| 216 while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b
[bestj + bestsize]) && | |
| 217 a[besti + bestsize] == b[bestj + bestsize]) { | |
| 218 bestsize++; | |
| 219 } | |
| 220 | |
| 221 return [besti, bestj, bestsize]; | |
| 222 } | |
| 223 | |
| 224 this.get_matching_blocks = function () { | |
| 225 if (this.matching_blocks != null) return this.matching_blocks; | |
| 226 var la = this.a.length; | |
| 227 var lb = this.b.length; | |
| 228 | |
| 229 var queue = [[0, la, 0, lb]]; | |
| 230 var matching_blocks = []; | |
| 231 var alo, ahi, blo, bhi, qi, i, j, k, x; | |
| 232 while (queue.length) { | |
| 233 qi = queue.pop(); | |
| 234 alo = qi[0]; | |
| 235 ahi = qi[1]; | |
| 236 blo = qi[2]; | |
| 237 bhi = qi[3]; | |
| 238 x = this.find_longest_match(alo, ahi, blo, bhi); | |
| 239 i = x[0]; | |
| 240 j = x[1]; | |
| 241 k = x[2]; | |
| 242 | |
| 243 if (k) { | |
| 244 matching_blocks.push(x); | |
| 245 if (alo < i && blo < j) | |
| 246 queue.push([alo, i, blo, j]); | |
| 247 if (i+k < ahi && j+k < bhi) | |
| 248 queue.push([i + k, ahi, j + k, bhi]); | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 matching_blocks.sort(difflib.__ntuplecomp); | |
| 253 | |
| 254 var i1 = j1 = k1 = block = 0; | |
| 255 var non_adjacent = []; | |
| 256 for (var idx in matching_blocks) { | |
| 257 if (matching_blocks.hasOwnProperty(idx)) { | |
| 258 block = matching_blocks[idx]; | |
| 259 i2 = block[0]; | |
| 260 j2 = block[1]; | |
| 261 k2 = block[2]; | |
| 262 if (i1 + k1 == i2 && j1 + k1 == j2) { | |
| 263 k1 += k2; | |
| 264 } else { | |
| 265 if (k1) non_adjacent.push([i1, j1, k1]); | |
| 266 i1 = i2; | |
| 267 j1 = j2; | |
| 268 k1 = k2; | |
| 269 } | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 if (k1) non_adjacent.push([i1, j1, k1]); | |
| 274 | |
| 275 non_adjacent.push([la, lb, 0]); | |
| 276 this.matching_blocks = non_adjacent; | |
| 277 return this.matching_blocks; | |
| 278 } | |
| 279 | |
| 280 this.get_opcodes = function () { | |
| 281 if (this.opcodes != null) return this.opcodes; | |
| 282 var i = 0; | |
| 283 var j = 0; | |
| 284 var answer = []; | |
| 285 this.opcodes = answer; | |
| 286 var block, ai, bj, size, tag; | |
| 287 var blocks = this.get_matching_blocks(); | |
| 288 for (var idx in blocks) { | |
| 289 if (blocks.hasOwnProperty(idx)) { | |
| 290 block = blocks[idx]; | |
| 291 ai = block[0]; | |
| 292 bj = block[1]; | |
| 293 size = block[2]; | |
| 294 tag = ''; | |
| 295 if (i < ai && j < bj) { | |
| 296 tag = 'replace'; | |
| 297 } else if (i < ai) { | |
| 298 tag = 'delete'; | |
| 299 } else if (j < bj) { | |
| 300 tag = 'insert'; | |
| 301 } | |
| 302 if (tag) answer.push([tag, i, ai, j, bj]); | |
| 303 i = ai + size; | |
| 304 j = bj + size; | |
| 305 | |
| 306 if (size) answer.push(['equal', ai, i, bj, j]); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 return answer; | |
| 311 } | |
| 312 | |
| 313 // this is a generator function in the python lib, which of course is no
t supported in javascript | |
| 314 // the reimplementation builds up the grouped opcodes into a list in the
ir entirety and returns that. | |
| 315 this.get_grouped_opcodes = function (n) { | |
| 316 if (!n) n = 3; | |
| 317 var codes = this.get_opcodes(); | |
| 318 if (!codes) codes = [["equal", 0, 1, 0, 1]]; | |
| 319 var code, tag, i1, i2, j1, j2; | |
| 320 if (codes[0][0] == 'equal') { | |
| 321 code = codes[0]; | |
| 322 tag = code[0]; | |
| 323 i1 = code[1]; | |
| 324 i2 = code[2]; | |
| 325 j1 = code[3]; | |
| 326 j2 = code[4]; | |
| 327 codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n),
j2]; | |
| 328 } | |
| 329 if (codes[codes.length - 1][0] == 'equal') { | |
| 330 code = codes[codes.length - 1]; | |
| 331 tag = code[0]; | |
| 332 i1 = code[1]; | |
| 333 i2 = code[2]; | |
| 334 j1 = code[3]; | |
| 335 j2 = code[4]; | |
| 336 codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Ma
th.min(j2, j1 + n)]; | |
| 337 } | |
| 338 | |
| 339 var nn = n + n; | |
| 340 var groups = []; | |
| 341 for (var idx in codes) { | |
| 342 if (codes.hasOwnProperty(idx)) { | |
| 343 code = codes[idx]; | |
| 344 tag = code[0]; | |
| 345 i1 = code[1]; | |
| 346 i2 = code[2]; | |
| 347 j1 = code[3]; | |
| 348 j2 = code[4]; | |
| 349 if (tag == 'equal' && i2 - i1 > nn) { | |
| 350 groups.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min
(j2, j1 + n)]); | |
| 351 i1 = Math.max(i1, i2-n); | |
| 352 j1 = Math.max(j1, j2-n); | |
| 353 } | |
| 354 | |
| 355 groups.push([tag, i1, i2, j1, j2]); | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 if (groups && groups[groups.length - 1][0] == 'equal') groups.pop(); | |
| 360 | |
| 361 return groups; | |
| 362 } | |
| 363 | |
| 364 this.ratio = function () { | |
| 365 matches = difflib.__reduce( | |
| 366 function (sum, triple) { return sum + triple[triple.
length - 1]; }, | |
| 367 this.get_matching_blocks(), 0); | |
| 368 return difflib.__calculate_ratio(matches, this.a.length + this.b.len
gth); | |
| 369 } | |
| 370 | |
| 371 this.quick_ratio = function () { | |
| 372 var fullbcount, elt; | |
| 373 if (this.fullbcount == null) { | |
| 374 this.fullbcount = fullbcount = {}; | |
| 375 for (var i = 0; i < this.b.length; i++) { | |
| 376 elt = this.b[i]; | |
| 377 fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1; | |
| 378 } | |
| 379 } | |
| 380 fullbcount = this.fullbcount; | |
| 381 | |
| 382 var avail = {}; | |
| 383 var availhas = difflib.__isindict(avail); | |
| 384 var matches = numb = 0; | |
| 385 for (var i = 0; i < this.a.length; i++) { | |
| 386 elt = this.a[i]; | |
| 387 if (availhas(elt)) { | |
| 388 numb = avail[elt]; | |
| 389 } else { | |
| 390 numb = difflib.__dictget(fullbcount, elt, 0); | |
| 391 } | |
| 392 avail[elt] = numb - 1; | |
| 393 if (numb > 0) matches++; | |
| 394 } | |
| 395 | |
| 396 return difflib.__calculate_ratio(matches, this.a.length + this.b.len
gth); | |
| 397 } | |
| 398 | |
| 399 this.real_quick_ratio = function () { | |
| 400 var la = this.a.length; | |
| 401 var lb = this.b.length; | |
| 402 return _calculate_ratio(Math.min(la, lb), la + lb); | |
| 403 } | |
| 404 | |
| 405 this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction; | |
| 406 this.a = this.b = null; | |
| 407 this.set_seqs(a, b); | |
| 408 } | |
| 409 } | |
| OLD | NEW |