html添加关键词-HTML实现关键字高亮字符串中的“跨标签关键字”匹配

2023-09-18 0 604 百度已收录

来源|

之前分享过一期《》。 明天我们将分享一个在HTML字符串中匹配“跨表关键字”的实现案例,类似于浏览器ctrl+f搜索结果。

实现方案是在文本字符串中搜索关键字,然后使用特殊标签(例如字体标签)将关键字包裹起来并替换匹配的内容,最后得到一个HTML字符串,渲染该字符串并在字体标签上使用CSS样式可以达到突出的效果。

1、匹配关键字:HTML字符串和文本字符串的比较 1、纯文本字符串的处理

对于纯文本字符串html添加关键词,比如:“谁第一次看到河边的月亮?河上的月亮第一次照到人身上是什么时候?”html添加关键词,如果我们要匹配关键字“河上的月亮”,则匹配结果可以处理为:

江畔何人初见月?<font style="background: #ff9632">江月</font>何年初照人?

这样,“江粤”二字就被字体标签包裹起来,并且给字体标签应用了特殊的背景样式,达到了关键词高亮的效果。

2. HTML字符串的处理

对于上面的示例,如果内容字符串是 HTML 文本:

江畔何人初见<b></b>?江<b></b>何年初照人?

对于同一个关键词“江悦”,如何处理? 由于关键字中的单词位于不同的标签中,因此只能分别用字体标签替换:

江畔何人初见<b></b><font style="background: #ff9632"></font><b><font style="background: #ff9632"></font></b>何年初照人?

这是一个比较简单的情况。 在实际情况中,关键词可能跨越多个级别、多个标签。

2.跨标签匹配关键词

跨标签解析关键词似乎就是将匹配的关键词提取到每个标签中对应的子段,然后用font等标签包裹起来,然后对font标签使用高亮样式。

对于整个 HTML 内容,呈现的文本由各种标签内的文本节点组成。

由于关键字匹配的内容会跨越标签,因此需要按顺序取出每个文本节点并将节点内容拼接在一起进行匹配。

拼接时,记录拼接字符串中节点文本的起止位置,这样当关键字匹配拼接字符串中的某个位置时,就可以截取文本片段并用字体标签包裹起来。

1.深度优先遍历DOM树去除文本节点

深度优先可以循环或递归地遍历。 这里用一个循环来实现,取出某个元素下的所有文本节点(通过nodeType来确定文本节点):

function getTextNodeList (dom) {  const nodeList = [...dom.childNodes]  const textNodes = []  while (nodeList.length) {    const node = nodeList.shift()    if (node.nodeType === node.TEXT_NODE) {      textNodes.push(node)    } else {      nodeList.unshift(...node.childNodes)    }  }  return textNodes}

2.取出所有文字内容并拼接

获得文本节点列表后,就可以提取出所有的文本内容,并在拼接结果中记录每个文本片段的起止索引:

getTextInfoList (textNodes) {  let length = 0  const textList = textNodes.map(text => {    let start = length, end = length + text.wholeText.length    length = end    return [text.wholeText, start, end]  })  return textList}

拼接文字:

const content = textList.map(([text]) => text).join('')

3、匹配关键词

获取到拼接文本后,可以借助拼接文本获取所有的拼接结果。 我们就偷个懒,这里就用正则匹配吧。 您必须使用正则表达式中使用的一些特殊符号通配符:

