Home | History | Annotate | Download | only in front_end
      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 modification,
      8  * are permitted provided that the following conditions are met:
      9  *
     10  *    * Redistributions of source code must retain the above copyright notice, this
     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 its
     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" AND ANY
     20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     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 PROFITS; OR
     25  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
     27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
     28  * DAMAGE.
     29  **/
     30 /* Author: Chas Emerick <cemerick (at) 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 function
     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[besti - 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[besti - 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 not supported in javascript
    314         // the reimplementation builds up the grouped opcodes into a list in their 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, Math.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.length);
    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.length);
    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 }
    410