差分表示にjsdifflibを使うようにしてみた。
@@ -0,0 +1,396 @@ | ||
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 | +__whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true}; | |
32 | + | |
33 | +difflib = { | |
34 | + defaultJunkFunction: function (c) { | |
35 | + return c in __whitespace; | |
36 | + }, | |
37 | + | |
38 | + stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); }, | |
39 | + | |
40 | + stringAsLines: function (str) { | |
41 | + var lfpos = str.indexOf("\n"); | |
42 | + var crpos = str.indexOf("\r"); | |
43 | + var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r"; | |
44 | + | |
45 | + var lines = str.split(linebreak); | |
46 | + for (var i = 0; i < lines.length; i++) { | |
47 | + lines[i] = difflib.stripLinebreaks(lines[i]); | |
48 | + } | |
49 | + | |
50 | + return lines; | |
51 | + }, | |
52 | + | |
53 | + // iteration-based reduce implementation | |
54 | + __reduce: function (func, list, initial) { | |
55 | + if (initial != null) { | |
56 | + var value = initial; | |
57 | + var idx = 0; | |
58 | + } else if (list) { | |
59 | + var value = list[0]; | |
60 | + var idx = 1; | |
61 | + } else { | |
62 | + return null; | |
63 | + } | |
64 | + | |
65 | + for (; idx < list.length; idx++) { | |
66 | + value = func(value, list[idx]); | |
67 | + } | |
68 | + | |
69 | + return value; | |
70 | + }, | |
71 | + | |
72 | + // comparison function for sorting lists of numeric tuples | |
73 | + __ntuplecomp: function (a, b) { | |
74 | + var mlen = Math.max(a.length, b.length); | |
75 | + for (var i = 0; i < mlen; i++) { | |
76 | + if (a[i] < b[i]) return -1; | |
77 | + if (a[i] > b[i]) return 1; | |
78 | + } | |
79 | + | |
80 | + return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1); | |
81 | + }, | |
82 | + | |
83 | + __calculate_ratio: function (matches, length) { | |
84 | + return length ? 2.0 * matches / length : 1.0; | |
85 | + }, | |
86 | + | |
87 | + // returns a function that returns true if a key passed to the returned function | |
88 | + // is in the dict (js object) provided to this function; replaces being able to | |
89 | + // carry around dict.has_key in python... | |
90 | + __isindict: function (dict) { | |
91 | + return function (key) { return key in dict; }; | |
92 | + }, | |
93 | + | |
94 | + // replacement for python's dict.get function -- need easy default values | |
95 | + __dictget: function (dict, key, defaultValue) { | |
96 | + return key in dict ? dict[key] : defaultValue; | |
97 | + }, | |
98 | + | |
99 | + SequenceMatcher: function (a, b, isjunk) { | |
100 | + this.set_seqs = function (a, b) { | |
101 | + this.set_seq1(a); | |
102 | + this.set_seq2(b); | |
103 | + } | |
104 | + | |
105 | + this.set_seq1 = function (a) { | |
106 | + if (a == this.a) return; | |
107 | + this.a = a; | |
108 | + this.matching_blocks = this.opcodes = null; | |
109 | + } | |
110 | + | |
111 | + this.set_seq2 = function (b) { | |
112 | + if (b == this.b) return; | |
113 | + this.b = b; | |
114 | + this.matching_blocks = this.opcodes = this.fullbcount = null; | |
115 | + this.__chain_b(); | |
116 | + } | |
117 | + | |
118 | + this.__chain_b = function () { | |
119 | + var b = this.b; | |
120 | + var n = b.length; | |
121 | + var b2j = this.b2j = {}; | |
122 | + var populardict = {}; | |
123 | + for (var i = 0; i < b.length; i++) { | |
124 | + var elt = b[i]; | |
125 | + if (elt in b2j) { | |
126 | + var indices = b2j[elt]; | |
127 | + if (n >= 200 && indices.length * 100 > n) { | |
128 | + populardict[elt] = 1; | |
129 | + delete b2j[elt]; | |
130 | + } else { | |
131 | + indices.push(i); | |
132 | + } | |
133 | + } else { | |
134 | + b2j[elt] = [i]; | |
135 | + } | |
136 | + } | |
137 | + | |
138 | + for (var elt in populardict) | |
139 | + delete b2j[elt]; | |
140 | + | |
141 | + var isjunk = this.isjunk; | |
142 | + var junkdict = {}; | |
143 | + if (isjunk) { | |
144 | + for (var elt in populardict) { | |
145 | + if (isjunk(elt)) { | |
146 | + junkdict[elt] = 1; | |
147 | + delete populardict[elt]; | |
148 | + } | |
149 | + } | |
150 | + for (var elt in b2j) { | |
151 | + if (isjunk(elt)) { | |
152 | + junkdict[elt] = 1; | |
153 | + delete b2j[elt]; | |
154 | + } | |
155 | + } | |
156 | + } | |
157 | + | |
158 | + this.isbjunk = difflib.__isindict(junkdict); | |
159 | + this.isbpopular = difflib.__isindict(populardict); | |
160 | + } | |
161 | + | |
162 | + this.find_longest_match = function (alo, ahi, blo, bhi) { | |
163 | + var a = this.a; | |
164 | + var b = this.b; | |
165 | + var b2j = this.b2j; | |
166 | + var isbjunk = this.isbjunk; | |
167 | + var besti = alo; | |
168 | + var bestj = blo; | |
169 | + var bestsize = 0; | |
170 | + var j = null; | |
171 | + | |
172 | + var j2len = {}; | |
173 | + var nothing = []; | |
174 | + for (var i = alo; i < ahi; i++) { | |
175 | + var newj2len = {}; | |
176 | + var jdict = difflib.__dictget(b2j, a[i], nothing); | |
177 | + for (var jkey in jdict) { | |
178 | + j = jdict[jkey]; | |
179 | + if (j < blo) continue; | |
180 | + if (j >= bhi) break; | |
181 | + newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1; | |
182 | + if (k > bestsize) { | |
183 | + besti = i - k + 1; | |
184 | + bestj = j - k + 1; | |
185 | + bestsize = k; | |
186 | + } | |
187 | + } | |
188 | + j2len = newj2len; | |
189 | + } | |
190 | + | |
191 | + while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { | |
192 | + besti--; | |
193 | + bestj--; | |
194 | + bestsize++; | |
195 | + } | |
196 | + | |
197 | + while (besti + bestsize < ahi && bestj + bestsize < bhi && | |
198 | + !isbjunk(b[bestj + bestsize]) && | |
199 | + a[besti + bestsize] == b[bestj + bestsize]) { | |
200 | + bestsize++; | |
201 | + } | |
202 | + | |
203 | + while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { | |
204 | + besti--; | |
205 | + bestj--; | |
206 | + bestsize++; | |
207 | + } | |
208 | + | |
209 | + while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) && | |
210 | + a[besti + bestsize] == b[bestj + bestsize]) { | |
211 | + bestsize++; | |
212 | + } | |
213 | + | |
214 | + return [besti, bestj, bestsize]; | |
215 | + } | |
216 | + | |
217 | + this.get_matching_blocks = function () { | |
218 | + if (this.matching_blocks != null) return this.matching_blocks; | |
219 | + var la = this.a.length; | |
220 | + var lb = this.b.length; | |
221 | + | |
222 | + var queue = [[0, la, 0, lb]]; | |
223 | + var matching_blocks = []; | |
224 | + var alo, ahi, blo, bhi, qi, i, j, k, x; | |
225 | + while (queue.length) { | |
226 | + qi = queue.pop(); | |
227 | + alo = qi[0]; | |
228 | + ahi = qi[1]; | |
229 | + blo = qi[2]; | |
230 | + bhi = qi[3]; | |
231 | + x = this.find_longest_match(alo, ahi, blo, bhi); | |
232 | + i = x[0]; | |
233 | + j = x[1]; | |
234 | + k = x[2]; | |
235 | + | |
236 | + if (k) { | |
237 | + matching_blocks.push(x); | |
238 | + if (alo < i && blo < j) | |
239 | + queue.push([alo, i, blo, j]); | |
240 | + if (i+k < ahi && j+k < bhi) | |
241 | + queue.push([i + k, ahi, j + k, bhi]); | |
242 | + } | |
243 | + } | |
244 | + | |
245 | + matching_blocks.sort(difflib.__ntuplecomp); | |
246 | + | |
247 | + var i1 = j1 = k1 = block = 0; | |
248 | + var non_adjacent = []; | |
249 | + for (var idx in matching_blocks) { | |
250 | + block = matching_blocks[idx]; | |
251 | + i2 = block[0]; | |
252 | + j2 = block[1]; | |
253 | + k2 = block[2]; | |
254 | + if (i1 + k1 == i2 && j1 + k1 == j2) { | |
255 | + k1 += k2; | |
256 | + } else { | |
257 | + if (k1) non_adjacent.push([i1, j1, k1]); | |
258 | + i1 = i2; | |
259 | + j1 = j2; | |
260 | + k1 = k2; | |
261 | + } | |
262 | + } | |
263 | + | |
264 | + if (k1) non_adjacent.push([i1, j1, k1]); | |
265 | + | |
266 | + non_adjacent.push([la, lb, 0]); | |
267 | + this.matching_blocks = non_adjacent; | |
268 | + return this.matching_blocks; | |
269 | + } | |
270 | + | |
271 | + this.get_opcodes = function () { | |
272 | + if (this.opcodes != null) return this.opcodes; | |
273 | + var i = 0; | |
274 | + var j = 0; | |
275 | + var answer = []; | |
276 | + this.opcodes = answer; | |
277 | + var block, ai, bj, size, tag; | |
278 | + var blocks = this.get_matching_blocks(); | |
279 | + for (var idx in blocks) { | |
280 | + block = blocks[idx]; | |
281 | + ai = block[0]; | |
282 | + bj = block[1]; | |
283 | + size = block[2]; | |
284 | + tag = ''; | |
285 | + if (i < ai && j < bj) { | |
286 | + tag = 'replace'; | |
287 | + } else if (i < ai) { | |
288 | + tag = 'delete'; | |
289 | + } else if (j < bj) { | |
290 | + tag = 'insert'; | |
291 | + } | |
292 | + if (tag) answer.push([tag, i, ai, j, bj]); | |
293 | + i = ai + size; | |
294 | + j = bj + size; | |
295 | + | |
296 | + if (size) answer.push(['equal', ai, i, bj, j]); | |
297 | + } | |
298 | + | |
299 | + return answer; | |
300 | + } | |
301 | + | |
302 | + // this is a generator function in the python lib, which of course is not supported in javascript | |
303 | + // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that. | |
304 | + this.get_grouped_opcodes = function (n) { | |
305 | + if (!n) n = 3; | |
306 | + var codes = this.get_opcodes(); | |
307 | + if (!codes) codes = [["equal", 0, 1, 0, 1]]; | |
308 | + var code, tag, i1, i2, j1, j2; | |
309 | + if (codes[0][0] == 'equal') { | |
310 | + code = codes[0]; | |
311 | + tag = code[0]; | |
312 | + i1 = code[1]; | |
313 | + i2 = code[2]; | |
314 | + j1 = code[3]; | |
315 | + j2 = code[4]; | |
316 | + codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2]; | |
317 | + } | |
318 | + if (codes[codes.length - 1][0] == 'equal') { | |
319 | + code = codes[codes.length - 1]; | |
320 | + tag = code[0]; | |
321 | + i1 = code[1]; | |
322 | + i2 = code[2]; | |
323 | + j1 = code[3]; | |
324 | + j2 = code[4]; | |
325 | + codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]; | |
326 | + } | |
327 | + | |
328 | + var nn = n + n; | |
329 | + var groups = []; | |
330 | + for (var idx in codes) { | |
331 | + code = codes[idx]; | |
332 | + tag = code[0]; | |
333 | + i1 = code[1]; | |
334 | + i2 = code[2]; | |
335 | + j1 = code[3]; | |
336 | + j2 = code[4]; | |
337 | + if (tag == 'equal' && i2 - i1 > nn) { | |
338 | + groups.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]); | |
339 | + i1 = Math.max(i1, i2-n); | |
340 | + j1 = Math.max(j1, j2-n); | |
341 | + } | |
342 | + | |
343 | + groups.push([tag, i1, i2, j1, j2]); | |
344 | + } | |
345 | + | |
346 | + if (groups && groups[groups.length - 1][0] == 'equal') groups.pop(); | |
347 | + | |
348 | + return groups; | |
349 | + } | |
350 | + | |
351 | + this.ratio = function () { | |
352 | + matches = difflib.__reduce( | |
353 | + function (sum, triple) { return sum + triple[triple.length - 1]; }, | |
354 | + this.get_matching_blocks(), 0); | |
355 | + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); | |
356 | + } | |
357 | + | |
358 | + this.quick_ratio = function () { | |
359 | + var fullbcount, elt; | |
360 | + if (this.fullbcount == null) { | |
361 | + this.fullbcount = fullbcount = {}; | |
362 | + for (var i = 0; i < this.b.length; i++) { | |
363 | + elt = this.b[i]; | |
364 | + fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1; | |
365 | + } | |
366 | + } | |
367 | + fullbcount = this.fullbcount; | |
368 | + | |
369 | + var avail = {}; | |
370 | + var availhas = difflib.__isindict(avail); | |
371 | + var matches = numb = 0; | |
372 | + for (var i = 0; i < this.a.length; i++) { | |
373 | + elt = this.a[i]; | |
374 | + if (availhas(elt)) { | |
375 | + numb = avail[elt]; | |
376 | + } else { | |
377 | + numb = difflib.__dictget(fullbcount, elt, 0); | |
378 | + } | |
379 | + avail[elt] = numb - 1; | |
380 | + if (numb > 0) matches++; | |
381 | + } | |
382 | + | |
383 | + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); | |
384 | + } | |
385 | + | |
386 | + this.real_quick_ratio = function () { | |
387 | + var la = this.a.length; | |
388 | + var lb = this.b.length; | |
389 | + return _calculate_ratio(Math.min(la, lb), la + lb); | |
390 | + } | |
391 | + | |
392 | + this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction; | |
393 | + this.a = this.b = null; | |
394 | + this.set_seqs(a, b); | |
395 | + } | |
396 | +} |
@@ -0,0 +1,83 @@ | ||
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 | +table.diff { | |
32 | + border-collapse:collapse; | |
33 | + border:1px solid darkgray | |
34 | +} | |
35 | +table.diff tbody { | |
36 | + font-family:Courier, monospace | |
37 | +} | |
38 | +table.diff tbody th { | |
39 | + font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif; | |
40 | + background:#EED; | |
41 | + font-size:11px; | |
42 | + font-weight:normal; | |
43 | + border:1px solid #BBC; | |
44 | + color:#886; | |
45 | + padding:.3em .5em .1em 2em; | |
46 | + text-align:right; | |
47 | + vertical-align:top | |
48 | +} | |
49 | +table.diff thead { | |
50 | + border-bottom:1px solid #BBC; | |
51 | + background:#EFEFEF; | |
52 | + font-family:Verdana | |
53 | +} | |
54 | +table.diff thead th.texttitle { | |
55 | + text-align:left | |
56 | +} | |
57 | +table.diff tbody td { | |
58 | + padding:0px .4em; | |
59 | + padding-top:.4em; | |
60 | + vertical-align:top; | |
61 | +} | |
62 | +table.diff .empty { | |
63 | + background-color:#DDD; | |
64 | +} | |
65 | +table.diff .replace { | |
66 | + background-color:#FD8 | |
67 | +} | |
68 | +table.diff .delete { | |
69 | + background-color:#E99; | |
70 | +} | |
71 | +table.diff .skip { | |
72 | + background-color:#EFEFEF; | |
73 | + border:1px solid #AAA; | |
74 | + border-right:1px solid #BBC; | |
75 | +} | |
76 | +table.diff .insert { | |
77 | + background-color:#9E9 | |
78 | +} | |
79 | +table.diff th.author { | |
80 | + text-align:right; | |
81 | + border-top:1px solid #BBC; | |
82 | + background:#EFEFEF | |
83 | +} | |
\ No newline at end of file |
@@ -0,0 +1,198 @@ | ||
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 | +diffview = { | |
32 | + /** | |
33 | + * Builds and returns a visual diff view. The single parameter, `params', should contain | |
34 | + * the following values: | |
35 | + * | |
36 | + * - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher | |
37 | + * - newTextLines: the array of strings that was used as the new text input to SequenceMatcher | |
38 | + * - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes() | |
39 | + * - baseTextName: the title to be displayed above the base text listing in the diff view; defaults | |
40 | + * to "Base Text" | |
41 | + * - newTextName: the title to be displayed above the new text listing in the diff view; defaults | |
42 | + * to "New Text" | |
43 | + * - contextSize: the number of lines of context to show around differences; by default, all lines | |
44 | + * are shown | |
45 | + * - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is | |
46 | + * generated | |
47 | + */ | |
48 | + buildView: function (params) { | |
49 | + var baseTextLines = params.baseTextLines; | |
50 | + var newTextLines = params.newTextLines; | |
51 | + var opcodes = params.opcodes; | |
52 | + var baseTextName = params.baseTextName ? params.baseTextName : "Base Text"; | |
53 | + var newTextName = params.newTextName ? params.newTextName : "New Text"; | |
54 | + var contextSize = params.contextSize; | |
55 | + var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0; | |
56 | + | |
57 | + if (baseTextLines == null) | |
58 | + throw "Cannot build diff view; baseTextLines is not defined."; | |
59 | + if (newTextLines == null) | |
60 | + throw "Cannot build diff view; newTextLines is not defined."; | |
61 | + if (!opcodes) | |
62 | + throw "Canno build diff view; opcodes is not defined."; | |
63 | + | |
64 | + function celt (name, clazz) { | |
65 | + var e = document.createElement(name); | |
66 | + e.className = clazz; | |
67 | + return e; | |
68 | + } | |
69 | + | |
70 | + function telt (name, text) { | |
71 | + var e = document.createElement(name); | |
72 | + e.appendChild(document.createTextNode(text)); | |
73 | + return e; | |
74 | + } | |
75 | + | |
76 | + function ctelt (name, clazz, text) { | |
77 | + var e = document.createElement(name); | |
78 | + e.className = clazz; | |
79 | + e.appendChild(document.createTextNode(text)); | |
80 | + return e; | |
81 | + } | |
82 | + | |
83 | + var tdata = document.createElement("thead"); | |
84 | + var node = document.createElement("tr"); | |
85 | + tdata.appendChild(node); | |
86 | + if (inline) { | |
87 | + node.appendChild(document.createElement("th")); | |
88 | + node.appendChild(document.createElement("th")); | |
89 | + node.appendChild(ctelt("th", "texttitle", baseTextName + " vs. " + newTextName)); | |
90 | + } else { | |
91 | + node.appendChild(document.createElement("th")); | |
92 | + node.appendChild(ctelt("th", "texttitle", baseTextName)); | |
93 | + node.appendChild(document.createElement("th")); | |
94 | + node.appendChild(ctelt("th", "texttitle", newTextName)); | |
95 | + } | |
96 | + tdata = [tdata]; | |
97 | + | |
98 | + var rows = []; | |
99 | + var node2; | |
100 | + | |
101 | + /** | |
102 | + * Adds two cells to the given row; if the given row corresponds to a real | |
103 | + * line number (based on the line index tidx and the endpoint of the | |
104 | + * range in question tend), then the cells will contain the line number | |
105 | + * and the line of text from textLines at position tidx (with the class of | |
106 | + * the second cell set to the name of the change represented), and tidx + 1 will | |
107 | + * be returned. Otherwise, tidx is returned, and two empty cells are added | |
108 | + * to the given row. | |
109 | + */ | |
110 | + function addCells (row, tidx, tend, textLines, change) { | |
111 | + if (tidx < tend) { | |
112 | + row.appendChild(telt("th", (tidx + 1).toString())); | |
113 | + row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); | |
114 | + return tidx + 1; | |
115 | + } else { | |
116 | + row.appendChild(document.createElement("th")); | |
117 | + row.appendChild(celt("td", "empty")); | |
118 | + return tidx; | |
119 | + } | |
120 | + } | |
121 | + | |
122 | + function addCellsInline (row, tidx, tidx2, textLines, change) { | |
123 | + row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString())); | |
124 | + row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString())); | |
125 | + row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); | |
126 | + } | |
127 | + | |
128 | + for (var idx = 0; idx < opcodes.length; idx++) { | |
129 | + code = opcodes[idx]; | |
130 | + change = code[0]; | |
131 | + var b = code[1]; | |
132 | + var be = code[2]; | |
133 | + var n = code[3]; | |
134 | + var ne = code[4]; | |
135 | + var rowcnt = Math.max(be - b, ne - n); | |
136 | + var toprows = []; | |
137 | + var botrows = []; | |
138 | + for (var i = 0; i < rowcnt; i++) { | |
139 | + // jump ahead if we've alredy provided leading context or if this is the first range | |
140 | + if (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change=="equal") { | |
141 | + var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize); | |
142 | + if (jump > 1) { | |
143 | + toprows.push(node = document.createElement("tr")); | |
144 | + | |
145 | + b += jump; | |
146 | + n += jump; | |
147 | + i += jump - 1; | |
148 | + node.appendChild(telt("th", "...")); | |
149 | + if (!inline) node.appendChild(ctelt("td", "skip", "")); | |
150 | + node.appendChild(telt("th", "...")); | |
151 | + node.appendChild(ctelt("td", "skip", "")); | |
152 | + | |
153 | + // skip last lines if they're all equal | |
154 | + if (idx + 1 == opcodes.length) { | |
155 | + break; | |
156 | + } else { | |
157 | + continue; | |
158 | + } | |
159 | + } | |
160 | + } | |
161 | + | |
162 | + toprows.push(node = document.createElement("tr")); | |
163 | + if (inline) { | |
164 | + if (change == "insert") { | |
165 | + addCellsInline(node, null, n++, newTextLines, change); | |
166 | + } else if (change == "replace") { | |
167 | + botrows.push(node2 = document.createElement("tr")); | |
168 | + if (b < be) addCellsInline(node, b++, null, baseTextLines, "delete"); | |
169 | + if (n < ne) addCellsInline(node2, null, n++, newTextLines, "insert"); | |
170 | + } else if (change == "delete") { | |
171 | + addCellsInline(node, b++, null, baseTextLines, change); | |
172 | + } else { | |
173 | + // equal | |
174 | + addCellsInline(node, b++, n++, baseTextLines, change); | |
175 | + } | |
176 | + } else { | |
177 | + b = addCells(node, b, be, baseTextLines, change); | |
178 | + n = addCells(node, n, ne, newTextLines, change); | |
179 | + } | |
180 | + } | |
181 | + | |
182 | + for (var i = 0; i < toprows.length; i++) rows.push(toprows[i]); | |
183 | + for (var i = 0; i < botrows.length; i++) rows.push(botrows[i]); | |
184 | + } | |
185 | + | |
186 | + rows.push(node = ctelt("th", "author", "diff view generated by ")); | |
187 | + node.setAttribute("colspan", inline ? 3 : 4); | |
188 | + node.appendChild(node2 = telt("a", "jsdifflib")); | |
189 | + node2.setAttribute("href", "http://snowtide.com/jsdifflib"); | |
190 | + | |
191 | + tdata.push(node = document.createElement("tbody")); | |
192 | + for (var idx in rows) node.appendChild(rows[idx]); | |
193 | + | |
194 | + node = celt("table", "diff" + (inline ? " inlinediff" : "")); | |
195 | + for (var idx in tdata) node.appendChild(tdata[idx]); | |
196 | + return node; | |
197 | + } | |
198 | +} | |
\ No newline at end of file |