使用 Javascript 正则表达式匹配带有重音符号的字符

61

今天我发现了一个有趣的代码片段:

/\ba/.test("a") --> true
/\bà/.test("à") --> false

然而,

/à/.test("à") --> true
首先,什么鬼?
其次,如果我想匹配一个单词开头的重音字符,该怎么办?(我真的不想使用过于复杂的选择器,比如/(?:^|\s|'|\(\) ....

10
你的WTF的答案是,Javascript在正则表达式中没有正确处理Unicode。请参考标准,了解其应该如何工作。或者使用在这方面符合标准的语言,比如Perl、PHP、PCRE和ICU正则表达式等。举个例子,在这些语言中,"à"肯定匹配模式/\bà/,它们更适合处理Unicode。 - tchrist
你可以删除重音,然后进行简单的[a-z]检查。请参见https://dev59.com/OnNA5IYBdhLWcg3wX8nk - Adriano
6个回答

73

这对我有效:

/^[a-z\u00E0-\u00FC]+$/i

这里得到帮助。


48
你的正则表达式无法捕获法语字母“ÿ”和“œ”。尝试使用/^[A-Za-z\u00C0-\u017F]+$/来匹配所有字母。 - Cœur
7
大写字符的等价物是什么? - Yanick Rochon
43
自从什么时候ÿ成为了法语字母?(我是母语法语者...) - Adriano
5
这个正则表达式包括00F7,它是除号。 - retorquere

43
/\bà/.test("à")不匹配的原因是"à"不是一个单词字符。转义序列\b仅在单词字符和非单词字符之间的边界处匹配。/\ba/.test("a")匹配是因为"a"是单词字符。因此,在字符串开头(不是单词字符)和单词字符“a”之间有一个边界。
JavaScript中正则表达式中定义的单词字符为[a-zA-Z0-9_]
要在字符串开头匹配重音符号,只需在正则表达式开头使用^字符(例如/^à/)。该字符表示字符串开头(与\b不同,后者匹配字符串中的任何单词边界)。这是最基本和标准的正则表达式,因此绝对不会过于复杂。

1
啊,好的,这解释了很多事情,但我想我在原问题中说错了。我需要匹配单词的开头,而不是字符串。我认为选择器会变得“过于复杂”的原因是因为它需要匹配字符串、空格、括号、逗号、句号等的开头。 - nickf
2
+1 我只想补充一点,使用 re.test() 方法时,需要注意 re.lastIndex 属性的行为,该属性包含最后匹配的偏移量(并且是下一次匹配尝试开始的位置)。虽然在这种情况下该方法被应用于正则表达式字面量,但如果正则表达式对象存储在变量中并被多次使用,则这一点很重要。 - ridgerunner
2
Javascript不符合Unicode标准,因为引用的标准非常明确地说明像à这样的字符绝对意味着在正则表达式中可以匹配\w - tchrist

3

Stack Overflow曾经有一个关于正则表达式中的非ASCII字符的问题,你可以在这里找到。他们没有处理单词边界,但也许还是可以给你一些有用的提示。

还有另一个页面,但他想匹配字符串而不是单词。

我不知道并且现在也没有找到你问题的解决方案,但是当我看到我第一个链接中使用的怪物正则表达式时,你想要避免的那个组合并不过分,我的意见是你的解决方案。


3

如果你想匹配字母,无论是否带有重音符号,Unicode 属性转义 可能会有所帮助。

/\p{Letter}*/u.test("à"); // true
/\p{Letter}/u.test('œ'); // true
/\p{Letter}/u.test('a'); // true
/\p{Letter}/u.test('3'); // false
/\p{Letter}/u.test('a'); // true

匹配单词开头有些棘手,但是(?<=(?:^|\s))似乎可以解决问题。其中(?<= )是一个正向后瞻,确保主表达式前面存在某些内容。(?: )是一个非捕获组,所以您不会在稍后使用的任何匹配中引用此部分。然后,如果未设置多行标志,则^将匹配字符串的开头,如果设置了多行标志,则匹配行的开头,而\s将匹配空格字符(空格/制表符/换行符)。
因此,将它们结合起来,它应该像这样:/(?<=(?:^|\s))\p{Letter}*/u 如果您只想匹配带重音符号的字符,请使用否定字符集a-zA-Z。
/(?<=(?:^|\s))[^a-zA-Z]\p{Letter}*/u.match("bœ") // false
/(?<=(?:^|\s))[^a-zA-Z]\p{Letter}*/u.match("œb") // true

// Match characters, accented or not
let regex = /\p{Letter}+$/u;

console.log(regex.test("œb")); // true
console.log(regex.test("bœb")); // true
console.log(regex.test("àbby")); // true
console.log(regex.test("à3")); // false
console.log(regex.test("16 tons")); // true
console.log(regex.test("3 œ")); // true

console.log('-----');

// Match characters to start of line, only match characters

regex = /(?<=(?:^|\s))\p{Letter}+$/u;

console.log(regex.test("œb")); // true
console.log(regex.test("bœb")); // true
console.log(regex.test("àbby")); // true
console.log(regex.test("à3")); // false

console.log('----');

// Match accented character to start of word, only match characters

regex = /(?<=(?:^|\s))[^a-zA-Z]\p{Letter}+$/u;

console.log(regex.test("œb")); // true
console.log(regex.test("bœb")); // false
console.log(regex.test("àbby")); // true
console.log(regex.test("à3")); // false


这绝对是最好的答案 - 当前的答案缺少许多字母并包含非字母字符。我已经添加了一个到MDN页面的链接。 - mikemaccana

2

const regex = /^[\-/A-Za-z\u00C0-\u017F ]+$/;
const test1 = regex.test("à");
const test2 = regex.test("Martinez-Cortez");
const test3 = regex.test("Leonardo da vinci");
const test4 = regex.test("ï");

console.log('test1', test1);
console.log('test2', test2);
console.log('test3', test3);
console.log('test4', test4);

在 Wak 和 Cœur 的回答基础上:

/^[\-/A-Za-z\u00C0-\u017F ]+$/

这个正则表达式可以匹配空格和破折号。

例如:Leonardo da Vinci,Martinez-Cortez

最初的回答。


你的示例中没有包含任何重音字符,这是 OP 的要求。 - alexandre-rousseau

1
Unicode允许一些带有重音符号的字符有两种可选但等效的表示形式。例如,é 有两种Unicode表示形式:'\u0039''\u0065\u0301'。前者被称为组合形式,后者被称为分解形式。JavaScript允许在两种形式之间进行转换:
'é'.normalize('NFD') // decompose: '\u0039' -> '\u0065\u0301'
'é'.normalize('NFC') // compose: '\u0065\u0301' -> '\u0039'
'é'.length // composed form: -> 1
'é'.length // decomposed form: -> 2 (looks identical but has different representation)
'é' == 'é' // -> false (composed and decomposed strings are not equal)

代码点'\u0301'属于Unicode组合变音标记代码块0300-036F。因此,匹配这些带重音符号的一种方法是以分解形式进行比较:
// matching accented characters
/[a-zA-Z][\u0300-\u036f]+/.test('é'.normalize('NFD')) // -> true
/\bé/.test('é') // -> false
/\bé/.test('é'.normalize('NFD')) // -> true (NOTE: /\bé/ uses the decomposed form)

// matching accented words
/^\w+$/.test('résumé') // -> false
/^(?:[a-zA-Z][\u0300-\u036f]*)+$/.test('résumé'.normalize('NFD')) // -> true

有趣!但是看起来你得到了错误的组合字符编码。根据我的Firefox浏览器和ISO-Latin,它似乎应该是'\u00e9'而不是'\u0039'用于' é'。 - Sebastian

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