JavaScript中的不区分大小写字符串替换?

54

我需要在JavaScript字符串中不区分大小写地突出显示给定的关键字。

例如:

  • highlight("foobar Foo bar FOO", "foo") 应该返回 "<b>foo</b>bar <b>Foo</b> bar <b>FOO</b>"

我需要使代码适用于任何关键字,因此使用像/foo/i这样的硬编码正则表达式并不是一个足够的解决方案。

最简单的方法是什么?

(这是一个更一般的问题的实例,在标题中有详细说明,但我觉得最好使用具体而有用的例子来解决。)

7个回答

74

如果您准备好搜索字符串,可以使用正则表达式。例如,在PHP中有一个名为preg_quote的函数,它将字符串中的所有正则表达式字符替换为它们的转义版本。

以下是JavaScript的这样一个函数(来源):

function preg_quote (str, delimiter) {
  //  discuss at: https://locutus.io/php/preg_quote/
  // original by: booeyOH
  // improved by: Ates Goral (https://magnetiq.com)
  // improved by: Kevin van Zonneveld (https://kvz.io)
  // improved by: Brett Zamir (https://brett-zamir.me)
  // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
  //   example 1: preg_quote("$40")
  //   returns 1: '\\$40'
  //   example 2: preg_quote("*RRRING* Hello?")
  //   returns 2: '\\*RRRING\\* Hello\\?'
  //   example 3: preg_quote("\\.+*?[^]$(){}=!<>|:")
  //   returns 3: '\\\\\\.\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:'

  return (str + '')
    .replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&')
}

那么你可以这样做:
function highlight(str, search) {
    return str.replace(new RegExp("(" + preg_quote(search) + ")", 'gi'), "<b>$1</b>");
}

你根本不应该使用RegExp。你可以将“'gi'”标志作为第三个参数传递给replace。你不必使用preg_quote或创建RegExp或任何类似的东西。 - Nathan Wall
1
String.replace确实有一个“flags”方法,但它是非标准的,因此不可靠。最好的方法是创建一个“polyfill”方法来选择适当的选项。 - YellowAfterlife
@YellowAfterlife 这里传递的标志是传递给正则表达式的,而不是传递给 String.replace 函数,所以没问题 :) - Mike Gleason jr Couturier

64
function highlightWords( line, word )
{
     var regex = new RegExp( '(' + word + ')', 'gi' );
     return line.replace( regex, "<b>$1</b>" );
}

1
当然,正如@bobince所指出的那样,你需要小心替换和搜索的内容。如果你小心引用你的正则表达式字符,上述方法将适用于纯文本和大多数搜索。 - tvanfosson
如果被替换的单词中有正则表达式字符,这将会遇到麻烦。@okoman的解决方案可以避免这种情况。 - Herb Caudill
如果工作是点或句号,这将无法正常工作,如何使其在点或句号的情况下正常工作,或多个句号(例如:"...")? - Santosh
@helpme 这些是正则表达式中的特殊字符。你需要首先使用反斜杠进行引用。请注意,反斜杠在字符串中也是一种引号字符,因此您需要在替换字符串中使用两个反斜杠。word.replace(/\./g, '\\.') - tvanfosson

12

您可以使用一个函数增强RegExp对象,该函数可以为您进行特殊字符转义:

RegExp.escape = function(str) 
{
  var specials = /[.*+?|()\[\]{}\\$^]/g; // .*+?|()[]{}\$^
  return str.replace(specials, "\\$&");
}

那么您就可以放心使用其他人建议的方法:

function highlightWordsNoCase(line, word)
{
  var regex = new RegExp("(" + RegExp.escape(word) + ")", "gi");
  return line.replace(regex, "<b>$1</b>");
}

在 JavaScript 中,正则表达式中的 ? 需要用双反斜杠进行转义,例如 \?。 - Jerinaw
@Jerinaw 你认为我的 RegExp.escape 函数是做什么用的? - Tomalak
我遇到了问题,需要用双\转义问号,但我猜在[]中不需要转义。 - Jerinaw
1
@Jerinaw 实际上,你只需要为正则表达式转义一次问号,因此当你使用正则表达式字面量时,最终会得到\?。但是对于JS字符串,你需要转义反斜杠本身,因此当你从字符串构建正则表达式时,最终会得到\\?。而且,在字符类中,你真正需要转义的唯一字符是] - Tomalak
1
请不要在JavaScript中鼓励猴子补丁。 - Damon

6
正则表达式只有在关键词是真正的单词时才有效,您可以使用RegExp构造函数而不是文字字面量从变量创建一个正则表达式:
var re= new RegExp('('+word+')', 'gi');
return s.replace(re, '<b>$1</b>');

如果“关键字”中包含标点符号,则会出现困难,因为在正则表达式中,标点符号往往具有特殊含义。不幸的是,与大多数其他支持正则表达式的语言/库不同,在JavaScript中没有用于转义正则表达式中标点符号的标准函数。

而且,您无法完全确定需要转义哪些字符,因为不是每个浏览器对正则表达式的实现都保证完全相同。(特别是新版浏览器可能会添加新功能。)尽管在实践中可以这样做,但反斜杠转义非特殊字符并不能保证仍然有效。

所以你能做的最好的一件事是:

  • 尝试捕获今天常见浏览器使用的每个特殊字符 [参见Sebastian的配方]
  • 反斜杠转义所有非字母数字字符。注意:\W也将匹配非ASCII Unicode字符,这并不是你真正想要的。
  • 只需确保在搜索之前没有非字母数字字符即可

但是,如果您要在已经带有标记的HTML中突出显示单词,则会遇到麻烦。您的“单词”可能出现在元素名称或属性值中,在这种情况下,尝试在其周围包装<b>将导致破裂。在更复杂的场景中,可能会出现HTML注入到XSS安全漏洞。如果您必须处理标记,则需要更复杂的方法,将“< ... >”标记拆分开来,然后尝试单独处理每个文本段。


5

这个怎么样:

if(typeof String.prototype.highlight !== 'function') {
  String.prototype.highlight = function(match, spanClass) {
    var pattern = new RegExp( match, "gi" );
    replacement = "<span class='" + spanClass + "'>$&</span>";

    return this.replace(pattern, replacement);
  }
}

这样,就可以按以下方式进行调用:
var result = "The Quick Brown Fox Jumped Over The Lazy Brown Dog".highlight("brown","text-highlight");

3
对于那些对正则表达式感到困惑或害怕的人:

function replacei(str, sub, f){
 let A = str.toLowerCase().split(sub.toLowerCase());
 let B = [];
 let x = 0;
 for (let i = 0; i < A.length; i++) {
  let n = A[i].length;
  B.push(str.substr(x, n));
  if (i < A.length-1)
   B.push(f(str.substr(x + n, sub.length)));
  x += n + sub.length;
 }
 return B.join('');
}

s = 'Foo and FOO (and foo) are all -- Foo.'
t = replacei(s, 'Foo', sub=>'<'+sub+'>')
console.log(t)

输出:

<Foo> and <FOO> (and <foo>) are all -- <Foo>.

0

为什么不在每次调用函数时创建一个新的正则表达式呢?您可以使用:

new Regex([pat], [flags])

其中[pat]是用于匹配的字符串,[flags]是标志。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接