正则表达式匹配包含和不包含特殊/重音字符的字符串?

10
有没有一个正则表达式可以匹配包含和不包含特殊字符的特定字符串?特殊字符不敏感,也就是说。
例如,céra将匹配cera,反之亦然。
有什么想法吗?
编辑:我想匹配具有特殊/重音字符和没有这些字符的特定字符串。而不仅仅是任何字符串/字符。 测试示例:
$clientName   = 'céra';
$this->search = 'cera';

$compareClientName = strtolower(iconv('utf-8', 'ascii//TRANSLIT', $clientName));
$this->search      = strtolower($this->search);

if (strpos($compareClientName, $this->search) !== false)
{
    $clientName = preg_replace('/(.*?)('.$this->search.')(.*?)/iu', '$1<span class="highlight">$2</span>$3', $clientName);
}

输出: <span class="highlight">céra</span>

如您所见,我想要突出显示特定的搜索字符串。但是,我仍然希望显示匹配字符串的原始(带重音)字符

我想我需要以某种方式与Michael Sivolobov的答案结合起来。

我认为我需要使用单独的preg_match()preg_replace(),对吗?


4
“é”不是特殊字符,而是一种带重音符号的字母。 - Toto
我正在尝试匹配特定的字符串,而不仅仅是任何字符串/字符。对于混淆造成的困扰,我已经更新了我的问题。 - jlmmns
1
我也根据 @Michael Sivolobov 的建议更新了我的答案。 - Kethryweryn
4个回答

9
你可以使用\p{L}模式来匹配任何字母。 来源 你需要在正则表达式后面使用u修饰符以启用unicode模式。
例如:/\p{L}+/u 编辑:
尝试这样做。它应该将每个带重音的字母替换为一个搜索模式,其中包含带重音的字母(单个字符和unicode双字符)和不带重音的字母。然后,您可以使用更正的搜索模式来突出显示文本。
function mbStringToArray($string)
{
    $strlen = mb_strlen($string);
    while($strlen)
    {
        $array[] = mb_substr($string, 0, 1, "UTF-8");
        $string = mb_substr($string, 1, $strlen, "UTF-8");
        $strlen = mb_strlen($string);
    }
    return $array;
}

