PHP 正则表达式和相邻的捕获组

PHP regex and adjacent capturing groups

我第一次在正则表达式中使用捕获组,我想知道我的问题是什么,因为我假设正则表达式引擎从左到右查看字符串。

我正在尝试将 UpperCamelCase 字符串转换为连字符小写字符串,例如:

1
HelloWorldThisIsATest => hello-world-this-is-a-test

我的前提是一个字母字符串,所以我不需要担心数字或其他字符。这是我尝试过的:

1
mb_strtolower(preg_replace('/([A-Za-z])([A-Z])/', '$1-$2',"HelloWorldThisIsATest"));

结果:

1
hello-world-this-is-atest

这几乎是我想要的,除了 atest 之间应该有一个连字符。我已经将 A-Z 包含在我的第一个捕获组中,因此我假设引擎会看到 AT 并将其连字符。

我做错了什么?


你的正则表达式不起作用的原因:重叠匹配

  • 您的正则表达式匹配 IsATest 中的 sA,允许您在 sA 之间插入 -
  • 为了在 AT 之间插入 -,正则表达式必须匹配 AT
  • 这是不可能的,因为 A 已经作为 sA 的一部分进行了匹配。在直接正则表达式中不能有重叠匹配。
  • 所有的希望都失去了吗?不!这是环视的完美情况。

用两条简单的线做到这一点

这是使用正则表达式的简单方法:

1
2
$regex = '~(?<=[a-zA-Z])(?=[A-Z])~';
echo strtolower(preg_replace($regex,"-","HelloWorldThisIsATest"));

查看 php 演示底部的输出:

Output: hello-world-this-is-a-test

稍后会添加解释。 :)

  • 正则表达式不匹配任何字符。相反,它针对字符串中的位置:字母大小写变化之间的位置。为此,它使用后视和前瞻
  • (?<=[a-zA-Z]) 后视断言当前位置之前是一个字母
  • (?=[A-Z]) 前瞻断言当前位置后面是一个大写字母。
  • 我们只是用 - 替换这些位置,并将手数转换为小写。

如果您仔细查看此 regex101 屏幕,您会看到单词之间的线条,其中 regex 匹配。

参考

  • 前瞻和后瞻零长度断言
  • 掌握前瞻和后瞻


为简单起见,我将两个正则表达式分开:

1
preg_replace(array('/([a-z])([A-Z])/', '/([A-Z]+)([A-Z])/'), '$1-$2', $string);

它处理字符串两次以找到:

  • 小写 -> 大写边界
  • 多个大写字母后跟另一个大写字母
  • 这将具有以下行为:

    1
    2
    ThisIsHTMLTest -> This-Is-HTML-Test
    ThisIsATest    -> This-Is-A-Test

    或者,使用前瞻断言(这将影响上一次匹配中使用的最后一个大写字母的重用):

    1
    preg_replace('/([A-Z]+|[a-z]+)(?=[A-Z])/', '$1-', $string);


    为了修复 Jack 在您的评论中提到的有趣用例(避免缩写词的拆分),我采用了 zx81 的使用前瞻和后瞻的路线。

    1
    (?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

    你可以一分为二来解释:

    第一部分

    1
    2
    3
    4
    5
    6
    (?<=                     look behind to see if there is:
      [a-z]                    any character of: 'a' to 'z'
    )                        end of look-behind
    (?=                      look ahead to see if there is:
      [A-Z]                    any character of: 'A' to 'Z'
    )                        end of look-ahead

    (TL;DR: CamelCase Pattern 的字符串之间的匹配。)

    第二部分

    1
    2
    3
    4
    5
    6
    7
    (?<=                     look behind to see if there is:
      [A-Z]                    any character of: 'A' to 'Z'
    )                        end of look-behind
    (?=                      look ahead to see if there is:
      [A-Z]                    any character of: 'A' to 'Z'
      [a-z]                    any character of: 'a' to 'z'
    )                        end of look-ahead

    (TL;DR: 特殊情况,缩写和驼峰模式匹配)

    所以你的代码是:

    1
    mb_strtolower(preg_replace('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', '-',"HelloWorldThisIsATest"));

    比赛演示

    代码演示