51

我正在构建搜索,我将使用 javascript 自动完成功能。我来自芬兰(芬兰语),所以我必须处理一些特殊字符,如 ä、ö 和 å

当用户在搜索输入字段中输入文本时,我尝试将文本与数据匹配。

如果用户键入例如“ää”,这是一个无法正常工作的简单示例。与“äl”相同

var title = "this is simple string with finnish word tämä on ääkköstesti älkää ihmetelkö";
// Does not work
var searchterm = "äl";

// does not work
//var searchterm = "ää";

// Works
//var searchterm = "wi";

if ( new RegExp("\\b"+searchterm, "gi").test(title) ) {
    $("#result").html("Match: ("+searchterm+"): "+title);
} else {
    $("#result").html("nothing found with term: "+searchterm);   
}

http://jsfiddle.net/7TsxB/

那么如何让这些 ä、ö 和 å 字符与 javascript 正则表达式一起使用?

我想我应该使用 unicode 代码,但我应该怎么做呢?这些字符的代码是:[\u00C4,\u00E4,\u00C5,\u00E5,\u00D6,\u00F6]

=> äÄåÅöö

4

12 回答 12

43

Regex 似乎存在问题,并且单词边界\b与字符串的开头匹配且起始字符超出了正常的 256 字节范围。

而不是使用\b,尝试使用(?:^|\\s)

var title = "this is simple string with finnish word tämä on ääkköstesti älkää ihmetelkö";
// Does not work
var searchterm = "äl";

// does not work
//var searchterm = "ää";

// Works
//var searchterm = "wi";

if ( new RegExp("(?:^|\\s)"+searchterm, "gi").test(title) ) {
    $("#result").html("Match: ("+searchterm+"): "+title);
} else {
    $("#result").html("nothing found with term: "+searchterm);   
}

分解:

(?:括号()形成正则表达式中的捕获组。括号以问号开头,冒号?:形成非捕获组。他们只是将术语组合在一起

^插入符号匹配字符串的开头

|条是“或”运算符。

\s匹配空格(出现\\s在字符串中,因为我们必须转义反斜杠)

)关闭组

因此,我们不使用\b匹配单词边界且不适用于 unicode 字符的 using ,而是使用匹配字符串开头或空格的非捕获组。

于 2012-05-14T20:25:43.960 回答
21

JavaScript RegEx 中的\b字符类实际上只对简单的 ASCII 编码有用。 是and设置 or和字符串的开头或结尾\b之间的边界的快捷代码。这些字符集只考虑 ASCII “单词”字符,其中等于并且是该类的否定。\w\W\w\w[a-zA-Z0-9_]\W

这使得 RegEx 字符类在处理任何真实语言时基本上没有用处。

\s应该适用于您想要做的事情,前提是搜索词仅由空格分隔。

于 2012-05-14T20:33:21.917 回答
14

这个问题很老,但我想我找到了一个更好的解决方案,用于使用 unicode 字母的正则表达式中的边界。使用 XRegExp 库,您可以实现一个有效的 \b 边界扩展它

XRegExp('(?=^|$|[^\\p{L}])')

结果是 4000+ 字符长,但它似乎工作得很好。

一些解释: (?= ) 是一个零长度的前瞻,它查找开始或结束边界或非字母 unicode 字符。最重要的想法是前瞻,因为 \b 不捕获任何东西:它只是对或错。

于 2015-09-13T21:44:52.523 回答
7

当您必须使用来自 Unicode 的特定字符集时,我建议您使用XRegExp,该库的作者映射了所有类型的区域字符集,使得使用不同语言的工作更容易。

于 2012-05-14T21:23:03.810 回答
7

\b是字母和非字母字符之间转换的快捷方式,反之亦然。

更新和改进max_massti的答案:

随着/uES2018 中 RegEx 修饰符的引入,您现在可以\p{L}用来表示任何 unicode 字母,并且\P{L}(注意大写字母P)表示除此之外的任何字符。

编辑:以前的版本不完整。

像这样:

const text = 'A Fé, o Império, e as terras viciosas';

text.split(/(?<=\p{L})(?=\P{L})|(?<=\P{L})(?=\p{L})/);

// ['A', ' Fé', ',', ' o', ' Império', ',', ' e', ' as', ' terras', ' viciosas']

