在上一篇文章 文本标记 - 高亮标记多个指定位置的文本 中, 说明了如何高亮后台返回的指定位置的多个数据. 现在解释下一个需求: 用户在高亮后台返回的数据的基础上, 从页面上选择文本高亮, 并且在表格中显示文本
上篇文章说到高亮的效果: 一般都是给指定的文本包裹一个标签 ( 如 ), 在给这个标签设置一个样式 ( 比如添加背景色 ), 这是实现这个需求的基础.
ps: 如果只有用户从网页上选择文本的需求, 可以考虑用 web-highlighter
插件, 比起自己手写方法来要简便一些.
首先介绍一下这个需求的逻辑:
-
点这个知识抽取的按钮, 然后左边文本高亮, 右面的表格填充 (实体这一列为只读状态), (有多个表格, 采取了不同的高亮样式)
-
点击这个按钮, 对应的高亮消失, 表格变空
-
用户从左边文本选择一段, 像这样; 再点击这个对钩, 就会填充, 然后获取它对应的下标起始位置, 保存下来, 并且将选中的文本高亮
这里的难点就是, 因为已经标记了后台返回的那些文本, 然后这些高亮文本都被包裹了span标签. 这时候获取的下标就不是从整个文本对应的下标0开始的了, 因为整个文本被span标签分成了很多部分, 用户标记的时候, 获取的这个下标只是这个分段里的下标, 但是我们要传给后端相对于整篇文本的下标, 这就是难点.
通过对不同位置的selection对象的研究 , 发现了以下几个不同点:
-
普通文本的
a) parentNode是<div class="fileText">...</div>
b) previousSibling是前一个已经标记的元素节点, 元素节点的nodeType=1, className可以看到该元素节点的类名; nextSibling也是已标记的元素节点 -
已标记文本的
a) parentNode是当前标签, eg.<span class="EntityHighlight">
纯电动汽车</span>
b) previousSibling 和 nextSibling都是null -
判断是否是开头的普通文本:
a) parentNode是整个div
后台返回的某个高亮本文对象:
思路
- 首先通过baseNode的parentNode是div还是span判断是普通文本还是标记文本,
- 如果是普通文本, 通过previousSibling寻找前面的元素节点:
a) 如果返回null => 说明是开头的元素, 直接用
开始位置: baseOffset
结束位置: baseOffset+length-1
b) 如果返回某个元素节点 => 通过className判断是table1还是table2里的元素 (项目中不只有右边一个表格, 这几个表格中使用了不同的高亮样式), 然后 前兄弟节点的end+1+baseOffset 就是用户选择的selection相对于整个文本的开始位置, 结束位置可以是: 开始位置+选中文本.length-1 - 如果是标记文本, 根据它的className判断是table1还是table2中的元素, 获取对应的start.
开始位置: start+baseOffset
结束位置: 开始位置+选中文本.length-1
代码
// 知识抽取按钮
knowExtract() {
if (this.isEdit) {
this.$alert("请先保存文本");
} else {
const _key = this.extract_key;
console.log(_key);
knowiExtract(_key).then((res) => {
console.log(res);
if (res.status == 0) {
// 知识抽取成功
this.$message.success(res.message);
// 表1数据渲染
this.tableData1 = res.entity;
console.log(this.tableData1);
// 表2数据渲染
// this.tableData2 = res.attribute
// 表1文本高亮
this.highlightEntity();
} else {
this.$message.error("知识抽取失败");
}
});
}
},
// 表1 文本高亮
highlightEntity() {
console.log("文本高亮");
let text = this.fixedText
let textList = text.split("");
// 表1中的实体高亮
this.tableData1.forEach((item) => {
let startNum = item.start;
let endNum = item.end + 1;
console.log(startNum,endNum)
if (startNum == endNum - 1) {
textList[startNum] = `<span class="EntityHighlight">${textList[startNum]}</span>`;
} else {
textList[startNum] = `<span class="EntityHighlight">${textList[startNum]}`;
textList[endNum - 1] = `${textList[endNum - 1]}</span>`;
}
});
// 表2 中的文本高亮
this.text = textList.join("");
},
// 表1 实体高亮初始化 (编辑按钮)
initEntiExtract(i) {
this.$message.warning("请从前向后选择文本内容")
console.log("实体高亮初始化" + i);
this.tableData1[i].entity = ""
this.tableData1[i].start = ""
this.tableData1[i].end = ""
console.log(this.tableData1)
this.highlightEntity()
this.selection = window.getSelection();
},
// 表1 实体抽取 (√按钮)
entityExtract(i) {
console.log(this.selection);
let newEntiry = this.selection.toString();
// 保证起始位置小于结束位置 (用户倒选的操作)(未完成)
// 获取截取开头的baseNode
let startNode = this.selection.baseNode
// 根据parentNode是div还是span判断是普通文本还是标记文本
let startParent = startNode.parentNode.nodeName
console.log("parentNode 父节点")
console.log(startParent)
if (startParent == "DIV") {
// 普通文本, 通过previousSibling寻找前面的元素节点
let preSib = startNode.previousSibling
console.log("前兄弟节点")
console.log(preSib)
if (!preSib) {
console.log("前兄弟节点不存在")
// 如果返回null => 说明是开头的元素, 直接用开始位置: baseOffset,结束位置: baseOffset+length-1
this.tableData1[i].start = this.selection.baseOffset
this.tableData1[i].end = this.selection.baseOffset + newEntiry.length - 1
console.log("起始位置")
console.log(this.tableData1[i].start, this.tableData1[i].end)
} else {
let preSibClass = preSib.className
console.log("前兄弟节点的className")
console.log(preSibClass)
if (preSibClass == "EntityHighlight") {
// 表1元素
// 寻找前兄弟节点的end位置
let preSibEnd = 0
for(let k=0;k<this.tableData1.length;k++){
if(this.tableData1[k].entity == preSib.innerText){
console.log(preSib.innerText)
console.log(this.tableData1[k].end)
preSibEnd = this.tableData1[k].end
}
}
console.log("前兄弟节点的end位置")
console.log(preSibEnd)
this.tableData1[i].start = preSibEnd + 1 + this.selection.baseOffset
this.tableData1[i].end = this.tableData1[i].start + newEntiry.length - 1
console.log("起始位置")
console.log(this.tableData1[i].start, this.tableData1[i].end)
}
}
} else if (startParent == "SPAN") {
// 标记文本, 根据它的className判断是table1还是table2中的元素
let nodeClass = startNode.className
console.log(nodeClass)
if (nodeClass == "EntityHighlight"){
// 表1元素
// 获取起始位置
for(let j=0;j<this.tableData1.length;j++) {
if( this.tableData1[j].entity = startNode.nodeValue) {
this.tableData1[i].start = this.tableData1[j].start + this.selection.baseOffset
this.tableData1[i].end = this.tableData1[i].start + this.selection.length - 1
}
}
}
}
// 给table1中相应的entity赋值
this.tableData1[i].entity = newEntiry;
console.log(this.tableData1)
this.highlightEntity()
},