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

时间:2014-06-23 06:43:07

标签: php regex string backreference camelcasing

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

我正在尝试将UpperCamelCase字符串转换为hyphened-lowercase-string,例如:

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

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

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

结果:

hello-world-this-is-atest

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

我做错了什么?

3 个答案:

答案 0 :(得分:6)

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

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

以两条简单的方式进行

以下是使用正则表达式执行此操作的简便方法:

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

请参阅php demo

底部的输出
  

输出:hello-world-this-is-a-test

稍后会添加解释。 :)

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

如果仔细查看regex101 screen,可以看到正则表达式匹配的单词之间的行。

<强>参考

答案 1 :(得分:5)

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

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

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

  1. 小写 - &gt;大写边界
  2. 多个大写字母后跟另一个大写字母
  3. 这将有以下行为:

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

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

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

答案 2 :(得分:4)

为了修复Jack在你的评论中提到的有趣用例(避免拆分缩写),我选择了zx81使用lookahead和lookbehinds的路线。

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

您可以将其拆分为两个以进行解释:

第一部分

(?<=                     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模式的字符串之间的匹配。)

第二部分

(?<=                     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:特例,缩写与CamelCase模式匹配)

那么你的代码就是:

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

Demo of matches

Demo of code