getMatchList (content, keyword) {  const characters = [...'\[]()?.+*^${}:'].reduce((r, c) => (r[c] = true, r), {})  keyword = keyword.split('').map(s => characters[s] ? `\${s}` : s).join('[\s\n]*')  const reg = new RegExp(keyword, 'gmi')  return [...content.matchAll(reg)] // matchAll结果是个迭代器,用扩展符展开得到数组}

关键字字符通配符处理后,在字符之间插入常规空白字符和换行符(sn),以在匹配时忽略一些不可见的字符。 上面的代码使用了matchAll函数。 匹配结果展开后,结果是一个链表。 链表中的每一项都包含匹配文本、匹配索引等。一个简单的matchAll反例:

4. 关键词替换为字体标签

根据关键字匹配结果索引和每个文本节点的起始和结束索引,我们可以估计每个关键字匹配哪些文本节点。 其中,开头和结尾文本节点可能只是部分匹配,而中间的文本节点可能部分匹配。 文本节点的所有内容都匹配。

例如对于 HTML 文本:

<span>江畔何人初见<b></b>?江月何年初照人?</span>

其 DOM 树对应三个文本节点:

如果关键字是“谁第一次看到月亮?”,那么此时第一个文本节点匹配后半部分,第二个文本节点完全匹配,第三个文本节点匹配第一个字符。 三个节点的匹配部分需要分别替换为字体标签:

<span>江畔<font>何人初见</font><b><font></font></b><font></font>江月何年初照人?</span>

默认情况下,连续文本将位于同一文本节点中。 对于匹配部分内容的文本节点,需要将其拆分为两个。 您可以使用 Text.splitText() API 来拆分文本节点。 API 接收一个索引值,该索引值从索引位置剪切文本节点的后半部分,并返回包含后半部分内容的新文本节点。 上面的例子中,匹配了3个节点,分裂后,会得到5个文本节点:

中间的三个文本节点就是需要替换的节点。 使用replaceChild,您可以直接用字体标签替换文本节点。

对于整个HTML字符串来说,同一个关键字可能同时有多个匹配结果,因此必须对所有匹配结果进行上述处理。 利用前面步骤得到的textNodes、textList、matchList,代码实现如下:

function replaceMatchResult (textNodes, textList, matchList) {  // 对于每一个匹配结果,可能分散在多个标签中,找出这些标签,截取匹配片段并用font标签替换出  for (let i = matchList.length - 1; i >= 0; i--) {    const match = matchList[i]    const matchStart = match.index, matchEnd = matchStart + match[0].length // 匹配结果在拼接字符串中的起止索引    // 遍历文本信息列表,查找匹配的文本节点    for (let textIdx = 0; textIdx < textList.length; textIdx++) {      const { text, startIdx, endIdx } = textList[textIdx] // 文本内容、文本在拼接串中开始、结束索引      if (endIdx < matchStart) continue // 匹配的文本节点还在后面      if (startIdx >= matchEnd) break // 匹配文本节点已经处理完了      let textNode = textNodes[textIdx] // 这个节点中的部分或全部内容匹配到了关键词,将匹配部分截取出来进行替换      const nodeMatchStartIdx = Math.max(0, matchStart - startIdx) // 匹配内容在文本节点内容中的开始索引      const nodeMatchLength = Math.min(endIdx, matchEnd) - startIdx - nodeMatchStartIdx // 文本节点内容匹配关键词的长度      if (nodeMatchStartIdx > 0) textNode = textNode.splitText(nodeMatchStartIdx) // textNode取后半部分      if (nodeMatchLength < textNode.wholeText.length) textNode.splitText(nodeMatchLength)      const font = document.createElement('font')      font.innerText = text.substr(nodeMatchStartIdx, nodeMatchLength)      textNode.parentNode.replaceChild(font, textNode)    }  }}

代码中采用逆序遍历的方式来遍历匹配结果。 原因是遍历过程对textNodes有副作用:遍历过程中textNodes中的文本节点会被剪切。 假设同一个文本节点有多个匹配,就会进行多次分割,而textNodes指的是原始文本节点,也就是前半部分,所以从后向前遍历会保证未处理的匹配文本节点的完整性。

同时代码中省略了字体节点的样式设置,可以根据自己的逻辑进行设置。

3. 完整代码调用

上述步骤描述了在HTML字符串中跨标签匹配关键字的所有过程的实现。 以下是完整的代码调用示例:

function replaceKeywords (htmlString, keyword) {  if (!keyword) return htmlString  const div = document.createElement('div')  div.innerHTML = htmlString  const textNodes = getTextNodeList(div)  const textList = getTextInfoList(textNodes)  const content = textList.map(({ text }) => text).join('')  const matchList = getMatchList(content, keyword)  replaceMatchResult(textNodes, textList, matchList)  return div.innerHTML}

输入 HTML 字符串和关键字,将关键字用字体标签包裹在 HTML 字符串中并返回。

4. 总结

上面的实现方案中省略了一些简单的细节,比如设置font标签的样式、忽略隐藏DOM匹配等。

字体标签样式设置取决于使用场景。 如果是长HTML字符串匹配,建议不要直接设置样式属性,而是通过操作样式表来达到目的。 您可以为字体标签设置特殊属性,然后使用属性选择器设置样式。 例如,您可以为字体设置highlight="${i}"属性,以将不同的样式应用于匹配的关键字。 要操作样式表,可以将innerText设置为样式标签或调用CSSStyleSheet.insertRule()和CSSStyleSheet.deleteRule()。

演示:#/搜索突出显示

查看github上的源码:

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 html html添加关键词-HTML实现关键字高亮字符串中的“跨标签关键字”匹配 https://www.wkzy.net/game/197200.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务