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@snowtide.com> */
31/* taken from https://github.com/cemerick/jsdifflib */
32
33__whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true};
34
35difflib = {
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