User:Cacycle/diff.js: Difference between revisions

Content deleted Content added
1.0.20 (September 14, 2014), paragraph split after double newline, sentence split after newline, bubbling to sliding
1.0.21 (September 14, 2014) fix split regexps (split newlines), css clean up, show blanks on hover, - wDiff.DebugGaps
Line 3:
// ==UserScript==
// @name wDiff
// @version 1.0.2021
// @date September 14, 2014
// @description improved word-based diff library with block move detection
Line 136:
// UniCode letter support for regexps, from http://xregexp.com/addons/unicode/unicode-base.js v1.0.0
if (wDiff.letters === undefined) { wDiff.letters = 'a-zA-Z0-9' + '00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05270531-055605590561-058705D0-05EA05F0-05F20620-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280840-085808A008A2-08AC0904-0939093D09500958-09610971-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E460E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EC60EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10A0-10C510C710CD10D0-10FA10FC-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317D717DC1820-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541AA71B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF11CF51CF61D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209C21022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E218321842C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2CF22CF32D00-2D252D272D2D2D30-2D672D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2E2F300530063031-3035303B303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A66EA67F-A697A6A0-A6E5A717-A71FA722-A788A78B-A78EA790-A793A7A0-A7AAA7F8-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2A9CFAA00-AA28AA40-AA42AA44-AA4BAA60-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADB-AADDAAE0-AAEAAAF2-AAF4AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC'.replace(/(\w{4})/g, '\\u$1'); }
if (wDiff.smallSpaces === undefined) { wDiff.smallSpaces = ' \\u00a0\\u1680​\\u180e\\u2000​\\u2002\\u2004-\\u200a​\\u2028\\u2029​​\\u202f\\u205f​\\u3000'; }
if (wDiff.wideSpaces === undefined) { wDiff.wideSpaces = '\\u2001\\u2003'; }
 
// new line characters without and with '\n' and '\r'
// regExps for splitting text
if (wDiff.newLines === undefined) { wDiff.newLines = '\\u0085\\u2028'; }
if (wDiff.newLinesAll === undefined) { wDiff.newLinesAll = '\\n\\r\\u0085\\u2028'; }
 
// full stops without '.'
if (wDiff.fullStops === undefined) { wDiff.fullStops = '058906D40701070209640DF41362166E180318092CF92CFE2E3C3002A4FFA60EA6F3FE52FF0EFF61'.replace(/(\w{4})/g, '\\u$1'); }
 
// new paragraph characters without '\n' and '\r'
if (wDiff.newParagraph === undefined) { wDiff.newParagraph = '\\u2029'; }
 
// exclamation marks without '!'
if (wDiff.exclamationMarks === undefined) { wDiff.exclamationMarks = '01C301C301C3055C055C07F919441944203C203C20482048FE15FE57FF01'.replace(/(\w{4})/g, '\\u$1'); }
 
// question marks without '?'
if (wDiff.questionMarks === undefined) { wDiff.questionMarks = '037E055E061F13671945204720492CFA2CFB2E2EA60FA6F7FE56FF1F'.replace(/(\w{4})/g, '\\u$1') + '\\u11143'; }
 
