Perl,删除 XML 节点

Perl, Remove XML node

Data.xml

<people>
  <person name="John">
     <param name="age" value="21" />
  </person>
  <person name="Jane">
     <param name="age" value="25" />
  </person>
</people>

我有这张XML。我正在编写一个脚本,将 <person> 节点附加到 <people> 节点。我正在使用 XML::Simple

(请不要建议我使用另一个库,我知道它的困难)。

my $remove_person = "Jane";

my $xml = XMLin('data.xml', ForceArray => 1, KeepRoot => 1, KeyAttr => []);
if(exists $xml->{people}[0]{person}){
        my $var = $xml->{people}[0]{person};
        my $count = @$var;
        my $person_index = 0;
        for(my $i = 0; $i < $count; $i++){
                if($xml->{people}[0]{person}[$i]->{name} eq $remove_person){
                        print "Person found at " . $person_index . " index";
                        $person_index = $i;
                        $person_to_remove = $xml->{people}[0]{person}[$i];
                }
        }
} else {
        print "Person not found in data.xml\r";
}

上面的代码会给我要删除的节点的索引。 从这一点来看,我遇到了麻烦。我想不出从数据中删除该索引的正确方法。
到目前为止,我已经尝试了一种使用 splice 的方法,它返回了我想删除的 XML 部分,然后我使用 XMLout() 将数组转换回XML。使用 =~ s///g,我能够编辑节点更改(<person> 变为 <opt>)。一旦我 XMLout()'ed 原来的 data.xml 结构,我试图用字符串替换 XML 的可移动部分的变量与原始结构的空字符串。

显然,这是行不通的。

my $new_xml    = XMLout($xml, KeepRoot => 1);
my $remove_xml = XMLout($person_to_remove, KeepRoot => 1);

$remove_xml =~ s/opt/person/g;
$new_xml =~ s/($remove_xml)//g; # facepalm, i know

我如何删除XML的这一部分,通过删除数组数据或删除纯文件文本以便写回原始data.xml 文件的新结构?

编辑:以下内容是在 将“请不要建议我使用其他库”添加到问题之前发布的。我离开它,因为我仍然认为正确答案是 "don't use XML::Simple"。你可以随心所欲地用锤子在墙上打螺丝,但这并不能改变这样一个事实,无论你用力敲它,结果都会很乱。

不要使用 XML::Simple 这真的很简单。甚至 XML::Simple 说:

The use of this module in new code is discouraged. Other modules are available which provide more straightforward and consistent interfaces.

根本问题是只有微不足道的(简单!)XML 可以直接通过散列和数组表示。如果您考虑一下 - XML 允许同一父级下的重复节点,但具有不同的属性和内容。它还允许一元标签。

使用 XML::Twig 怎么样:

#!/urs/bin/env perl
use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig -> new ('pretty_print' => 'indented_a' ) -> parsefile ( 'your_xml' ); 
foreach my $element ( $twig -> get_xpath('person[@name="Jane"]') ) {
   $element -> delete;
}

$twig -> print; 

如果需要,您也可以使用 parsefile_inplace 通过就地编辑来执行此操作。否则打开一个新文件并通过 $twig -> sprint 输出新的 XML。

例如:

XML::Twig->new(
    'pretty_print'  => 'indented_a',
    'twig_handlers' => {
        'person[@name="Jane"]' => sub { $_->delete }
    }
)->parsefile_inplace('xml_filename.xml');

如果您打算使用锤子来拧螺丝 - 这应该使用您的初始代码和 XML::Simple:

$xml->{people}[0]{person} = 
     [ grep { not $_->{name} eq $remove_person }
                      @{ $xml->{people}[0]{person} } ];

将有问题的数组替换为 name 属性上的过滤数组。

输出:

<people>
  <person name="John">
    <param name="age" value="21" />
  </person>
</people>

如您所见,, the point of XML::Simple 是使用 Perl 数据结构而不是字符串操作。所以,忘记 s/// 并尝试

my $xml = XMLin($data, ForceArray => 1, KeepRoot => 1);
my $remove = 'Jane';
delete $xml->{people}[0]{person}{$remove};
print XMLout($xml, KeepRoot => 1);

或者,空 KeyAttr

my $xml = XMLin($data, ForceArray => 1, KeepRoot => 1, KeyAttr => []);
@{ $xml->{people}[0]{person} } = grep $_->{name} ne $remove,
                                 @{ $xml->{people}[0]{person} };
print XMLout($xml, KeepRoot => 1);

为了比较,XML::XSH2中的相同任务:

 open data.xml ;
 my $remove = 'Jane' ;
 delete /people/person[@name=$remove] ;
 save :b ;

遗憾的是,我最终遇到了大致相同的问题,我不得不在 AIX 上编辑一些 XML 而没有额外的库。我最终删除了这样的东西

perl -0777 -p -i -e "s;(<HARDWARE>.*)<DESCRIPTION>.*<\/DESCRIPTION>(.*<\/HARDWARE>);$1$2;s" my.xml

这很丑。我不喜欢它。但它当时有效,前提是您知道如何编写一个时不时应该执行的正则表达式。