对比两个简单的、没有格式的文本可以直接使用google的diff-match-patch进行对比。
对比html文本难点在于
- 如何忽略掉不需要进行对比的标签,只对比标签中间的文本
- 如果修改了样式我们该怎么对比出来
- 对比完文本后,我们该怎样把标签原封不动的分别加到两段html标签中
github地址:https://github.com/NPCDW/HtmlDiff
demo地址:https://npcdw.github.io/HtmlDiff/HtmlDiff.html
我提供的思路只解决了第一条和第三条,关于第二条,没找到解决的方法,下面说一下我的思路和实现:
首先、我们需要保存好两段原始的html文本,以下简称为原始html1和原始html2。
然后、分别创建一个原始html1和原始html2的副本,然后分别将副本的标签去掉,只保留其中的文本,以下简称原始文本1和原始文本2。
第三、使用google的diff-match-patch将文本进行对比,对比完成返回的结果样式应该是这样的
[{"0": 0,"1": "Hamlet: Do you see "},{"0": -1,"1": "yonder cloud"},{"0": 1,"1": "the cloud over there"}]
解释一下:
字符串"0"代表对比结果的类型,数字0代表相同的文本,-1代表原始文本1相对于原始文本2删除的文本,1代表原始文本1相对于原始文本2增加的文本。
字符串"1"代表对比结果的文本
这个列表是有顺序的,所有的类型为0和类型为-1的文本前后拼接起来就是原始文本1,所有的类型为0和类型为1的文本前后拼接起来就是原始文本2。
我们将所有的类型为0和类型为-1的列表称为对比列表1,将所有的类型为0和类型为1的列表称为对比列表2
第四、开始还原原始的html文本,并在其中加入插入标签<ins></ins>和删除标签<del></del>,还原的过程大致如下:
因为两个html文本的还原过程一致,我们只拿第一个举例。
1、创建一个用于还原的html文本,我们称之为还原html
2、我们取出原始html1中的最前面一段,选取的规则是取出一段【文本】,如果一开始就是文本,而非标签(开始标签和结束标签都算标签),那么就取这段文本,遇到标签结束,如果一开始就是标签,那么取出这段标签和之后遇到文本,同样是遇到下一段标签结束,例如:
</span><div></div><div><p>这是一块文本
我们将取出的标签和文本称之为选取标签和选取文本
3、将选取标签直接追加到还原文本末尾,将选取文本与对比列表中的第一段文本(以下简称第一段对比文本)进行对比,
- 如果选取文本等于第一段对比文本,将选取文本追加到还原文本末尾,删除原始html的选取部分,删除选取列表的第一块
- 如果选取文本小于第一段对比文本,将选取文本追加到还原文本末尾,删除原始html的选取部分,删除第一段对比文本中选取文本的部分,再取出原始html文本。。。。。重复此过程
- 如果选取文本大于第一段对比文本,将第一段对比文本追加到还原文本末尾,删除选取文本中第一段对比文本的部分,删除选取列表的第一块,再取出选取列表的第一块,再与选取文本对比。。。。重复此过程
直到原始html的长度变成0,或对比列表的长度变成0,直接将剩下的部分都追加到还原文本末尾,那么还原的过程就结束了。
附:JS版本的实现
var HtmlDiff = function() {this.ignore_tag = []this.Diff_Timeout = 0}HtmlDiff.prototype.diff_launch = function(html1, html2) {var text1 = this.convertTextFromHtml(html1);var text2 = this.convertTextFromHtml(html2);var dmp = new diff_match_patch();dmp.Diff_Timeout = this.Diff_Timeout;var ms_start = (new Date).getTime();var diff = dmp.diff_main(text1, text2, true);var ms_end = (new Date).getTime();// if (diff.length > 2) {// dmp.diff_cleanupSemantic(diff);// }let time = ms_end - ms_start;let oldDiff = [];let newDiff = [];for (let i = 0,len=diff.length; i < len; i++) {if (diff[i][0] === 0) {oldDiff.push({...diff[i]})newDiff.push({...diff[i]})} else if (diff[i][0] === -1) {oldDiff.push({...diff[i]})} else {newDiff.push({...diff[i]})}}let oldDiffHtml = this.restoreToHtml(html1, oldDiff)let newDiffHtml = this.restoreToHtml(html2, newDiff)return {time, oldDiffHtml, newDiffHtml}}HtmlDiff.prototype.restoreToHtml = function(originalHtml, diffResultList) {let diffHtml = ''while (true) {let {tag, text} = this.getOneTextFromHtml(originalHtml)diffHtml += tagoriginalHtml = originalHtml.substr(tag.length + text.length)for (let i = 0,len=diffResultList.length; i < len; i++) {let diffType = diffResultList[i][0]let diffText = diffResultList[i][1]if (diffText === text) {diffHtml += this.formatText(diffType, diffText)diffResultList.splice(i,1)break}if (diffText.length > text.length) {diffHtml += this.formatText(diffType, text)diffResultList[i][1] = diffText.substr(text.length)break}if (text.length > diffText.length) {diffHtml += this.formatText(diffType, diffText)text = text.substr(diffText.length)diffResultList.splice(i,1)i--len--}}if (!originalHtml || !diffResultList || diffResultList.length <= 0) {break}}return diffHtml + originalHtml}HtmlDiff.prototype.convertTextFromHtml = function(html) {let text = ''let tagFlag = falsethis.ignore_tag.map(item => {item.flag = false})for (let i=0,len=html.length;i<len;i++) {if (!tagFlag && html[i] === '<') {tagFlag = truethis.ignore_tag.map(item => {if (html.substr(i+1, item.openTag.length) == item.openTag) {item.flag = true}})} else if (tagFlag && html[i] === '>') {tagFlag = falsethis.ignore_tag.map(item => {if (item.flag && html.substring(i-item.closeTag.length, i) == item.closeTag) {item.flag = false}})continue}let notDiffFlag = falsethis.ignore_tag.map(item => {if (item.flag) {notDiffFlag = true}})if (!tagFlag && !notDiffFlag) {text += html[i]}}return text}HtmlDiff.prototype.getOneTextFromHtml = function(html) {let tag = ''let text = ''let tagFlag = falsethis.ignore_tag.map(item => {item.flag = false})for (let i=0,len=html.length;i<len;i++) {if (!tagFlag && html[i] === '<') {tagFlag = trueif (text) {return {tag, text}}this.ignore_tag.map(item => {if (html.substr(i+1, item.openTag.length) == item.openTag) {item.flag = true}})} else if (tagFlag && html[i] === '>') {tagFlag = falsetag += html[i]this.ignore_tag.map(item => {if (item.flag && html.substring(i-item.closeTag.length, i) == item.closeTag) {item.flag = false}})continue}let notDiffFlag = falsethis.ignore_tag.map(item => {if (item.flag) {notDiffFlag = true}})if (!tagFlag && !notDiffFlag) {text += html[i]} else {tag += html[i]}}return {tag, text}}HtmlDiff.prototype.formatText = function(diffType, diffText) {if (diffType === 0) {return diffText} else if (diffType === -1) {return '<del>' + diffText + '</del>'} else {return '<ins>' + diffText + '</ins>'}}this.HtmlDiff = HtmlDiff;