// I had to use this ugly function to remove accents as iconv didn't work properly on my test server.
function stripAccents($stripAccents){
    return utf8_encode(strtr(utf8_decode($stripAccents),utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'),'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY'));
}

$clientName = 'céra';

$clientNameNoAccent = stripAccents($clientName);

$clientNameArray = mbStringToArray($clientName);

foreach($clientNameArray as $pos => &$char)
{
    $charNA =$clientNameNoAccent[$pos];
    if($char != $charNA)
    {
        $char = "(?:$char|$charNA|$charNA\p{M})";
    }
}

$clientSearchPattern = implode($clientNameArray); // c(?:é|e|e\p{M})ra

$text = 'the client name is Céra but it could be Cera or céra too.';

$search = preg_replace('/(.*?)(' . $clientSearchPattern . ')(.*?)/iu', '$1<span class="highlight">$2</span>$3', $text);

echo $search; // the client name is <span class="highlight">Céra</span> but it could be <span class="highlight">Cera</span> or <span class="highlight">céra</span> too.

OP 真的想匹配“任何”字母吗?还是说例如仅指定“e”时要匹配 é | e - MrWhite
@Kethryweryn 确实如此,但我正在寻找特定的字符串。就像 substr() 一样,如果这有任何意义的话。 - jlmmns
2
@Kethryweryn:是的,但它也会匹配abcd,这是不可取的。据我理解,OP想要一种通用/简单的方法来仅匹配c[ée]ra,但适用于任何单词和任何重音- 这并不是那么简单。 - MrWhite
修正了一个错别字 $pos => &$char。并且更改了变量 $charNA = $clientNameNoAccent[$pos];(期望是字符串而不是数组)。非常好的代码,可以直接使用,谢谢! - jlmmns
@jlmmns 你可以将字符串作为数组使用。由于它不是迭代器,所以无法在字符串上使用foreach,但是您可以像访问数组一样访问任何字符(在C中,它确实是一个数组)。关于iconv(),我曾在另一个stackoverflow问题中读到过它是一个糟糕的iconv库。虽然我没有努力找出原因,但每次我使用它时都会遇到问题... - Kethryweryn
显示剩余7条评论

7

如果你想知道某个字母上是否有重音或其他标记,可以通过匹配模式\p{M}来检查。

更新

你需要将模式中的所有有重音的字母转换为备选组:

例如:céra -> c(?:é|e|e\p{M})ra

我为什么要添加e\p{M}?因为你的é字母可能是Unicode中的一个字符,也可能是两个字符的组合(e和重音符号)。e\p{M}匹配带重音符号的e(两个单独的Unicode字符)

将模式转换为匹配所有字符后,就可以在preg_match中使用它了。


3

正如您在评论中所指出的,您不需要使用正则表达式来查找特定字符串。为什么不使用explode呢?像这样:

$clientName   = 'céra';
$this->search = 'cera';

$compareClientName = strtolower(iconv('utf-8', 'ascii//TRANSLIT', $clientName));
$this->search      = strtolower($this->search);

$pieces = explode($compareClientName, $this->search);

if (count($pieces) > 1)
{
    $clientName = implode('<span class="highlight">'.$clientName.'</span>', $pieces);
}

编辑:

如果您的 $search 变量可能包含特殊字符,为什么不将其 translit,并使用带有 $offsetmb_strpos?像这样:

$offset = 0;
$highlighted = '';
$len = mb_strlen($compareClientName, 'UTF-8');
while(($pos = mb_strpos($this->search, $compareClientName, $offset, 'UTF-8')) !== -1) {
    $highlighted .= mb_substr($this->search, $offset, $pos-$offset, 'UTF-8').
         '<span class="highlight">'.
         mb_substr($this->search, $pos, $len, 'UTF-8').'</span>';
    $offset = $pos + $len;
}
$highlighted .= mb_substr($this->search, $offset, 'UTF-8');

更新2:

在IT技术中,使用mb_函数而不是简单的strlen等函数非常重要。这是因为带有重音符号的字符使用两个或更多字节存储。此外,始终确保您使用正确的编码方式,例如,请参考以下内容:

echo strlen('é');
> 2

echo mb_strlen('é');
> 2

echo mb_internal_encoding();
> ISO-8859-1

echo mb_strlen('é', 'UTF-8');
> 1

mb_internal_encoding('UTF-8');
echo mb_strlen('é');
> 1

我认为处理字符串位置是保持原始字符的唯一简单方法。我会研究一下,谢谢! - jlmmns
如果您认为这个回答解决了您的问题,请毫不犹豫地将其标记为被接受的答案 :) - Adam Zielinski
顺便说一下,我添加了另一个更新,涵盖了与多字节字符相关的重要警告。 - Adam Zielinski
我会回到这个答案。:) 感谢关于 mb_ 的信息! - jlmmns
我目前正在使用Kethryweryn的更新答案,因为它格式更好,更易于阅读和实现。但我也会尝试你的答案,看看哪个效果更好。 - jlmmns

2

正如你在这里看到的那样,POSIX等价类用于匹配具有相同排序顺序的字符,可以通过以下正则表达式完成:

[=a=]

根据你的地区,这将匹配 áä 以及 a


但是随着PHP 5.3的推出,POSIX正则表达式语法(以及相关的ereg()函数)不是已经被弃用了吗? - MrWhite
@w3d 是的,但是 PCRE 甚至支持所有 POSIX 命名字符类。 - revo
在哪个版本的PHP中?启用它有什么技巧吗?我得到了这个错误信息:“警告:preg_match()编译失败:不支持POSIX排序元素...” - MrWhite

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