用于模板子循环的递归正则表达式

时间:2016-04-13 21:53:49

标签: regex templates recursion

所以我查看了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

正则表达式与{{#each columns...中的<thead>匹配就好了,但它似乎忽略了|(?R)部分并且只匹配{{#each rows...,直到第一次 {{/each}}。当然,我希望它能够匹配内部和外部#each表达式。怎么样?这可能比简单的嵌套括号复杂得多。

(我总是觉得自己是RegEx的专业人员,直到遇到这样的事情。我已经尝试了一段时间才能完成这项工作,而regular-expressions.info让我更加困惑。)

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

1 个答案:

答案 0 :(得分:1)

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

要使此技术有效,(?R)之前的分支必须匹配 分隔符的任何内容。由于您的分隔符由多个字符组成,因此您无法使用否定的字符类,就像您在链接的问题中所做的那样。相反,您需要使用tempered greedy token

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

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

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

如果第一个分支失败,则表示您遇到了类似分隔符的内容。如果它是开放分隔符,则第二个分支接管并尝试递归地匹配整个模式。否则,它会弹出循环(注意组之后的* - 你也错过了它)并尝试匹配一个结束分隔符。

虽然上面的正则表达式在有效输入上可以正常工作,但如果输入格式不正确,它会受到灾难性的回溯。为避免这种情况,您可以使用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}}