Python 使用正则表达式查找和替换每个匹配项中任意数量的元素

Python 使用正则表达式查找和替换每个匹配项中任意数量的元素,python,regex,Python,Regex,我的目标是识别标记语言中的粗体括号文本,例如: [B] blah blah (foo) blah [/B] 并使用regex用另一个标记将其包围,如下所示: [B] blah blah [C](foo)[/C] blah [/B] 下面是我使用Python对此的尝试: outtext = re.sub(r'(\[B\].*?)(\(.*?\))(.*?\[/B\])', r'\1[C]\2[/C]\3', intext) 问题是,如果块中有多个带括号的字符串,则它不起作用: Input:

我的目标是识别标记语言中的粗体括号文本,例如:

[B] blah blah (foo) blah [/B]
并使用regex用另一个标记将其包围,如下所示:

[B] blah blah [C](foo)[/C] blah [/B]
下面是我使用Python对此的尝试:

outtext = re.sub(r'(\[B\].*?)(\(.*?\))(.*?\[/B\])', r'\1[C]\2[/C]\3', intext)
问题是,如果块中有多个带括号的字符串,则它不起作用:

Input: [B] (foo) (bar) [/B]
Expected: [B] [C](foo)[/C] [C](bar)[/C] [/B]
Actual: [B] [C](foo)[/C] (bar) [/B]

我知道发生这种情况的原因,但我不知道如何解决它。是否可以更改我的正则表达式,以便它能够在每个块中查找和替换任意数量的括号字符串,而不是仅一个?

首先,我认为仅正则表达式无法解决问题。JvdV证明这是错误的,做得好。老实说,我不明白这个正则表达式了

我用一些简单的正则表达式和一点python解决了这个问题

import re

intext = '[B] (foo) (bar) [/B] (not) [B] (this again) [/B]'

boldParts = re.findall(r'\[B\].*?\[/B\]', intext)
outtext = intext
for part in boldParts:
    replacement = re.sub(r'(\(.*?\))', r'[C]\1[/C]', part)
    outtext = outtext.replace(part, replacement)

print(outtext)
首先,我只查找intext中的粗体部分,然后很容易替换括号中的内容。并在outtext中再次替换它


诚然,这不是最短或最优雅的方法,但可能更具可读性。

这种问题通常通过仅在其他匹配中替换匹配来解决。您需要使用一个正则表达式运行一个
re.sub
,该正则表达式将匹配所有
B
标记的子字符串,并使用一个可调用的in
re.sub
作为替换参数,仅替换这些匹配项中括号之间出现的多个字符串

以下是解决方案:

import re
text = "[B] blah blah (foo) blah [/B]\n[B] (foo) (bar) [/B]"
print(re.sub(r'(?s)\[B].*?\[/B]', lambda x: re.sub(r'\([^()]*\)', r'[C]\g<0>[/C]', x.group()), text))

输出:

[B] blah blah [C](foo)[/C] blah [/B]
[B] [C](foo)[/C] [C](bar)[/C] [/B]

(?s)\[B].\[/B]
模式匹配
[B]
,然后0+字符尽可能少,直到最左边的
[/B]
(注意
(?s)
允许
匹配任何字符,包括换行字符)。然后,一旦找到匹配项,就将其传递给可调用对象,并在该匹配项上运行
\([^()]*\)
正则表达式
\([^()]*\)
匹配最近括号之间的任何子字符串,即
),然后是
以外的0+字符,然后是
。替换模式中的
\g
是整个匹配的替换反向引用。

好的。。这花了我一些时间。。我不确定标记语法的细节,但我将做一些假设:括号内的文本可以是除括号外的任何字符,除非它们被转义。转义字符是反斜杠。尽管如此。。这是我想到的

>>> expr = r"""
...    \(                            # Match left paren.
...       (
...         (?:      [^ \( \) \\] |  # Match any char not a paren or escape, OR
...              \\  [  \( \) \\] |  # Match an escaped paren or escape, OR
...              \s                  # whitespace.
...          )*
...        )
...    \)                            # Match right paren. """
...
>>> re.sub(expr, r"[C](\1)[/C]",  "[B] (foo) (bar) [/B]", flags=re.VERBOSE)
'[B] [C](foo)[/C] [C](bar)[/C] [/B]'
这也适用于包含转义括号的目标字符串。上面的缩写形式是

re.sub(r"\(((?:[^\(\)\\]|\\[\(\)\\]|\s)*)\)", 
       r"[C](\1)[/C]",  "[B] (foo) (bar) [/B]")

去掉空格和注释后…

处理此问题的基本思路是使用两个正则表达式,首先选择
B
标记内的值,然后捕获
()
内的值并替换,如果括号之间的文本是自由形式的,并且可能包含其他转义括号,我认为你不能用一个正则表达式来完成。。除了简单的案例外,其他答案不会有太多的应用。如果我说的是真的,你可能想看看我的=)解。你建议我更详细地解释这个表达式吗?我需要先确认一下它是否正确=)。感谢你的回答+我很好奇你的正则表达式是如何工作的。我在你提供的链接中查到了。这有点帮助。然后我发现我可以想象它。然后它发出咔嗒声,我得到了它。对我来说,这种想象是关键。不知道(?!)反向查找仅简单[^]。所以,谢谢你,今天学到了一些东西。:)我会添加此链接作为对您答案的评论。但是我还不允许留下评论:((为您添加=)。顺便说一句,添加了
\B
以防止
测试(test)测试上的匹配。谢谢,我感到荣幸=)我相信[^()\]不匹配表达式中的空格的原因是使用re.VERBOSE表达式语法的副作用。这就是表达式中包含“\s”alt的原因。另外,请注意,如果括号的转义符类似于“('和')”,则可以轻松修改此表达式。我认为,使用re.VERBOSE标志=)可以让人们了解到,从这个表达式中可以获得更易于管理的表达式
re.sub(r"\(((?:[^\(\)\\]|\\[\(\)\\]|\s)*)\)", 
       r"[C](\1)[/C]",  "[B] (foo) (bar) [/B]")