正则表达式没有进行最少的匹配

Regex not taking the least possible match

我有这个正则表达式模式:

/(?J){% *(?P<tag>[a-zA-Z_]+) *(?P<args>[a-zA-Z0-9 _-]+) *%}(?P<block>.*){% *end(?P<tag>[a-zA-Z_]+) *%}/s

和这个搜索字符串:

{% import add %}{% endimport %}
{% extends base.html %}{% endextends %}
{%       block              title %}
Changed
{% endblock %}
{% block content %}
Yay!
{% endblock %}

当 运行 通过 preg_match_all 时,它返回完整的搜索字符串而不是第一个 {% import add %}{% endimport %}。为什么,我该如何解决?

您有一个命名模式:(?P<block>.*)

改为(?P<block>.*?)(在星号后加?)。

一般说明:像.*这样的模式(greedy版本)应该与 极度小心,因为他们可能会消耗 far 太多。

您还可以进一步改进您的正则表达式:

  • (?P<tag>[a-zA-Z_]+) 的第二个实例更改为 (?P=tag) - a 对首次使用的 tag 组的反向引用。 我假设在 end 之后应该有 相同的 文本, 捕获了第一个 tag 组。
  • 然后你可以删除 (?J),因为没有命名模式多次出现 没有了。
  • 也许你还应该将 (?P<args>[a-zA-Z0-9 _-]+) 更改为 (?P<args>[a-zA-Z0-9\. _-]+)(将文字点添加到允许的集合中 人物)。 或者将允许的字符列表更改为 [^%]。 那么这个模式也会匹配 {% extends base.html %}{% endextends %}(样本的第一行)。

正则表达式默认为 "greedy" - 它们采用 最长 可能的匹配,而不是 最短.

在这种情况下,您的问题似乎是 .* 标记,它基本上转换为 "match anything at all"。这将通过立即匹配字符串的整个剩余部分来操作,然后 回溯 直到可以满足正则表达式的后续部分。结果是最后一个 {% something %} 标签之前的所有内容都被视为您的最终匹配项。

最简单的解决方案是只使用 .*?,这意味着 "match anything, but don't be greedy about it"。这将从不匹配任何内容开始,然后继续前进,直到可以匹配模式,可能会为您提供所需的结果。

但是,如评论中所述,标记化解析器可能更适合此类任务:跟踪整个字符串,将其分成一系列标签、非标签、标签、非标签,然后之后匹配标签。这将使您的语法更加灵活,减少对嵌套标签等复杂性或检测格式不正确的输入的困扰。