最近客户提出了一个文本对比功能。想通过选着任意版本的富文本内容来对比显示不同的地方。 我的富文本是通过 Froala 来实现的,但对方并没有通过相应的功能。
那我咋整?
没法子,智能硬整。
先梳理下对比思路。
版本:123
目标:456
1
42536
差不多就实现这个效果。
第一个版本 文本对比
那么我直接一个文本一个文本的对比应该就可以了。不管了,先码一下。
但问题又来了,我的版本是网页对比呢 这个对比是纯文本的对比。显然没有实现我的需求。
灵光一闪,想到了了思路。使用dom节点的方式依次对比不就行了?不同的节点直接append作为子节点?不错的想法。
具体思路就是:
版本:<p>12</p><p>3</p>
目标:<p>4</p><p>12</p><p>4</p>
结果:<p reduce>4</p><p normal>12</><p add>3</p><p reduce>4</p>
reduce 意味着 减少的节点
add 意味着 增加的节点
normal 意味着未变更的节点
嗯 那么需要先实现一下字符串转换为dom的层级结构。
const MatchTag = /<\/?.+?>/g;
/**
* 拆分字符串为单个节点字符串
*/
function splitHtml(html: string): { str: string; tag: boolean }[] {
const response: { str: string; tag: boolean }[] = [];
const nodes = Array.from(html.matchAll(MatchTag));
let start = 0;
let end = 0;
const tempNodes: string[] = [];
for (let i = 0; i < nodes.length; i++) {
const [value] = nodes[i];
const { index } = nodes[i];
end = i + 1;
// 拆分前置字符串
if (start < index) {
response.push({ str: html.slice(start, index), tag: false });
}
const { name: tagName, close } = getTagName(value);
tempNodes.push(tagName);
if (close) {
throw new Error('process error');
}
// 拆分节点字符串
while (tempNodes.length) {
const current = nodes[end];
if (!current) {
// 未匹配闭合当作单节点处理
response.push({
str: html.slice(index, index + value.length),
tag: true,
});
start = index + value.length;
end = end + 1;
tempNodes.length = 0;
continue;
}
const [currentValue] = current;
const { index: currentIndex } = current;
const { name, close: closed } = getTagName(currentValue);
if (!closed) {
end = end + 1;
tempNodes.push(name);
continue;
}
let prevName = tempNodes[tempNodes.length - 1];
while (tempNodes.length && name !== prevName) {
tempNodes.pop();
prevName = tempNodes[tempNodes.length - 1];
}
if (tempNodes.length <= 1 && name === tagName) {
response.push({
str: html.slice(nodes[i].index, currentIndex + currentValue.length),
tag: true,
});
start = currentIndex + currentValue.length;
i = end;
tempNodes.pop();
break;
}
end = end + 1;
tempNodes.pop();
}
const current = nodes[nodes.length - 1];
const [currentValue] = current;
const { index: currentIndex } = current;
// 拆分后置字符串
if (i >= nodes.length - 1 && currentIndex + currentValue.length < html.length) {
response.push({
str: html.slice(currentIndex + currentValue.length, html.length),
tag: false,
});
}
}
if (nodes.length === 0) {
response.push({ str: html, tag: false });
}
return response;
}
这里看出来了是通过正则匹配闭合节点的方式 一个一个将字符串拆分出来的。
当然简单一点也可以直接 document.createElement(div).innerHTML 来做
节点拆分出来之后 可能是下面这样
str: 123<p>123</p><table><tr></td>123</td></tr></table>
str0: 123
str1: <p>123</p>
str2: <table><tr></td>123</td></tr></table>
还需要一个步骤 就是将所有节点转换为单个节点
/**
* 拆分parent child html
*/
function splitPCHtml(html: string): { root: string; child: string } {
const count = Array.from(html.matchAll(MatchTag));
const first = count[0];
const last = count[count.length - 1];
if (!count.length || first === last) {
return { root: html, child: '' };
}
const [value] = first;
const { index } = last;
return { root: html.slice(0, value.length), child: html.slice(value.length, index) };
}
上面这一段就是将多个节点拆分为 单节点及child子节点的代码。子节点然后再通过 splitPCHtml 继续拆分为细小节点
然后通过对比对应的内容即可实现我所需要的功能了。
这是个简单版本,当你对比Table的时候就能发现。我将继续完善...