匹配字母和带重音符号的字母

6
我正在寻找一个符合以下规律的正则表达式代码:
- 必须包含以下至少一种情况,且与整个字符串匹配。 - 只能包含字母(a-z A-Z)... - 以及带重音的字母(如á ä à等)。
我现在使用的是 preg_match('/^([\p{L}]*)$/iu', $input) ,但 \p{L} 匹配所有 Unicode 字母,包括中文。我只想允许英文字母和它们的变体。
因此,JohnDoe、Fübar、Lòrem、FírstNäme 和 Çákë 都是有效输入,因为它们都包含至少 1 个字母和/或带重音的字母,并且整个字符串匹配。

你有比较的样本输入数据吗? - Giacomo1968
[[:alpha:]]不能解决它吗? - Afonso Tsukamoto
@AfonsoTsukamoto 试一下吧。它仍然可以匹配中文和日文字符。 - Giacomo1968
4个回答

3
我建议使用这个简洁的正则表达式:
(?i)(?:(?![×Þß÷þø])[a-zÀ-ÿ])+

请看演示
  1. 这个正则表达式利用了你想要的重音字母似乎都存在于Unicode字符范围从Àÿ(参见这个表格),所以我们只需将其添加到字符类中。
  2. À-ÿ中有一些不想要的字符。与某些引擎不同,PCRE(PHP的正则表达式引擎)不支持字符类减法,但我们可以使用负向先行断言(?![×Þß÷þø])来模拟它。
  3. 请注意,有些字符(如à)可以由多个Unicode代码点表示(à字形或带有重音符号的a)。这仅匹配非组合字形。捕捉所有变化真的很难。

在您的代码中:

$regex = "~(?i)(?:(?![×Þß÷þø])[a-zÀ-ÿ])+~u";
$hit = preg_match($regex,$subject,$match);

太棒了!在JavaScript中它不起作用,但这个修改可以在PCRE、JavaScript和Python中工作:(?:(?![×Þß÷þø])[a-zA-ZÀ-ÿ])+ - Juan Garcia
@JuanGarcia 是的,这是一个很好的方法。我经常用这种方法来处理自定义范围。顺便感谢你的点赞(我猜是你点的赞 :) )。 - zx81
请更新答案以支持所有功能。这对其他人可能会很有用。 - Juan Garcia
@JuanGarcia 每个引擎都有自己打开不区分大小写模式的方法。我想在我的答案中使用它。如果有人阅读答案并想在JS中使用它,他们可以像这样添加模式:/(?:(?![×Þß÷þø])[a-zÀ-ÿ])+/i - zx81
这是一个非常好的正则表达式zx81!它缺少开头的“^”和结尾的“$”锚点,但如果添加了它们,它似乎可以很好地工作。我认为我在这个页面上提出的“iconv”解决方案可能会匹配更多的重音符号,虽然我已经给出了+1,因为它使用纯正则表达式回答了问题。 - Andrew Mackrodt

1
使用您的样本数据以及一些中文和日文字符,正则表达式/[!\p{Common}\p{Latin}]*/iu似乎可以工作。有关更多详细信息,请参阅预定义Unicode类别这个网站以及一个漂亮的简单解释在这里。但是,在此版本中,它对非拉丁字符返回一个空匹配。底部的EDIT:中的那个将为干净的布尔逻辑提供最佳解决方案:
// Set a test array.
$test_array = array();
$test_array[] = 'JóhnDoe';
$test_array[] = 'Fübar';
$test_array[] = 'Lòrem';
$test_array[] = 'FírstNäme';
$test_array[] = '•••••••';
$test_array[] = 'Çákë';
$test_array[] = '形声字 / 形聲字';
$test_array[] = 'ラドクリフ、マラソン';

 // Set the header for debugging output.
header('Content-Type: text/plain; charset=utf-8');

