2

我有一个带有多个 XML 标记的文件,例如:

<Good>Yay!</Good>
<Great>Yup!</Great>
<Bad>booo</Bad>
<Bad>
<Ok>not that great</ok>
</Bad>
<Good>Wheee!</Good>

我想摆脱“坏”标签以及介于两者之间的任何东西。所以它会变成:

<Good>Yay!</Good>
<Great>Yup!</Great>
<Good>Wheee!</Good>

我知道这个单行:

perl -pe "undef $/;s/<Bad>.*?<\/Bad>//msg" < originalFile > newlyStrippedFile

似乎可以做我想做的一切(除了添加额外的换行符,但希望我可以很容易地处理)

但是我需要把它放在一个脚本中(两个文件被读入命令行,一个带有所有标签,另一个带有要拉出的标签列表),所以同一件事将被多次调用。

而我只是遇到了麻烦。要么它只读取一行,要么我得到错误或两者兼而有之。

这是我最近尝试的相关部分:

open ORIGINAL_FILE, $sdb_pathname
  or die "Can't open '$sdb_pathname' : $!";

@sdb_input_array = <ORIGINAL_FILE>;  

close ORIGINAL_FILE;
@sdb_input_scalar=join("",@sdb_input_array);

foreach $tag (@tags) {
  &remove_tag($tag);
}

sub remove_tag 
{
   my($current_tag) = @_;

   $sdb_input_scalar  =~ s/<$current_tag>.*?<\/$current_tag>//msg; 

   open NEWLY_STRIPPED_FILE, $clean_sdb_pathname
     or die "Can't open '$clean_sdb_pathname' : $!";

   print(NEWLY_STRIPPED_FILE $sdb_input_scalar);
   close(NEWLY_STRIPPED_FILE);  

}

这给了我“在我的 $sdb_input_scalar =~ 行中使用未初始化的值 $sdb_input_scalar 代替 (s///)。并且文件句柄 NEWLY_STRIPPED_FILE 仅用于输入

当然,我的两个文件看起来仍然相同,就好像我对它们什么都没做一样。

如果我遗漏了一些明显的东西,我很抱歉,但我对 perl 来说是全新的。有人在工作中给出了 8 小时的估计来完成这个脚本,而我已经用了 5 多个小时来安装 perl,学习语法并让其他方面顺利进行。我知道有一个 XML::Parser 模块,但我发现这些示例在我剩下的很短的时间内非常难以完成。

我必须假设我的正则表达式是正确的,因为单线工作得很好。任何人都可以帮我调整它以适应我的需要吗?

4

5 回答 5

6

您确实应该使用 XML 解析器。这几乎可以保证 XML 文件不会像您期望的那样使用正则表达式进行解析。但是,让我们先让您开始。

你在哪里:

@sdb_input_scalar=join("",@sdb_input_array);

你实际上想要:

$sdb_input_scalar=join("",@sdb_input_array);

现在还有一些其他的提示。

在脚本的顶部,确保使用 -w 标志启用警告,如下所示:

#!/path/to/perl -w

use strict;

一旦你添加use strict它会导致你出现几个错误,但这是一件好事。我们将实施一些范围和其他良好做法。您现在需要使用 my.xml 初始化变量(以 $、@ 或 % 开头)。例如:

my @sdb_input_array = <ORIGINAL_FILE>;

或者:

foreach my $tag (@tags) { ... }

不要像你一样调用 open ,而是使用三个争论版本:

open ($originalFile, "<", $sdb_pathname)
  or die "Can't open '$sdb_pathname' : $!";

my @sdb_input_array = <$originalFile>;

这会将其设置为只读。见http://perldoc.perl.org/functions/open.html

一般来说,你应该避免依赖全局变量。更改调用 remove_tag() 的方式:

foreach $tag (@tags) {
  $sdb_input_scalar = remove_tag($sdb_input_scalar, $tag);
}

为了支持这一点,您还需要更改函数:

sub remove_tag 
{
   my($input, $current_tag) = @_;

   $input  =~ s/<$current_tag>.*?<\/$current_tag>//msg; 

   return $input;    
}

然后,您可以通过将其移到 remove_tag 函数之外,在遍历所有标签后写出一次:

   open ($strippedFile, ">", $clean_sdb_pathname)
     or die "Can't open '$clean_sdb_pathname' : $!";

   print $strippedFile $sdb_input_scalar;
   close($strippedFile);
于 2012-12-18T09:56:37.657 回答
2

这是使用的解决方案XML::Twig

use warnings;
use strict;

use XML::Twig;

my $xml = XML::Twig->new(
    pretty_print  => 'indented',
    twig_handlers => {
            #Define a sub that will be called for all 'Bad' tags
            Bad => sub {
                $_->set_tag('Good'); 
        }
    }
);

$xml->parse(\*DATA);
$xml->print;

__DATA__
<xml><Good>Yay!</Good><Great>Yup!</Great><Bad>booo</Bad><Bad>
<Ok>not that great</Ok></Bad><Good>Wheee!</Good></xml>

XML::Twig还有直接获取文件名parsefile()parsefile_inplace()对其进行处理的方法——这正是你所需要的。

这种方法有一点学习曲线,但好处很大。

于 2012-12-18T10:30:49.587 回答
2

第一:不要使用正则表达式来处理XML!然后,假设来自问题标题的疑问,而不是具体的用例。你的单行最好写成:

perl -0777 -pe "s/<(Bad)>.*?<\/\1>//msg" < originalFile > newlyStrippedFile

现在,使用 Perl 本身来“膨胀”单线:

perl -MO=Deparse -0777 -pe "s/<(Bad)>.*?<\/\1>//msg" > oneliner.pl

这就是你得到的:

BEGIN { $/ = undef; $\ = undef; }
LINE: while (defined($_ = <ARGV>)) {
    s[<(Bad)>.*?</\1>][]gms;
}
continue {
    die "-p destination: $!\n" unless print $_;
}

只需添加use strict; use warnings;.

于 2012-12-18T11:16:46.020 回答
0

这是一个使用XML::Twig. 我假设您的 XML 文档格式正确,并且已将您在其中显示的数据包装在一个<root>元素中以使其如此。

$twig对象为元素定义了一个单独的twig 处理程序<Bad>,如果它在解析过程中出现,它会简单地删除该元素。

解析输入后,$twig-print显示剩余的 XML。

use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new(
  twig_handlers => { Bad => sub { $_->delete } },
  pretty_print => 'record',
);

$twig->parse(<<'END_XML');

<root>
  <Good>Yay!</Good>
  <Great>Yup!</Great>
  <Bad>booo</Bad>
  <Bad>
    <Ok>not that great</Ok>
  </Bad>
  <Good>Wheee!</Good>
</root>

END_XML

$twig->print;

输出

<root>
  <Good>Yay!</Good>
  <Great>Yup!</Great>
  <Good>Wheee!</Good>
</root>
于 2012-12-18T19:20:04.970 回答
-1

这应该可以解决问题:

    $tags=join("",@sdb_input_array);
    print "contents before : $tags \n";
    $tags =~ s/<Bad>.*?<\/Bad>//msg;
    print "content cleaned : $tags \n";

tags 变量现在不应带有“BAD”标签 - 唯一的问题是标签行将留下一个空白未填充的行,以便您在 GOOD 标签行之间有空行 - 但您可以删除空行你的最后一步

于 2012-12-18T11:28:42.733 回答