PHP regex and adjacent capturing groups
我第一次在正则表达式中使用捕获组,我想知道我的问题是什么,因为我假设正则表达式引擎从左到右查看字符串。
我正在尝试将 UpperCamelCase 字符串转换为连字符小写字符串,例如:
1
|
HelloWorldThisIsATest => hello–world–this–is–a–test
|
我的前提是一个字母字符串,所以我不需要担心数字或其他字符。这是我尝试过的:
1
|
结果:
1
|
hello–world–this–is–atest
|
这几乎是我想要的,除了 a 和 test 之间应该有一个连字符。我已经将 A-Z 包含在我的第一个捕获组中,因此我假设引擎会看到 AT 并将其连字符。
我做错了什么?
- “HelloWorldHTMLTest” 呢?那应该变成 “hello-world-html-test” 还是 “hello-world-h-t-m-l-test”?
- @Jack 我没想到的有趣用例……我会说第一个。
你的正则表达式不起作用的原因:重叠匹配
- 您的正则表达式匹配 IsATest 中的 sA,允许您在 s 和 A 之间插入 –
- 为了在 A 和 T 之间插入 -,正则表达式必须匹配 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 匹配。
参考
- 前瞻和后瞻零长度断言
- 掌握前瞻和后瞻
- 您的正则表达式无法工作的原因是它需要允许连续匹配。稍后将对此进行解释。您必须像我的回答一样使用环视。
- 你的问题说 I’m wondering what my problem is… 我的回答实际上提供了一个解释。
- 我也喜欢你的回答,因为它解释了为什么我的正则表达式不起作用。
- 您的正则表达式将仅匹配两个字母。
- @AvinashRaj 查看我的演示。 :)
- @AvinashRaj 谢谢,我知道你是一名正则表达式学者——这是解决这类问题的最佳解决方案(零宽度匹配。)
- 谢谢林克,下次见。 :)
为简单起见,我将两个正则表达式分开:
1
|
它处理字符串两次以找到:
这将具有以下行为:
1
2 |
ThisIsHTMLTest -> This–Is–HTML–Test
ThisIsATest -> This–Is–A–Test |
或者,使用前瞻断言(这将影响上一次匹配中使用的最后一个大写字母的重用):
1
|
- +1,好清晰的解决方案,也解决了多个大写缩写的用例
- 我们确定没有因为角色安排而不需要三遍的情况吗?……或者更多……?我有一种预感,可能就是这种情况。
- 1 因为它涵盖了将来可能适用于我的用例,也是一个很好的解决方案
- @zx81 虽然在技术上是可行的,但您使用的首字母缩略词将完全以大写字母命名。
- @rink.attendant.6 顺便说一句,我已经设法将它全部压缩到一个正则表达式中:)
- 嗯,不太确定…在我看来,第一次通过时,字符串被 – 的任意数字扩展,这可能是奇数或偶数…因此,在第一次通过时遗漏了两个位置最终可能会得到不同的平价。但是我的直觉可能是错误的,我并没有停下来在纸上解决它。
- @zx81 当你有的时候让我;我很想看看是否有”失败”的案例。
- 嘿,杰克,IMO 最好的确定方法是对具有随机序列的文件进行测试。没有时间,但我拿了一篇维基百科文章,删除了 [^a-zA-Z]+ 并运行了两个替换。如你所料,剩下的大写字母还不少,一个也没有。值得尝试更大的样本,因为在普通文本中没有那么多大小写切换。下次见! :)
为了修复 Jack 在您的评论中提到的有趣用例(避免缩写词的拆分),我采用了 zx81 的使用前瞻和后瞻的路线。
1
|
(?<=[a–z])(?=[A–Z])|(?<=[A–Z])(?=[A–Z][a–z])
|
你可以一分为二来解释:
第一部分
1
2 3 4 5 6 |
(TL;DR: CamelCase Pattern 的字符串之间的匹配。)
第二部分
1
2 3 4 5 6 7 |
(TL;DR: 特殊情况,缩写和驼峰模式匹配)
所以你的代码是:
1
|
mb_strtolower(preg_replace(‘/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/’, ‘-‘,“HelloWorldThisIsATest”));
|
比赛演示
代码演示
- 当我看到帖子被编辑时,我正要评论要求解释!谢谢,这就是我要找的。我会在几分钟内接受答案。
- 它们都是很好的答案,但我不得不接受另一个答案,因为它为我的问题的第一部分提供了解释。
来源:https://www.codenong.com/24359865/