summaryrefslogtreecommitdiff
path: root/public/javascripts/cacycle_diff.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/javascripts/cacycle_diff.js')
-rw-r--r--public/javascripts/cacycle_diff.js1112
1 files changed, 1112 insertions, 0 deletions
diff --git a/public/javascripts/cacycle_diff.js b/public/javascripts/cacycle_diff.js
new file mode 100644
index 0000000..24f9d0b
--- /dev/null
+++ b/public/javascripts/cacycle_diff.js
@@ -0,0 +1,1112 @@
1// <pre><nowiki>
2
3/*
4
5Name: diff.js
6Version: 0.9.5a (April 6, 2008)
7Info: http://en.wikipedia.org/wiki/User:Cacycle/diff
8Code: http://en.wikipedia.org/wiki/User:Cacycle/diff.js
9
10JavaScript diff algorithm by [[en:User:Cacycle]] (http://en.wikipedia.org/wiki/User_talk:Cacycle).
11Outputs html/css-formatted new text with highlighted deletions, inserts, and block moves.
12
13The program uses cross-browser code and should work with all modern browsers. It has been tested with:
14* Mozilla Firefox 1.5.0.1
15* Mozilla SeaMonkey 1.0
16* Opera 8.53
17* Internet Explorer 6.0.2900.2180
18* Internet Explorer 7.0.5730.11
19This program is also compatibel with Greasemonkey
20
21An implementation of the word-based algorithm from:
22
23Communications of the ACM 21(4):264 (1978)
24http://doi.acm.org/10.1145/359460.359467
25
26With the following additional feature:
27
28* Word types have been optimized for MediaWiki source texts
29* Additional post-pass 5 code for resolving islands caused by adding
30 two common words at the end of sequences of common words
31* Additional detection of block borders and color coding of moved blocks and their original position
32* Optional "intelligent" omission of unchanged parts from the output
33
34This code is used by the MediaWiki in-browser text editors [[en:User:Cacycle/editor]] and [[en:User:Cacycle/wikEd]]
35and the enhanced diff view tool wikEdDiff [[en:User:Cacycle/wikEd]].
36
37Usage: var htmlText = WDiffString(oldText, newText);
38
39This code has been released into the public domain.
40
41Datastructures:
42
43text: an object that holds all text related datastructures
44 .newWords: consecutive words of the new text (N)
45 .oldWords: consecutive words of the old text (O)
46 .newToOld: array of corresponding word number in old text (NA)
47 .oldToNew: array of corresponding word number in new text (OA)
48 .message: output message for testing purposes
49
50symbol['word']: symbol table for passes 1 - 3, holds words as a hash
51 .newCtr: new word occurences counter (NC)
52 .oldCtr: old word occurences counter (OC)
53 .toNew: table last old word number
54 .toOld: last new word number (OLNA)
55
56block: an object that holds block move information
57 blocks indexed after new text:
58 .newStart: new text word number of start of this block
59 .newLength: element number of this block including non-words
60 .newWords: true word number of this block
61 .newNumber: corresponding block index in old text
62 .newBlock: moved-block-number of a block that has been moved here
63 .newLeft: moved-block-number of a block that has been moved from this border leftwards
64 .newRight: moved-block-number of a block that has been moved from this border rightwards
65 .newLeftIndex: index number of a block that has been moved from this border leftwards
66 .newRightIndex: index number of a block that has been moved from this border rightwards
67 blocks indexed after old text:
68 .oldStart: word number of start of this block
69 .oldToNew: corresponding new text word number of start
70 .oldLength: element number of this block including non-words
71 .oldWords: true word number of this block
72
73*/
74
75
76// css for change indicators
77if (typeof(wDiffStyleDelete) == 'undefined') { window.wDiffStyleDelete = 'font-weight: normal; text-decoration: none; color: #fff; background-color: #990033;'; }
78if (typeof(wDiffStyleInsert) == 'undefined') { window.wDiffStyleInsert = 'font-weight: normal; text-decoration: none; color: #fff; background-color: #009933;'; }
79if (typeof(wDiffStyleMoved) == 'undefined') { window.wDiffStyleMoved = 'font-weight: bold; color: #000; vertical-align: text-bottom; font-size: xx-small; padding: 0; border: solid 1px;'; }
80if (typeof(wDiffStyleBlock) == 'undefined') { window.wDiffStyleBlock = [
81 'color: #000; background-color: #ffff80;',
82 'color: #000; background-color: #c0ffff;',
83 'color: #000; background-color: #ffd0f0;',
84 'color: #000; background-color: #ffe080;',
85 'color: #000; background-color: #aaddff;',
86 'color: #000; background-color: #ddaaff;',
87 'color: #000; background-color: #ffbbbb;',
88 'color: #000; background-color: #d8ffa0;',
89 'color: #000; background-color: #d0d0d0;'
90]; }
91
92// html for change indicators, {number} is replaced by the block number
93// {block} is replaced by the block style, class and html comments are important for shortening the output
94if (typeof(wDiffHtmlMovedRight) == 'undefined') { window.wDiffHtmlMovedRight = '<input class="wDiffHtmlMovedRight" type="button" value="&gt;" style="' + wDiffStyleMoved + ' {block}"><!--wDiffHtmlMovedRight-->'; }
95if (typeof(wDiffHtmlMovedLeft) == 'undefined') { window.wDiffHtmlMovedLeft = '<input class="wDiffHtmlMovedLeft" type="button" value="&lt;" style="' + wDiffStyleMoved + ' {block}"><!--wDiffHtmlMovedLeft-->'; }
96
97if (typeof(wDiffHtmlBlockStart) == 'undefined') { window.wDiffHtmlBlockStart = '<span class="wDiffHtmlBlock" style="{block}">'; }
98if (typeof(wDiffHtmlBlockEnd) == 'undefined') { window.wDiffHtmlBlockEnd = '</span><!--wDiffHtmlBlock-->'; }
99
100if (typeof(wDiffHtmlDeleteStart) == 'undefined') { window.wDiffHtmlDeleteStart = '<span class="wDiffHtmlDelete" style="' + wDiffStyleDelete + '">'; }
101if (typeof(wDiffHtmlDeleteEnd) == 'undefined') { window.wDiffHtmlDeleteEnd = '</span><!--wDiffHtmlDelete-->'; }
102
103if (typeof(wDiffHtmlInsertStart) == 'undefined') { window.wDiffHtmlInsertStart = '<span class="wDiffHtmlInsert" style="' + wDiffStyleInsert + '">'; }
104if (typeof(wDiffHtmlInsertEnd) == 'undefined') { window.wDiffHtmlInsertEnd = '</span><!--wDiffHtmlInsert-->'; }
105
106// minimal number of real words for a moved block (0 for always displaying block move indicators)
107if (typeof(wDiffBlockMinLength) == 'undefined') { window.wDiffBlockMinLength = 3; }
108
109// exclude identical sequence starts and endings from change marking
110if (typeof(wDiffWordDiff) == 'undefined') { window.wDiffWordDiff = true; }
111
112// enable recursive diff to resolve problematic sequences
113if (typeof(wDiffRecursiveDiff) == 'undefined') { window.wDiffRecursiveDiff = true; }
114
115// enable block move display
116if (typeof(wDiffShowBlockMoves) == 'undefined') { window.wDiffShowBlockMoves = true; }
117
118// remove unchanged parts from final output
119
120// characters before diff tag to search for previous heading, paragraph, line break, cut characters
121if (typeof(wDiffHeadingBefore) == 'undefined') { window.wDiffHeadingBefore = 1500; }
122if (typeof(wDiffParagraphBefore) == 'undefined') { window.wDiffParagraphBefore = 1500; }
123if (typeof(wDiffLineBeforeMax) == 'undefined') { window.wDiffLineBeforeMax = 1000; }
124if (typeof(wDiffLineBeforeMin) == 'undefined') { window.wDiffLineBeforeMin = 500; }
125if (typeof(wDiffBlankBeforeMax) == 'undefined') { window.wDiffBlankBeforeMax = 1000; }
126if (typeof(wDiffBlankBeforeMin) == 'undefined') { window.wDiffBlankBeforeMin = 500; }
127if (typeof(wDiffCharsBefore) == 'undefined') { window.wDiffCharsBefore = 500; }
128
129// characters after diff tag to search for next heading, paragraph, line break, or characters
130if (typeof(wDiffHeadingAfter) == 'undefined') { window.wDiffHeadingAfter = 1500; }
131if (typeof(wDiffParagraphAfter) == 'undefined') { window.wDiffParagraphAfter = 1500; }
132if (typeof(wDiffLineAfterMax) == 'undefined') { window.wDiffLineAfterMax = 1000; }
133if (typeof(wDiffLineAfterMin) == 'undefined') { window.wDiffLineAfterMin = 500; }
134if (typeof(wDiffBlankAfterMax) == 'undefined') { window.wDiffBlankAfterMax = 1000; }
135if (typeof(wDiffBlankAfterMin) == 'undefined') { window.wDiffBlankAfterMin = 500; }
136if (typeof(wDiffCharsAfter) == 'undefined') { window.wDiffCharsAfter = 500; }
137
138// maximal fragment distance to join close fragments
139if (typeof(wDiffFragmentJoin) == 'undefined') { window.wDiffFragmentJoin = 1000; }
140if (typeof(wDiffOmittedChars) == 'undefined') { window.wDiffOmittedChars = '…'; }
141if (typeof(wDiffOmittedLines) == 'undefined') { window.wDiffOmittedLines = '<hr style="height: 2px; margin: 1em 10%;">'; }
142if (typeof(wDiffNoChange) == 'undefined') { window.wDiffNoChange = '<hr style="height: 2px; margin: 1em 20%;">'; }
143
144// compatibility fix for old name of main function
145window.StringDiff = window.WDiffString;
146
147
148// WDiffString: main program
149// input: oldText, newText, strings containing the texts
150// returns: html diff
151
152window.WDiffString = function(oldText, newText) {
153
154// IE / Mac fix
155 oldText = oldText.replace(/(\r\n)/g, '\n');
156 newText = newText.replace(/(\r\n)/g, '\n');
157
158 var text = {};
159 text.newWords = [];
160 text.oldWords = [];
161 text.newToOld = [];
162 text.oldToNew = [];
163 text.message = '';
164 var block = {};
165 var outText = '';
166
167// trap trivial changes: no change
168 if (oldText == newText) {
169 outText = newText;
170 outText = WDiffEscape(outText);
171 outText = WDiffHtmlFormat(outText);
172 return(outText);
173 }
174
175// trap trivial changes: old text deleted
176 if ( (oldText == null) || (oldText.length == 0) ) {
177 outText = newText;
178 outText = WDiffEscape(outText);
179 outText = WDiffHtmlFormat(outText);
180 outText = wDiffHtmlInsertStart + outText + wDiffHtmlInsertEnd;
181 return(outText);
182 }
183
184// trap trivial changes: new text deleted
185 if ( (newText == null) || (newText.length == 0) ) {
186 outText = oldText;
187 outText = WDiffEscape(outText);
188 outText = WDiffHtmlFormat(outText);
189 outText = wDiffHtmlDeleteStart + outText + wDiffHtmlDeleteEnd;
190 return(outText);
191 }
192
193// split new and old text into words
194 WDiffSplitText(oldText, newText, text);
195
196// calculate diff information
197 WDiffText(text);
198
199//detect block borders and moved blocks
200 WDiffDetectBlocks(text, block);
201
202// process diff data into formatted html text
203 outText = WDiffToHtml(text, block);
204
205// IE fix
206 outText = outText.replace(/> ( *)</g, '>&nbsp;$1<');
207
208 return(outText);
209}
210
211
212// WDiffSplitText: split new and old text into words
213// input: oldText, newText, strings containing the texts
214// changes: text.newWords and text.oldWords, arrays containing the texts in arrays of words
215
216window.WDiffSplitText = function(oldText, newText, text) {
217
218// convert strange spaces
219 oldText = oldText.replace(/[\t\u000b\u00a0\u2028\u2029]+/g, ' ');
220 newText = newText.replace(/[\t\u000b\u00a0\u2028\u2029]+/g, ' ');
221
222// split old text into words
223
224// / | | | | | | | | | | | | | | /
225 var pattern = /[\w]+|\[\[|\]\]|\{\{|\}\}|\n+| +|&\w+;|'''|''|=+|\{\||\|\}|\|\-|./g;
226 var result;
227 do {
228 result = pattern.exec(oldText);
229 if (result != null) {
230 text.oldWords.push(result[0]);
231 }
232 } while (result != null);
233
234// split new text into words
235 do {
236 result = pattern.exec(newText);
237 if (result != null) {
238 text.newWords.push(result[0]);
239 }
240 } while (result != null);
241
242 return;
243}
244
245
246// WDiffText: calculate diff information
247// input: text.newWords and text.oldWords, arrays containing the texts in arrays of words
248// optionally for recursive calls: newStart, newEnd, oldStart, oldEnd, recursionLevel
249// changes: text.newToOld and text.oldToNew, containing the line numbers in the other version
250
251window.WDiffText = function(text, newStart, newEnd, oldStart, oldEnd, recursionLevel) {
252
253 symbol = new Object();
254 symbol.newCtr = [];
255 symbol.oldCtr = [];
256 symbol.toNew = [];
257 symbol.toOld = [];
258
259// set defaults
260 newStart = newStart || 0;
261 newEnd = newEnd || text.newWords.length;
262 oldStart = oldStart || 0;
263 oldEnd = oldEnd || text.oldWords.length;
264 recursionLevel = recursionLevel || 0;
265
266// limit recursion depth
267 if (recursionLevel > 10) {
268 return;
269 }
270
271// pass 1: parse new text into symbol table s
272
273 var word;
274 for (var i = newStart; i < newEnd; i ++) {
275 word = text.newWords[i];
276
277// add new entry to symbol table
278 if ( symbol[word] == null) {
279 symbol[word] = { newCtr: 0, oldCtr: 0, toNew: null, toOld: null };
280 }
281
282// increment symbol table word counter for new text
283 symbol[word].newCtr ++;
284
285// add last word number in new text
286 symbol[word].toNew = i;
287 }
288
289// pass 2: parse old text into symbol table
290
291 for (var j = oldStart; j < oldEnd; j ++) {
292 word = text.oldWords[j];
293
294// add new entry to symbol table
295 if ( symbol[word] == null) {
296 symbol[word] = { newCtr: 0, oldCtr: 0, toNew: null, toOld: null };
297 }
298
299// increment symbol table word counter for old text
300 symbol[word].oldCtr ++;
301
302// add last word number in old text
303 symbol[word].toOld = j;
304 }
305
306// pass 3: connect unique words
307
308 for (var i in symbol) {
309
310// find words in the symbol table that occur only once in both versions
311 if ( (symbol[i].newCtr == 1) && (symbol[i].oldCtr == 1) ) {
312 var toNew = symbol[i].toNew;
313 var toOld = symbol[i].toOld;
314
315// do not use spaces as unique markers
316 if ( ! /\s/.test( text.newWords[toNew] ) ) {
317
318// connect from new to old and from old to new
319 text.newToOld[toNew] = toOld;
320 text.oldToNew[toOld] = toNew;
321 }
322 }
323 }
324
325// pass 4: connect adjacent identical words downwards
326
327 for (var i = newStart; i < newEnd - 1; i ++) {
328
329// find already connected pairs
330 if (text.newToOld[i] != null) {
331 j = text.newToOld[i];
332
333// check if the following words are not yet connected
334 if ( (text.newToOld[i + 1] == null) && (text.oldToNew[j + 1] == null) ) {
335
336// if the following words are the same connect them
337 if ( text.newWords[i + 1] == text.oldWords[j + 1] ) {
338 text.newToOld[i + 1] = j + 1;
339 text.oldToNew[j + 1] = i + 1;
340 }
341 }
342 }
343 }
344
345// pass 5: connect adjacent identical words upwards
346
347 for (var i = newEnd - 1; i > newStart; i --) {
348
349// find already connected pairs
350 if (text.newToOld[i] != null) {
351 j = text.newToOld[i];
352
353// check if the preceeding words are not yet connected
354 if ( (text.newToOld[i - 1] == null) && (text.oldToNew[j - 1] == null) ) {
355
356// if the preceeding words are the same connect them
357 if ( text.newWords[i - 1] == text.oldWords[j - 1] ) {
358 text.newToOld[i - 1] = j - 1;
359 text.oldToNew[j - 1] = i - 1;
360 }
361 }
362 }
363 }
364
365// recursively diff still unresolved regions downwards
366
367 if (wDiffRecursiveDiff) {
368 i = newStart;
369 j = oldStart;
370 while (i < newEnd) {
371 if (text.newToOld[i - 1] != null) {
372 j = text.newToOld[i - 1] + 1;
373 }
374
375// check for the start of an unresolved sequence
376 if ( (text.newToOld[i] == null) && (text.oldToNew[j] == null) ) {
377
378// determine the ends of the sequences
379 var iStart = i;
380 var iEnd = i;
381 while ( (text.newToOld[iEnd] == null) && (iEnd < newEnd) ) {
382 iEnd ++;
383 }
384 var iLength = iEnd - iStart;
385
386 var jStart = j;
387 var jEnd = j;
388 while ( (text.oldToNew[jEnd] == null) && (jEnd < oldEnd) ) {
389 jEnd ++;
390 }
391 var jLength = jEnd - jStart;
392
393// recursively diff the unresolved sequence
394 if ( (iLength > 0) && (jLength > 0) ) {
395 if ( (iLength > 1) || (jLength > 1) ) {
396 if ( (iStart != newStart) || (iEnd != newEnd) || (jStart != oldStart) || (jEnd != oldEnd) ) {
397 WDiffText(text, iStart, iEnd, jStart, jEnd, recursionLevel + 1);
398 }
399 }
400 }
401 i = iEnd;
402 }
403 else {
404 i ++;
405 }
406 }
407 }
408
409// recursively diff still unresolved regions upwards
410
411 if (wDiffRecursiveDiff) {
412 i = newEnd - 1;
413 j = oldEnd - 1;
414 while (i >= newStart) {
415 if (text.newToOld[i + 1] != null) {
416 j = text.newToOld[i + 1] - 1;
417 }
418
419// check for the start of an unresolved sequence
420 if ( (text.newToOld[i] == null) && (text.oldToNew[j] == null) ) {
421
422// determine the ends of the sequences
423 var iStart = i;
424 var iEnd = i + 1;
425 while ( (text.newToOld[iStart - 1] == null) && (iStart >= newStart) ) {
426 iStart --;
427 }
428 var iLength = iEnd - iStart;
429
430 var jStart = j;
431 var jEnd = j + 1;
432 while ( (text.oldToNew[jStart - 1] == null) && (jStart >= oldStart) ) {
433 jStart --;
434 }
435 var jLength = jEnd - jStart;
436
437// recursively diff the unresolved sequence
438 if ( (iLength > 0) && (jLength > 0) ) {
439 if ( (iLength > 1) || (jLength > 1) ) {
440 if ( (iStart != newStart) || (iEnd != newEnd) || (jStart != oldStart) || (jEnd != oldEnd) ) {
441 WDiffText(text, iStart, iEnd, jStart, jEnd, recursionLevel + 1);
442 }
443 }
444 }
445 i = iStart - 1;
446 }
447 else {
448 i --;
449 }
450 }
451 }
452 return;
453}
454
455
456// WDiffToHtml: process diff data into formatted html text
457// input: text.newWords and text.oldWords, arrays containing the texts in arrays of words
458// text.newToOld and text.oldToNew, containing the line numbers in the other version
459// block data structure
460// returns: outText, a html string
461
462window.WDiffToHtml = function(text, block) {
463
464 var outText = text.message;
465
466 var blockNumber = 0;
467 var i = 0;
468 var j = 0;
469 var movedAsInsertion;
470
471// cycle through the new text
472 do {
473 var movedIndex = [];
474 var movedBlock = [];
475 var movedLeft = [];
476 var blockText = '';
477 var identText = '';
478 var delText = '';
479 var insText = '';
480 var identStart = '';
481
482// check if a block ends here and finish previous block
483 if (movedAsInsertion != null) {
484 if (movedAsInsertion == false) {
485 identStart += wDiffHtmlBlockEnd;
486 }
487 else {
488 identStart += wDiffHtmlInsertEnd;
489 }
490 movedAsInsertion = null;
491 }
492
493// detect block boundary
494 if ( (text.newToOld[i] != j) || (blockNumber == 0 ) ) {
495 if ( ( (text.newToOld[i] != null) || (i >= text.newWords.length) ) && ( (text.oldToNew[j] != null) || (j >= text.oldWords.length) ) ) {
496
497// block moved right
498 var moved = block.newRight[blockNumber];
499 if (moved > 0) {
500 var index = block.newRightIndex[blockNumber];
501 movedIndex.push(index);
502 movedBlock.push(moved);
503 movedLeft.push(false);
504 }
505
506// block moved left
507 moved = block.newLeft[blockNumber];
508 if (moved > 0) {
509 var index = block.newLeftIndex[blockNumber];
510 movedIndex.push(index);
511 movedBlock.push(moved);
512 movedLeft.push(true);
513 }
514
515// check if a block starts here
516 moved = block.newBlock[blockNumber];
517 if (moved > 0) {
518
519// mark block as inserted text
520 if (block.newWords[blockNumber] < wDiffBlockMinLength) {
521 identStart += wDiffHtmlInsertStart;
522 movedAsInsertion = true;
523 }
524
525// mark block by color
526 else {
527 if (moved > wDiffStyleBlock.length) {
528 moved = wDiffStyleBlock.length;
529 }
530 identStart += WDiffHtmlCustomize(wDiffHtmlBlockStart, moved - 1);
531 movedAsInsertion = false;
532 }
533 }
534
535 if (i >= text.newWords.length) {
536 i ++;
537 }
538 else {
539 j = text.newToOld[i];
540 blockNumber ++;
541 }
542 }
543 }
544
545// get the correct order if moved to the left as well as to the right from here
546 if (movedIndex.length == 2) {
547 if (movedIndex[0] > movedIndex[1]) {
548 movedIndex.reverse();
549 movedBlock.reverse();
550 movedLeft.reverse();
551 }
552 }
553
554// handle left and right block moves from this position
555 for (var m = 0; m < movedIndex.length; m ++) {
556
557// insert the block as deleted text
558 if (block.newWords[ movedIndex[m] ] < wDiffBlockMinLength) {
559 var movedStart = block.newStart[ movedIndex[m] ];
560 var movedLength = block.newLength[ movedIndex[m] ];
561 var str = '';
562 for (var n = movedStart; n < movedStart + movedLength; n ++) {
563 str += text.newWords[n];
564 }
565 str = WDiffEscape(str);
566 str = str.replace(/\n/g, '&para;<br>');
567 blockText += wDiffHtmlDeleteStart + str + wDiffHtmlDeleteEnd;
568 }
569
570// add a placeholder / move direction indicator
571 else {
572 if (movedBlock[m] > wDiffStyleBlock.length) {
573 movedBlock[m] = wDiffStyleBlock.length;
574 }
575 if (movedLeft[m]) {
576 blockText += WDiffHtmlCustomize(wDiffHtmlMovedLeft, movedBlock[m] - 1);
577 }
578 else {
579 blockText += WDiffHtmlCustomize(wDiffHtmlMovedRight, movedBlock[m] - 1);
580 }
581 }
582 }
583
584// collect consecutive identical text
585 while ( (i < text.newWords.length) && (j < text.oldWords.length) ) {
586 if ( (text.newToOld[i] == null) || (text.oldToNew[j] == null) ) {
587 break;
588 }
589 if (text.newToOld[i] != j) {
590 break;
591 }
592 identText += text.newWords[i];
593 i ++;
594 j ++;
595 }
596
597// collect consecutive deletions
598 while ( (text.oldToNew[j] == null) && (j < text.oldWords.length) ) {
599 delText += text.oldWords[j];
600 j ++;
601 }
602
603// collect consecutive inserts
604 while ( (text.newToOld[i] == null) && (i < text.newWords.length) ) {
605 insText += text.newWords[i];
606 i ++;
607 }
608
609// remove leading and trailing similarities betweein delText and ins from highlighting
610 var preText = '';
611 var postText = '';
612 if (wDiffWordDiff) {
613 if ( (delText != '') && (insText != '') ) {
614
615// remove leading similarities
616 while ( delText.charAt(0) == insText.charAt(0) && (delText != '') && (insText != '') ) {
617 preText = preText + delText.charAt(0);
618 delText = delText.substr(1);
619 insText = insText.substr(1);
620 }
621
622// remove trailing similarities
623 while ( delText.charAt(delText.length - 1) == insText.charAt(insText.length - 1) && (delText != '') && (insText != '') ) {
624 postText = delText.charAt(delText.length - 1) + postText;
625 delText = delText.substr(0, delText.length - 1);
626 insText = insText.substr(0, insText.length - 1);
627 }
628 }
629 }
630
631// output the identical text, deletions and inserts
632
633// moved from here indicator
634 if (blockText != '') {
635 outText += blockText;
636 }
637
638// identical text
639 if (identText != '') {
640 outText += identStart + WDiffEscape(identText);
641 }
642 outText += preText;
643
644// deleted text
645 if (delText != '') {
646 delText = wDiffHtmlDeleteStart + WDiffEscape(delText) + wDiffHtmlDeleteEnd;
647 delText = delText.replace(/\n/g, '&para;<br>');
648 outText += delText;
649 }
650
651// inserted text
652 if (insText != '') {
653 insText = wDiffHtmlInsertStart + WDiffEscape(insText) + wDiffHtmlInsertEnd;
654 insText = insText.replace(/\n/g, '&para;<br>');
655 outText += insText;
656 }
657 outText += postText;
658 } while (i <= text.newWords.length);
659
660 outText += '\n';
661 outText = WDiffHtmlFormat(outText);
662
663 return(outText);
664}
665
666
667// WDiffEscape: replaces html-sensitive characters in output text with character entities
668
669window.WDiffEscape = function(text) {
670
671 text = text.replace(/&/g, '&amp;');
672 text = text.replace(/</g, '&lt;');
673 text = text.replace(/>/g, '&gt;');
674 text = text.replace(/\"/g, '&quot;');
675
676 return(text);
677}
678
679
680// HtmlCustomize: customize indicator html: replace {number} with the block number, {block} with the block style
681
682window.WDiffHtmlCustomize = function(text, block) {
683
684 text = text.replace(/\{number\}/, block);
685 text = text.replace(/\{block\}/, wDiffStyleBlock[block]);
686
687 return(text);
688}
689
690
691// HtmlFormat: replaces newlines and multiple spaces in text with html code
692
693window.WDiffHtmlFormat = function(text) {
694
695 text = text.replace(/ /g, ' &nbsp;');
696 text = text.replace(/\n/g, '<br>');
697
698 return(text);
699}
700
701
702// WDiffDetectBlocks: detect block borders and moved blocks
703// input: text object, block object
704
705window.WDiffDetectBlocks = function(text, block) {
706
707 block.oldStart = [];
708 block.oldToNew = [];
709 block.oldLength = [];
710 block.oldWords = [];
711 block.newStart = [];
712 block.newLength = [];
713 block.newWords = [];
714 block.newNumber = [];
715 block.newBlock = [];
716 block.newLeft = [];
717 block.newRight = [];
718 block.newLeftIndex = [];
719 block.newRightIndex = [];
720
721 var blockNumber = 0;
722 var wordCounter = 0;
723 var realWordCounter = 0;
724
725// get old text block order
726 if (wDiffShowBlockMoves) {
727 var j = 0;
728 var i = 0;
729 do {
730
731// detect block boundaries on old text
732 if ( (text.oldToNew[j] != i) || (blockNumber == 0 ) ) {
733 if ( ( (text.oldToNew[j] != null) || (j >= text.oldWords.length) ) && ( (text.newToOld[i] != null) || (i >= text.newWords.length) ) ) {
734 if (blockNumber > 0) {
735 block.oldLength[blockNumber - 1] = wordCounter;
736 block.oldWords[blockNumber - 1] = realWordCounter;
737 wordCounter = 0;
738 realWordCounter = 0;
739 }
740
741 if (j >= text.oldWords.length) {
742 j ++;
743 }
744 else {
745 i = text.oldToNew[j];
746 block.oldStart[blockNumber] = j;
747 block.oldToNew[blockNumber] = text.oldToNew[j];
748 blockNumber ++;
749 }
750 }
751 }
752
753// jump over identical pairs
754 while ( (i < text.newWords.length) && (j < text.oldWords.length) ) {
755 if ( (text.newToOld[i] == null) || (text.oldToNew[j] == null) ) {
756 break;
757 }
758 if (text.oldToNew[j] != i) {
759 break;
760 }
761 i ++;
762 j ++;
763 wordCounter ++;
764 if ( /\w/.test( text.newWords[i] ) ) {
765 realWordCounter ++;
766 }
767 }
768
769// jump over consecutive deletions
770 while ( (text.oldToNew[j] == null) && (j < text.oldWords.length) ) {
771 j ++;
772 }
773
774// jump over consecutive inserts
775 while ( (text.newToOld[i] == null) && (i < text.newWords.length) ) {
776 i ++;
777 }
778 } while (j <= text.oldWords.length);
779
780// get the block order in the new text
781 var lastMin;
782 var currMinIndex;
783 lastMin = null;
784
785// sort the data by increasing start numbers into new text block info
786 for (var i = 0; i < blockNumber; i ++) {
787 currMin = null;
788 for (var j = 0; j < blockNumber; j ++) {
789 curr = block.oldToNew[j];
790 if ( (curr > lastMin) || (lastMin == null) ) {
791 if ( (curr < currMin) || (currMin == null) ) {
792 currMin = curr;
793 currMinIndex = j;
794 }
795 }
796 }
797 block.newStart[i] = block.oldToNew[currMinIndex];
798 block.newLength[i] = block.oldLength[currMinIndex];
799 block.newWords[i] = block.oldWords[currMinIndex];
800 block.newNumber[i] = currMinIndex;
801 lastMin = currMin;
802 }
803
804// detect not moved blocks
805 for (var i = 0; i < blockNumber; i ++) {
806 if (block.newBlock[i] == null) {
807 if (block.newNumber[i] == i) {
808 block.newBlock[i] = 0;
809 }
810 }
811 }
812
813// detect switches of neighbouring blocks
814 for (var i = 0; i < blockNumber - 1; i ++) {
815 if ( (block.newBlock[i] == null) && (block.newBlock[i + 1] == null) ) {
816 if (block.newNumber[i] - block.newNumber[i + 1] == 1) {
817 if ( (block.newNumber[i + 1] - block.newNumber[i + 2] != 1) || (i + 2 >= blockNumber) ) {
818
819// the shorter one is declared the moved one
820 if (block.newLength[i] < block.newLength[i + 1]) {
821 block.newBlock[i] = 1;
822 block.newBlock[i + 1] = 0;
823 }
824 else {
825 block.newBlock[i] = 0;
826 block.newBlock[i + 1] = 1;
827 }
828 }
829 }
830 }
831 }
832
833// mark all others as moved and number the moved blocks
834 j = 1;
835 for (var i = 0; i < blockNumber; i ++) {
836 if ( (block.newBlock[i] == null) || (block.newBlock[i] == 1) ) {
837 block.newBlock[i] = j++;
838 }
839 }
840
841// check if a block has been moved from this block border
842 for (var i = 0; i < blockNumber; i ++) {
843 for (var j = 0; j < blockNumber; j ++) {
844
845 if (block.newNumber[j] == i) {
846 if (block.newBlock[j] > 0) {
847
848// block moved right
849 if (block.newNumber[j] < j) {
850 block.newRight[i] = block.newBlock[j];
851 block.newRightIndex[i] = j;
852 }
853
854// block moved left
855 else {
856 block.newLeft[i + 1] = block.newBlock[j];
857 block.newLeftIndex[i + 1] = j;
858 }
859 }
860 }
861 }
862 }
863 }
864 return;
865}
866
867
868// WDiffShortenOutput: remove unchanged parts from final output
869// input: the output of WDiffString
870// returns: the text with removed unchanged passages indicated by (...)
871
872window.WDiffShortenOutput = function(diffText) {
873
874// html <br/> to newlines
875 diffText = diffText.replace(/<br[^>]*>/g, '\n');
876
877// scan for diff html tags
878 var regExpDiff = new RegExp('<\\w+ class=\\"(\\w+)\\"[^>]*>(.|\\n)*?<!--\\1-->', 'g');
879 var tagStart = [];
880 var tagEnd = [];
881 var i = 0;
882 var found;
883 while ( (found = regExpDiff.exec(diffText)) != null ) {
884
885// combine consecutive diff tags
886 if ( (i > 0) && (tagEnd[i - 1] == found.index) ) {
887 tagEnd[i - 1] = found.index + found[0].length;
888 }
889 else {
890 tagStart[i] = found.index;
891 tagEnd[i] = found.index + found[0].length;
892 i ++;
893 }
894 }
895
896// no diff tags detected
897 if (tagStart.length == 0) {
898 return(wDiffNoChange);
899 }
900
901// define regexps
902 var regExpHeading = new RegExp('\\n=+.+?=+ *\\n|\\n\\{\\||\\n\\|\\}', 'g');
903 var regExpParagraph = new RegExp('\\n\\n+', 'g');
904 var regExpLine = new RegExp('\\n+', 'g');
905 var regExpBlank = new RegExp('(<[^>]+>)*\\s+', 'g');
906
907// determine fragment border positions around diff tags
908 var rangeStart = [];
909 var rangeEnd = [];
910 var rangeStartType = [];
911 var rangeEndType = [];
912 for (var i = 0; i < tagStart.length; i ++) {
913 var found;
914
915// find last heading before diff tag
916 var lastPos = tagStart[i] - wDiffHeadingBefore;
917 if (lastPos < 0) {
918 lastPos = 0;
919 }
920 regExpHeading.lastIndex = lastPos;
921 while ( (found = regExpHeading.exec(diffText)) != null ) {
922 if (found.index > tagStart[i]) {
923 break;
924 }
925 rangeStart[i] = found.index;
926 rangeStartType[i] = 'heading';
927 }
928
929// find last paragraph before diff tag
930 if (rangeStart[i] == null) {
931 lastPos = tagStart[i] - wDiffParagraphBefore;
932 if (lastPos < 0) {
933 lastPos = 0;
934 }
935 regExpParagraph.lastIndex = lastPos;
936 while ( (found = regExpParagraph.exec(diffText)) != null ) {
937 if (found.index > tagStart[i]) {
938 break;
939 }
940 rangeStart[i] = found.index;
941 rangeStartType[i] = 'paragraph';
942 }
943 }
944
945// find line break before diff tag
946 if (rangeStart[i] == null) {
947 lastPos = tagStart[i] - wDiffLineBeforeMax;
948 if (lastPos < 0) {
949 lastPos = 0;
950 }
951 regExpLine.lastIndex = lastPos;
952 while ( (found = regExpLine.exec(diffText)) != null ) {
953 if (found.index > tagStart[i] - wDiffLineBeforeMin) {
954 break;
955 }
956 rangeStart[i] = found.index;
957 rangeStartType[i] = 'line';
958 }
959 }
960
961// find blank before diff tag
962 if (rangeStart[i] == null) {
963 lastPos = tagStart[i] - wDiffBlankBeforeMax;
964 if (lastPos < 0) {
965 lastPos = 0;
966 }
967 regExpBlank.lastIndex = lastPos;
968 while ( (found = regExpBlank.exec(diffText)) != null ) {
969 if (found.index > tagStart[i] - wDiffBlankBeforeMin) {
970 break;
971 }
972 rangeStart[i] = found.index;
973 rangeStartType[i] = 'blank';
974 }
975 }
976
977// fixed number of chars before diff tag
978 if (rangeStart[i] == null) {
979 rangeStart[i] = tagStart[i] - wDiffCharsBefore;
980 rangeStartType[i] = 'chars';
981 if (rangeStart[i] < 0) {
982 rangeStart[i] = 0;
983 }
984 }
985
986// find first heading after diff tag
987 regExpHeading.lastIndex = tagEnd[i];
988 if ( (found = regExpHeading.exec(diffText)) != null ) {
989 if (found.index < tagEnd[i] + wDiffHeadingAfter) {
990 rangeEnd[i] = found.index + found[0].length;
991 rangeEndType[i] = 'heading';
992 }
993 }
994
995// find first paragraph after diff tag
996 if (rangeEnd[i] == null) {
997 regExpParagraph.lastIndex = tagEnd[i];
998 if ( (found = regExpParagraph.exec(diffText)) != null ) {
999 if (found.index < tagEnd[i] + wDiffParagraphAfter) {
1000 rangeEnd[i] = found.index;
1001 rangeEndType[i] = 'paragraph';
1002 }
1003 }
1004 }
1005
1006// find first line break after diff tag
1007 if (rangeEnd[i] == null) {
1008 regExpLine.lastIndex = tagEnd[i] + wDiffLineAfterMin;
1009 if ( (found = regExpLine.exec(diffText)) != null ) {
1010 if (found.index < tagEnd[i] + wDiffLineAfterMax) {
1011 rangeEnd[i] = found.index;
1012 rangeEndType[i] = 'break';
1013 }
1014 }
1015 }
1016
1017// find blank after diff tag
1018 if (rangeEnd[i] == null) {
1019 regExpBlank.lastIndex = tagEnd[i] + wDiffBlankAfterMin;
1020 if ( (found = regExpBlank.exec(diffText)) != null ) {
1021 if (found.index < tagEnd[i] + wDiffBlankAfterMax) {
1022 rangeEnd[i] = found.index;
1023 rangeEndType[i] = 'blank';
1024 }
1025 }
1026 }
1027
1028// fixed number of chars after diff tag
1029 if (rangeEnd[i] == null) {
1030 rangeEnd[i] = tagEnd[i] + wDiffCharsAfter;
1031 if (rangeEnd[i] > diffText.length) {
1032 rangeEnd[i] = diffText.length;
1033 rangeEndType[i] = 'chars';
1034 }
1035 }
1036 }
1037
1038// remove overlaps, join close fragments
1039 var fragmentStart = [];
1040 var fragmentEnd = [];
1041 var fragmentStartType = [];
1042 var fragmentEndType = [];
1043 fragmentStart[0] = rangeStart[0];
1044 fragmentEnd[0] = rangeEnd[0];
1045 fragmentStartType[0] = rangeStartType[0];
1046 fragmentEndType[0] = rangeEndType[0];
1047 var j = 1;
1048 for (var i = 1; i < rangeStart.length; i ++) {
1049 if (rangeStart[i] > fragmentEnd[j - 1] + wDiffFragmentJoin) {
1050 fragmentStart[j] = rangeStart[i];
1051 fragmentEnd[j] = rangeEnd[i];
1052 fragmentStartType[j] = rangeStartType[i];
1053 fragmentEndType[j] = rangeEndType[i];
1054 j ++;
1055 }
1056 else {
1057 fragmentEnd[j - 1] = rangeEnd[i];
1058 fragmentEndType[j - 1] = rangeEndType[i];
1059 }
1060 }
1061
1062// assemble the fragments
1063 var outText = '';
1064 for (var i = 0; i < fragmentStart.length; i ++) {
1065
1066// get text fragment
1067 var fragment = diffText.substring(fragmentStart[i], fragmentEnd[i]);
1068 var fragment = fragment.replace(/^\n+|\n+$/g, '');
1069
1070// add inline marks for omitted chars and words
1071 if (fragmentStart[i] > 0) {
1072 if (fragmentStartType[i] == 'chars') {
1073 fragment = wDiffOmittedChars + fragment;
1074 }
1075 else if (fragmentStartType[i] == 'blank') {
1076 fragment = wDiffOmittedChars + ' ' + fragment;
1077 }
1078 }
1079 if (fragmentEnd[i] < diffText.length) {
1080 if (fragmentStartType[i] == 'chars') {
1081 fragment = fragment + wDiffOmittedChars;
1082 }
1083 else if (fragmentStartType[i] == 'blank') {
1084 fragment = fragment + ' ' + wDiffOmittedChars;
1085 }
1086 }
1087
1088// add omitted line separator
1089 if (fragmentStart[i] > 0) {
1090 outText += wDiffOmittedLines;
1091 }
1092
1093// encapsulate span errors
1094 outText += '<div>' + fragment + '</div>';
1095 }
1096
1097// add trailing omitted line separator
1098 if (fragmentEnd[i - 1] < diffText.length) {
1099 outText = outText + wDiffOmittedLines;
1100 }
1101
1102// remove leading and trailing empty lines
1103 outText = outText.replace(/^(<div>)\n+|\n+(<\/div>)$/g, '$1$2');
1104
1105// convert to html linebreaks
1106 outText = outText.replace(/\n/g, '<br />');
1107
1108 return(outText);
1109}
1110
1111
1112// <pre><nowiki> \ No newline at end of file