// Roll through the test array.
foreach ($test_array as $test_value) {

  // Run a regex to detect latin and common characters.
  preg_match('/[!\p{Common}\p{Latin}]*/iu', $test_value, $matches);

  // Kludge using array filtering to get rid of empty matches.
  $matches = array_filter($matches);

  // Dump the matches for debugging.
  print_r($matches);

}

输出如下所示:请注意中文和日文字符返回空匹配。还请注意,由于 \p{Common} 正则表达式匹配,••••••• 得以传递。如果您不希望像这样的常用字符通过,请将正则表达式更改为 /[!\p{Latin}]*/iu。我正在使用 array_filter 清除那些空值,但这是笨拙的。因此,这不是完美的,但可以使用:
Array
(
    [0] => JóhnDoe
)
Array
(
    [0] => Fübar
)
Array
(
    [0] => Lòrem
)
Array
(
    [0] => FírstNäme
)
Array
(
    [0] => •••••••
)
Array
(
    [0] => Çákë
)
Array
(
)
Array
(
)
编辑: 这个测试代码使用了我上面发布的正则表达式变体来消除上面提到的空非匹配问题;/(?:[\p{Latin}])+/iu。请注意,这是通过仅针对 \p{Latin} 进行匹配来工作的,因此 \p{Common} 在这里不适用。但是,使用 /(?:[\p{Latin}])+/iu 的结果更加清晰,并确保您可以使用简单的布尔检查 preg_match 来检查字符,而无需使用将 $matches 数组与 array_filter 混合的糟糕解决方案。
// Set a test array.
$test_array = array();
$test_array[] = 'JóhnDoe';
$test_array[] = 'Fübar';
$test_array[] = 'Lòrem';
$test_array[] = 'FírstNäme';
$test_array[] = '•••••••';
$test_array[] = 'Çákë';
$test_array[] = '形声字 / 形聲字';
$test_array[] = 'ラドクリフ、マラソン';

 // Set the header for debugging output.
header('Content-Type: text/plain; charset=utf-8');

// Roll through the test array.
foreach ($test_array as $test_value) {

  // Run a regex to detect latin and common characters.
  preg_match('/(?:[\p{Latin}])+/iu', $test_value, $matches);

  // Dump the matches for debugging.
  print_r($matches);

}

以下是新的结果。请注意,空数组是真正的空的,在这些情况下,prey_match将返回布尔值false

Array
(
    [0] => JóhnDoe
)
Array
(
    [0] => Fübar
)
Array
(
    [0] => Lòrem
)
Array
(
    [0] => FírstNäme
)
Array
(
)
Array
(
    [0] => Çákë
)
Array
(
)
Array
(
)

在阅读后面发布的zx81答案之后,我认为它应该成为这个问题中的最佳答案。他简洁优雅的正则表达式与我的更新现在可以在PCRE、JavaScript和Python中使用,对于其他人来说可能非常有用。 - Juan Garcia
1
@JuanGarcia 好的。这不是一场比赛。他的代码实际上比我的正则表达式更好,因为它不会返回奇怪的空集合,所以我只是+1。但是,我的代码在JavaScript中无法工作并不重要。原帖作者正在寻找PHP解决方案。我的答案是解决此问题的一种方法。在Stack Overflow上,多个答案都是好的,因为如果将来有人遇到这个问题,他们可以查看所有有效的选项。我的答案和zx81的解决方案都是有效的选项。 - Giacomo1968
当我输入类似于!Jóhn这样的内容时,它不应该被视为有效。整个字符串应该只包含字母或带重音符号的变体。 - 502 Error
@502错误,然后只需使用拉丁匹配即可。 - Giacomo1968

1

我使用 preg_matchiconv 的组合提出了以下解决方案。已在 Windows 和 Linux 的 php 5.5 上进行了测试:

