使用多个字符分隔符标记字符串

Tokening a string with multiple character separator

我正在尝试使用以下规则标记表达式:

两个异常(在括号中标记)可以通过 post-在解析中处理(正确的)结果来完成。结果将被提供给递归下降解析器。

这里有一些试验没有通过我包含的单元测试。 find_all 函数是最接近匹配的,但仍然设法删除了一些部分。我没有在下面的代码中使用 re.split()(它会保留空字符串),但我试过了,但运气并不好。我希望正则表达式能够避免在我的代码中逐字符扫描字符串。

def tokenize_search(line):
    token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
    tokens = re.search(token_pat, line).groups()
    return list (tokens)

def tokenize_findall(line):
    token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
    tokens = re.findall(token_pat, line)
    return tokens

def check(a1, a2):
    print(a1 == a2)

def main():
    check(tokenize_search('{{}}'), ['{{', '}}'])
    check(tokenize_search('aaa {{}}'), ['{{', '}}'])
    check(tokenize_search('{{aaa}}'), ['{{', 'aaa', '}}'])
    check(tokenize_search('{{aa}} {{bbb}}'), ['{{', 'aa', '}}', '{{', 'bbb', '}}'])
    check(tokenize_search('{{aaa {{ bb }} }}'), ['{{', 'aaa ', '{{', ' bb ', '}}', '}}'])
    check(tokenize_search('{{aa {{ bbb {{ c }} }} }}'), ['{{', 'aa ', '{{', ' bbb ', '{{', ' c ', '}}', '}}', '}}'])
    check(tokenize_search('{{a{a}{{ b{b}b {{ c }} }} }}'), ['{{', 'a{a}', '{{', ' b{b}b ', '{{', ' c ', '}}', '}}', '}}'])

更新

感谢 Olivier 提供了有效的解决方案。如果我能更好地理解 regex lookout,我仍然希望 regex 解决方案能够工作。如果我使用下面的 tokenize_finditer 方法,它会通过测试,它所做的只是用中间的内容填充 skipped 组(除了 space 我可以 post-经过处理使代码更简单)。所以我希望我可以在 '({{)|(}})' 正则表达式中添加一个 or 子句,它表示:`或获取任何字符后跟任何不匹配 '}}' 或 '{{' 的字符。不幸的是,我无法成功编写此匹配器。我见过 regex 甚至可以进行递归匹配的例子,因为这不是递归的,所以听起来更可行。

def tokenize_finditer(line):
    token_pat = re.compile(r'({{)|(}})')
    result = []
    if re.search(token_pat, line):
        prev = len(line)
        for match in re.finditer(token_pat, line):
            start, end = match.span()
            if start > prev:
                expr = line[prev:start]
                if not expr.isspace():
                    result.append(expr)
            prev = end
            result.append(match.group())

    return result

这个问题不完全是 parentheses matching problem,但我建议不要尝试用正则表达式解决它。

既然你想要做的是 partition 你用给定的分隔符串起来,那么我们可以编写一个基于 partition 函数的解决方案,并进行一些调整符合所有规则。

import re

def partition(s, sep):
    tokens = s.split(sep)

    # Intersperse the separator betweem found tokens
    partition = [sep] * (2 * len(tokens) - 1)
    partition[::2] = tokens

    # We remove empty and whitespace-only tokens
    return [tk for tk in partition if tk and not tk.isspace()]


def tokenize_search(line):
    # Only keep what is inside brackets
    line = re.search(r'{{.*}}', line).group() or ''

    return [tk for sub in partition(line, '{{') for tk in partition(sub, '}}')]

以上代码通过了所有测试。您需要将该结果提供给解析器以检查括号匹配。

我相信 Olivier Melançon 的分区方法是可行的方法。然而,正则表达式仍然有一些用途,例如检查有问题的模式是否适当平衡或从较大的字符串中提取平衡(如第二个示例所示)。

这样做需要像这样的递归正则表达式:

{{((?>(?:(?!{{|}}).)++|(?R))*+)}}

Demo

由于 Python re 模块不支持正则表达式递归,您将需要依赖替代 regex 模块来使用它。

要进一步处理匹配结果,您需要查看 </code> 中的内部部分并一次深入一层,例如<a href="https://regex101.com/r/EUwMHq/1" rel="nofollow noreferrer"><code>\w+|{{((?>(?:(?!(?:{{|}})).)++|(?R))*+)}} 但这很麻烦。

刚在 Twitter 上收到你的消息 :) 我知道我迟到了 2 个月,但如果你感兴趣的话,我有一些新想法。

我查看了示例并注意到您几乎可以匹配和捕获所有“{{”或“}}”或 "a token that is in the middle of a {{ }} pair"。幸运的是,这很容易表达:

/({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?2)*?}}|(?:(?!{{|}}).)*)*$))/g

On regex101 using your examples

"in the middle of a {{ }} pair" 是唯一棘手的部分。为此,我使用负前瞻来确保我们不在后面跟着平衡数量的(可能嵌套的){{ }} 对的位置,然后是字符串的末尾。 对于均衡输入,这将确保所有匹配的标记都在 {{ }} 对内。

现在,您问 "well-balanced input" 部分呢?如果输入无效,则 "aaa}}" 之类的示例将产生 ["aaa", "}}"] 作为结果。不理想。您可以单独验证输入;或者,如果你想把它变成一个无法驯服的怪物,那么你可以这样做:

/(?:^(?!({{(?1)*?}}|(?:(?!{{|}}).)*)*+$)(*COMMIT)(*F))?({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?3)*?}}|(?:(?!{{|}}).)*)*+$))/g

Unleashed on regex101

这真的只是为了展示。我同意推荐解析器或其他一些更易于维护的工具的其他建议。但是如果你看过我的博客,你就会明白我对这些怪物有一种亲和力:)