Perl中的智能RegEx?

时间:2009-12-11 17:55:43

标签: html regex perl parsing

背景


考虑以下输入:

<Foo
    Bar="bar"
    Baz="1"
    Bax="bax"
>

处理完毕后,我需要它看起来如下:

<Foo
    Bar="bar"
    Baz="1"
    Bax="bax"
    CustomAttribute="TRUE"
>

实施


这是所有我需要做的不超过5个文件,所以使用除正则表达式之外的任何东西似乎有点矫枉过正。无论如何,我想出了以下(Perl)正则表达式来实现这个目标:

$data =~ s/(<\s*Foo)(.*?)>/$1$2 CustomAttribute="TRUE">/sig;

问题


这很有效,但是,有一个明显的问题。这种模式是“愚蠢的”,因为如果已添加CustomAttribute,上面列出的操作将只是盲目追加另一个CustomAttribute=...

当然,一个简单的解决方案是编写一个辅助表达式,在运行替换操作之前尝试匹配CustomAttribute

问题


由于我对脚本语言和正则表达式世界都不熟悉,我想知道是否可以在不引入任何宿主语言结构(即Perl中的if语句)的情况下解决此问题,并且只需使用我上面写的更“智能”的版本?

4 个答案:

答案 0 :(得分:5)

我不会因为你不应该使用正则表达式来打败你。我的意思是,你不应该这样做,但是你明显知道你在问题中所说的话,所以继续...

可以完成你所要求的东西被称为negative lookahead assertion(通常是(?!...)),基本上说如果断言中的模式是你的,你不希望匹配应用在此之前找到了。在您的示例中,如果CustomAttribute已经存在,则不希望它应用,因此:

$data =~ s/(<\s*Foo)(?![^>]*\bCustomAttribute=)(.*?)>/$1$2CustomAttribute="TRUE">/sig;

答案 1 :(得分:5)

这听起来像是XML::Twig的工作,它可以处理XML并在其中运行时更改部分内容,包括向标记添加属性。我怀疑你花了很多时间习惯Twig,你会发现一个只有大部分工作的正则表达式解决方案。而且,最后你会知道足够的Twig在下一个项目中使用它。 :)

答案 2 :(得分:4)

我猜演讲的时间; - )

我不确定为什么你认为使用一个完整的XML处理器是过度的。使用适当的工具编写代码实际上更容易。正则表达式将更复杂,并将依赖于对数据的不成文假设,这是危险的。其中一些假设很可能是:没有'&gt;'在属性值中,没有CDATA部分,标签或属性名称中没有非ascii字符,一致的属性值引用...

正则表达式唯一能给你的是保证输出保持数据的原始格式(在你的情况下,属性都是在一个单独的行上)。但是如果你的格式是可以完成的,那么它应该没关系,除非你把XML放在面向行的版本控制系统中。

以下是XML :: Twig的示例。它假设你有足够的内存来保存内存中的任何整个Foo元素,它甚至可以在DATA部分中公认的XML设计中工作。使用XML :: LibXML可能同样容易(在内存中读取XML,选择所有Foo元素,为每个元素添加属性,输出,这是我的计数中易于理解的5行)。

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

my( $tag, $att, $val)= ( 'Foo', 'CustomAttribute', 'TRUE');

XML::Twig->new(                 # only process those elements
                twig_roots => { $tag => sub { 
                                              # add/set attribute
                                              $_->set_att( $att => $val); 
                                              # output and free memory
                                              $_->flush;
                                            }
                              },
                twig_print_outside_roots => 1, # output everything else
                pretty_print => 'cvs',         # seems to be the right format
              )
         ->parse( \*DATA)  # use parsefile( $file) if parsing... a file
         ->flush;          # not needed in XML::Twig 3.33
__DATA__
<doc>
  <Foo
      Bar="bar"
      Baz="1"
      Bax="bax"
  >
  here is some text
  </Foo>
  <Foo CustomAttribute="TRUE"><Foo no_att="1"/></Foo>
  <bar><![CDATA[<Foo no_att="1">tricked?</Foo>]]></bar>
  <Foo><![CDATA[<Foo no_att="1" CustomAttribute="TRUE">tricked?</Foo>]]></Foo>
  <Foo
      Bar=">"
      Baz="1"
      Bax="bax"
  ></Foo>
  <Foo
      Bar="
>"
      Baz="1"
      Bax="bax"
  ></Foo>
  <Foo
      Bar=">"
      Baz="1"
      Bax="bax"
      CustomAttribute="TRUE"
  ></Foo>
  <Foo
      Bar="
>"
      Baz="1"
      Bax="b
ax"
      CustomAttribute="TR
UE"
  ></Foo>
</doc>

答案 3 :(得分:1)

您可以通过带有'e'修饰符的函数发送匹配项以进行更多处理。

my $str = qq`
<Foo
    Bar="bar"
    Baz="1"
    Bax="bax"
    CustomAttribute="TRUE"
>
<Foo
    Bar="bar"
    Baz="1"
    Bax="bax"
>
`;

sub foo {
    my $guts = shift;
    $guts .= qq` CustomAttribute="TRUE"` if $guts !~ m/CustomAttribute/;
    return $guts;
}
$str =~ s/(<Foo )([^>]*)(>)/$1.foo($2).$3/xsge;