// regExps for splitting text (included separators)
if (wDiff.regExpSplit === undefined) {
wDiff.regExpSplit = {
 
// paragraphs: after double newlines
paragraph: /new RegExp('(.|\\n)*?((\\r\\n|\\n|\\r){2,}/|[' + wDiff.newParagraph + '])+', 'g'),
 
// sentences: after newlines and .spaces
sentence: new RegExp('[^' + wDiff.newLinesAll + ']*?([.!?;]+[^\\S' + wDiff.newLinesAll + ']+|[' + wDiff.fullStops + wDiff.exclamationMarks + wDiff.questionMarks + ']+[^\\S' + wDiff.newLinesAll + ']*|[' + wDiff.newLines + ']|\\r\\n|\\n|\\r)', 'g'),
sentence: /.*?(\.\s+|\n)/g,
 
// inline chunks
Line 162 ⟶ 176:
 
// regExps for sliding gaps
if (wDiff.regExpSlideStop === undefined) { wDiff.regExpSlideStop = /new RegExp('[\\n\\r' + wDiff.newLines + ']$/'); }
if (wDiff.regExpSlideClosingregExpSlideBorder === undefined) { wDiff.regExpSlideClosingregExpSlideBorder = /^new RegExp('[\s)\]}>\-–—^' + wDiff.,:;?!’\/\\=letters + ']/$'); }
 
// regExp for counting words
if (wDiff.regExpWordCount === undefined) { wDiff.regExpWordCount = new RegExp('[' + wDiff.letters + ']+([\'’_]?[' + wDiff.letters + ']+)*', 'g'); }
 
// regExp fordetecting wikiblank-only codeand nonsingle-letterchar charactersblocks
if (wDiff.regExpWikiCodeCharsregExpBlankBlock === undefined) { wDiff.regExpWikiCodeCharsregExpBlankBlock = /^([ ^\t\n\[\S]{}+|+[^\-!*#:;=<>'\/_,.&?t]+)$/; }
 
// regExp detecting blank-only blocks
if (wDiff.regExpBlankBlock === undefined) { wDiff.regExpBlankBlock = new RegExp('^([' + wDiff.smallSpaces + ']{0,2}|[' + wDiff.wideSpaces + ']?|[' + wDiff.smallSpaces + ']?\\n([' + wDiff.smallSpaces + ']{0,2}|[' + wDiff.wideSpaces + ']?|[' + wDiff.smallSpaces + ']?\\n)?)$'); }
 
//
Line 214 ⟶ 225:
if (wDiff.stylesheet === undefined) {
wDiff.stylesheet =
 
'.wDiffTab:before { content: "→"; color: #bbb; font-size: smaller; }' +
// insert
'.wDiffNewline:before { content: "¶"; color: transparent; }' +
'.wDiffInsert:hover .wDiffNewline:before { color: #999; }' +
'.wDiffDelete:hover .wDiffNewline:before { color: #aaa; }' +
'.wDiffInsertBlank:hover .wDiffNewline:before { color: #888; }' +
'.wDiffDeleteBlank:hover .wDiffNewline:before { color: #999; }' +
'.wDiffBlockLeft:hover .wDiffNewline:before, .wDiffBlockRight:hover .wDiffNewline:before { color: #ccc; }' +
'.wDiffMarkRight:before { content: "' + wDiff.symbolMarkRight + '"; }' +
'.wDiffMarkLeft:before { content: "' + wDiff.symbolMarkLeft + '"; }' +
'.wDiffDelete { font-weight: bold; background-color: #ffe49c; color: #222; border-radius: 0.25em; padding: 0.2em 1px; }' +
'.wDiffInsert { font-weight: bold; background-color: #bbddff; color: #222; border-radius: 0.25em; padding: 0.2em 1px; }' +
'.wDiffDeleteBlankwDiffInsertBlank { background-color: #ffc84066bbff; }' +
'.wDiffFragment:hover .wDiffInsertBlank { background-color: #66b8ffbbddff; }' +
 
'.wDiffBlockLeft { font-weight: bold; background-color: #e8e8e8; border-radius: 0.25em; padding: 0.2em 1px; margin: 0 1px; }' +
// delete
'.wDiffBlockRight { font-weight: bold; background-color: #e8e8e8; border-radius: 0.25em; padding: 0.2em 1px; margin: 0 1px; }' +
'.wDiffMarkLeft wDiffDelete { font-weight: bold; background-color: #ffe49c; color: #666222; border-radius: 0.25em; padding: 0.2em; margin: 0 1px; }' +
'.wDiffMarkRightwDiffDeleteBlank { font-weight: bold; background-color: #ffe49c; color: #666; border-radius: 0.25em; padding: 0.2em; margin: 0 1pxffd064; }' +
'.wDiffFragment:hover .wDiffDeleteBlank { background-color: #ffe49c; }' +
 
// block
'.wDiffBlockLeft, .wDiffBlockRight { font-weight: bold; background-color: #e8e8e8; border-radius: 0.25em; padding: 0.2em 1px; margin: 0 1px; }' +
'.wDiffBlockHighlight { background-color: #777; color: #fff; border: solid #777; border-width: 1px 0; }' +
'.wDiffBlock { }' +
'.wDiffBlock0 { background-color: #ffff60; }' +
Line 241 ⟶ 249:
'.wDiffBlock7 { background-color: #ffbbbb; }' +
'.wDiffBlock8 { background-color: #a0e8a0; }' +
 
// mark
'.wDiffMarkLeft, .wDiffMarkRight { font-weight: bold; background-color: #ffe49c; color: #666; border-radius: 0.25em; padding: 0.2em; margin: 0 1px; }' +
'.wDiffMarkRight:before { content: "' + wDiff.symbolMarkRight + '"; }' +
'.wDiffMarkLeft:before { content: "' + wDiff.symbolMarkLeft + '"; }' +
'.wDiffMark { }' +
'.wDiffMarkHighlight { background-color: #777; color: #fff; }' +
'.wDiffMark0 { color: #ffff60; }' +
'.wDiffMark1 { color: #c0ff60; }' +
Line 251 ⟶ 265:
'.wDiffMark7 { color: #ff9999; }' +
'.wDiffMark8 { color: #90d090; }' +
 
'.wDiffBlockHighlight { background-color: #777; color: #fff; border: solid #777; border-width: 1px 0; }' +
// wrappers
'.wDiffMarkHighlight { background-color: #777; color: #fff; }' +
'.wDiffContainer { }' +
'.wDiffFragment { white-space: pre-wrap; background: #fff; border: #bbb solid; border-width: 1px 1px 1px 0.5em; border-radius: 0.5em; font-family: sans-serif; font-size: 88%; line-height: 1.6; box-shadow: 2px 2px 2px #ddd; padding: 1em; margin: 0; }' +
'.wDiffNoChange { white-space: pre-wrap; background: #f0f0f0; border: #bbb solid; border-width: 1px 1px 1px 0.5em; border-radius: 0.5em; font-family: sans-serif; font-size: 88%; line-height: 1.6; box-shadow: 2px 2px 2px #ddd; padding: 0.5em; margin: 1em 0; }' +
'.wDiffSeparator { margin-bottom: 1em; }' +
'.wDiffOmittedChars { }'; +
 
// newline
'.wDiffNewline:before { content: "¶"; color: transparent; }' +
'.wDiffBlockHighlight .wDiffNewline:before { color: transparent; }' +
'.wDiffBlockHighlight:hover .wDiffNewline:before { color: #ccc; }' +
'.wDiffBlockHighlight:hover .wDiffInsert .wDiffNewline:before, .wDiffInsert:hover .wDiffNewline:before { color: #999; }' +
'.wDiffBlockHighlight:hover .wDiffDelete .wDiffNewline:before, .wDiffDelete:hover .wDiffNewline:before { color: #aaa; }' +
 
// tab
'.wDiffTab { position: relative; }' +
'.wDiffTabSymbol { position: absolute; top: -0.2em; }' +
'.wDiffTabSymbol:before { content: "→"; font-size: smaller; color: transparent; color: #ccc; }' +
'.wDiffBlockLeft .wDiffTabSymbol:before, .wDiffBlockRight .wDiffTabSymbol:before { color: #aaa; }' +
'.wDiffBlockHighlight .wDiffTabSymbol:before { color: #aaa; }' +
'.wDiffInsert .wDiffTabSymbol:before { color: #aaa; }' +
'.wDiffDelete .wDiffTabSymbol:before { color: #bbb; }' +
 
// space
'.wDiffSpace { position: relative; }' +
'.wDiffSpaceSymbol { position: absolute; top: -0.2em; left: -0.05em; }' +
'.wDiffSpaceSymbol:before { content: "·"; color: transparent; }' +
'.wDiffBlockHighlight .wDiffSpaceSymbol:before { color: transparent; }' +
'.wDiffBlockHighlight:hover .wDiffSpaceSymbol:before { color: #ddd; }' +
'.wDiffBlockHighlight:hover .wDiffInsert .wDiffSpaceSymbol:before, .wDiffInsert:hover .wDiffSpaceSymbol:before { color: #888; }' +
'.wDiffBlockHighlight:hover .wDiffDelete .wDiffSpaceSymbol:before, .wDiffDelete:hover .wDiffSpaceSymbol:before { color: #999; }';
}
 
Line 264 ⟶ 303:
//
 
if (wDiff.styleDelete === undefined) { wDiff.styleDelete = ''; }
if (wDiff.styleInsert === undefined) { wDiff.styleInsert = ''; }
if (wDiff.styleDeleteBlankstyleDelete === undefined) { wDiff.styleDeleteBlankstyleDelete = ''; }
if (wDiff.styleInsertBlank === undefined) { wDiff.styleInsertBlank = ''; }
if (wDiff.styleDeleteBlank === undefined) { wDiff.styleDeleteBlank = ''; }
if (wDiff.styleBlockLeft === undefined) { wDiff.styleBlockLeft = ''; }
if (wDiff.styleBlockRight === undefined) { wDiff.styleBlockRight = ''; }
Line 280 ⟶ 319:
if (wDiff.styleSeparator === undefined) { wDiff.styleSeparator = ''; }
if (wDiff.styleOmittedChars === undefined) { wDiff.styleOmittedChars = ''; }
if (wDiff.styleTab === undefined) { wDiff.styleTab = ''; }
if (wDiff.styleNewline === undefined) { wDiff.styleNewline = ''; }
if (wDiff.styleTab === undefined) { wDiff.styleTab = ''; }
if (wDiff.styleTabSymbol === undefined) { wDiff.styleTabSymbol = ''; }
if (wDiff.styleSpace === undefined) { wDiff.styleSpace = ''; }
if (wDiff.styleSpaceSymbol === undefined) { wDiff.styleSpaceSymbol = ''; }
 
//
Line 308 ⟶ 350:
if (wDiff.htmlBlockRightEnd === undefined) { wDiff.htmlBlockRightEnd = '</span><!--wDiffBlockRight-->'; }
 
if (wDiff.htmlMarkLeft === undefined) { wDiff.htmlMarkLeft = '<span class="wDiffMarkLeft wDiffMark{class}" style="' + wDiff.styleMarkLeft + '{mark}"{title} id="wDiffMark{number}"' + wDiff.blockEvent + '></span><!--wDiffMarkLeft-->'; }
if (wDiff.htmlMarkRight === undefined) { wDiff.htmlMarkRight = '<span class="wDiffMarkRight wDiffMark{class}" style="' + wDiff.styleMarkRight + '{mark}"{title} id="wDiffMark{number}"' + wDiff.blockEvent + '></span><!--wDiffMarkRight-->'; }
if (wDiff.htmlMarkLeft === undefined) { wDiff.htmlMarkLeft = '<span class="wDiffMarkLeft wDiffMark{class}" style="' + wDiff.styleMarkLeft + '{mark}"{title} id="wDiffMark{number}"' + wDiff.blockEvent + '></span><!--wDiffMarkLeft-->'; }
 
if (wDiff.htmlNewline === undefined) { wDiff.htmlNewline = '<span class="wDiffNewline" style="' + wDiff.styleNewline + '">\n</span>\n'; }
if (wDiff.htmlTab === undefined) { wDiff.htmlTab = '<span class="wDiffTab" style="' + wDiff.styleTab + '"><span class="wDiffTabSymbol" style="' + wDiff.styleTabSymbol + '"></span>\t</span>'; }
if (wDiff.htmlSpace === undefined) { wDiff.htmlSpace = '<span class="wDiffSpace" style="' + wDiff.styleSpace + '"><span class="wDiffSpaceSymbol" style="' + wDiff.styleSpaceSymbol + '"></span> </span>'; }
 
//
Line 324 ⟶ 367:
if (wDiff.htmlSeparator === undefined) { wDiff.htmlSeparator = '<div class="wDiffSeparator" style="' + wDiff.styleSeparator + '"></div>'; }
if (wDiff.htmlOmittedChars === undefined) { wDiff.htmlOmittedChars = '<span class="wDiffOmittedChars" style="' + wDiff.styleOmittedChars + '">…</span>'; }
 
 
//
Line 512 ⟶ 554:
}
 
// parse and count count words in texts for later identification of unique words
wDiff.CountTextWords(text.newText);
wDiff.CountTextWords(text.oldText);
Line 961 ⟶ 1,003:
 
 
// wDiff.SlideGaps: move gaps with ambiguous identical fronts andto backslast upnewline or, if absent, last word border
// start ambiguous gap borders after line breaks and text section closing characters
// changes: text (text.newText or text.oldText) .tokens list
// called from: wDiff.Diff()
Line 984 ⟶ 1,025:
var front = gapStart;
var back = i;
var frontPrevfrontTest = null;
var backPrevbackTest = null;
while (
(front !== null) && (back !== null) &&
Line 994 ⟶ 1,035:
textLinked.tokens[ text.tokens[front].link ].link = front;
text.tokens[back].link = null;
frontPrevfrontTest = front;
backPrevbackTest = back;
front = text.tokens[front].next;
back = text.tokens[back].next;
}
 
// test baloonslide up, remember last line break or closingword textborder
var frontStop = null;
var frontTest = frontPrev;
var backTest = backPrev;
while (
(frontTest !== null) && (backTest !== null) &&
Line 1,013 ⟶ 1,052:
break;
}
else if ( (frontStop === null) && (wDiff.regExpSlideClosingregExpSlideBorder.test(text.tokens[frontTest].token) === true) ) {
frontStop = frontTest;
}
Line 1,020 ⟶ 1,059:
}
 
// actually slide up to last line break or, if absent, closingword textborder
if (frontStop !== null) {
while (
Line 1,337 ⟶ 1,376:
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
var iStart = i;
var iEnd = null;
Line 1,351 ⟶ 1,390:
}
 
// determine the limits of of the unresolved old sequence
var jStart = j;
var jEnd = null;
Line 1,407 ⟶ 1,446:
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
var iStart = null;
var iEnd = i;
Line 1,421 ⟶ 1,460:
}
 
// determine the limits of of the unresolved old sequence
var jStart = null;
var jEnd = j;
Line 2,578 ⟶ 2,617:
text.diff = text.diff.replace(/<\/(\w+)><!--wDiff(Delete|Insert)--><\1\b[^>]*\bclass="wDiff\2"[^>]*>/g, '');
text.diff = text.diff.replace(/\t/g, wDiff.htmlTab);
text.diff = text.diff.replace(/(<[^>]*>)|( )/g, function (p, p1, p2) {
if (p2 == ' ') {
return wDiff.htmlSpace;
}
return p1;
});
text.diff = wDiff.htmlContainerStart + wDiff.htmlFragmentStart + text.diff + wDiff.htmlFragmentEnd + wDiff.htmlContainerEnd;
return;
Line 3,015 ⟶ 3,060:
for (var i = 0; i < groups.length; i ++) {
dump += i + ' \t' + groups[i].blockStart + ' \t' + groups[i].blockEnd + ' \t' + groups[i].unique + ' \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';
}
return dump;
};
 
 
//
// wDiff.DebugGaps: dump gaps object
//
 
wDiff.DebugGaps = function (gaps) {
var dump = '\ni \tnFirs \tnLast \tnTok \toFirs \toLast \toTok \tcharSplit\n';
for (var i = 0; i < gaps.length; i ++) {
dump += i + ' \t' + gaps[i].newFirst + ' \t' + gaps[i].newLast + ' \t' + gaps[i].newTokens + ' \t' + gaps[i].oldFirst + ' \t' + gaps[i].oldLast + ' \t' + gaps[i].oldTokens + ' \t' + gaps[i].charSplit + '\n';
}
return dump;