我一直在试图让一个可选组在preg_match_all()正则表达式中工作,整天撞在砖墙上。非可选版本完美地解析数据,但只要我将正则表达式的一部分作为可选项,该可选部分就永远不会用于解析数据,即使它所针对的行存在于数据中也是如此。
这是有效的原始正则表达式:
$regex = "~:begin(.*)[\r\n]+:desc(.*)[\r\n]+(.*)[\r\n]+:end(?:.*)[\r\n]+~msU";
preg_match_all($regex, $text, $matches);
这是要解析的文本:
:begin test
:desc testing
some code
more code
last code
:end test
:begin test2
:desc testing2
some code2
last code2
:end test2
正则表达式将以“:desc”开头的行正确解析为自己的组,但是当我将“:desc”行设置为可选时,同一组始终为空,并且该行将被添加到以下组中, “代码”块的开头。
这是带有desc的可选组的调整后的正则表达式:
$regex = "~:begin(.*)[\r\n]+(:desc(.*)[\r\n]+)?(.*)[\r\n]+:end(?:.*)[\r\n]+~msU";
我相信我明白发生了什么 - 只是不知道为什么或如何解决问题。显然,因为在代码块的开头没有某种明确的标记,当前一行是可选的时,正则表达式绕过可选组并将其全部放入随后的代码块中。我已经尝试过使用标志,将组更改为贪婪/非贪婪的各种组合,但是没有插入类似“:code”前缀的东西来指示下一个块的开始,我就是无法停止将可选行放在代码块之后的正则表达式。
我只想让单行:desc语句可选,而不必为数据添加更多标签或分隔符。
此时,我陷入困境,需要一些资深的正则表达专家来解释发生了什么,以及如何解决(如果可能的话)。
答案 0 :(得分:1)
否定前瞻可以帮到这里:
~:begin (.*)[\r\n]+(?::desc (.*)[\r\n]+)?^(?!:desc)(?:(.*)[\r\n]+)?:end(?:.*)[\r\n]+~msU
添加的主要部分:^(?!:desc)
- 这将检查下一行是否不以开头:desc
我还为可选组添加了(?:...)
,因此不会为结果数组捕获它们。如有必要,请删除它们。
负面前瞻究竟做了什么?多线和(。)*的主要问题是点匹配(差不多!)任何字符。几乎意味着,换行符(Details)除外。但正如你的正则表达式使用“多线模式”,这使得这更加棘手。
让我们将你的第二个正则表达分解为更小的部分:
:begin(.*)[\r\n]+
这部分只是找到第一行。我只在这里添加了一个空格以将其从结果中排除。
(:desc(.*)[\r\n]+)?
这是您原来的可选方,应该找到第二行。这里也增加了空间。
(.*)[\r\n]+
这是代码方,但在您的情况下,这是贪婪的,所以它还找到了:desc 的可选方为了更改这一点,排除了否定前瞻这一部分,并且您希望将其更改为可选,这已更改为:^(?!:desc)(.*)[\r\n]+
- “^”也确保它是新行的开头。
:end(?:.*)[\r\n]+
此处无需更改。
其他改进
不确定是否需要或想要,但为了清理语句,我稍微改了一下,这个也捕获了第二个文本块。
~:begin ([^$]*)(?::desc([^$]*))?^(?!:desc)(?:([^$]*))?:end+~msU
此代码使用“$”来检查每行的结尾,因此您不必再检查换行符。