我们使用后(?<=...)向查找来查找字母,使用前瞻(?=...)查找非字母,反之亦然。

于 2019-07-31T12:15:14.900 回答
4

尽管这个问题似乎已经有 8 年历史了,但不久前我遇到了一个类似的问题(我必须匹配西里尔字母)。我花了一整天的时间在 StackOverflow 上找不到任何合适的答案。因此,为了避免其他人付出很多努力,我想分享我的解决方案。

是的,\b单词边界仅适用于拉丁字母(单词边界: \b):

单词边界 \b 不适用于非拉丁字母 单词边界测试 \b 检查该位置的一侧是否应该有 \w,而另一侧是否应该有“not \w”。但 \w 表示拉丁字母 az(或数字或下划线),因此该测试不适用于其他字符,例如西里尔字母或象形文字。

是的,JavaScriptRegExp实现几乎不支持 UTF-8 编码。

因此,我尝试在非拉丁字符的支持下实现自己的单词边界功能。为了使单词边界仅使用西里尔字符,我创建了这样的正则表达式:

new RegExp(`(?<![\u0400-\u04ff])${cyrillicSearchValue}(?![\u0400-\u04ff])`,'gi')

代码表中\u0400-\u04ff提供的西里尔字符范围在哪里。这不是一个理想的解决方案,但是,它在大多数情况下都能正常工作。

要使其适用于您的情况,您只需从Unicode characters 列表中选择适当范围的代码。

要试用我的示例,请运行下面的代码片段。

function getMatchExpression(cyrillicSearchValue) {
  return new RegExp(
    `(?<![\u0400-\u04ff])${cyrillicSearchValue}(?![\u0400-\u04ff])`,
    'gi',
  );
}

const sentence = 'Будь-який текст кирилицею, де необхідно знайти слово з контексту';

console.log(sentence.match(getMatchExpression('текст')));
// expected output: ["текст"]


console.log(sentence.match(getMatchExpression('но')));
// expected output: null

于 2020-08-13T08:51:39.087 回答
2

\b在使用 Unicode 时,我注意到一些非常奇怪的事情:

/\bo/.test("pop"); // false (obviously)
/\bä/.test("päp"); // true (what..?)

/\Bo/.test("pop"); // true
/\Bä/.test("päp"); // false (what..?)

似乎\band的含义\B是相反的,但仅在与非 ASCII Unicode 一起使用时?这里可能有更深层次的东西,但我不确定它是什么。

无论如何,似乎单词边界是问题,而不是 Unicode 字符本身。也许您应该替换\b(^|[\s\\/-_&]),因为这似乎可以正常工作。(不过,让你的符号列表比我的更全面。)

于 2012-05-14T20:18:55.913 回答
1

我的想法是使用代表芬兰字母的代码进行搜索

new RegExp("\\b"+asciiOnly(searchterm), "gi").test(asciiOnly(title))

我最初的想法是使用普通encodeURI但 % 符号似乎干扰了正则表达式。

http://jsfiddle.net/7TsxB/5/

我使用 encodeURI 编写了一个粗略的函数来编码超过 128 的代码的每个字符,但删除它的 % 并在开头添加“QQ”。它不是最好的标记,但我无法让非字母数字工作。

于 2012-05-14T19:58:09.403 回答
1

您正在寻找的是 Unicode 字边界标准:

http://unicode.org/reports/tr29/tr29-9.html#Word_Boundaries

这里有一个 JavaScript 实现(unciodejs.wordbreak.js)

https://github.com/wikimedia/unicodejs

于 2016-03-14T14:30:26.980 回答
0

我遇到了类似的问题,但我不得不替换一系列术语。如果两个术语在文本中彼此相邻(因为它们的边界重叠),我发现的所有解决方案都不起作用。所以我不得不使用一些修改过的方法:

