如何使用 PERL 脚本从 XML 中过滤掉节点

How to filter out nodes from an XML using PERL script

这个问题在互联网上到处都是,但我看到的所有例子都没有考虑到我明显独特的情况。这是我的 XML:

的摘录
<message type="error" from="Realtime" timestamp="Mon Nov 24 19:28:55 2014"> Could not receive from Loader </message>
<message type="warning" from="Dcd_Mux" timestamp="Mon Dec  1 02:31:18 2014"> Could not connect to Dcd </message>

我没有多个级别的节点,我只在一个消息节点上有几个属性。我希望能够根据我的 Perl 脚本的参数过滤掉节点。例如:如果我想过滤掉所有 type="error" 的消息,并且我使用的 XML 只有上面的两行,我的输出将只是上面的警告消息。此处显示的输出:

<message type="warning" from="Dcd_Mux" timestamp="Mon Dec  1 02:31:18 2014"> Could not connect to Dcd </message>

我需要一些指导,了解如何开始打开 ​​XML、遍历整个内容以及删除任何具有与我的过滤器匹配的属性的节点。我有兴趣使用 LibXML 来完成这项工作。

我使用 XML::LibXML 作为我的 XML 解析器。

use XML::LibXML qw( );

die "usage\n" if @ARGV != 2;

my ($type, $qfn) = @ARGV;
my $doc = XML::LibXML->new->parse_file($qfn);
for my $node ($doc->findnodes('//message') {
   my $type_addr = $node->getAttribute('type');
   next if !$type_addr || $type_addr ne $type;

   $node->parentNode->removeChild($node);
}

$doc->toFile($qfn);

使用 XML::LibXML 可能看起来像这样:

use strict;
use warnings; 

use XML::LibXML;

my $filename = $ARGV[0] 
   or die "Missing XML filename to parse";
my $type = $ARGV[1] 
   or die "Missing type of node to exclude";

open(my $xml_file, '<', $filename) 
   or die "Cannot open XML file '$filename' for reading: $!";

my $dom = XML::LibXML->load_xml(IO => $xml_file);
NODE:
foreach my $message_node ( $dom->findnodes('/root/message') ) {
   next NODE 
      unless $message_node->hasAttribute('type');

   $message_node->unbindNode() 
      if $message_node->getAttribute('type') eq $type;
}
$dom->toFile($filename);

您的问题有两个要素 - 首先构建过滤条件,然后根据它选择或删除元素。

特别是 - 混合使用 'add' 和 'remove' 可能会非常困难,因为如果它们不适用或相互矛盾,决定要做什么可能会很烦人。

无论如何,我提供 XML::Twig 尽管这不是您所要求的 - 因为我已经使用了它一些,并没有真正触及 LibXML。

#!/usr/bin/perl
use strict;
use warnings;

use XML::Twig;

#read these from ARGV, just here as example.
my @sample_filters = qw ( -type=error
                          -from=Not_Dcd_Mux );

my %exclude;
for (@sample_filters) {
    if (m/^-/) {
        my ( $att, $criteria ) = (
            m/^-     #starts with -
              (\w+)  #word
              =     
              (\w+)
              $      #end of string
              /x
        );
        next unless $att;
        $exclude{$att} = $criteria;
    }
}

#process_message is called for each 'message' element, and tests filters for exclusion.
sub process_message {
    my ( $twig, $message ) = @_;
    foreach my $att ( keys %exclude ) {
        if ( $message->att($att) eq $exclude{$att} ) {
            $message->delete();
            last;
        }
    }
}

my $twig = XML::Twig->new(
    pretty_print  => 'indented',
    twig_handlers => { 'message' => \&process_message }
);
$twig->parse( \*DATA ); #might use 'parsefile ( $filename )' or 'STDIN' instead
$twig->print;


__DATA__
<XML>
<message type="error" from="Realtime" timestamp="Mon Nov 24 19:28:55 2014"> Could not receive from Loader </message>
<message type="warning" from="Not_Dcd_Mux" timestamp="Mon Dec  1 02:31:18 2014"> Could not connect to Dcd </message>
<message type="warning" from="Dcd_Mux" timestamp="Mon Dec  1 02:31:18 2014"> Could not connect to Dcd </message>
</XML>

此解决方案是 Hunter McMillen 解决方案的变体,此处主要是为了说明我所说的 "looks like a Java program written in Perl".

参数验证是其中的一部分,虽然我已将其简化为简单的计数检查,但我通常根本不会写任何东西。它的价值值得怀疑,因为问题是关于如何处理数据,任何此类调整都取决于谁将使用该程序以及使用频率。

我选择序列化输出并将其打印到 STDOUT,因为能够根据命令行要求重定向输出通常更有用。

通过对验证的关注和一般性 "protecting me from myself",我认识到我认为是 Java 风格的方法。我不认为添加标签并在 next 中使用它有任何帮助,尤其是在如此短的循环中。

use strict;
use warnings; 

use XML::LibXML::PrettyPrint;

@ARGV == 2 or die <<END_USAGE;
Usage:
  [=10=] <XML file> <node type>
END_USAGE

my ($xml_file, $exclude_type) = @ARGV;

my $dom = XML::LibXML->load_xml(location => $xml_file);

for my $node ( $dom->findnodes('/root/message[@type]') ) {
  my $type = $node->getAttribute('type');
  $node->unbindNode if $type eq $exclude_type;
}

local $XML::LibXML::skipXMLDeclaration = 1;
my $pp = XML::LibXML::PrettyPrint->new;
print $pp->pretty_print($dom)->toString;

输出

<root>
  <message type="warning" from="Dcd_Mux" timestamp="Mon Dec  1 02:31:18 2014">
    Could not connect to Dcd
  </message>
</root>