匹配不应该跟随另一个特定字符串的字符串所需的正则表达式

时间:2014-09-11 15:12:33

标签: regex perl

我使用以下代码来匹配字符串(EX: <jdgdt\s+mdy=.*?>\s*),而不应该跟随另一个特定字符串(<jdg>)。但是我无法按照以下代码获得所需的输出。任何人都可以帮我这个吗?

输入文件:

<dckt>Docket No. 7677-12.</dckt>
<jdgdt mdy='02/25/2014'>
<jdg>Opinion by Marvel, <e>J.</e></jdg>
<taxyr></taxyr>
<disp></disp>
</tcpar>

<dckt>Docket No. 7237-13.</dckt>
<jdgdt mdy='02/24/2014'>
</tcpar>

期望的输出:

<dckt>Docket No. 7677-12.</dckt>
<jdgdt mdy='02/25/2014'>
<jdg>Opinion by Marvel, <e>J.</e></jdg>
<taxyr></taxyr>
<disp></disp>
</tcpar>

<dckt>Docket No. 7237-13.</dckt>
<jdgdt mdy='02/24/2014'>
<jdg>Opinion by Marvel, <e>J.</e></jdg>
<taxyr></taxyr>
<disp></disp>
</tcpar>

代码:

#/usr/bin/perl

my $filename = $ARGV[0];
my $ext = $ARGV[1];

my $InputFile = "$filename" . "\." . "$ext";

my $document = do {
    local $/ = undef;
    open my $fh, "<", $InputFile or die "Error: Could Not Open File $InputFile: $!";
  <$fh>;
};

$document =~ s/(<jdgdt\s+mdy=.*?>\s*)(?!<jdg>)/$1<jdg>Opinion by Marvel,<e>J.<\/e><\/jdg>\n<taxyr><\/taxyr>\n<disp><\/disp>/isg;

print $document;

2 个答案:

答案 0 :(得分:3)

我必须对你的正则表达式进行两次小调整才能获得所需的输出:

$document =~ s{(<jdgdt\s+mdy\=[^>]*>\s*)(?!\s*<jdg>)}{$1<jdg>Opinion by Marvel,<e>J.</e></jdg>\n<taxyr></taxyr>\n<disp></disp>}isg;

另外,为了清理代码,我使用/切换到使用{}来分隔正则表达式;这样,你不需要反击你替换中实际需要的所有斜杠。

解释我改变了什么:

首先,否定前瞻是棘手的。你必须要记住的是,perl会尝试将你的表达与最大可能的次数相匹配。因为你最初有这个:

/(<jdgdt\s+mdy\=.*?>\s*)(?!<jdg>)/

在第一个条款中,您将获得此匹配:

<jdgdt mdy='02/25/2014'>\n<jdg>Opinion by Marvel, <e>J.</e></jdg>
^^^^^^^^^^^^^^^^^^^^^^^^
(this part matched by paren. Note the \n is not matched!)

Perl认为这是一个匹配,因为在第一个带括号的表达式之后,你有&#34; \n<jdg>&#34;。嗯,这与表达式&#34; <jdg>&#34;不匹配。 (因为最初的换行),所以耶!发现了一场比赛。

换句话说,最初,perl会使\s*结束你的括号表达式并匹配空字符串,因此它会找到一个匹配项,你最终会把东西塞进第一个你不想要的条款。另一种说法是,由于可以自由选择进入\s*的内容,perl会选择允许整个表达式匹配的数量。 (并使用第一个文档记录的空字符串填充\s*,并为第二个文档记录添加换行符)

为了让perl永远不会在第一个文档记录中找到匹配项,我还在负面预测中重复了\s*。这样,没有选择放入\s*的内容可以使表达式在初始文档记录中完全匹配,并且perl必须放弃并移动到第二个文档记录。

但是还有第二个问题!还记得我是怎么说perl在任何地方找到匹配都非常积极吗?好吧,接下来perl会扩展你的mdy\=.*?>位,以便在第一个docket记录中找到结果。在我向负向前瞻添加\s*之后,第一个文档仍在匹配(但在不同的位置):

<jdgdt mdy='02/25/2014'>\n<jdg>Opinion by Marvel, <e>J.</e></jdg>
^^^^^^^^^^^???????????????????^
(Underlined part matched by paren. ? denotes the bit matched by .*?)

了解perl如何将.*?方式扩展到您的预期之外?您希望该位仅匹配第一个>字符的内容,但perl会根据需要扩展您的非贪婪匹配,以便整个模式匹配。这一次,它延长了.*?以覆盖关闭>标记的<jdg>,以便它可以找到负向前瞻不会阻止匹配的位置。

为了防止perl延伸到.*?模式,我将.*?替换为[^>]*,这就是你的意思。

在这两次更改之后,我们只在最初需要的第二个文档记录中找到匹配。

答案 1 :(得分:-2)

使用正向前瞻。 (?!<jdg>)或类似的东西,查找它。