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

问题描述:

这个问题遍布互联网,但我看到的所有示例都没有考虑到我显然独特的情况。这里是我的XML摘录:如何使用PERL脚本过滤掉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来完成这件事。

+0

我不清楚自己需要什么。例如,'perl logview.pl -type =“error”'应该发生什么?你说它应该*“用'type =”error“'”*删除所有消息,但是从什么?你有一个你想编辑的静态XML数据文件吗? – Borodin 2015-03-13 21:05:47

+0

@Borodin我编辑了这个问题。我并不关心这些论据,但是要回答你的评论......是的,脚本直接访问要解析/过滤的文件。 – 2015-03-13 21:39:13

我使用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); 
+1

OP的。 “我希望能够根据perl脚本的参数过滤掉节点。” – ikegami 2015-03-13 21:12:01

+1

我认为这很有帮助,但一些解释发生了什么的评论可能会帮助更多。什么是qw();在顶部? – 2015-03-13 21:45:46

+1

这是从模块导入的东西的列表。在这种情况下,没有什么。如果还有其他不清楚的地方,请告诉我。 – ikegami 2015-03-13 21:46:37

它可以使用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); 
+1

@ikegami:我对你的好斗方式太熟悉了,并且期望它不符合Stack Overflow的人口众多的利益来回答你。我再一次不得不将你的评论标记为*非建设性*,并且希望你可以考虑你对Perl的普及的影响。 – Borodin 2015-03-13 21:23:55

+0

很好,请允许我重新表述:我已将您的评论标记为非建设性的。 “看起来像Java”不是一种建设性的,即使它是真实的。然而,几乎每一行都是一个没有Java等价物的Perl成语。评论人: – ikegami 2015-03-13 21:30:15

+0

:糟糕的一天 - 心情不好? – jm666 2015-03-13 21:30:17

有两个元素,您的问题 - 先建立一个筛选条件,选择或基于它删除元素。

特别是 - 混合'添加'和'删除'可能是相当困难的,因为决定如果不适用或相互矛盾会怎么做可能会很烦人。

无论如何,我提供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> 
+0

不包括作为问题一部分的参数。我确实看了一下XML :: Twig,但我不相信我们已经安装了。我知道libxml在我们所有的机器上。我会进一步深入...也许我们可以添加它。 – 2015-03-13 21:42:50

+1

关于XML:Twig的主题,它附带了命令行工具'xml_grep',如果该文件完全由要筛选的邮件组成,则可以使用 – ikegami 2015-03-13 21:55:09

+0

谢谢。看起来我们确实有xml_grep – 2015-03-13 22:18:16

该解决方案是从猎人麦克迈林的一个变化,并且在这里主要是为了说明我的意思通过“看起来像Perl编写的Java程序”

参数验证是其中的一部分,虽然我已将它简化为一个简单的计数检查,但我通常不会写任何东西。这是值得怀疑的,因为问题是如何处理数据,而任何这样的裁剪都取决于谁将使用该程序以及多久。

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

我意识到我认为是一种Java风格的方法,注意验证和一般“保护我免于我自己”。我不相信添加标签并在next中使用它完全有帮助,特别是在这样一个短的循环中。

use strict; 
use warnings; 

use XML::LibXML::PrettyPrint; 

@ARGV == 2 or die <<END_USAGE; 
Usage: 
    $0 <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> 
+0

@HunterMcMillen:我已经写了这个作为我对解决方案的评论的解释。我希望它能帮助你理解我的想法。 – Borodin 2015-03-14 14:52:55