Press "Enter" to skip to content

自定义分词起始规则实现关键词全词高亮项目实战(全语种通吃)

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

背景

 

最近有BU给我们这边提了一个需求,希望我们能改进现有的邮件关键词匹配功能,希望能支持英文的全词匹配。

 

目前前端页面是会对后台配置的关键词进行高亮显示的,只不过算是 模糊匹配 了,也就是说如果关键词配的是 book ,邮件中的 booked 中的 book 也会高亮,而这并不是BU希望的。

 

现状

 

 

我看了下原来高亮功能的具体实现

 

export function escapeHtml(text) {
  var map = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ' ',
  };
  return text.replace(/[&<>"']/g, function(m) {
    return map[m];
  });
}
// 搜索html关键字并高亮
export function htmlKeyWordHighlight(parentNode, keyWards, color = 'yellow') {
  if (keyWards === void 0 || !parentNode) return;
  for (var i = 0; i < parentNode.childNodes.length; i++) {
    var child = parentNode.childNodes[i];
    if (child.nodeType == 3 && child.data.indexOf(keyWards) != -1) {
      var newChild = document.createElement('span');
      var tagStripper = new RegExp(keyWards, 'g');
      newChild.innerHTML = escapeHtml(child.data).replace(
        tagStripper,
        `<span>` + keyWards + '</span>',
      );
      parentNode.replaceChild(newChild, child);
    } else {
      htmlKeyWordHighlight(child, keyWards, color);
    }
  }
}

 

打出这 keyWards 的我猜测用的编辑器多半是vscode或者是个心态特别好的老哥,但凡是idea系列的那波浪线就容易让人有强迫症。

 

用法大致就是这样 htmlKeyWordHighlight(document.body, "book","#FFB10A") ,这样就会把 body 上所有包含 book 的字符串高亮起来了。

 

方法里面执行的是字符串的replace操作,以 book 的替换为例,实际执行的是 "A guest who booked xxx".replace(/book/g, "***") 操作,此时是不会顾及是否是全词匹配的,只要匹配上都会替换的。

 

常规解决方案——正则表达式\b

 

既然之前用的是正则表达式,我们优先考虑能不能优化下正则表达式来完成需求。

 

如果只是想简单的应对英文的话,我们用上正则表达式的元字符 \b 就行,它代表着单词的开头或结尾,也就是单词的分界处。 更精确的说法, \b 匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在) \w ,我们可以简单的理解 \b 等识别出一个分词的开始和结束。

 

很符合英文单词全词匹配,可以测试一下。 "A guest who booked xxx".replace(/\bbook\b/g, "***")

 

 

带不带 \b 效果很明显,客户提的需求也就算满足了。

 

 

终极解决方案——逐词匹配

 

如果想对中文等非英文语种进行类似的分词用 \b 就不行了,我们也没法更换 \b 的识别机制。

 

我们试着自己实现下,对潜在的目标文本进行逐词匹配就行。

 

记得首先要确定潜在目标文本,缩小逐词匹配的范围。

 

export function htmlKeyWordHighlight(parentNode, keyword, color = 'yellow') {
  if (keyword === void 0 || !parentNode) {
    return;
  }
  for (let i = 0; i < parentNode.childNodes.length; i++) {
    let child = parentNode.childNodes[i];
    if (child.nodeType === 3 && child.data.indexOf(keyword) !== -1) {
      let newChild = document.createElement('span');
      newChild.innerHTML = keyWordPreciseReplacer(escapeHtml(child.data), keyword,
        `<span>` + keyword + '</span>'
      );
      parentNode.replaceChild(newChild, child);
    } else {
      htmlKeyWordHighlight(child, keyword, color);
    }
  }
}
/**
 * 根据分词规则精准替换关键词
 * @param keyword
 * @param target
 * @param replaceText
 * @returns {*}
 */function keyWordPreciseReplacer(keyword, target, replaceText) {
  function isOver(str) {
    // 根据常用分词标点符号和空格进行分词
    const regStr = '[。!!??,,\\.\\s()()]';
    return new RegExp(regStr).test(str);
  }
  let index = 0;
  let targetIndex = 0;
  const result = [];
  const text = keyword + ' '; // 结尾添加一个空格方便isOver判断
  for (let i = 0; i < text.length; i++) {
    const str = text[i];
    if (isOver(str)) { // text新的分词开始
      if (targetIndex === target.length) { // target也刚好全匹配
        result.push([index, i - target.length]);
        index = i;
      }
      targetIndex = 0; // 重新计数
    } else if (str === target[targetIndex]) {
      targetIndex++; // 继续匹配
    } else {
      targetIndex = -1; // 本轮分词已没戏,等待下轮分词
    }
  }
  result.push([index])
  return result.reduce((acc, curr) => acc + keyword.slice(...curr) + (curr.length > 1 ? replaceText : ''), '');
}

 

注释写的算比较详细了。

 

isOver 里面判断的分词的依据可能会有遗漏,后续可能动态调整,建议写到配置文件里面。

 

现在可没有用 \b 了哦。

 

 

现在我们搞个中文测试下,我们把关键词设置为 后续 ,把分词的正则表达式里面加入 司会 ,即 const regStr = '[。!!??,,\\.\\s()()司会]';

 

 

预期的效果是: 我们 里面的 不需要高亮,而 我司 需要高亮,同时 后续 也需要高亮,因为 代表分词结束。

 

 

高亮结果符合预期,后期无非是遗漏了分词符号(比如、——),需要改下配置来调整正则即可。

 

总结

 

如果场景较为单一,仅需要支持英文的话,直接用 \b 即可,如果需要特别卷的话,那就用逐词匹配吧。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。