正则表达式中使用反向引用的行为很奇怪

3

我已经尝试了过去两天来解决这个问题...

请帮助我理解为什么会发生这种情况。我的意图只是选择具有<DTL1 val="92">.....</HDR><HDR>

这是我的正则表达式

(?<=<HDR>).*?<DTL1\sval="3".*?</HDR>

输入的字符串是:

<HDR>abc<DTL1 val="1"><DTL2 val="2"></HDR><HDR><DTL1 val="92"><DTL2 val="55"></HDR><HDR><DTL1 val="3"><DTL2 val="4"></HDR>

但是这个正则表达式选择了

abc<DTL1 val="1"><DTL2 val="2"></HDR><HDR><DTL1 val="92"><DTL2 val="55"></HDR>

请问有人能帮助我吗?


你能否格式化你的输入? - Avinash Raj
@AvinashRaj:我已经格式化了查询。谢谢你告诉我...我不知道。 - sundar
1
这是哪种正则表达式的语言,Java还是Python?(不是JavaScript,因为它不支持反向引用。)请删除不适用的标签。 - Alan Moore
2个回答

2
一个正则表达式引擎总是会在字符串中返回最左边的匹配项(即使你使用非贪婪量词)。这正是你所获得的。因此,解决方案是禁止在由“.*?”描述的部分存在另一个,这太宽容了。
有两种技术可以实现这一点,你可以将.*?替换为:
(?>[^<]+|<(?!/HDR))*

或者使用:
(?:(?!</HDR).)*+

大多数情况下,第一种技术更高效,但如果您的字符串包含大量的<,第二种方法也可以得到很好的结果。

使用独占量词原子组可以减少获得特定结果的步骤数量,尤其是当子模式失败时。

例子:

使用第一种方式:

(?<=<HDR>)(?>[^<]+|<(?!/HDR))*<DTL1\sval="3"(?>[^<]+|<(?!/HDR))*</HDR>

或者这个版本:
(?<=<HDR>)(?:[^<]+|<(?!/HDR|DTL1))*+<DTL1\sval="3"(?:[^<]+|<(?!/HDR))*+</HDR>

使用第二种方式:

(?<=<HDR>)(?:(?!</HDR).)*<DTL1\sval="3"(?:(?!</HDR).)*+</HDR>

或者选择这个变体:
(?<=<HDR>)(?:(?!</HDR|DTL1).)*+<DTL1\sval="3"(?:(?!</HDR).)*+</HDR>

1
我尝试用你上面提到的表达式替换正则表达式中的第一个.*?。但不知何故,结果没有匹配成功。我有什么遗漏吗? - sundar
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Casimir et Hippolyte
@sundar:另外一件事,即使您使用自由间隔模式编写模式,也不能使用空格拆分像(?<=这样的标记。 - Casimir et Hippolyte
@CasimiretHippolyte:在代码示例中,所有换行符和大部分其他空格都是当Sundar使用片段功能格式化代码时引入的。 我已将其还原并采用了老式的格式化方式。 - Alan Moore
@sundar:我看到了问题,我把模式改成了其他变体,并且添加了更清晰的示例。 - Casimir et Hippolyte

1
Casimir和Hippolyte已经给出了一些好的解决方案。我想详细说明一些事情。
首先,为什么你的正则表达式无法实现你想要的功能:(?<=<HDR>).*?告诉它匹配任意数量的字符,从第一个以<HDR>为前缀的字符开始,直到遇到非贪婪量词(<DTL1...)后面的内容。好吧,第一个以<HDR>为前缀的字符是第一个a,所以它匹配从那里开始的所有内容,直到遇到固定字符串<DTL1\sval="3"
Casimir和Hippolyte的解决方案适用于广义情况,其中标记的内容可以是除嵌套之外的任何内容。您还可以使用正向查找:
(?<=<HDR>)(.(?!</HDR>))*<DTL1\sval="3".*?</HDR>

然而,如果可以保证字符串的结构如所示,其中<HDR>标记仅包含一个或多个<DTL1 val="##">标记,因此您知道其中不会有任何闭合标记,您可以通过将第一个.*?替换为[^/]*来更有效地完成操作。
(?<=<HDR>)[^/]*<DTL1\sval="3".*?</HDR>

一种否定的字符类比零宽断言更有效,而且如果您使用否定的字符类,则贪婪量词比懒惰量词更有效。
还要注意,通过使用回顾后发匹配开头的 <HDR>,您将其从匹配中排除,但是包括结束标记 </HDR>。您确定这是您想要的吗?您正在匹配以下内容...
<DTL1 val="3"><DTL2 val="4"></HDR>

"...当你大概想要这个..."
<HDR><DTL1 val="3"><DTL2 val="4"></HDR>

...或者这个...

<DTL1 val="3"><DTL2 val="4">

因此,在第一种情况下,不要使用前置断言来匹配开标签:
<HDR>(.(?!</HDR>))*<DTL1\sval="3".*?</HDR>
<HDR>[^/]*<DTL1\sval="3".*?</HDR>

在第二种情况下,使用向前查找来查找闭合标签:
(?<=<HDR>)(.(?!</HDR>))*<DTL1\sval="3".*?(?=</HDR>)
(?<=<HDR>)[^/]*<DTL1\sval="3".*?(?=</HDR>)

太棒了!您的回答让我开心了一整天......!!!非常感谢......!!!我一直在努力了解这个问题,即使是在感恩节周末...... - sundar
如果你想选择<DTL1 val="92">.....</HDR>这个块,那么为什么在你的正则表达式中写成了<DTL1\sval="3" - Avinash Raj
(?<=<HDR>)(.(?!</HDR>))*<DTL1\sval="3".*?</HDR> 是我在这个问题的输入上使用的正则表达式,它匹配了我想要选择的确切内容。如果您需要更多信息,请告诉我。 - sundar
说得好,但有一个更正。当您使用被动前瞻技术时,应该将点放在前瞻之后。为了看到原因,请尝试匹配最后一个<HDR>...</HDR>元素,无论其中包含什么内容。<HDR>(.(?!</HDR>))*</HDR>$不匹配,因为点不允许匹配关闭</HDR>标记之前的>,但是<HDR>((?!</HDR>).)*</HDR>$可以。 - Alan Moore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接