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?'
: es
和 está
匹配,这很糟糕。仅应匹配 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']
我们使用后向 (?<=...)
查找字母,使用前瞻 (?=...)
查找非字母,反之亦然。
【讨论】:
很酷,我使用(?<!\\S)$1(?!\\S)
进行 unicode 单词匹配。
我实际上已经尝试过(?<=^|\PL)xxx(?=\PL|$)
,但它实际上并不能正常工作,至少在 JavaScript 中是这样。
请注意,lookbehind 的浏览器支持实际上比 /u
修饰符更差——“每个人”除了 IE 都有 /u
,但 Safari 和相关浏览器还没有lookbehind。【参考方案10】:
andrefs 给出了问题的正确答案。 在把所有需要的东西放在一起之后,我只会更清楚地重写它。
对于 ASCII 文本,您可以使用 \b
在模式的开头和结尾匹配单词边界。使用 Unicode 文本时,您需要使用 2 种不同的模式来做同样的事情:
(?<=^|\PL)
匹配主要模式之前的开始或单词边界。
使用(?=\PL|$)
匹配主模式之后的结尾或单词边界。
此外,在所有内容的开头使用 (?i)
,以使所有这些匹配不区分大小写。
所以得到的答案是:(?i)(?<=^|\PL)xxx(?=\PL|$)
,其中 xxx 是您的主要模式。这相当于 ASCII 文本的 (?i)\bxxx\b
。
要使您的代码正常工作,您现在需要执行以下操作:
将您要查找的模式或单词分配给您的变量“searchterm”。 转义变量的内容。例如,将'\'
替换为'\\'
,并对正则表达式的任何保留特殊字符执行相同操作,例如'\^', '\$', '\/'
等。请查看here,了解如何执行此操作的问题。
只需使用string.replace()
方法,将变量的内容插入上述模式中的“xxx”位置。
【讨论】:
谢谢,但是当我使用new RegExp(pattern.replace('xxx', searchterm), "g");
代替var pattern = '(?i)(?<=^|\PL)xxx(?=\PL|$)'
时,我得到了一个SyntaxError: Invalid regular expression: /(?i)(?<=^|PL)äl(?=PL|$)/: Invalid group
。
所以错误是由于(?i)
。如果我删除它,我会得到/(?<=^|PL)äl(?=PL|$)/g
,但是当我执行时我没有匹配。
@loretoparisi 我的回复描述了使用 PCRE (php) 风格编写正则表达式的答案。您收到错误是因为您在使用 ECMAScript 风格的环境中应用它。为了使其正常工作,您需要通过删除第一个术语并添加 i
修饰符来修改它:/(?<=^|\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
【讨论】:
只要使用(?<!\\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 字符的主要内容,如果未能解决你的问题,请参考以下文章
Javascript RegExp 用于精确匹配具有特殊字符的多个单词
用于匹配单词的 javascript 正则表达式模式,具有自定义单词边界