User:Lupin/diff.js

/*

* Javascript Diff Algorithm

* By John Resig (http://ejohn.org/) and :en:User:Lupin

*

* More Info:

* http://ejohn.org/projects/javascript-diff-algorithm/

*/

function diffEscape(n) {

return n.replace(/&/g, "&").replace(//g, ">")

.replace(RegExp('"','g'), """);

}

function delFmt(x) {

if (!x.length) return '';

return "" + diffEscape(x.join('')) +"";

}

function insFmt(x) {

if (!x.length) return '';

return "" + diffEscape(x.join('')) +"";

}

function copyDiffObj(x){

var ret=[];

for (var i=0; i

if (typeof x[i] == 'string') ret[i]=x[i];

else {

ret[i]={};

for (var prop in x[i]) ret[i][prop]=x[i][prop];

}

}

return ret;

}

function countCrossings(a, b, i) {

// count the crossings on the edge starting at b[i]

if (b[i].row==null) return -1;

var count=0;

for (var j=0; j

if (a[j].row==null) continue;

if ( (j-b[i].row)*(i-a[j].row) > 0) count++;

}

return count;

}

//function debug(s){

// try {document.getElementById('debug').value+=s+'\n'; } catch(foo) {};

//}

function untangle( a, b) {

// try to remove crossing edges from an ordered bipartite graph,

// removing the least number possible

var aa=copyDiffObj(a);

var bb=copyDiffObj(b);

// remove the edge with the largest number of crossings until no

// crossings remain

do {

var maxCrossings=0;

var worstEdge=null;

for (var i=0; i

var c=countCrossings(aa,bb,i);

if (c > maxCrossings) { maxCrossings=c; worstEdge=i; }

}

if (worstEdge!=null) {

aa[ bb[worstEdge].row ] = aa[ bb[worstEdge].row ].text;

bb[worstEdge] = bb[worstEdge].text;

}

} while (maxCrossings > 0);

return { a: aa, b: bb };

}

window.max=function(a,b){return a

window.min=function(a,b){return a>b ? b : a;}

function shortenDiffString(str, context) {

var output=[];

var s=str;

var m=true;

while ( true ) {

var re=RegExp('(|)+');

m=re.exec(s);

if(!m) break;

var t=(s.substring(max(0,m.index-context), min(s.length, m.index+m[0].length+context)));

//alert(s+'\n\n\n'+m[0] +'\n\n'+(m.index-context) + '\n\n'+t);

output.push(t);

s=s.substring(min(s.length, m.index+m[0].length));

}

return output;

}

function diffString( o, n ) {

var out = diff( o.split(/\b/), n.split(/\b/) );

var str = "";

var acc=[]; // accumulator for prettier output

// crossing pairings -- 'A B' vs 'B A' -- cause problems, so let's iron them out

//var untangled=untangle(out.o, out.n); <-- too slow!

//out.o=untangled.a; out.n=untangled.b;

var maxOutputPair=0;

for (var i=0; i

if ( out.n[i].row != null) {

if( maxOutputPair > out.n[i].row ) {

// tangle - delete pairing

out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text;

out.n[i]=out.n[i].text;

}

if (maxOutputPair < out.n[i].row) maxOutputPair = out.n[i].row;

}

}

// output the stuff preceding the first paired old line

for (var i=0; i

str += delFmt(acc); acc=[];

// main loop

for ( var i = 0; i < out.n.length; ++i ) {

// output unpaired new "lines"

while ( i < out.n.length && out.n[i].text == null ) acc.push( out.n[i++] );

str += insFmt(acc); acc=[];

if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line"

str += out.n[i].text;

// output unpaired old rows starting after this new line's partner

var m = out.n[i].row + 1;

while ( m < out.o.length && out.o[m].text == null ) acc.push ( out.o[m++] );

str += delFmt(acc); acc=[];

}

}

return str;

}

function diff( o, n ) {

var ns = new Object();

var os = new Object();

// pass 1: make hashtable ns with new rows as keys

for ( var i = 0; i < n.length; i++ ) {

if ( ns[ n[i] ] == null )

ns[ n[i] ] = { rows: new Array(), o: null };

ns[ n[i] ].rows.push( i );

}

// pass 2: make hashtable os with old rows as keys

for ( var i = 0; i < o.length; i++ ) {

if ( os[ o[i] ] == null )

os[ o[i] ] = { rows: new Array(), n: null };

os[ o[i] ].rows.push( i );

}

// pass 3: pair unique new rows and matching unique old rows

for ( var i in ns ) {

if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {

n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };

o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };

}

}

// pass 4: pair matching rows immediately following paired rows (not necessarily unique)

for ( var i = 0; i < n.length - 1; i++ ) {

if ( n[i].text != null && n[i+1].text == null &&

n[i].row < o.length - 1 && o[ n[i].row + 1 ].text == null &&

n[i+1] == o[ n[i].row + 1 ] ) {

n[i+1] = { text: n[i+1], row: n[i].row + 1 };

o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };

}

}

// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)

for ( var i = n.length - 1; i > 0; i-- ) {

if ( n[i].text != null && n[i-1].text == null &&

n[i].row > 0 && o[ n[i].row - 1 ].text == null &&

n[i-1] == o[ n[i].row - 1 ] ) {

n[i-1] = { text: n[i-1], row: n[i].row - 1 };

o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };

}

}

return { o: o, n: n };

}