如何使用 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>
这个问题在互联网上到处都是,但我看到的所有例子都没有考虑到我明显独特的情况。这是我的 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>