Content deleted Content added
1.0.6a (August 27, 2014) shorten output fix (substring) |
1.0.7 (September 01, 2014) jshint, split wDiff.DetectBlocks, unlinking too short groups, Greasemonkey header |
||
Line 1:
// <syntaxhighlight lang="JavaScript">
// ==UserScript==
// @name wDiff
// @version 1.0.
// @date
// @description improved word-based diff library with block move detection
// @homepage https://en.wikipedia.org/wiki/User:Cacycle/diff
Line 9 ⟶ 10:
// @author Cacycle (https://en.wikipedia.org/wiki/User:Cacycle)
// @license released into the public ___domain
// ==/UserScript==
/*
Line 61 ⟶ 63:
blocks[]: array of objects that holds block (consecutive text tokens) data in order of the new text
.oldBlock:
.newBlock: number of block in new text order
.oldNumber: old text token number of first token in block
.newNumber: new text token number of first token in block
.oldStart: old text token index of first token in block
.count number of token in block
.chars: char length of block
.type: 'same', 'del', 'ins'
Line 75 ⟶ 80:
blockStart: first block index of group
blockEnd: last block index of group
maxWords: word count of longest
words: word count of group
chars: char count of group
Line 85 ⟶ 90:
*/
// JSHint options: W004: is already defined, W097: Use the function form of "use strict", W100: This character may get silently deleted by one or more browsers
/* jshint -W004, -W097, -W100, newcap: false, browser: true, jquery: true, sub: true, bitwise: true, curly: false, evil: true, forin: true, freeze: true, immed: true, latedef: true, loopfunc: true, quotmark: single, undef: true */
/* global console */
// turn on ECMAScript 5 strict mode
'use strict';
// define global object
var wDiff; if (wDiff === undefined) { wDiff = {}; }
var WED;
//
Line 95 ⟶ 106:
//
if (
if (
if (
if (
if (
if (
'background-color: #ffff60;',
'background-color: #c0ff60;',
Line 111 ⟶ 122:
'background-color: #a0e8a0;'
]; }
if (
if (
if (
'color: #ffff60;',
'color: #c0ff60;',
Line 124 ⟶ 135:
'color: #90d090;'
]; }
if (
if (
if (
//
Line 132 ⟶ 143:
//
if (
if (
if (
if (
//
Line 143 ⟶ 154:
// {block} and {mark} are replaced by block number color style, {title} is replaced by title attribute (popup)
// class plus html comment are required indicators for wDiff.ShortenOutput()
if (
if (
if (
if (
if (
if (
if (
if (
if (
if (
if (
if (
if (
if (
//
Line 168 ⟶ 179:
//
if (
if (
if (
if (
if (
//
Line 180 ⟶ 191:
// enable block move layout with color coded blocks and marks at their original position
if (
// minimal number of real words for a moved block (0 for always showing color coded blocks)
if (
// further resolve replacements character-wise from start and end
if (
// enable recursive diff to resolve problematic sequences
if (
// UniCode letter support for regexps, from http://xregexp.com/addons/unicode/unicode-base.js v1.0.0
if (
// regExp for splitting into paragraphs after newline
if (
// regExp for splitting into sentences after .spaces or before newline
if (
// regExp for splitting into words, multi-char markup, and chars
if (
// regExp for splitting into chars
if (
// regExps for bubbling up gaps
if (
if (
// regExp for counting words
if (wDiff.regExpWordCount === undefined) { wDiff.regExpWordCount = new RegExp('(^|[^' + wDiff.letters + '])[' + wDiff.letters + '][' + wDiff.letters + '_\'’]*', 'g'); }
Line 216 ⟶ 230:
// characters before diff tag to search for previous heading, paragraph, line break, cut characters
if (
if (
if (
if (
if (
if (
if (
// characters after diff tag to search for next heading, paragraph, line break, or characters
if (
if (
if (
if (
if (
if (
if (
// lines before and after diff tag to search for previous heading, paragraph, line break, cut characters
if (
if (
// maximal fragment distance to join close fragments
if (
if (
// wDiff.Init: initialize wDiff
// called from: on code load
// calls: wDiff.AddStyleSheet()
wDiff.Init = function () {
// compatibility fixes for old names of functions
Line 255 ⟶ 268:
// shortcut to wikEd.Debug()
if (
if (typeof console == 'object') {
}
else {
}
}
Line 277 ⟶ 290:
// returns: diff html code, call wDiff.ShortenOutput() for shortening this output
wDiff.Diff = function (oldString, newString) {
var diff = '';
Line 304 ⟶ 317:
// trap trivial changes: no change
if (oldString == newString) {
text.diff = wDiff.HtmlEscape(newString);
wDiff.HtmlFormat(text);
return text.diff;
Line 310 ⟶ 323:
// trap trivial changes: old text deleted
if ( (oldString === null) || (oldString.length === 0) ) {
text.diff = wDiff.htmlInsertStart + wDiff.HtmlEscape(newString) + wDiff.htmlInsertEnd;
wDiff.HtmlFormat(text);
Line 317 ⟶ 330:
// trap trivial changes: new text deleted
if ( (newString === null) || (newString.length === 0) ) {
text.diff = wDiff.htmlDeleteStart + wDiff.HtmlEscape(oldString) + wDiff.htmlDeleteEnd;
wDiff.HtmlFormat(text);
Line 349 ⟶ 362:
// split tokens into chars in selected unresolved gaps
if (wDiff.charDiff === true) {
wDiff.SplitRefineChars(text);
Line 381 ⟶ 394:
// called from: wDiff.Diff()
wDiff.Split = function (text, regExp, token) {
var prev = null;
Line 390 ⟶ 403:
// split full text or specified token
if (token ===
string = text.string;
}
Line 402 ⟶ 415:
var number = 0;
var regExpMatch;
while ( (regExpMatch = regExp.exec(string)) !== null) {
// insert current item, link to previous
Line 411 ⟶ 424:
link: null,
number: null,
parsed: false
};
number ++;
// link previous item to current
if (prev !== null) {
text.tokens[prev].next = current;
}
Line 424 ⟶ 437:
// connect last new item and existing next item
if ( (number > 0) && (token !==
if (prev !== null) {
text.tokens[prev].next = next;
}
if (next !== null) {
text.tokens[next].prev = prev;
}
Line 437 ⟶ 450:
// initial text split
if (token ===
text.first = 0;
text.last = prev;
Line 461 ⟶ 474:
// calls: wDiff.Split()
wDiff.SplitRefine = function (text, regExp) {
// cycle through tokens list
var i = text.first;
while ( (i !== null) && (text.tokens[i] !== null) ) {
// refine unique unmatched tokens into smaller tokens
if (text.tokens[i].link === null) {
wDiff.Split(text, regExp, i);
}
Line 494 ⟶ 507:
// refine words into chars in selected gaps
wDiff.SplitRefineChars = function (text) {
//
Line 505 ⟶ 518:
var i = text.newText.first;
var j = text.oldText.first;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
// get
var newLink = text.newText.tokens[i].link;
var oldLink = null;
if (j !== null) {
oldLink = text.oldText.tokens[j].link;
}
// start of gap in new and old
if ( (gap === null) && (newLink === null) && (oldLink === null) ) {
gap = gaps.length;
gaps.push({
Line 529 ⟶ 542:
// count chars and tokens in gap
else if ( (gap !== null) && (newLink === null) ) {
gaps[gap].newLast = i;
gaps[gap].newTokens ++;
Line 535 ⟶ 548:
// gap ended
else if ( (gap !== null) && (newLink !== null) ) {
gap = null;
}
// next list elements
if (newLink !== null) {
j = text.oldText.tokens[newLink].next;
}
Line 551 ⟶ 564:
// cycle trough old text tokens list
var j = gaps[gap].oldFirst;
while ( (j !== null) && (text.oldText.tokens[j] !== null) && (text.oldText.tokens[j].link === null) ) {
// count old chars and tokens in gap
Line 590 ⟶ 603:
var i = gaps[gap].newFirst;
var j = gaps[gap].oldFirst;
while (i !== null) {
var newToken = text.newText.tokens[i].token;
var oldToken = text.oldText.tokens[j].token;
Line 660 ⟶ 673:
// do not split into chars this gap
charSplit = false;
break;
}
}
Line 679 ⟶ 692:
for (var gap = 0; gap < gaps.length; gap ++) {
if (gaps[gap].charSplit === true) {
// cycle trough new text tokens list
var i = gaps[gap].newFirst;
var j = gaps[gap].oldFirst;
while (i !== null) {
var newToken = text.newText.tokens[i].token;
var oldToken = text.oldText.tokens[j].token;
Line 721 ⟶ 734:
// called from: wDiff.Diff()
wDiff.BubbleUpGaps = function (text, textLinked) {
// cycle through tokens list
var i = text.first;
var gapStart = null;
while ( (i !== null) && (text.tokens[i] !== null) ) {
// remember gap start
if ( (gapStart === null) && (text.tokens[i].link === null) ) {
gapStart = i;
}
// find gap end
else if ( (gapStart !== null) && (text.tokens[i].link !== null) ) {
// bubble up, stop at line breaks
Line 740 ⟶ 753:
var back = text.tokens[i].prev;
while (
(front !== null) && (back !== null) && (wDiff.regExpBubbleStop.test(text.tokens[front].token) === false) &&
(text.tokens[front].link !== null) && (text.tokens[back].link === null) &&
(text.tokens[front].token == text.tokens[back].token)
) {
Line 750 ⟶ 763:
back = text.tokens[back].prev;
}
// do not start gap with spaces or other closing characters, roll back (bubble down)
if ( (back !== null) && (front !== null) ) {
front = text.tokens[front].next;
back = text.tokens[back].next;
}
while (
(back !== null) && (front !== null) && (wDiff.regExpBubbleClosing.test(text.tokens[front].token) === true) &&
(text.tokens[front].link === null) && (text.tokens[back].link !== null) &&
(text.tokens[front].token === text.tokens[back].token)
) {
text.tokens[front].link = text.tokens[back].link;
Line 779 ⟶ 792:
// called from: wDiff.Diff()
wDiff.EnumerateTokens = function (text) {
// enumerate tokens list
var number = 0;
var i = text.first;
while ( (i !== null) && (text.tokens[i] !== null) ) {
text.tokens[i].number = number;
number ++;
Line 806 ⟶ 819:
// recursively diff still unresolved regions upwards
wDiff.CalculateDiff = function (text, recurse, newStart, newEnd, oldStart, oldEnd, recursionLevel) {
// symbol (token) data
Line 813 ⟶ 826:
// set defaults
if (
if (
if (
if (
if (
// limit recursion depth
Line 830 ⟶ 843:
// cycle trough new text tokens list
var i = newStart;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
// parse token only once during split refinement
if ( (text.newText.tokens[i].parsed === false) || (recursionLevel > 0) ) {
text.newText.tokens[i].parsed = true;
// add new entry to symbol table
var token = text.newText.tokens[i].token;
if (Object.prototype.hasOwnProperty.call(symbols, token) === false) {
var current = symbol.length;
symbols[token] = current;
Line 871 ⟶ 884:
// cycle trough old text tokens list
var j = oldStart;
while ( (j !== null) && (text.oldText.tokens[j] !== null) ) {
// parse token only once during split refinement
if ( (text.oldText.tokens[j].parsed === false) || (recursionLevel > 0) ) {
text.oldText.tokens[j].parsed = true;
// add new entry to symbol table
var token = text.oldText.tokens[j].token;
if (Object.prototype.hasOwnProperty.call(symbols, token) === false) {
var current = symbol.length;
symbols[token] = current;
Line 903 ⟶ 916:
// next list element
if (j === oldEnd) {
break;
}
Line 922 ⟶ 935:
// do not use spaces as unique markers
if (/^\s+$/.test(text.newText.tokens[newToken].token) === false) {
// connect from new to old and from old to new
if (text.newText.tokens[newToken].link === null) {
text.newText.tokens[newToken].link = oldToken;
text.oldText.tokens[oldToken].link = newToken;
Line 939 ⟶ 952:
// cycle trough new text tokens list
var i = text.newText.first;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
var iNext = text.newText.tokens[i].next;
// find already connected pairs
var j = text.newText.tokens[i].link;
if (j !== null) {
var jNext = text.oldText.tokens[j].next;
// check if the following tokens are not yet connected
if ( (iNext !== null) && (jNext !== null) ) {
if ( (text.newText.tokens[iNext].link === null) && (text.oldText.tokens[jNext].link === null) ) {
// connect if the following tokens are the same
Line 968 ⟶ 981:
// cycle trough new text tokens list
var i = text.newText.last;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
var iNext = text.newText.tokens[i].prev;
// find already connected pairs
var j = text.newText.tokens[i].link;
if (j !== null) {
var jNext = text.oldText.tokens[j].prev;
// check if the preceeding tokens are not yet connected
if ( (iNext !== null) && (jNext !== null) ) {
if ( (text.newText.tokens[iNext].link === null) && (text.oldText.tokens[jNext].link === null) ) {
// connect if the preceeding tokens are the same
Line 992 ⟶ 1,005:
// refine by recursively diffing unresolved regions caused by addition of common tokens around sequences of common tokens, only at word level split
if ( (recurse === true) && (wDiff.recursiveDiff === true) ) {
//
Line 1,002 ⟶ 1,015:
var j = oldStart;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
// get j from previous tokens match
var iPrev = text.newText.tokens[i].prev;
if (iPrev !== null) {
var jPrev = text.newText.tokens[iPrev].link;
if (jPrev !== null) {
j = text.oldText.tokens[jPrev].next;
}
Line 1,014 ⟶ 1,027:
// check for the start of an unresolved sequence
if ( (j !== null) && (text.oldText.tokens[j] !== null) && (text.newText.tokens[i].link === null) && (text.oldText.tokens[j].link === null) ) {
// determine the limits of of the unresolved new sequence
Line 1,021 ⟶ 1,034:
var iLength = 0;
var iNext = i;
while ( (iNext !== null) && (text.newText.tokens[iNext].link === null) ) {
iEnd = iNext;
iLength ++;
Line 1,035 ⟶ 1,048:
var jLength = 0;
var jNext = j;
while ( (jNext !== null) && (text.oldText.tokens[jNext].link === null) ) {
jEnd = jNext;
jLength ++;
Line 1,069 ⟶ 1,082:
var i = newEnd;
var j = oldEnd;
while ( (i !== null) && (text.newText.tokens[i] !== null) ) {
// get j from next matched tokens
var iPrev = text.newText.tokens[i].next;
if (iPrev !== null) {
var jPrev = text.newText.tokens[iPrev].link;
if (jPrev !== null) {
j = text.oldText.tokens[jPrev].prev;
}
Line 1,081 ⟶ 1,094:
// check for the start of an unresolved sequence
if ( (j !== null) && (text.oldText.tokens[j] !== null) && (text.newText.tokens[i].link === null) && (text.oldText.tokens[j].link === null) ) {
// determine the limits of of the unresolved new sequence
Line 1,088 ⟶ 1,101:
var iLength = 0;
var iNext = i;
while ( (iNext !== null) && (text.newText.tokens[iNext].link === null) ) {
iStart = iNext;
iLength ++;
Line 1,102 ⟶ 1,115:
var jLength = 0;
var jNext = j;
while ( (jNext !== null) && (text.oldText.tokens[jNext].link === null) ) {
jStart = jNext;
jLength ++;
Line 1,138 ⟶ 1,151:
// blocks: empty array for block data
// groups: empty array for group data
// changes: text, blocks, groups
// called from: wDiff.Diff()
// scheme of blocks, sections, and groups (old block numbers):
// old: 1 2 3D4 5E6 7 8 9 10 11
Line 1,165 ⟶ 1,162:
// type: = + =-= = -= = + = = = = =
wDiff.DetectBlocks = function (text, blocks, groups) {
// WED('text.oldText', wDiff.DebugText(text.oldText));
// WED('text.newText', wDiff.DebugText(text.newText));
// collect identical corresponding ('same') blocks from old text and sort by new text
wDiff.GetSameBlocks(text, blocks);
// collect independent block sections (no old/new crosses outside section) for per-section determination of non-moving (fixed) groups
var sections = [];
wDiff.GetSections(blocks, sections);
// find groups of continuous old text blocks
wDiff.GetGroups(blocks, groups);
// convert groups to insertions/deletions if maximal block length is too short
if ( (wDiff.blockMinLength > 0) && (wDiff.UnlinkBlocks(text, blocks, groups) === true) ) {
// repeat from start after conversion to insertions/deletions
wDiff.GetSameBlocks(text, blocks);
wDiff.GetSections(blocks, sections);
wDiff.GetGroups(blocks, groups);
}
// set longest sequence of increasing groups in sections as fixed (not moved)
wDiff.SetFixed(blocks, groups, sections);
// collect deletion ('del') blocks from old text
wDiff.GetDelBlocks(text, blocks);
// position 'del' blocks into new text order
wDiff.PositionDelBlocks(blocks);
// collect insertion ('ins') blocks from new text
wDiff.GetInsBlocks(text, blocks);
// sort blocks by new text token number and update groups
wDiff.SortBlocks(blocks, groups);
// set group numbers of 'ins' and 'del' blocks
wDiff.SetInsDelGroups(blocks, groups);
// mark original positions of moved groups
wDiff.MarkMoved(groups);
// set moved block colors
wDiff.ColorMoved(groups);
// WED('Groups', wDiff.DebugGroups(groups));
// WED('Blocks', wDiff.DebugBlocks(blocks));
return;
};
// wDiff.GetSameBlocks: collect identical corresponding ('same') blocks from old text and sort by new text
// called from: DetectBlocks()
// changes: creates blocks
wDiff.GetSameBlocks = function (text, blocks) {
// clear blocks array
blocks.splice(0);
// cycle through old text to find matched (linked) blocks
var j = text.oldText.first;
var i = null;
while (j !== null) {
//
while ( (j !== null) && (text.oldText.tokens[j].link === null) ) {
j = text.oldText.tokens[j].next;
}
// get 'same' block
if (j !== null) {
i = text.oldText.tokens[j].link;
var iStart = i;
Line 1,206 ⟶ 1,243:
// detect matching blocks ('same')
var count = 0;
var chars = 0;
var string = '';
while ( (i !== null) && (j !== null) && (text.oldText.tokens[j].link == i) ) {
var token = text.oldText.tokens[j].token;
count ++;
chars += token.length;
string += token;
Line 1,219 ⟶ 1,258:
blocks.push({
oldBlock: blocks.length,
newBlock: null,
oldNumber: text.oldText.tokens[jStart].number,
newNumber: text.newText.tokens[iStart].number,
oldStart: jStart,
count: count,
chars: chars,
type: 'same',
Line 1,231 ⟶ 1,273:
}
// sort blocks by new text token number
blocks.sort(function(a, b) {
return a.newNumber - b.newNumber;
});
// number blocks in new text order
for (var block = 0; block < blocks.length; block ++) {
blocks[block].newBlock = block;
}
return;
};
// wDiff.GetSections: collect independent block sections (no old/new crosses outside section) for per-section determination of non-moving (fixed) groups
// called from: DetectBlocks()
// changes: creates sections, blocks[].section
wDiff.GetSections = function (blocks, sections) {
// clear sections array
sections.splice(0);
// cycle through blocks
for (var block = 0; block < blocks.length; block ++) {
Line 1,280 ⟶ 1,329:
blockStart: sectionStart,
blockEnd: sectionEnd,
deleted: false
});
block = sectionEnd;
}
}
return;
};
// wDiff.GetGroups: find groups of continuous old text blocks
// called from: DetectBlocks()
// changes: creates groups, blocks[].group
wDiff.GetGroups = function (blocks, groups) {
// clear groups array
groups.splice(0);
// cycle through blocks
for (var block = 0; block < blocks.length; block ++) {
if (blocks[block].deleted === true) {
continue;
}
var groupStart = block;
var groupEnd = block;
var oldBlock = blocks[groupStart].oldBlock;
// get word and char count of block
var words = wDiff.WordCount(blocks[block].string
var maxWords = words;
var chars = blocks[block].chars;
// check right
Line 1,315 ⟶ 1,371:
// get word and char count of block
var blockWords = wDiff.WordCount(blocks[i].string
if (blockWords > maxWords) {
maxWords = blockWords;
Line 1,321 ⟶ 1,377:
words += blockWords;
chars += blocks[i].chars;
groupEnd = i;
}
// save crossing
if
// set groups outside sections as fixed
var fixed = false;
if (blocks[groupStart].section === null) {
fixed = true;
}
Line 1,346 ⟶ 1,400:
blockStart: groupStart,
blockEnd: groupEnd,
maxWords: maxWords,
words: words,
chars: chars,
fixed: fixed,
Line 1,358 ⟶ 1,412:
}
}
return;
};
// wDiff.UnlinkBlocks: remove 'same' blocks in groups of continuous old text blocks if too short
// called from: DetectBlocks()
// changes: text.newText/oldText[].link
// returns: true if text tokens were unlinked
wDiff.UnlinkBlocks = function (text, blocks, groups) {
var unlinked = false;
// cycle through groups
for (var group = 0; group < groups.length; group ++) {
if ( (groups[group].maxWords < wDiff.blockMinLength) && (groups[group].fixed === false) ) {
var blockStart = groups[group].blockStart;
var blockEnd = groups[group].blockEnd;
// cycle through blocks
for (var block = blockStart; block <= blockEnd; block ++) {
// cycle through old text
var j = blocks[block].oldStart;
for (var count = 0; count < blocks[block].count; count ++) {
// unlink tokens
text.newText.tokens[ text.oldText.tokens[j].link ].link = null;
text.oldText.tokens[j].link = null;
j = text.oldText.tokens[j].next;
}
unlinked = true;
}
}
}
return unlinked;
};
// wDiff.SetFixed: set longest sequence of increasing groups in sections as fixed (not moved)
// called from: DetectBlocks()
// calls: wDiff.FindMaxPath()
// changes: groups[].fixed, blocks[].fixed
wDiff.SetFixed = function (blocks, groups, sections) {
// cycle through sections
Line 1,381 ⟶ 1,476:
if (pathObj.chars > maxChars) {
maxPath = pathObj.path;
maxChars = pathObj.chars;
}
}
Line 1,388 ⟶ 1,483:
for (var i = 0; i < maxPath.length; i ++) {
var group = maxPath[i];
groups[group].fixed = true;
// mark fixed blocks
Line 1,396 ⟶ 1,491:
}
}
return;
};
// wDiff.FindMaxPath: recusively find path of groups in increasing old group order with longest char length
// input: start, path start group; path, array of path groups; chars, char count of path; cache, cached sub-path lengths; groups, groups, group object; groupEnd, last group
// returns: returnObj, contains path and length
// called from: wDiff.SetFixed()
// calls: itself recursively
wDiff.FindMaxPath = function (start, path, chars, cache, groups, groupEnd) {
// add current path point
var pathLocal = path.slice();
pathLocal.push(start);
chars = chars + groups[start].chars;
// last group, terminate recursion
var returnObj = { path: pathLocal, chars: chars };
if (start == groupEnd) {
return returnObj;
}
// find longest sub-path
var maxChars = 0;
var oldNumber = groups[start].oldNumber;
for (var i = start + 1; i <= groupEnd; i ++) {
// only in increasing old group order
if (groups[i].oldNumber < oldNumber) {
continue;
}
// get longest sub-path from cache
if (
}
// get longest sub-path by recursion
else {
var pathObj = wDiff.FindMaxPath(i, pathLocal, chars, cache, groups, groupEnd);
// select longest sub-path
if (pathObj.chars > maxChars) {
returnObj = pathObj;
}
}
}
if (cache[i] === undefined) {
cache[start] = returnObj;
}
return returnObj;
};
// wDiff.GetDelBlocks: collect deletion ('del') blocks from old text
// called from: DetectBlocks()
// changes: blocks
wDiff.GetDelBlocks = function (text, blocks) {
// cycle through old text to find matched (linked) blocks
var j = text.oldText.first;
var i = null;
while (j !== null) {
// collect 'del' blocks
var oldStart = j;
var count = 0;
var string = '';
while ( (j !== null) && (text.oldText.tokens[j].link === null) ) {
count ++;
string += text.oldText.tokens[j].token;
j = text.oldText.tokens[j].next;
}
// save old text 'del' block
if (count !== 0) {
blocks.push({
oldBlock: null,
newNumber: null,
oldStart: oldStart,
count: count,
chars: null,
type: '
section: null,
group: null,
Line 1,431 ⟶ 1,585:
string: string
});
}
// skip 'same' block
if (j !== null) {
i = text.oldText.tokens[j].link;
while ( (i !== null) && (j !== null) && (text.oldText.tokens[j].link == i) ) {
i = text.newText.tokens[i].next;
j = text.oldText.tokens[j].next;
}
}
}
return;
};
// called from: DetectBlocks()
// changes: blocks[].section/group/fixed/newNumber
//
// deletion blocks move with fixed neighbor (new number +/- 0.1):
// old: 1 D 2 1 D 2
// / / \ / \ \
// new: 1 D 2 1 D 2
// fixed: * *
// new number: 1 1.1 1.9 2
wDiff.PositionDelBlocks = function (blocks) {
// sort shallow copy of blocks by oldNumber
blocksOld.sort(function(a, b) {
return a.oldNumber - b.oldNumber;
});
// cycle through 'del' blocks in old text order
for (var blockOld = 0; blockOld < blocksOld.length; blockOld ++) {
var delBlock = blocksOld[blockOld];
if (delBlock.type != 'del') {
continue;
}
// get old text prev block
var prevBlock
if (
prevBlock =
}
// get old text next block
var nextBlock;
if (blockOld < blocksOld.length - 1) {
nextBlock = blocks[ blocksOld[blockOld + 1].newBlock ];
}
// move after prev block if fixed
var neighbor
if ( (prevBlock !==
neighbor =
delBlock.newNumber = neighbor.newNumber + 0.
}
// move before next block if fixed
else if ( (nextBlock !==
neighbor =
delBlock.newNumber = neighbor.newNumber - 0.
}
// move after prev block if existent
else if (prevBlock !==
neighbor =
delBlock.newNumber = neighbor.newNumber + 0.
}
// move before next block
else if (nextBlock !==
neighbor =
delBlock.newNumber = neighbor.newNumber - 0.
}
// move before first block
else {
delBlock.newNumber = -0.
}
//
if (neighbor !== undefined) {
delBlock.section = neighbor.section;
delBlock.group = neighbor.group;
delBlock.fixed = neighbor.fixed;
}
}
return;
};
// wDiff.GetInsBlocks: collect insertion ('ins') blocks from new text
// called from: DetectBlocks()
// changes: blocks
wDiff.GetInsBlocks = function (text, blocks) {
// cycle through new text to find insertion blocks
var i = text.newText.first;
while (i !== null) {
// jump over linked (matched) block
while ( (i !== null) && (text.newText.tokens[i].link !== null) ) {
i = text.newText.tokens[i].next;
}
// detect insertion blocks ('ins')
if (i !== null) {
var iStart = i;
var count = 0;
var string = '';
while ( (i !== null) && (text.newText.tokens[i].link === null) ) {
count ++;
string += text.newText.tokens[i].token;
i = text.newText.tokens[i].next;
}
// save new text 'ins' block
blocks.push({
oldBlock: null,
newBlock: null,
oldNumber: null,
newNumber: text.newText.tokens[iStart].number,
oldStart: null,
count: count,
chars: null,
type: 'ins',
section: null,
group: null,
fixed: null,
string: string
});
}
}
return;
};
// called from: DetectBlocks()
// changes: blocks
wDiff.SortBlocks = function (blocks, groups) {
// sort by newNumber
Line 1,538 ⟶ 1,741:
for (var block = 0; block < blocks.length; block ++) {
var blockGroup = blocks[block].group;
if (blockGroup !== null) {
if (blockGroup != group) {
group = blocks[block].group;
Line 1,544 ⟶ 1,747:
groups[group].oldNumber = blocks[block].oldNumber;
}
groups[blockGroup].blockEnd = block;
}
}
return;
};
// wDiff.SetInsDelGroups: set group numbers of 'ins' and 'del' blocks
// called from: DetectBlocks()
// changes: groups, blocks[].fixed/group
wDiff.SetInsDelGroups = function (blocks, groups) {
// set group numbers of 'ins' and 'del' blocks inside existing groups
for (var group = 0; group < groups.length; group ++) {
var fixed = groups[group].fixed;
for (var block = groups[group].blockStart; block <= groups[group].blockEnd; block ++) {
if (blocks[block].group === null) {
blocks[block].group = group;
blocks[block].fixed = fixed;
Line 1,562 ⟶ 1,771:
}
// add remaining 'ins' and 'del' blocks to groups
// cycle through blocks
Line 1,570 ⟶ 1,777:
// skip existing groups
if (blocks[block].group === null) {
blocks[block].group = groups.length;
var fixed = blocks[block].fixed;
Line 1,590 ⟶ 1,797:
}
}
return;
};
// changes: groups[].moved/movedFrom
// moved block marks at original positions relative to fixed groups:
// | >| 5
// | |> 9 (no next larger fixed)
// fixed: * *
// mark direction: groups[movedGroup].blockStart < groups[group].blockStart
// group side: groups[movedGroup].oldNumber < groups[group].oldNumber
wDiff.MarkMoved = function (groups) {
// cycle through groups (moved group)
for (var movedGroup = 0; movedGroup < groups.length; movedGroup ++) {
if (groups[movedGroup].fixed !== false) {
continue;
}
Line 1,622 ⟶ 1,833:
// cycle through groups (original positions)
for (var group = 0; group < groups.length; group ++) {
if ( (groups[group].fixed !== true) || (group == movedGroup) ) {
continue;
}
Line 1,628 ⟶ 1,839:
// find fixed group with closest smaller oldNumber
var oldNumber = groups[group].oldNumber;
if ( (oldNumber < movedOldNumber) && ( (nextSmallerNumber === null) || (oldNumber > nextSmallerNumber) ) ) {
nextSmallerNumber = oldNumber;
nextSmallerGroup = group;
Line 1,634 ⟶ 1,845:
// find fixed group with closest larger oldNumber
if ( (oldNumber > movedOldNumber) && ( (nextLargerNumber === null) || (oldNumber < nextLargerNumber) ) ) {
nextLargerNumber = oldNumber;
nextLargerGroup = group;
Line 1,642 ⟶ 1,853:
// no larger fixed group, moved right
var movedFrom = '';
if (nextLargerGroup === null) {
movedFrom = 'left';
}
// no smaller fixed group, moved right
else if (nextSmallerGroup === null) {
movedFrom = 'right';
}
Line 1,691 ⟶ 1,902:
for (var group = 0; group < groups.length; group ++) {
var moved = groups[group].moved;
if (moved !== null) {
moved.sort(function(a, b) {
return groups[a].oldNumber - groups[b].oldNumber;
Line 1,697 ⟶ 1,908:
}
}
return;
};
// called from: DetectBlocks()
// changes: groups[].color
wDiff.ColorMoved = function (groups) {
// cycle through groups
Line 1,717 ⟶ 1,933:
for (var i = 0; i < moved.length; i ++) {
var movedGroup = moved[i];
if
groups[movedGroup].color = color;
color ++;
}
}
return;
};
Line 1,790 ⟶ 1,948:
// calls: wDiff.HtmlCustomize(), wDiff.HtmlFormat()
wDiff.AssembleDiff = function (text, blocks, groups) {
//
Line 1,806 ⟶ 1,964:
// check for colored block and move direction
var blockFrom = null;
if ( (fixed === false) && (color !== null) ) {
if (groups[ groups[group].movedFrom ].blockStart < blockStart) {
blockFrom = 'left';
Line 1,830 ⟶ 1,988:
// html escape text string
string = wDiff.HtmlEscape(string);
// add 'same' (unchanged) text
diff += string;
}
Line 1,881 ⟶ 2,031:
for (var i = 0; i < moved.length; i ++) {
var movedGroup = moved[i];
var markColor = groups[movedGroup].color;
var mark = '';
Line 1,893 ⟶ 2,043:
// moved block too small, make it a deletion at its original position
if
mark = wDiff.htmlDeleteStart + wDiff.HtmlEscape(movedText) + wDiff.htmlDeleteEnd;
}
Line 1,952 ⟶ 2,102:
// called from: wDiff.AssembleDiff()
wDiff.HtmlCustomize = function (text, number, title) {
text = text.replace(/\{block\}/, wDiff.styleBlockColor[number] || '');
Line 1,958 ⟶ 2,108:
// shorten title text, replace {title}
if ( (title !==
var max = 512;
var end = 128;
Line 1,980 ⟶ 2,130:
// called from: wDiff.Diff(), wDiff.AssembleDiff()
wDiff.HtmlEscape = function (text) {
text = text.replace(/&/g, '&');
Line 1,995 ⟶ 2,145:
// called from: wDiff.Diff(), wDiff.AssembleDiff()
wDiff.HtmlFormat = function (text) {
text.diff = text.diff.replace(/<\/(\w+)><!--wDiff(Delete|Insert)--><\1\b[^>]*\bclass="wDiff\2"[^>]*>/g, '');
Line 2,008 ⟶ 2,158:
// returns: shortened html with removed unchanged passages indicated by (...) or separator
wDiff.ShortenOutput = function (html) {
var diff = '';
// empty text
if ( (html ===
return '';
}
Line 2,021 ⟶ 2,171:
html = html.replace(wDiff.htmlFragmentStart, '');
html = html.replace(wDiff.htmlFragmentEnd, '');
html = html.replace(wDiff.htmlContainerEnd, '');
// scan for diff html tags
var regExpDiff = /<\w+\b[^>]*\bclass="wDiff(MarkLeft|MarkRight|BlockLeft|BlockRight|Delete|Insert)"[^>]*>(.|\n)*?<!--wDiff\1-->/g;
Line 2,031 ⟶ 2,181:
// save tag positions
while ( (regExpMatch = regExpDiff.exec(html)) !== null ) {
// combine consecutive diff tags
Line 2,045 ⟶ 2,195:
// no diff tags detected
if (tagStart.length === 0) {
return wDiff.htmlNoChange;
}
Line 2,069 ⟶ 2,219:
} while (pos != -1);
lineBreaks.push(html.length);
// cycle through diff tag start positions
for (var i = 0; i < tagStart.length; i ++) {
Line 2,091 ⟶ 2,241:
}
regExpHeading.lastIndex = lastPos;
while ( (regExpMatch = regExpHeading.exec(html)) !== null ) {
if (regExpMatch.index > tagStart[i]) {
break;
Line 2,100 ⟶ 2,250:
// find last paragraph before diff tag
if (rangeStart[i] ===
lastPos = tagStart[i] - wDiff.paragraphBefore;
if (lastPos < rangeStartMin) {
Line 2,106 ⟶ 2,256:
}
regExpParagraph.lastIndex = lastPos;
while ( (regExpMatch = regExpParagraph.exec(html)) !== null) {
if (regExpMatch.index > tagStart[i]) {
break;
Line 2,116 ⟶ 2,266:
// find last line break before diff tag
if (rangeStart[i] ===
lastPos = tagStart[i] - wDiff.lineBeforeMax;
if (lastPos < rangeStartMin) {
Line 2,122 ⟶ 2,272:
}
regExpLine.lastIndex = lastPos;
while ( (regExpMatch = regExpLine.exec(html)) !== null ) {
if (regExpMatch.index > tagStart[i] - wDiff.lineBeforeMin) {
break;
Line 2,132 ⟶ 2,282:
// find last blank before diff tag
if (rangeStart[i] ===
lastPos = tagStart[i] - wDiff.blankBeforeMax;
if (lastPos < rangeStartMin) {
Line 2,138 ⟶ 2,288:
}
regExpBlank.lastIndex = lastPos;
while ( (regExpMatch = regExpBlank.exec(html)) !== null ) {
if (regExpMatch.index > tagStart[i] - wDiff.blankBeforeMin) {
break;
Line 2,148 ⟶ 2,298:
// fixed number of chars before diff tag
if (rangeStart[i] ===
if (rangeStart[i] > rangeStartMin) {
rangeStart[i] = tagStart[i] - wDiff.charsBefore;
Line 2,156 ⟶ 2,306:
// fixed number of lines before diff tag
if (rangeStart[i] ===
rangeStart[i] = rangeStartMin;
rangeStartType[i] = 'lines';
Line 2,175 ⟶ 2,325:
// find first heading after diff tag
regExpHeading.lastIndex = tagEnd[i];
if ( (regExpMatch = regExpHeading.exec(html)) !== null ) {
if ( (regExpMatch.index < tagEnd[i] + wDiff.headingAfter) && (regExpMatch.index < rangeEndMax) ) {
rangeEnd[i] = regExpMatch.index + regExpMatch[0].length;
Line 2,183 ⟶ 2,333:
// find first paragraph after diff tag
if (rangeEnd[i] ===
regExpParagraph.lastIndex = tagEnd[i];
if ( (regExpMatch = regExpParagraph.exec(html)) !== null ) {
if ( (regExpMatch.index < tagEnd[i] + wDiff.paragraphAfter) && (regExpMatch.index < rangeEndMax) ) {
rangeEnd[i] = regExpMatch.index;
Line 2,194 ⟶ 2,344:
// find first line break after diff tag
if (rangeEnd[i] ===
regExpLine.lastIndex = tagEnd[i] + wDiff.lineAfterMin;
if ( (regExpMatch = regExpLine.exec(html)) !== null ) {
if ( (regExpMatch.index < tagEnd[i] + wDiff.lineAfterMax) && (regExpMatch.index < rangeEndMax) ) {
rangeEnd[i] = regExpMatch.index;
Line 2,206 ⟶ 2,356:
// find blank after diff tag
if (rangeEnd[i] ===
regExpBlank.lastIndex = tagEnd[i] + wDiff.blankAfterMin;
if ( (regExpMatch = regExpBlank.exec(html)) !== null ) {
if ( (regExpMatch.index < tagEnd[i] + wDiff.blankAfterMax) && (regExpMatch.index < rangeEndMax) ) {
rangeEnd[i] = regExpMatch.index;
Line 2,217 ⟶ 2,367:
// fixed number of chars after diff tag
if (rangeEnd[i] ===
if (rangeEnd[i] < rangeEndMax) {
rangeEnd[i] = tagEnd[i] + wDiff.charsAfter;
Line 2,223 ⟶ 2,373:
}
}
// fixed number of lines after diff tag
if (rangeEnd[i] ===
rangeEnd[i] = rangeEndMax;
rangeEndType[i] = 'lines';
Line 2,242 ⟶ 2,392:
var j = 1;
for (var i = 1; i < rangeStart.length; i ++) {
// get lines between fragments
var lines = 0;
Line 2,313 ⟶ 2,463:
//
wDiff.AddStyleSheet = function (css) {
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet !==
style.styleSheet.cssText = css;
}
Line 2,325 ⟶ 2,475:
document.getElementsByTagName('head')[0].appendChild(style);
return;
};
//
// wDiff.WordCount: count words in string
//
wDiff.WordCount = function (string) {
return (string.match(wDiff.regExpWordCount) || []).length;
};
Line 2,332 ⟶ 2,492:
//
wDiff.DebugText = function (text) {
var dump = 'first: ' + text.first + '\tlast: ' + text.last + '\n';
dump += '\ni \tlink \t(prev \tnext) \t#num \t"token"\n';
var i = text.first;
while ( (i !== null) && (text.tokens[i] !== null) ) {
dump += i + ' \t' + text.tokens[i].link + ' \t(' + text.tokens[i].prev + ' \t' + text.tokens[i].next + ') \t#' + text.tokens[i].number + ' \t' + wDiff.DebugShortenString(text.tokens[i].token) + '\n';
i = text.tokens[i].next;
Line 2,348 ⟶ 2,508:
//
wDiff.DebugBlocks = function (blocks) {
var dump = '\ni \toldBl \tnewBl \toldNm \tnewNm \toldSt \tcount \tchars \ttype \tsect \tgroup \tfixed \tstring\n';
for (var i = 0; i < blocks.length; i ++) {
dump += i + ' \t' + blocks[i].oldBlock + ' \t' + blocks[i].newBlock + ' \t' + blocks[i].oldNumber + ' \t' + blocks[i].newNumber + ' \t' + blocks[i].oldStart + ' \t' + blocks[i].count + ' \t' + blocks[i].chars + ' \t' + blocks[i].type + ' \t' + blocks[i].section + ' \t' + blocks[i].group + ' \t' + blocks[i].fixed + ' \t' + wDiff.DebugShortenString(blocks[i].string) + '\n';
}
return dump;
Line 2,361 ⟶ 2,521:
//
wDiff.DebugGroups = function (groups) {
var dump = '\ni \tblSta \tblEnd \
for (var i = 0; i < groups.length; i ++) {
dump += i + ' \t' + groups[i].blockStart + ' \t' + groups[i].blockEnd + ' \t' + groups[i].maxWords + ' \t' + groups[i].words + ' \t' + groups[i].chars + ' \t' + groups[i].fixed + ' \t' + groups[i].oldNumber + ' \t' + groups[i].movedFrom + ' \t' + groups[i].color + ' \t' + groups[i].moved.toString() + ' \t' + wDiff.DebugShortenString(groups[i].diff) + '\n';
Line 2,374 ⟶ 2,534:
//
wDiff.DebugGaps = function (gaps) {
var dump = '\ni \tnFirs \tnLast \tnTok \toFirs \toLast \toTok \tcharSplit\n';
for (var i = 0; i < gaps.length; i ++) {
Line 2,387 ⟶ 2,547:
//
wDiff.DebugShortenString = function (string) {
if (string === null) {
return 'null';
}
|