$testWords = array(
    // pass
    'Çákë',
    'JohnDoe',
    'Fübar',
    'Lòrem',
    'FírstNäme',
    // fail
    'Ç@kë',
    'J0hnDoe',
    'F行bar',
    'L高rem',
    'F前rstNäme',
    'Ç学kë',
    '0'
);

$matchedWords = array_filter($testWords, function ($word) {
    // these characters should not be in the search string but may appear after iconv conversion
    $regexCharsNot = '\^~"`\'';

    $valid = false;

    if (!preg_match("/[$regexCharsNot]/u", $word)) {
        if ($word = @iconv('UTF-8', 'ASCII//TRANSLIT', $word)) {
            $valid = preg_match("/^[A-Za-z$regexCharsNot]+$/u", $word);
        }
    }

    return $valid;
});

echo print_r($matchedWords, true);

/*
Array
(
    [0] => Çákë
    [1] => JohnDoe
    [2] => Fübar
    [3] => Lòrem
    [4] => FírstNäme
)
 */

iconvASCII//TRANSLIT 引入了多余的字符,这就是为什么需要进行 $regexCharsNot 双重验证的原因。我使用以下方法来得出该列表:

// mb_str_split regex           http://www.php.net/manual/en/function.mb-split.php#99851
// list of accented characters  http://fasforward.com/list-of-european-special-characters/

$accentedCharacters = preg_split(
    '/(?<!^)(?!$)/u',
    'ÄäÀàÁáÂâÃãÅåĄąĂăÆæÇçĆćĈĉČčĎđĐďðÈèÉéÊêËëĚěĘęĜĝĢģĤĥÌìÍíÎîÏïĴĵĶķĹĺĻļŁłĽľÑñŃńŇňÖöÒòÓóÔôÕõŐőØøŒœŔŕŘřߌśŜŝŞşŠšŤťŢţÞþÜüÙùÚúÛûŰűŨũŲųŮůŴŵÝýŸÿŶŷŹźŽžŻż');

/*
$unsupported = ''; // 'Ǎǎẞ';

foreach ($accentedCharacters as $c) {
    if (!@iconv('UTF-8', 'ASCII//TRANSLIT', $c)) {
        $unsupported .= $c;
    }
}
*/

有趣的想法,但是你是不是想说 ^~" 应该是 [^~"](笔误)? - zx81

0

重音的字母是由它们自己的Unicode字符组成,与它们的非重音变体没有任何关系。对于人类观察者来说,它们可能看起来相关,但计算机无法通过将其与标准ASCII集合中的字母进行比较来知道其中的区别。

实现这一点的方法是通过提供一个允许哪些字符的白名单来为您的正则表达式提供支持。

如果您不需要有重音符号的字符,另一种方法是使用像Apache Lucene这样的库(这是一个Java库,但我认为它可以用在PHP中),使用正确的分析器将带重音符号的字符替换为无重音符号的变体。


1
不是完全正确的。您可以使用预定义的Unicode脚本来匹配特定的Unicode字符组,如此处所述。http://www.regular-expressions.info/unicode.html#category - Giacomo1968
@JakeGould 不错的发现。但是如果输入不是字母+标记格式,而只是字母,这个方法会失败吗? - Juan Garcia
不是很清楚你的意思。请看我的回答,我解释了\p{Common}\p{Latin}是什么。最终,你只能在广泛、预定义的区域内进行比较。但根据脚本的实用性和需求,有办法规避这个问题。 - Giacomo1968
1
@JakeGould 哦,我明白了,你是按组搜索,这很聪明。在你的答案中加上+1。我不知道正则表达式支持这样的功能。不幸的是,据我所知,JavaScript没有实现这个功能(或者它没有记录在案)。 - Juan Garcia
基本上,像高级正则表达式这样的东西只适用于能够处理完整的PCRE(Perl兼容正则表达式)库的语言。JavaScript受限制。MySQL也是如此。但在PHP、Perl、Ruby和Java(我相信?)中,这些东西是可用的。 - Giacomo1968

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