Javascript RegExp + 单词边界 + unicode 字符

Posted

技术标签:

【中文标题】Javascript RegExp + 单词边界 + unicode 字符【英文标题】:Javascript RegExp + Word boundaries + unicode characters 【发布时间】:2012-05-22 08:05:08 【问题描述】:

我正在构建搜索,我将使用 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]

=> äÄåÅöÖ

【问题讨论】:

@Walkerneo: \b 表示正则表达式中的“单词边界”;斜线在这里被转义,因为它在一个字符串中。 @apsillers,谢谢,奇怪的是我以前没见过这个:/ 我使用 \b 是因为我想匹配每个单词的开头。 如您所见,Javascript 停留在 1960 年代那种愚蠢的纯 ASCII 思维模式中。它甚至不满足UTS#18 on Unicode Regular Expressions 级别 1 的“基本 Unicode 支持”所需的最基本的一致性要求。试图在 Javascript 中进行真正的 Unicode 文本处理工作是一个可怕的笑话,也是一个残酷的笑话:它无法完成。下面提到的 XRegexp 插件对于这些目的是必要的,但还不够。 新手注意:这不能在正则表达式中完成。不适用于 \b,不适用于 \s,不适用于 XRegExp,不适用于前瞻或环视。相信我,我已经尝试了所有方法,但一切都以某种或其他方式破裂。到目前为止,我发现唯一可靠的方法是将 unicode 字符串编码回 ascii 并按照最初的预期使用 \b 执行仅 ascii 的正则表达式搜索/替换。见这里:***.com/a/10590188/1329367 【参考方案1】:

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

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

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

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

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

【讨论】:

这是个好主意,也是唯一对我有用的东西。代替QQ,您可以使用___ 的控制字符串,它更安全且仍然是ascii,而不是encodeURI,您可以利用javascript 的本机escape/unescape 方法,否则它可以完成工作. 对于那些想用匹配的子字符串做某事的人来说,这不是一个好的解决方案 这是否假定任何非 ASCII 字符都是单词字符?例如,“äl”不会被视为“?älkää”中单词的开头,尽管它应该是。【参考方案2】:

在使用 Unicode 时,我注意到 \b 有一些很奇怪的地方:

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

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

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

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

【讨论】:

\b\B 在 JavaScript 中不支持 Unicode,因此他们认为 ä 是非字母数字字符,因此会在 pä 之间看到单词边界。 【参考方案3】:

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);   

细分:

(?: 括号 () 在 Regex 中形成一个捕获组。括号以问号开头,冒号?: 形成一个非捕获组。他们只是将术语组合在一起

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

| 条是“或”运算符。

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

) 关闭群组

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

【讨论】:

“试试这个”不是解决方案。提供一些关于为什么建议的正则表达式有效的信息。 (?:^|\\s) 到底是做什么的?你根本不解释这个解决方案。 这不是一个正确的解决方案。 (?:^|\\s) 不是像 \b 那样的零宽度断言,它将消耗匹配中的字符。积极的前瞻将是一个更好的主意((?=^|\\s)),但仅在比赛之后才有效,因为仍然不支持后瞻。此外,单词边界不仅仅是空格和字符串边界,还有大量其他字符。 有什么理由不在正则表达式中包含 $(字符串结尾)吗? IE。 (?:^|\s|$) 建议的正则表达式在匹配位于字符串开头或空格之后的行为不同。当它在开头匹配时返回匹配的文本,但是当它在空格之后匹配时,它也会将空格作为匹配的一部分返回,即使捕获是用冒号完成的。测试代码(在 Firefox 控制台中执行): let str1 = "un ejemlo";让 str2 = "ejemlo uno";让 reg = /(?:^|\s)un/gi; str1.match(reg); // ["un"] str2.match(reg); // ["un"] 这也匹配部分字符串匹配。 '¿dónde está la alcaldesa?': esestá 匹配,这很糟糕。仅应匹配 está\\b 应该有助于全字边界。【参考方案4】:

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

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

\s 应该可以满足您的需求,前提是搜索词仅由空格分隔。

【讨论】:

+1,但\b 不是像\w\s 那样的字符类简写,它是像\A$ 和lookarounds 这样的零宽度断言。跨度> 【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

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

