使用regex,提取可能包含嵌套引号的带引号的字符串

2024-09-29 23:30:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我有下面的绳子:

'Well, I've tried to say "How Doth the Little Busy Bee," but it all came different!' Alice replied in a very melancholy voice. She continued, 'I'll try again.'

现在,我想摘录以下引文:

^{pr2}$

我尝试了下面的代码,但我没有得到我想要的。[^\1]*未按预期工作。或者问题在别处?在

import re

s = "'Well, I've tried to say \"How Doth the Little Busy Bee,\" but it all came different!' Alice replied in a very melancholy voice. She continued, 'I'll try again.'"

for i, m in enumerate(re.finditer(r'([\'"])(?!(?:ve|m|re|s|t|d|ll))(?=([^\1]*)\1)', s)):
    print("\nGroup {:d}: ".format(i+1))
    for g in m.groups():
        print('  '+g)

Tags: thetoinrevehowbutsay
3条回答

这对pythonregex来说是一个很好的问题,因为在我看来,re模块是{a1}。这就是为什么对于Python中任何严肃的regex工作,我都会求助于matthewbarnett的stellarregex模块,它集成了Perl、PCRE和.NET的一些优秀特性。在

我将向您展示的解决方案可以适应使用re,但是使用regex更具可读性,因为它是模块化的。另外,将它作为更复杂的嵌套匹配的起始块,因为regex允许您编写与Perl和PCRE中类似的recursive regular expressions。在

好了,说够了,这里是代码(除了导入和定义只有四行)。请不要让长正则表达式吓到你:它很长,因为它是为可读而设计的。解释如下。在

代码

import regex

quote = regex.compile(r'''(?x)
(?(DEFINE)
(?<qmark>["']) # what we'll consider a quotation mark
(?<not_qmark>[^'"]+) # chunk without quotes
(?<a_quote>(?P<qopen>(?&qmark))(?&not_qmark)(?P=qopen)) # a non-nested quote
) # End DEFINE block

# Start Match block
(?&a_quote)
|
(?P<open>(?&qmark))
  (?&not_qmark)?
  (?P<quote>(?&a_quote))
  (?&not_qmark)?
(?P=open)
''')

str = """'Well, I have tried to say "How Doth the Little Busy Bee," but it all came different!' Alice replied in a very melancholy voice. She continued, 'I will try again.'"""

for match in quote.finditer(str):
    print(match.group())
    if match.group('quote'):
        print(match.group('quote'))

输出

^{pr2}$

工作原理

首先,为了简化,请注意,我已经自由地将I'll转换为I will,减少了引号的混淆。对I'll进行寻址对于否定的lookahead是没有问题的,但是我想让regex可读。在

(?(DEFINE)...)块中,我们定义了三个子表达式qmarknot_qmark和{},这与定义变量或子例程以避免重复一样。在

在定义块之后,我们继续匹配:

  • (?&a_quote)匹配整个引号
  • |或者。。。在
  • ^{14{14}匹配
  • (?&not_qmark)?匹配非引号的可选文本
  • (?P<quote>(?&a_quote))匹配一个完整的引号并将其捕获到quote组中
  • (?&not_qmark)?匹配非引号的可选文本
  • (?P=open)与在引号开头捕获的引号匹配。在

Python代码只需要打印匹配项和quote捕获组(如果存在的话)。在

这个可以精炼吗?当然。以这种方式使用(?(DEFINE)...),可以构建漂亮的模式,以后可以重新阅读和理解。在

添加递归

如果您想使用纯正则表达式处理更复杂的嵌套,则需要使用递归。在

要添加递归,只需定义一个组并使用子例程语法引用它。{23>在组内执行代码。要在组something中执行代码,请使用(?&something)。记住,通过使递归成为可选的(?)或交替的一侧,为引擎留下一个出口。在

参考文献

如果您真的需要从一个只应用一次的正则表达式返回所有结果,那么就需要使用lookahead((?=findme)),以便查找位置在每次匹配之后返回到开始位置—请参见this answer以获得更详细的解释。在

为了防止错误匹配,还需要一些关于增加复杂性的引号的子句,例如I've中的撇号不应算作左引号或右引号。没有一种明确的方法可以做到这一点,但我坚持的原则是:

  1. 开头的引号不能紧跟着一个单词字符(例如字母)。例如,A"不算开首引号,但,"可以算。在
  2. 右引号不能紧跟单词字符(例如字母)。{{cd5>不算}例如,{cd6}不算。在

应用上述规则将得到以下正则表达式:

(?=(?:(?<!\w)'(\w.*?)'(?!\w)|"(\w.*?)"(?!\w)))

Regular expression visualization

Debuggex Demo

对于任何可能的候选正则表达式,一个很好的快速健全性检查测试是反转引号。这已经在演示中完成了:https://regex101.com/r/vX4cL9/1

编辑

我修改了正则表达式,它与更复杂的情况匹配:

(?=(?<!\w|[!?.])('|\")(?!\s)(?P<content>(?:.(?!(?<=(?=\1).)(?!\w)))*)\1(?!\w))

DEMO

现在更复杂了,主要的改进是不直接匹配一些标点符号([!?.])和更好的引号大小写分隔。通过各种实例进行验证。在

句子将在content捕获组中。当然,它有一些限制,与空格的使用有关,等等,但是它应该适用于大多数格式正确的句子,或者至少可以用于示例。在

  • (?=(?<!\w|[!?.])('|\")(?!\s)-匹配'或{}前面没有单词或标点符号((?<!\w|[!?.]))或不带空格((?!\s))的'或{}部分在组1中捕获以供进一步使用
  • (?P<content>(?:.(?!(?<=(?=\1).)(?!\w)))*)\1(?!\w))-匹配句子,后跟 与开始时相同的字符('或{}在组1中捕获),忽略其他引号

它不直接匹配整个句子,但是由于捕捉组嵌套在lookaround结构中,所以使用全局匹配修饰符,它也将匹配句子中的句子-因为它只直接匹配句子开始之前的位置。在

关于您的regex:

我想,[^\1]*你指的是任何字符,但不是组1中捕获的字符,但character类不是这样工作的,因为它将\1作为八进制表示法中的字符(我认为这是某种空白)而不是对捕获组的引用。看看this example-阅读说明。还要比较THISTHIS正则表达式的匹配。在

为了达到您想要的效果,您应该使用lookaround,类似这样的方法:^{}-捕获开始字符,然后匹配每个不紧跟捕获的开始字符的字符,然后再捕获一个字符,它直接位于捕获的字符之前-并且您在排除的字符之间有完整的内容。在

相关问题 更多 >

    热门问题