使用多个字符分隔符标记字符串
Tokening a string with multiple character separator
我正在尝试使用以下规则标记表达式:
分隔符是'}}'和'{{'
分隔符之间的字符串应保持完整(不包括被丢弃的单个 spaces(可以在解析器中完成)
可以嵌入分隔符,保持顺序
单次出现的“{”和“}”应保持不变,不要用作分隔符(参见上次测试)。
结果中不应有空字符串(可以在解析器中完成)
两个异常(在括号中标记)可以通过 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))*+)}}
由于 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
这真的只是为了展示。我同意推荐解析器或其他一些更易于维护的工具的其他建议。但是如果你看过我的博客,你就会明白我对这些怪物有一种亲和力:)
我正在尝试使用以下规则标记表达式:
分隔符是'}}'和'{{'
分隔符之间的字符串应保持完整(不包括被丢弃的单个 spaces(可以在解析器中完成)
可以嵌入分隔符,保持顺序
单次出现的“{”和“}”应保持不变,不要用作分隔符(参见上次测试)。
结果中不应有空字符串(可以在解析器中完成)
两个异常(在括号中标记)可以通过 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))*+)}}
由于 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
这真的只是为了展示。我同意推荐解析器或其他一些更易于维护的工具的其他建议。但是如果你看过我的博客,你就会明白我对这些怪物有一种亲和力:)