如何在两个已知令牌之间进行最小匹配?

时间:2009-09-01 20:15:30

标签: regex perl

我选择的文字如下所示。我需要对它做一个基本的编辑,但无法理解我需要的正则表达式。也许这只是漫长的一天,我没有看到我需要的东西。

示例数据:

START ITEM = 1235
    BEGIN
        WORD
        RATE = 98
        MORE WORDS
        CODE = XX
        STUFF
    END
    BEGIN
        TEXT
        MORE WORDS
        RATE = 57
        ADDITIONAL TEXT
        CODE = YY
        OTHER THINGS
    END
STOP
START ITEM = 9983
    BEGIN
        WORD
        RATE = 01
        MORE WORDS
        CODE = AA
        STUFF
    END
    BEGIN
        TEXT
        MORE WORDS
        RATE = 99
        ADDITIONAL TEXT
        CODE = XX
        OTHER THINGS
    END
STOP

我已获得CODEITEM个号码,需要在相应的BEGIN / END部分中修改费率。幸运的是,这些部分定义明确,STOP / START BEGIN / END(他们是关键字,而不是其他任何地方)。

我的工具箱是Perl正则表达式。*

我尝试的第一个解决方案不起作用(值硬编码):

    $tx =~ s/(START \s ITEM \s = \s 9983.*?
                            BEGIN
                                .*?
                               RATE \s = \s )\d+
                                    (.*?       # Goes too far
                                CODE \s = \s XX)
                        /$1$newRate$2
                        /sx;

因为指示的代码过多地匹配,找到更正确的代码,但总是编辑第一个条目。

建议?


*实际代码依赖于将正则表达式添加到一堆正则表达式(一种后处理过滤器)上,每个正则表达式依次应用于要编辑的文本。哎呀,如果我有文本,我可以做一个全功能的解析器。但是我希望不要打开代码并坚持使用我已经拥有的API。

3 个答案:

答案 0 :(得分:6)

正则表达式不适合这类问题。我推荐一个简单的迭代解决方案:

while (<FILE>) {
    # push lines straight to output until we find the START that we want
    print OUT $_;
    next unless m/START ITEM = $number/;

    # save the lines until we get to the CODE that we want
    my @lines;
    while (<FILE>)
    {
        push @lines, $_;
        last if m/CODE = $code/;
    }

    # @lines now has everything from the START to the CODE. Get the last RATE in
    # @lines and change its value.
    my $strref = \( grep m/RATE/ @lines )[-1];
    $$strref = $new_value;

    # print out the lines we saved and exit the loop
    print OUT @lines;
    last;
}

编辑:如果你真的想要一个正则表达式,你可以使用这样的东西(未经测试):

$tx =~ s/(START \s+ ITEM \s+ = \s+ 9983.*?
                            BEGIN
                                .*?
                               RATE \s+ = \s+ )\d+
                                ( (?: (?! END ) . )*
                                    CODE \s+ = \s+ XX)
                        /$1$newRate$2
                        /sx;

添加的(?: (?! END ) . )*确保RATE和CODE之间的匹配不会越过END。但这将比非正则表达方法慢得多。

答案 1 :(得分:4)

虽然我不喜欢它回溯多少,但在BEGINRATE之间进行捕捉贪婪将允许您跳至RATE in CODE = XX的部分。像这样:

$tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+ 
                        BEGIN
                            .*
                           RATE \s+ = \s+ )\d+
...

这方面的主要问题是,如果有必要,它会跳转到另一个ITEM,因此您必须确保不会吞噬STOP。像这样:

my $tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+
                 BEGIN
                     (?: (?! \b STOP \b ) . )*
                    RATE \s+ = \s+ )\d+
                         (.*?       # Goes too far
                     CODE \s+ = \s+ XX)
          /msx
          ;

它仍然比我想要的还要多。

(一小时后)我意识到价值为RATE的{​​{1}}和CODE字段不能除以XX。因此另一个解决方案是:

END

(我将其修改为仅在一行中查找END。如果my $tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+ BEGIN .*? RATE \s+ = \s+ )\d+ ((?:(?! ^ \s+ END \s* $ ) . )*? CODE \s+ = \s+ XX) /msx ; 可以包含单个END,那么无论如何都难以解析)

我认为这个版本不会回溯太多,因为它只是从ADDITIONAL TEXT开始,如果我们没有RATE =,则会在CODE =之前进行扫描END {1}},然后它将修剪回到它认为匹配CODE = XX的位置并继续寻找下一个RATE。如果我们不知道Item#9983肯定会有代码'XX',我们可以为RATE添加负前瞻。


已编辑以消除错误STOP问题。

注意:现在需要以下部分:

\s

答案 2 :(得分:0)

正则表达式并不总是解析文本的最佳答案。您的示例显示您确实有一个可以用语法表示的文件。使用解析器提取字段然后对提取的信息进行更新将会更加简单。