var text = "Ještě. že; \"už\" à. Fürs, 'anlässlich' že že že.";
var terms = ["à","anlässlich","Fürs","už","Ještě", "že"];
var replaced = [];
var order = 0;
for (i = 0; i < terms.length; i++) {
    terms[i] = "(^\|[ \n\r\t.,;'\"\+!?-])(" + terms[i] + ")([ \n\r\t.,;'\"\+!?-]+\|$)";
}
var re = new RegExp(terms.join("|"), "");
while (true) {
    var replacedString = "";
    text = text.replace(re, function replacer(match){
        var beginning = match.match("^[ \n\r\t.,;'\"\+!?-]+");
        if (beginning == null) beginning = "";
        var ending = match.match("[ \n\r\t.,;'\"\+!?-]+$");
        if (ending == null) ending = "";
        replacedString = match.replace(beginning,"");
        replacedString = replacedString.replace(ending,"");
        replaced.push(replacedString);
        return beginning+"{{"+order+"}}"+ending;
    });
if (replacedString == "") break;
order += 1;
}

请参阅小提琴中的代码:http: //jsfiddle.net/antoninslejska/bvbLpdos/1/

正则表达式的灵感来自:http ://breakthebit.org/post/3446894238/word-boundaries-in-javascripts-regular

我不能说,我觉得解决方案很优雅......

于 2015-06-24T13:07:18.280 回答
0

该问题的正确答案由andrefs给出。在将所有必需的东西放在一起之后,我只会更清楚地重写它。

对于 ASCII 文本,您可以\b用于匹配模式开头和结尾的单词边界。使用 Unicode 文本时,您需要使用 2 种不同的模式来执行相同的操作:

  • 用于(?<=^|\P{L})匹配主要模式之前的开始或单词边界。
  • 用于(?=\P{L}|$)匹配主模式之后的结尾或单词边界。
  • 此外,(?i)在所有内容的开头使用,以使所有这些匹配不区分大小写。

所以得到的答案是:(?i)(?<=^|\P{L})xxx(?=\P{L}|$),其中 xxx 是您的主要模式。这相当于(?i)\bxxx\bASCII 文本。

为了使您的代码正常工作,您现在需要执行以下操作:

  • 将要查找的模式或单词分配给您的变量“searchterm”。
  • 转义变量的内容。例如,替换'\''\\'并且对正则表达式的任何保留特殊字符也执行相同的操作,例如'\^', '\$', '\/'等。请在此处查看有关如何执行此操作的问题。
  • string.replace()只需使用该方法,将变量的内容插入到上面的模式中,代替“xxx” 。
于 2020-02-12T12:14:55.780 回答
0

我有一个类似的问题,我试图用不同的 unicode 词替换所有特定的 unicode 词,我不能使用lookbehind,因为它在 JS 引擎中不受支持,该代码将被使用。我最终像这样解决了它:

const needle = "КАРТОПЛЯ";
const replace = "БАРАБОЛЯ";
const regex = new RegExp(
  String.raw`(^|[^\n\p{L}])`
    + needle
    + String.raw`(?=$|\P{L})`,
   "gimu",
);

const result = (
    'КАРТОПЛЯ сдффКАРТОПЛЯдадф КАРТОПЛЯ КАРТОПЛЯ КАРТОПЛЯ??? !!!КАРТОПЛЯ ;!;!КАРТОПЛЯ/#?#?'
    + '\n\nКАРТОПЛЯ КАРТОПЛЯ - - -КАРТОПЛЯ--'
  )
    .replace(regex, function (match, ...args) {
      return args[0] + replace;
    });
console.log(result)

输出:

БАРАБОЛЯ сдффКАРТОПЛЯдадф БАРАБОЛЯ БАРАБОЛЯ БАРАБОЛЯ??? !!!БАРАБОЛЯ ;!;!БАРАБОЛЯ/#?#?

БАРАБОЛЯ БАРАБОЛЯ - - -БАРАБОЛЯ--

拆开它

第一个正则表达式:(^|[^\n\p{L}])

  • ^|= 行首或
  • [^\n\p{L}]= 任何不是字母或换行符的字符

第二个正则表达式:(?=$|\P{L})

  • ?== 前瞻
  • $|= 行尾或
  • \P{L}= 任何不是字母的字符

第一个正则表达式捕获该组,然后args[0]在替换期间使用 via 将其放回字符串中,从而避免向后查找。第二个正则表达式使用了前瞻。

请注意,第二个必须是前瞻,因为如果我们捕获它,则不会触发重叠的正则表达式匹配(例如КАРТОПЛЯ КАРТОПЛЯ КАРТОПЛЯ,只会匹配第一个和第三个)。

于 2021-12-06T04:28:55.750 回答