正则表达式替换 preg_replace() 以删除开头的 2 个字母 Eexcept NW/SE 等方向标记

Regular expression replace preg_replace() to remove 2 letter at beginning Eexcept NW/SE etc. direction markers

我正在处理一个相对不一致和混乱的数据源,需要一些关于非常具体的正则表达式的帮助。

我们得到的很多字符串都以 2 个字母数字(大写或小写)开头,后跟 space,我们需要清除这些字符串,因此我们可以执行以下操作:

$town = "PE Springfield" // truncate to "Springfield"
$town = "Kr Nashville" // truncate to "Nashville"

但在某些情况下,前缀是我们需要保留的指令,例如 NE、Sw 等。

$town = "NW Brockvillle" // keep to "NW Brockville"
$town = "Se Nashville" // uppercase to " SE Nashville"

我可以编写一系列更复杂的 if/else 语句来分别处理方向字符串,然后将其合并回去,但我希望有一个出色的正则表达式。

到目前为止,我用于 NW/NE 匹配(所有情况)或任何 2 个字母数字开头后跟 space 的正则表达式是:

$cleanpattern[] = '/^[Nn][Ww]\s/';
$cleanpattern[] = '/^[Nn][Ee]\s/';
$cleanpattern[] = '/^[Ss][Ww]\s/';
$cleanpattern[] = '/^[Ss][Ee]\s/';
$cleanpattern[] = '/^[A-Za-z]{2}\s/';

请注意,出于某种原因,此数据源绝不会只提供 N、S、E 或 W(单个字母)。但我想把它覆盖起来以防将来有什么坏处?

然后对于替换字符串,我有以下数组(无论 Sw/ne 是小写还是大写,我都希望它们全部大写):

$replacementpattern[] = 'NW ';
$replacementpattern[] = 'NE ';
$replacementpattern[] = 'SW ';
$replacementpattern[] = 'SE ';
$replacementpattern[] = '';

当我 运行 preg_replace($cleanpattern, $replacementpattern, $town, 1) 时,问题是在检查并设置 NW/SE 等之后,它们会被第 5 条规则清除。事实证明,此函数中“LIMIT”的第 4 个参数并未限制 pattern/replace 数组中的循环,而是仅限制每个字符串中完成的替换次数。

您“可以”做到的一种方式,而不是说它“很棒”...

正则表达式

^(((?:NW|NE|SW|SE|N|E|S|W)\s)|[a-z]{2}\s)

在单独的捕获组中捕获有效案例和无效案例,然后在替换中只输出有效案例。

例子

这里我们还根据需要使用回调将字母大写。

<?php
$tests = [
    "PE Springfield", // truncate to "Springfield"
    "Kr Nashville", // truncate to "Nashville"
    "NW Brockvillle", // keep to "NW Brockville"
    "Se Nashville" // uppercase to " SE Nashville"
];

foreach ($tests as $subject) {
    $result = preg_replace_callback('/^(((?:NW|NE|SW|SE|N|E|S|W)\s)|[a-z]{2}\s)/i', 
        function ($groups) {
            return isset($groups[2]) ? strtoupper($groups[2]) : '';
        }, $subject);
    echo "$subject = $result\n";
}

输出

PE Springfield = Springfield
Kr Nashville = Nashville
NW Brockvillle = NW Brockvillle
Se Nashville = SE Nashville

详细的 RegEx 细分

搜索

选项:不区分大小写;

断言字符串开头的位置 «^» 匹配下面的正则表达式并将其匹配捕获到反向引用编号 1 «(((?:NW|NE|SW|SE|N|E|S|W)\s)|[a-z]{2}\s)»

  • 匹配这个备选方案(仅当这个备选方案失败时才尝试下一个备选方案)«((?:NW|NE|SW|SE|N|E|S|W)\s)»
    • 匹配下面的正则表达式并将其匹配捕获到反向引用编号 2 «((?:NW|NE|SW|SE|N|E|S|W)\s)»
      • 匹配下面的正则表达式 «(?:NW|NE|SW|SE|N|E|S|W)»
        • 匹配这个备选方案(仅当这个备选方案失败时才尝试下一个备选方案)«NW»
        • 或匹配此备选方案(仅当此备选方案失败时才尝试下一个备选方案)«NE»
        • 或匹配此选项(仅当此选项失败时才尝试下一个选项)«SW»
        • 或匹配此备选方案(仅当此备选方案失败时才尝试下一个备选方案)«SE»
        • 或匹配此备选方案(仅当此备选方案失败时才尝试下一个备选方案)«N»
        • 或匹配此选项(仅当此选项失败时才尝试下一个选项)«E»
        • 或匹配此备选方案(仅当此备选方案失败时才尝试下一个备选方案)«S»
        • 或匹配这个选项(如果这个匹配失败,则整个组失败)«W»
      • 匹配作为“空白字符”的单个字符(任何 Unicode 分隔符、制表符、换行符、回车 return、垂直制表符、换页符、下一行)«\s»
    • 或匹配这个选项(如果这个匹配失败,则整个组失败)«[a-z]{2}\s»
      • 匹配“a”和“z”之间的单个字符(不区分大小写)«[a-z]{2}»
        • 恰好 2 次 «{2}»
      • 匹配作为“空白字符”的单个字符(任何 Unicode 分隔符、制表符、换行符、回车 return、垂直制表符、换页符、下一行)«\s»

替换


只输出反向引用#2

与@Dean 的出色方法(preg_replace_callback 和捕获组)没有什么不同,但在模式中有一种避免交替的魔法。目标:无需测试捕获组是否存在于回调中,因为它总是成功。

$result = preg_replace_callback(
    '~^([ns]?[ew]?\b ?)(?:^[a-z]{2} )?~mi',
    fn($m) => strtoupper($m[1]),
    $str
);

demo

解释:

在捕获组 ([ns]?[ew]?\b ?) 中,除单词边界 \b 外,所有内容都是可选的。这个单词边界在两种情况下成功:

  • 一个字母和 space 之间(至少匹配一个字母)
  • 当字母和 space 在此组中不匹配时在字符串的开头。

此捕获组的结果:

  • 字母后面总是跟着 space
  • a space 前面没有至少一个字母是不可能的
  • 即使没有任何匹配项,捕获组也会以空字符串成功。

当捕获组匹配空串时,可选的非捕获组(?:^[a-z]{2} )?最终可以成功。请注意,它以 ^ 锚点开头,以确保它之前的捕获组为空,并且单词边界不是问题,因为它在字符串的开头和非字符串的第一个字母之间成功-捕获组,显然,当捕获组不为空时,^ 失败,非捕获组也失败。