匹配一个单词,候选人可以跨越顺序组(跨度)

时间:2016-05-18 00:42:15

标签: javascript html regex algorithm search

用户将提供一些搜索字词。我们只是说它是一个字符串,可能包含任何单词或特殊字符(如/?,$*等。

我需要将这些字符序列与HTML中出现的任何字符进行匹配,即使搜索字词跨越连续跨度;在我的HTML中,特殊字符有时会单独包装。

例如:用户提供“你的妈妈?”,并且有一段包含<span>Your mom</span><span class="special">?</span>

的段落

我需要一种有效的方法来确定a)查询是否存在,以及b)哪些元素包含查询。搜索到的文本可以是复杂的HTML,包含很多单词,跨度,div等等。

3 个答案:

答案 0 :(得分:2)

我可能会尝试编写一个解析器来区分标签的打开和关闭,以及它们的文本内容(希望HTML不包含不完整的标签)。对于索引,也许你可以使用一堆元组,每个元组代表深度和计数,以及当前状态的内存。您的简单示例将索引为:

[(1,1)] tag opens, text: 'Your mom'
query text matches so far
[(1,1),(1,1)] tag closes, remove. 
[(1,2)] tag opens, maintain depth, increase count, text: '?'
query text continues to match
[(1,2)] tag closes, remove

答案 1 :(得分:1)

首先,您必须将“字符”拆分为多个组。最偏执的方式是按性格,但最终效率会非常低。知道我对你的数据做了什么,我认为任何匹配[a-zA-Z\s]+的东西都会成为一个令牌而其他一切都变成另一个令牌。

可能合乎逻辑的另一件事是做一个迭代过程,在每次失败的尝试之后,你将其进一步分解。

无论你决定什么,你都需要使用一些JavaScript来做到这一点。但这应该相当容易。

在拆分之后,你需要开始考虑构建一个正则表达式。

您可以在每个令牌之间放置(?:<[^>]*>\s*)*,但在将它们放入正则表达式之前,某些字符需要进行转义。某处有一个完整的列表,但其中包括:$^*.+?/\{}[]()

对于您的示例,您最终可能会得到以下内容:

/your mom(?:<[^>]*>\s*)*\?/i

i表示不区分大小写。

您可以获取匹配位置的索引,例如this

var match = /regex/.exec("string to match against");
if (match) {
    alert("match found at " + match.index);
}

答案 2 :(得分:1)

此解决方案将查找并返回包含搜索文本的第一个元素,即使该文本包含嵌入的标记。

TL; DR 播放示例!

var content = $("#content");
var search = $("#search");
var go = $("#go");

function escapeRegExp(str) {
  return str.replace(/[\/\\{}()*+?.^$|[\]-]/g, "\\$&");
}

function recursiveElementSearch(regex, element) {
  var text = element.text();
  
  if (text.match(regex)) {
    var children = element.children();
    var len = children.length;
    
    for (var i=0; i < len; ++i) {
      var child = $(children[i]);
      var found = recursiveElementSearch(regex, child);
      
      if (found != null) {
        return found;
      }
    }
    
    return element;
  }
  
  return null;
}

go.click(function() {
  var value = $.map(search.val().split(""), function(value, index) {
    return escapeRegExp(value);
  });
  var regex = new RegExp(value.join(""), "i");
  var element = recursiveElementSearch(regex, content);

  console.log("Element: ", element ? element.attr("id") : "null");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="content">
  <div id="first">
    <span id="a">Your mom</span><span class="special">?</span>
  </div>
  <div id="second">
    <span id="b">Where is <strong>your</strong> mom</span><span class="special">?</span>
  </div>
  <div id="third">
    <span id="c">Yours<span>&nbsp;</span><a href="#">mom</a></span><span class="special">?</span>
  </div>
  <div id="fourth">
    <span id="d">My mom</span><span class="special">!</span>
  </div>
  <div id="fifth">
    <span id="e">Their mom<i>s</i></span><span class="special">...</span>
  </div>
</div>

<input id="search" value="Your mom?">
<label for="search">Search:</label>
<button id="go">Go!</button>

这种方法的工作方式是对输入文本进行清理(转义),然后递归检查每个元素的文本以查看是否包含搜索文本。

将返回的元素是在最深层找到的第一个元素。搜索是深度优先的,因此在第一个元素上找到3个深度的匹配将在第二个元素的深度为1级之前返回。

提供的HTML代码段显示嵌套代码不是问题。使用此HTML,“你的妈妈?”的搜索结果返回div id="first",搜索“妈妈!”返回div id="fourth",搜索“你的妈妈”会返回div id="c"

可以进行一些简单的改进。以下是我在测试中看到的有用信息:

  • 折叠搜索文本中的空格以匹配任意数量的空格(例如,搜索“你的妈妈”应与搜索“你的妈妈”相同)
  • 处理Unicode空格,包括“&amp; nbsp;”
  • 包含一个返回所有匹配项的版本(例如,搜索“你的妈妈?”应返回[div id="first"div id="second"],因为两者都匹配)

鉴于所有这些,这是一种非常有用的搜索页面文本的方法。