所以我查看了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}}
解决这个问题,因此我的正则表达式不会停留在第一个结束标记上,但这显然是一种次优的方式。我有几个其他应用程序可以从递归正则表达式中受益,但无法弄清楚我做错了什么。
答案 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}}