diff.html:
<!DOCTYPE html>
<html>
<head>
<title>文件比较</title>
<meta charset="UTF-8">
</head>
<body>
<h1>文件比较</h1>
<form>
<label for="file1">版本1:</label>
<input type="file" id="file1" name="file1"><br><br>
<label for="file2">版本2:</label>
<input type="file" id="file2" name="file2"><br><br>
<button type="button" onclick="compare()">开始比较</button>
</form>
<div id = "div_info" style="width:1000px;height:500px;border:1px solid gray;overflow:auto">
</div>
<script>
var infoDiv = document.getElementById("div_info");
function compare() {
var file1 = document.getElementById("file1").files[0];
var file2 = document.getElementById("file2").files[0];
var reader1 = new FileReader();
var reader2 = new FileReader();
reader1.readAsText(file1);
reader2.readAsText(file2);
var lines_v1 = null;
var lines_v2 = null;
reader1.onload = function() {
lines_v1 = reader1.result.split('\n');
// printArray( lines_v1 );
reader2.onload = function() {
lines_v2 = reader2.result.split('\n');
// printArray( lines_v2 );
// 比较两个文档的区别
docDiff( lines_v1,lines_v2 );
}
}
}
function printArray( array ){
var len = array.length;
for (let i = 0; i < len; i++){
console.log( "[" + array[ i ] + "]" );
}
}
function docDiff( lines_v1,lines_v2 ){
var dp = calculateShortestEditDistance(lines_v1, lines_v2);
var index1 = lines_v1.length - 1;
var index2 = lines_v2.length - 1;
console.log("一共需要" + dp[ index1 ][ index2 ] + "步编辑操作");
var results = [];
while ( index1 >= 0 && index2 >= 0 ){
var line_v1 = lines_v1[ index1 ];
var line_v2 = lines_v2[ index2 ];
if( line_v1 == line_v2 ){
// v1:...a
// v2:...a
// 原封不动的输出
results.push( " " + line_v1 );
index1--;
index2--;
}else {
// v1:...a
// v2:...b
// v1:... a
// v2:... b
// 此时,a修改修改为b
var sed1 = 0;
if( index1 > 0 && index2 >0 ){
sed1 = dp[index1 - 1][index2 - 1];
}
// v1:...a
// v2: ... b
// 此时,需要插入b
var sed2 = 0;
if( index2 >0 ){
sed2 = dp[index1][index2 - 1];
}
// v1: ... a
// v2:...b
// 此时,需要删除a
var sed3 = 0;
if( index1 > 0 ){
sed3 = dp[index1-1][index2];
}
var sed = Math.min( sed1,sed2,sed3 );
if( sed == sed1 ){
// results.add( "edit " + line_v2 + " DIFF:" + StringDiffTest.diff( line_v1,line_v2 ) );
// var diffInfo = StringDiffTest.diff(line_v1, line_v2);
// results.add( "edit " + diffInfo );
results.push( "- " + line_v1 );
results.push( "+ " + line_v2 );
index1--;
index2--;
}else if( sed == sed2 ){
results.push( "+ " + line_v2 );
index2--;
}else if( sed == sed3 ){
results.push( "- " + line_v1 );
index1--;
}
}
}
while ( index1 >= 0 ){
// v1 中多出的 "首行们" 都是需要删除的
results.push( "- " + lines_v1[ index1 ] );
index1--;
}
while ( index2 >= 0 ){
// v2 中多出的 "首行们" 都是需要被插入的
results.push( "+ " + lines_v2[ index2 ] );
index2--;
}
for ( var i=results.length -1;i>=0;i-- ){
var line = results[ i ];
var small = document.createElement( "small" );
small.innerHTML = line;
if( line.startsWith( "-" ) ){
small.style.color = "red";
}else if( line.startsWith( "+" ) ){
small.style.color = "green";
}
infoDiv.appendChild( small );
infoDiv.appendChild( document.createElement( "br" ) );
}
}
// 返回 int[][]
function calculateShortestEditDistance( lines_v1,lines_v2 ){
// dp[i][j] 表示的是将 lines_v1 的前i个元素变换为 lines_v2 中的前j个元素需要使用的最优( 即需要转换步骤最少 )的转换方式
var size_v1 = lines_v1.length;
var size_v2 = lines_v2.length;
var dp = createArray( size_v1,size_v2 );
for (var index1 = 0; index1 < size_v1; index1++) {
var line_v1 = lines_v1[ index1 ];
for (var index2 = 0; index2 < size_v2; index2++) {
var line_v2 = lines_v2[ index2 ];
if( index1 == 0 ){
if( index2 == 0 ){
if( line_v1 == line_v2 ){
// v1:a
// v2:a
dp[ index1 ][ index2 ] = 0;
}else {
// v1:a
// v2:b
dp[ index1 ][ index2 ] = 1;
}
}else {
if( contains( lines_v2,line_v1,0,index2 ) ){
// v1: a
// v2:...a... size = index2 + 1
// v1转换为 v2需要 size - 1步( 也就是 index2步 )插入操作
dp[ index1 ][ index2 ] = index2;
}else {
// v1: a
// v2:...b... size = index2 + 1
// v1转换为 v2需要 1步编辑操作,size-1= index2 步插入操作,一共index2 + 1步操作
dp[ index1 ][ index2 ] = index2 + 1;
}
}
}else {
if( index2 == 0 ){
if( contains(lines_v1, line_v2, 0, index1) ){
// v1:....a... size = index1 + 1
// v2: a
// v1转换为 v2需要 size-1=index1步删除操作
dp[ index1 ][ index2 ] = index1;
}else {
// v1:....a... size = index1 + 1
// v2: b
// v1转换为 v2需要 1步编辑操作和size-1=index1步删除操作,一共index1+1步操作
dp[ index1 ][ index2 ] = index1 + 1;
}
}else {
if( line_v1 == line_v2 ){
// v1:...a
// v2:...a
dp[ index1 ][ index2 ] = dp[ index1 - 1 ][ index2 - 1 ];
}else {
// v1:...a
// v2:...b
// v1:... a
// v2:... b
// 此时 v1 的前部分和 v2的前部分做dp运算,a修改为b
var sed_prev1 = dp[ index1 - 1 ][ index2 - 1 ];
// v1: ... a
// v2:...b
// 此时v1的前部分和v2做dp运算,删除a
var sed_prev2 = dp[ index1 - 1 ][ index2 ];
// v1: ...a
// v2: ... b
// 此时 v1和v2的前部分做dp运算,插入b
var sed_prev3 = dp[ index1 ][ index2 - 1 ];
dp[ index1 ][ index2 ] = Math.min( sed_prev1,sed_prev2,sed_prev3 ) + 1;
}
}
}
}
}
return dp;
}
// todo 测试行列是否写反了
function createArray(rowCount, colCount) {
var arr = [];
for (var i = 0; i < rowCount; i++) {
arr[i] = [];
for (var j = 0; j < colCount; j++) {
arr[i][j] = 0;
}
}
return arr;
}
function contains(lines, targetLine, beginIndex, endIndex) {
for (var i = beginIndex; i <=endIndex ; i++) {
if( lines[ i ] == targetLine ){
return true;
}
}
return false;
}
</script>
</body>
</html>
doc_v1.txt:
盼望着,盼望着,东风来了,春天的脚步近了。
一切都像刚睡醒的样子,欣欣然张开了眼。
山朗润起来了,水涨起来了,太阳的脸红起来了。
小草偷偷地从土地里钻出来,嫩嫩的,绿绿的。
园子里,田野里,瞧去,一大片一大片满是的。
坐着,躺着,打两个滚,踢几脚球,赛几趟跑,捉几回迷藏。
风轻俏俏的,草软绵绵的。
桃树,杏树,梨树,你不让我,我不让你,都开满了花赶趟儿。
红的像火,粉的像霞,白的像雪。
花里带着甜味;闭了眼,树上仿佛已经满是桃儿,杏儿,梨儿。
花下成千成百的蜜蜂嗡嗡的闹着,大小的蝴蝶飞来飞去。
野花遍地是:杂样儿,有名字的,没名字的,散在草丛里像眼睛像星星,还眨呀眨。
“吹面不寒杨柳风”,不错的,像母亲的手抚摸着你,风里带着些心翻的泥土的气息,混着青草味儿,还有各种花的香,都在微微润湿的空气里酝酿。
鸟儿将巢安在繁花嫩叶当中,高兴起来,呼朋引伴的卖弄清脆的歌喉,唱出婉转的曲子,跟清风流水应和着。
牛背上牧童的短笛,这时候也成天嘹亮的响着。
雨是最寻常的,一下就是三两天。
可别恼。看,像牛牦,像花针,像细丝,密密的斜织着,人家屋顶上全笼着一层薄烟。
树叶却绿得发亮,小草也青得逼你的眼。傍晚时候,上灯了,一点点黄晕的光,烘托出一片安静而和平的夜。
在乡下,小路上,石桥边,有撑着伞慢慢走着的人,地里还有工作的农民,披着所戴着笠。
他们的房屋稀稀疏疏的,在雨里静默着。
天上的风筝渐渐多了,地上的孩子也多了。
城里乡下,家家户户,老老小小,也赶趟似的,一个个都出来了。
舒活舒活筋骨,抖擞抖擞精神,各做各的一份事儿去。
“一年之计在于春”,刚起头儿,有的是功夫,有的是希望 春天像刚落地的娃娃,从头到脚都是新的,它生长着。
春天像小姑娘,花枝招展的笑着走着。 春天像健壮的青年,有铁一般的胳膊和腰脚,领着我们向前去。
doc_v2.txt:
盼望着,盼望着,东风来了,春天的脚步进了。
一切都像刚睡醒的样子,欣欣然张开了眼。
山朗润起来了,水涨起来了,太阳的脸红起来了。
小草偷偷地从土地里钻出来,嫩嫩的,绿绿的。
园子里,田野里,瞧去,一大片一大片满是的。
坐着,躺着,打两个滚、踢几脚球,赛几趟跑、捉几回迷藏。
风轻巧巧的,草软绵绵的。
桃树,杏树,梨树,你不让我,我不让你,都开满了花赶趟儿。
红的像火,粉的像霞,白的像雪。
花里带着甜味;闭了眼,树上仿佛已经满是桃儿,杏儿,梨儿。
花下成千成百的蜜蜂嗡嗡的闹着,大小的蝴蝶非来非去。
野花遍地是:杂样儿,有名字的,没名字的,散在草丛里像眼睛像星星,还眨呀眨。
“吹面不寒杨柳风”,不错的,像母亲的手抚摸着你,风里带着些心翻的泥土的气息,混着青草味儿,还有各种花的香,都在微微润湿的空气里酝酿。
鸟儿将巢安在繁花嫩叶当中,高兴起来,呼朋引伴的卖弄风骚清脆的歌喉,唱出婉转的曲子,跟清风流水应和着。
牛背上牧童的断敌,这时候也成天嘹亮的响着。
雨是最寻常的,一下就是三两天。
可别恼。看,像牛牦,像花针,象细丝,密密的斜织着,人家屋顶上全笼着亿层薄烟。
树叶却绿得发亮,小草也青得逼你的眼。傍晚时候,上灯了,一点点黄晕的光,烘托出一片安静而和平的夜。
在乡下,小路上,石桥边,有撑着伞慢慢走着的人,地里还有工作的农民,披星戴月着所戴着笠。
他们的房屋稀稀疏疏的,在雨里静默着。
天上的风筝渐渐多了,地上的孩子也多了。
城里乡下,家家户户,老老小小,也赶趟似的,一各各都出来了。
舒活舒活筋骨,抖擞抖擞精神,各做各的一份事儿去。
"一年之计在于春",刚起头儿,有的是功夫,有的是希望 春天刚落地的娃娃,从头到脚都是,它生长着。
春天像小菇娘,花枝招展的笑着走着。 春天像键壮的青年,有铁一般的胳膊和腰脚,领着我们向前去。
测试效果: