【问题标题】:Recursive regex for templating sub loops用于模板子循环的递归正则表达式
【发布时间】:2016-04-13 21:53:48
【问题描述】:

所以我查看了How to write a recursive regex that matches nested parentheses? 和其他递归正则表达式匹配的解决方案,但我仍然没有在 RegexBuddy 上得到正确的匹配。

我有一个我想自己解析的通用车把样式模板,一个带有标题的表格:

<table>
    <thead>
        <tr>
            {{#each columns as col }}<th>{{col}}</th>{{/each}}
        </tr>
    </thead>
    <tbody>
        {{#each rows as row }}
        <tr>
            {{#each row as col }}<td>col</td>{{/each}}
        </tr>
        {{/each}}
    </tbody>
</table>

并试图匹配

/{{\#each (\w+) as (\w+) }}(.*?|(?R)){{/each}}/s

正则表达式匹配&lt;thead&gt; 中的{{#each columns... 就好了,但它似乎忽略了|(?R) 部分并且仅匹配{{#each rows... 直到第一个 {{/each}}。当然,我希望它匹配内部和外部 #each 表达式。如何?这可能比简单的嵌套括号复杂得多。

(我一直觉得我是 RegEx 的专业人士,直到我遇到这样的事情。我已经尝试了一段时间来完成这项工作,而正则表达式.info 让我更加困惑。)


我目前正在通过{{#each_sub...}}...{{/each_sub}} 解决此问题,因此我的正则表达式不会在第一个结束标记处停止,但这显然是一种次优的方法。我还有其他几个应用程序可以从递归正则表达式中受益,但无法弄清楚我做错了什么。

【问题讨论】:

标签: regex templates recursion


【解决方案1】:

它没有忽略递归,它只是永远不会到达它。因为.*? 能够匹配您的分隔符({{#each...}}{{/each}}),所以它匹配找到的第一个结束分隔符并报告成功,而无需递归。

要使这项技术发挥作用,(?R) 之前的分支必须匹配任何 分隔符。由于您的分隔符由多个字符组成,因此您不能像在您链接到的问题中那样使用否定字符类。相反,您需要使用tempered greedy token

(?:(?!{{[#/]each\b).)*

这与.* 相同,除了在使用每个字符之前,它会检查以确保它不是{{#each{{/each 的开头。这是在上下文中:

{{\#each (\w+) as (\w+) }}(?:(?:(?!{{[#/]each\b).)*|(?R))*{{/each}}

如果第一个分支失败,这意味着您遇到了一些看起来像分隔符的东西。如果它是 opening 分隔符,则第二个分支接管并尝试递归匹配整个模式。否则,它会跳出循环(注意组后面的 * ——你也错过了)并尝试匹配结束分隔符。

虽然上面的正则表达式可以在有效输入上正常工作,但如果输入格式错误,它会遭受灾难性的回溯。为避免这种情况,您可以使用unrolled loop 代替交替(正如@Wiktor 在他的评论中所做的那样):

{{\#each\s+(\w+)\s+as\s+(\w+)\s*}}(?:(?!{{[#/]each\b).)*(?:(?R)(?:(?!{{[#/]each\b).)*)*{{/each}}

这是一个更易读的版本,添加了所有格量词以提高速度:

{{\#each\s+(\w+)\s+as\s+(\w+)\s*}}
(?:(?!{{[#/]each\b).)*+
(?:
  (?R)
  (?:(?!{{[#/]each\b).)*+
)*+
{{/each}}

【讨论】:

  • 哇,很棒的答案,我想我不会轻易发现这个问题。它在我的应用程序中工作。我需要一点时间才能理解它是如何工作的,但你的回答是一个很好的资源。感谢您和 Wiktor!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-10
  • 2012-07-25
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
相关资源
最近更新 更多