应同学的公司需求,需要对比源文件和录音文件之间的文字差别,将配音文件与源文件不同的地方进行高亮显示,于是决定花半天的时间帮他写出匹配功能的网页。
先看成果截图:
当然你也可以自己体验一下:
文件1: 下载测试源文件
文件2: 下载测试待配对文件
地址: 演示地址
上代码:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<title>文件比较</title>
<style type="text/css">
@import "../js/layui/css/layui.css";
</style>
<style type="text/css">
</style>
</head>
<script src="../js/layui/layui.js"></script>
<script src="../js/jquery_need.js"></script>
<!-- <script src="../js/layui/lay/modules/layer.js"></script> -->
<body style="margin: 0;" class="layui-nav" >
<ul class="layui-nav">
<li class="layui-nav-item">
<a>文档匹配<span class="layui-badge">+</span></a>
</li>
<li class="layui-nav-item">
<a id="name1"></a>
</li>
<li class="layui-nav-item">
<a id="name2"></a>
</li>
</ul>
<div style="width: 100vwpx;margin: 0 auto;height: 600px;">
<div class="layui-row " style="color: black;font-family: '幼圆';">
<div class="layui-col-md5">
<div id="filecon1" style="word-break: break-all;word-wrap: break-word;padding: 15px;width:80%;height:600px;margin: 0 auto;overflow-y: auto;border-color: red;border-width: 1px;border-style: solid;background-color: white;box-shadow: 0 0 10px 1px rgba(255,0,0,0.09);border-radius: 5px;"></div>
</div>
<div class="layui-col-md5">
<div id="filecon2" style="word-break: break-all;word-wrap: break-word;padding: 15px;width:80%;height:600px;margin: 0 auto;overflow-y: auto;border-color: red;border-width: 1px;border-style: solid;background-color: white;box-shadow: 0 0 10px 1px rgba(255,0,0,0.09);border-radius: 5px;position: relative;left: -60px;"></div>
</div>
<div class="layui-col-md2">
<button type="button" class="layui-btn" id="start">开始匹配</button>
<br><br>
<div style="margin: 0 0 0 -20px;">
<font style="color: white;">高亮背景颜色</font>
<div id="bgcolor_test"></div>
<a hidden id="bgcolor">black</a>
<a id="bgcolor_" style="width: 2px;height: 2px;background-color: black;"> </a>
</div>
<br>
<div style="justify-content: center;display:flex;margin: 0 auto;position: relative;width: 300px;left: -40px;">
<label style="color: white;" class="layui-form-label">字体大小</label>
<div class="layui-input-block" style="width: 50px;justify-content: center;display:flex;margin: 0 auto;position: relative;position: relative;left: -60px;">
<input type="text" id="fontSize" name="title" required lay-verify="required" value="15" autocomplete="off" class="layui-input">
</div>
</div>
<br>
<div class="layui-form-item">
<!-- <label class="layui-form-label">短输入框</label> -->
<div class="layui-input-inline">
<label class="layui-form-label" style="color: white;margin: -10px 0 0 -60px;">编码格式</label>
<div class="layui-input-block" style="color: white;margin: 0px 0 0 -40px;">
<input type="radio" name="code" value="GBK" title="GBK" checked>GBK
<input type="radio" name="code" value="UTF-8" title="UTF-8" >UTF-8
</div>
</div>
<div class="layui-input-inline">
<input type="text" style="position: relative;left: -40px;" name="username" lay-verify="required" placeholder="间隔次数长度+60" autocomplete="off" class="layui-input">
</div>
<br><br><br>
<div class="layui-input-inline">
<input type="text" style="position: relative;left: -40px;" name="username" lay-verify="required" placeholder="positionCril-4000" autocomplete="off" class="layui-input">
</div>
<br><br><br>
<div class="layui-input-inline">
<input type="text" style="position: relative;left: -40px;" name="username" lay-verify="required" placeholder="精确度-Lv10" autocomplete="off" class="layui-input">
</div>
<br><br><br>
<div class="layui-input-inline">
<input type="text" style="position: relative;left: -40px;" name="username" lay-verify="required" placeholder="精确度截距-0.5" autocomplete="off" class="layui-input">
</div>
<br><br><br>
<br><br><br>
<div class="layui-input-inline">
<img src="../img/myWechat.jpg" style="width: 100%;height: 100%;position: relative;left: -40px;top: 20px;" alt="">
</div>
</div>
<div id="test1" class="demo-transfer"></div>
</div>
</div>
<div style="height: 5px;"></div>
<div class="layui-row">
<div class="layui-col-md5" >
<button id="btn1" type="button" class="layui-btn layui-btn-danger" style="justify-content: center;display: flex;margin: 0 auto;">点击选择文件1</button>
<input type="file" hidden id="file1" οnchange="readFiles(this,'1')"/>
</div>
<div class="layui-col-md5">
<button type="button" id="btn2" class="layui-btn layui-btn-danger" style="justify-content: center;display: flex;margin: 0 auto;position: relative;left: -60px;">点击选择文件2</button>
<input type="file" hidden id="file2" οnchange="readFiles(this.files,2)"/>
</div>
<div class="layui-col-md2" style="position: relative;left: -60px;">
说明,可能由于算法原因,会存在那么一些2%左右的文字会不见,会逐渐改善。有啥问题请扫二维码进行咨询。包括功能的改善...
</div>
</div>
</div>
</body>
</html>
JavaScript
<script>
$(document).ready(function(){
});
layui.use('colorpicker', function(){
var colorpicker = layui.colorpicker;
//渲染
colorpicker.render({
elem: '#bgcolor_test' //绑定元素
,change: function(color){
// alert(color)
$("#bgcolor").html(color);
$("#bgcolor_").css("background-color",color);
}
})
});
layui.use('layer', function(){
var layer = layui.layer;
// layer.msg('请先选择好文件!');
});
//无需再执行layui.use()方法加载模块,直接使用即可
var control_lock = 0;
var form = layui.form
,layer = layui.layer;
$("#btn1").click(function(){
$("#file1").click();
})
$("#btn2").click(function(){
$("#file2").click();
})
function readFiles(file,type){
// alert("A");
}
$("#file1").change(function(e) {
var fileObj = document.getElementById('file1');
var file = fileObj.files[0];
// alert(file.type);
if(file.type.indexOf("text") == -1){
// alert("只能打开text文本");
layer.msg("只能打开text文本");
return ;
}
var reader = new FileReader();
layui.use('layer', function(){
var layer = layui.layer;
layer.load();
// layer.msg('请先选择好文件!');
});
reader.onload = function() {
// alert("?凄凄惨惨"+this.result);
$("#filecon1").empty();
$('#filecon1').append(this.result);
$("#name1").html(file.name);
layui.use('layer', function(){
var layer = layui.layer;
layer.closeAll();
// layer.msg('请先选择好文件!');
});
}
var chkRadio = $('input:radio[name="code"]:checked').val();
reader.readAsText(file,chkRadio);
});
$("#file2").change(function(e) {
var fileObj = document.getElementById('file2');
var file = fileObj.files[0];
var reader = new FileReader();
if(file.type.indexOf("text") == -1){
// alert("只能打开text文本");
layer.msg("只能打开text文本");
return ;
}
layui.use('layer', function(){
var layer = layui.layer;
layer.load();
// layer.msg('请先选择好文件!');
});
reader.onload = function() {
// alert("?凄凄惨惨"+this.result);
$("#filecon2").empty();
$('#filecon2').append(this.result );
control_lock = 0;
$("#name2").html(file.name);
layui.use('layer', function(){
var layer = layui.layer;
layer.closeAll();
// layer.msg('请先选择好文件!');
});
}
var chkRadio = $('input:radio[name="code"]:checked').val();
reader.readAsText(file,chkRadio);
});
$("#start").click(function(){
// alert($('#filecon1').html().length);
if($('#filecon2').html().length == 0 || $('#filecon1').html().length == 0){
layui.use('layer', function(){
var layer = layui.layer;
layer.msg('请先选择好文件!');
});
return ;
}
//可以进行筛选了
// layer.msg("开始筛选...");
if(control_lock == 1){
layui.use('layer', function(){
var layer = layui.layer;
layer.msg('已经匹配!');
});
return ;
}
//判断大小。
var digit = ["1","2","3","4","5","6","7","8","9","0"];
for(var i = 0;i< $("#fontSize").val().length;i++){
// alert($("#fontSize").val().charAt(i));
if(digit.indexOf($("#fontSize").val().charAt(i)) == -1){
$("#fontSize").val(15);
break;
}
}
// alert($("#fontSize").val());
$("#filecon1").css("font-size",$("#fontSize").val()+"px");
$("#filecon2").css("font-size",$("#fontSize").val()+"px");
//
var start = new Date().getTime(); // 开始时间 shaixuan();
shaixuan();
var end = new Date().getTime(); // 结束时间 alert(timeUse);
layer.msg("执行花费了:"+(end - start)/1000+"秒!");
})
function shaixuan(){
layui.use('layer', function(){
var layer = layui.layer;
layer.load(2);
// layer.msg('请先选择好文件!');
});
/**
* 原理:
* 先从a中拿出20个字,前两字 ,(中间16字),后两字
* 然后去匹配b中的这个范围
* 然后在这个范围中,匹配不同的地方。
* 万一b没有开头或者尾巴呢?
* 此时,a的索引从0 变为 1 ,尾巴在去匹配。
* */
//获取内容
var contentA = $("#filecon1").html();
var contentB = $("#filecon2").html();
//获取颜色
var stringBgColor = $("#bgcolor").html();
//更新好
$("#name1").html($("#name1").html()+ "["+contentA.length+"字]");
$("#name2").html($("#name2").html()+ "["+contentB.length+"字]");
//待装配的html
var contentB_wait = "";
var contentA_wait = "";
var num = 0;
//间隔次数长度
var position_MAX_done_split = 600
//开头
var positionTou = 0;
//精确度
var calc_jqd = 10;
//精确
var boundOut = 2;
//开头+尾巴+中间长度
// var positionCril = (contentA.length<contentB.length?contentA.length:contentB.length)- 1 - calc_jqd;
var positionCril = 500-calc_jqd;
//精确度- 范围
var range = 15;
//精确度。。截距
var percent = 0.7;
// var
//记忆,上次B截取到哪了
//这里存储待比较的b的内容。也就是头找到了,尾巴也找到了。 一定要纪录此时的history
var historyT = 0,historyW = 0,i,j,k;
//这里需要改成while循环的,以便控制进度
//判断长度
//判断 是否大于20字节
if(contentA.length < positionCril || contentB.length < positionCril){
layui.use('layer', function(){
var layer = layui.layer;
layer.closeAll();
// layer.msg('请先选择好文件!');
});
layer.msg("您的文件长度不够,配置的参数跟文本长度不符合要求!文件a和b的内容长度为"+contentA.length+","+contentB.length+",需要的尾巴长度为:"+positionCril);
return ;
}
layer.msg("开始")
var count = 0;
control_lock = 1;
//给你一次 清理最后尾巴的机会
var giveYouTurnHasUse = 0;
while(true){
//加退出判断
if(positionCril > contentB.length || count >= 10 || positionTou > contentA.length){
break;
}
//先从A中拿出字符串[0,20],精确度为2
var currA = contentA.substr(positionTou,positionCril - positionTou);
var currStart = contentA.substr(positionTou,calc_jqd);
var currEnd = contentA.substr(positionCril-calc_jqd,calc_jqd);
// alert(currEnd+" <这个是 尾巴 位置是 "+positionCril);
//临时组装的
var currB = "";
// alert(currA + " ||||||本次 头:"+currStart+" 本次尾巴: "+currEnd);
for(j = historyT;j<contentB.length;j+=calc_jqd){
//先找到B的头,就是上面A的头
if(contentB.substr(j,calc_jqd) == currStart){
//说明找到头了。。要纪录history
historyT = j;
//然后要找尾巴。。。 + 精确度 从刚找到的开头后 + 精确度个字符开开始找..
for(k = historyT;k<contentB.length;k++){
// console.log("比较:"+contentB.substr(k,calc_jqd)+" 和 "+currEnd);
if(contentB.substr(k,calc_jqd) == currEnd && (k+calc_jqd-j) > (positionCril - positionTou)*percent){
// alert("wotm找到尾巴了!e")
//找到尾巴了! 此时应该截取比较的字符串。以及当前b的索引和a的索引。以便下次使用 而且当前长度不能小于 currA的长度
currB = contentB.substr(j,k+calc_jqd-j);
// console.log(currB+" -----------------;"+contentB.substr(j,k+2*calc_jqd-j))
//其实好像不用记录尾巴。。
historyW = k+calc_jqd;
break;
}
}
//已经找到头部了,应该beak;
break;
}
}
// alert("???????? ["+currA+"] 开头: [ {"+currStart+"} ]结尾[ {"+currEnd+"} ] ,需要比对的b[ {"+currB+"} ]");
//如果说,j的值大于文本长度,也就是说,没有找到开头。
if(j > contentB.length - 1){
//判断 头部 + 精确度 是否超过 尾巴 ,如果是,那么进行下一轮。
//此时应该变换开头。重新找。。
// console.log("为什么头都没有找到?"+positionTou);
positionTou = positionTou + boundOut;
continue;
}
//万一没有找到尾巴?这么办,那么此时应该改变尾巴,变为 + 2
if(currB == ""){
//尾巴长度加二
// console.log("为什么尾巴都没有找到?"+positionCril);
positionCril = positionCril + boundOut;
continue;
}
// alert(currA+ " ????????? "+currB);
// alert("本次 A:"+currA+" ; 本次,B"+currB);
//如果成功,那么进行进一步的筛选。。然后组装b,添加到 wait中 当然是遍历b的每个字符串,看看a中有没有 一切是a源文件比较
//注意。 比较的是 a的当前截取的 和 b的当前截取的
var currB_copy = "";
//定义一个回溯变量
var cancelVaire = 0;
var sign = [".","。","`","~","·",",",",","!","@","#","$","%","^","&","*","(",")","_","+","=","{","}","[","]",";","*","-","<",">","/","\""];
for(var r = 0;r < currB.length;r++){
var is_has_element = 0;
// alert("?currA = "+currA);
if(sign.indexOf(currB.charAt(r))!= -1){
currB_copy = currB_copy + currB.charAt(r);
continue;
}
for(var q = cancelVaire ; q < currA.length; q ++){
//从B开始 因为头部和尾部 b都有了,所以不需要搜索
// alert("右边的: "+currB.charAt(r)+" 左边的:"+currA.charAt(q)+ " 上次回溯的是啥 "+"q - cancelVaire : "+q+" "+cancelVaire+" = "+(q - cancelVaire));
if(currB.charAt(r) == currA.charAt(q) && q - cancelVaire < range){
//不需要动手脚
// alert("竟然是相等的///");
currB_copy = currB_copy + currB.charAt(r);
is_has_element = 1;
cancelVaire = q;
break;
}
}
if(is_has_element == 0){
//说明没有,需要加粗
currB_copy = currB_copy +'<a id="num'+num+'" style="background-color:'+stringBgColor+';color:rgba(255,255,255,1);">'+ currB.charAt(r)+'</a>';
}
// alert("这次的: "+currB_copy);
}
//添加
contentB_wait = contentB_wait + currB_copy+"<br><a style='color:red;'>[循环分割线]</a><br>";
//为了那个列表展示定位
num++;
//加
positionTou = positionCril;
positionCril += position_MAX_done_split;
//测试,只执行前20代码
// count++;
//头,
historyT = historyW;
if(positionCril > contentA.length && giveYouTurnHasUse == 0){
//如果
positionCril = contentA.length-calc_jqd;
giveYouTurnHasUse = 1;
}
}
//执行好后,设置html
// alert(contentB_wait);
document.getElementById("filecon2").innerHTML = contentB_wait;
//判断解析后,
$("#name2").html($("#name2").html()+ " 解析后: "+$("#filecon2").text().length+"字]");
layui.use('layer', function(){
var layer = layui.layer;
layer.closeAll();
});
}
</script>
BUG肯定是有很多的。毕竟只用了半天时间来粗略写好,但基本的识别率已经达到了85%,匹配准确率达到了90%。时间复杂度也略微优化了一些。如果有时间,后续会完善逻辑和算法。每次新的一轮循环后,感觉contentB_wait头部会少那么几个文字,大概知道在哪里出问题。但没去解决。