使用perl删除XML中的特定节点

时间:2015-10-22 18:04:27

标签: xml perl

我有一个package.xml文件,其结构如下: -

<package name="com/avinash/foo1">
    <sourcefile name="bar1.java">
        <line no="1" mi="3"/>
        <line no="3" mi="2"/>
    </sourcefile>
    <sourcefile name="bar2.java">
        <line no="1" mi="5"/>
        <line no="6" mi="8"/>
        <line no="7" mi="3"/>
    </sourcefile>
</package>
<package name="com/avinash/foo2">
.
.
.
.
</package>

使用Perl,我必须删除line的所有no="1"个节点。我发现splice可用于删除xml中的节点。我写了以下代码来做到这一点: -

my $xmlFilePath = 'package.xml';
use XML::Simple;
my $xs = XML::Simple->new (ForceArray => 1);
my $ref = $xs->XMLin($xmlFilePath);

foreach(@{$ref->{'package'}}) {
    my %packageTag = %{$_};        

    foreach(@{$packageTag{'sourcefile'}}){
        my %sourcefileTag = %{$_};

        my $lineCtr = 0;

        foreach(@{$sourcefileTag{'line'}}){
            my %lineTag = %{$_};

            if($lineTag{'no'}==1){
                #splice : something like "splice @{$ref{$packageTag{$sourcefileTag->{'line'}}}}, $lineCtr, 1;"
            }

            $lineCtr = $lineCtr + 1;

        }
    }
}

我是新手,对Perl中的@,%,$转换非常困惑。我不知道如何编写splice函数的数组部分(第一个参数)。任何人都可以告诉我什么是剪切函数,它将删除行节点?

提前致谢。

3 个答案:

答案 0 :(得分:1)

我会建议不要使用XML::Simple,但如果你继续进行下面的建议,我认为还有其他问题需要讨论。

你不能在splicefor/foreach,你正在修改你正在循环的阵列,这会导致各种各样的问题。

要过滤列表,您应该从其外部使用grep

此外,您的示例文件对我不起作用。我需要向XML文件添加更多标签(XML声明节点和包含根节点)或XML::Simple抱怨。

最后,name属性是特殊的(另一个不使用XML::Simple的原因)。您需要提供KeyAttr设置才能停止折叠数据。

尝试以下内容。

use XML::Simple;
my $xs = XML::Simple->new (ForceArray => 1, KeyAttr => []);
my $packages = $xs->XMLin('package.xml');

for my $package (@{$packages->{'package'}}) {
    for my $sourcefile ( @{$package->{'sourcefile'}} ) {
        my $lines = $sourcefile->{'line'};

        my @filtered = grep { $_->{'no'} != 1 } @{$lines};
        $sourcefile->{'line'} = \@filtered;
    }   
}

答案 1 :(得分:1)

作为XML :: Simple的替代方案,这里使用XML::Twig的解决方案,其优点是不将整个文档加载到内存中(如果输入文件很大,则很有用),同时保持相当简单。

use XML::Twig;

my $twig = XML::Twig->new(
  twig_roots => {
    'package/sourcefile/line' => \&handle_line,
  },
  twig_print_outside_roots => 1,
);

sub handle_line {
  my ($twig, $line) = @_;
  $line->print unless $line->att('no') == 1;
} 

$twig->parsefile('package.xml');

是的,这很容易。 twig_print_outside_roots表示line内的sourcefilepackage元素内的任何内容都应该打印到输出而不进行任何处理,而line handle_line应将元素传递给handle_line子进行处理。 no只是检查元素的twig_print_outside_roots属性是否为1,并且仅在元素不存在时才打印该元素。

从package.xml读取并打印到标准输出,您可以将其重定向到新文件。或者您可以修改它以直接打印到文件,方法是自己打开文件,然后将文件句柄传递给printdef spider_opened(self, spider): #open a static/dynamic file to read and write to file = open('%s_items.json' % spider.name, 'w+b') self.files[spider] = file file.write('''{ "product": [''') self.exporter = JsonLinesItemExporter(file) self.exporter.start_exporting() def spider_closed(self, spider): self.exporter.finish_exporting() file = self.files.pop(spider) file.write("]}") file.close() 方法。

答案 2 :(得分:0)

使用XML :: Twig删除节点:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new ( 'pretty_print' => 'indented', 
                              'twig_handlers' => { 
                                   'line[@no="1"]' => sub { $_ -> delete } } );
   $twig -> parsefile ( 'your_file');
   $twig -> print;

您可以parsefile_inplace使用XML::Twig来执行此操作:

my $twig = XML::Twig -> new ( 'pretty_print' => 'indented', 
                              'twig_handlers' => { 'line[@no="1"]' => sub { $_ -> delete } } );
   $twig -> parsefile_inplace ( 'your_file');

或者您可以简单地操作已解析的XML:

my $twig = XML::Twig->new( 'pretty_print' => 'indented' );
$twig->parsefile ('your_file'); 
foreach my $line ( $twig->get_xpath('//line') ) {
    if ( $line->att("no") eq "1" ) {
        $line->delete;
    }
}
$twig->print;