XRegExp('(?=^|$|[^\\pL])')

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

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

【讨论】:

【参考方案8】:

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

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

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

https://github.com/wikimedia/unicodejs

【讨论】:

我不认为 Javascript 在这方面遵循 Unicode 标准。 在这种情况下仍然是一个有趣的资源! 这很酷,但不清楚如何在这种情况下使用它。【参考方案9】:

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

更新和改进max_masseti的回答:

随着在 ES2018 中为 RegEx 引入 /u 修饰符,您现在可以使用 \pL 来表示任何 unicode 字母,并使用 \PL(注意大写的 P)来表示除此之外的任何内容。

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

这样:

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

text.split(/(?<=\pL)(?=\PL)|(?<=\PL)(?=\pL)/);

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

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

【讨论】:

很酷,我使用(?&lt;!\\S)$1(?!\\S) 进行 unicode 单词匹配。 我实际上已经尝试过(?&lt;=^|\PL)xxx(?=\PL|$),但它实际上并不能正常工作,至少在 JavaScript 中是这样。 请注意,lookbehind 的浏览器支持实际上比 /u 修饰符更差——“每个人”除了 IE 都有 /u,但 Safari 和相关浏览器还没有lookbehind。【参考方案10】:

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

对于 ASCII 文本,您可以使用 \b 在模式的开头和结尾匹配单词边界。使用 Unicode 文本时,您需要使用 2 种不同的模式来做同样的事情:

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

所以得到的答案是:(?i)(?&lt;=^|\PL)xxx(?=\PL|$),其中 xxx 是您的主要模式。这相当于 ASCII 文本的 (?i)\bxxx\b

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

将您要查找的模式或单词分配给您的变量“searchterm”。 转义变量的内容。例如,将'\' 替换为'\\',并对正则表达式的任何保留特殊字符执行相同操作,例如'\^', '\$', '\/' 等。请查看here,了解如何执行此操作的问题。 只需使用string.replace() 方法,将变量的内容插入上述模式中的“xxx”位置。

【讨论】:

谢谢,但是当我使用new RegExp(pattern.replace('xxx', searchterm), "g"); 代替var pattern = '(?i)(?&lt;=^|\PL)xxx(?=\PL|$)' 时,我得到了一个SyntaxError: Invalid regular expression: /(?i)(?&lt;=^|PL)äl(?=PL|$)/: Invalid group 所以错误是由于(?i)。如果我删除它,我会得到/(?&lt;=^|PL)äl(?=PL|$)/g,但是当我执行时我没有匹配。 @loretoparisi 我的回复描述了使用 PCRE (php) 风格编写正则表达式的答案。您收到错误是因为您在使用 ECMAScript 风格的环境中应用它。为了使其正常工作,您需要通过删除第一个术语并添加 i 修饰符来修改它:/(?&lt;=^|\PL)xxx(?=\PL|$)/gmi【参考方案11】:

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

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

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

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

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

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

其中\u0400-\u04ff 是table of codes 中提供的一系列西里尔字符。这不是一个理想的解决方案,但是,它在大多数情况下都能正常工作。

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

要试用我的示例,请运行下面的代码 sn-p。

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

【讨论】:

只要使用(?&lt;!\\S)$cyrillicSearchValue(?!\\S)就可以工作 它不适用于 Webkit (Safari)。 SyntaxError: 无效的正则表达式组说明符名称【参考方案12】:

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

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

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

输出:

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

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

分开

第一个正则表达式:(^|[^\n\pL])

^| = 行首或 [^\n\pL] = 任何不是字母或换行符的字符

第二个正则表达式:(?=$|\PL)

?= = 前瞻 $| = 行尾或 \PL = 任何非字母字符

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

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

【讨论】:

以上是关于Javascript RegExp + 单词边界 + unicode 字符的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式RegExp

Javascript RegExp 用于精确匹配具有特殊字符的多个单词

用于匹配单词的 javascript 正则表达式模式,具有自定义单词边界

JavaScript -- 时光流逝:js中的正则表达式 -- RegExp 对象

RegExp实现字符替换

JS常用正则表达&RegExp对象